/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package javax.microedition.lcdui.game; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.Graphics; import com.sun.midp.log.Logging; import com.sun.midp.log.LogChannels; /** * A Sprite is a basic visual element that can be rendered with one of * several frames stored in an Image; different frames can be shown to * animate the Sprite. Several transforms such as flipping and rotation * can also be applied to a Sprite to further vary its appearance. As with * all Layer subclasses, a Sprite's location can be changed and it can also * be made visible or invisible. * <P> * <h3>Sprite Frames</h3> * The raw frames used to render a Sprite are provided in a single Image * object, which may be mutable or immutable. If more than one frame is used, * the Image is broken up into a series of equally-sized frames of a specified * width and height. As shown in the figure below, the same set of frames may * be stored in several different arrangements depending on what is the most * convenient for the game developer. * <br> * <center><img src="doc-files/frames.gif" width=777 height=402 * ALT="Sprite Frames"></center> * <br> * <p> * Each frame is assigned a unique index number. The frame located in the * upper-left corner of the Image is assigned an index of 0. The remaining * frames are then numbered consecutively in row-major order (indices are * assigned across the first row, then the second row, and so on). The method * {@link #getRawFrameCount()} returns the total number of raw frames. * <P> * <h3>Frame Sequence</h3> * A Sprite's frame sequence defines an ordered list of frames to be displayed. * The default frame sequence mirrors the list of available frames, so * there is a direct mapping between the sequence index and the corresponding * frame index. This also means that the length of the default frame sequence * is equal to the number of raw frames. For example, if a Sprite has 4 * frames, its default frame sequence is {0, 1, 2, 3}. * <center><img src="doc-files/defaultSequence.gif" width=182 height=269 * ALT="Default Frame Sequence"></center> * <P> * The developer must manually switch the current frame in the frame sequence. * This may be accomplished by calling {@link #setFrame}, * {@link #prevFrame()}, or {@link #nextFrame()}. Note that these methods * always operate on the sequence index, they do not operate on frame indices; * however, if the default frame sequence is used, then the sequence indices * and the frame indices are interchangeable. * <P> * If desired, an arbitrary frame sequence may be defined for a Sprite. * The frame sequence must contain at least one element, and each element must * reference a valid frame index. By defining a new frame sequence, the * developer can conveniently display the Sprite's frames in any order * desired; frames may be repeated, omitted, shown in reverse order, etc. * <P> * For example, the diagram below shows how a special frame sequence might be * used to animate a mosquito. The frame sequence is designed so that the * mosquito flaps its wings three times and then pauses for a moment before * the cycle is repeated. * <center><img src="doc-files/specialSequence.gif" width=346 height=510 * ALT="Special Frame Sequence"></center> * By calling {@link #nextFrame} each time the display is updated, the * resulting animation would like this: * <br> * <center><img src="doc-files/sequenceDemo.gif" width=96 height=36></center> * <P> * <h3>Reference Pixel</h3> * Being a subclass of Layer, Sprite inherits various methods for setting and * retrieving its location such as {@link #setPosition setPosition(x,y)}, * {@link #getX getX()}, and {@link #getY getY()}. These methods all define * position in terms of the upper-left corner of the Sprite's visual bounds; * however, in some cases, it is more convenient to define the Sprite's position * in terms of an arbitrary pixel within its frame, especially if transforms * are applied to the Sprite. * <P> * Therefore, Sprite includes the concept of a <em>reference pixel</em>. * The reference pixel is defined by specifying its location in the * Sprite's untransformed frame using * {@link #defineReferencePixel defineReferencePixel(x,y)}. * By default, the reference pixel is defined to be the pixel at (0,0) * in the frame. If desired, the reference pixel may be defined outside * of the frame's bounds. * <p> * In this example, the reference pixel is defined to be the pixel that * the monkey appears to be hanging from: * <p> * <center><img src="doc-files/refpixel.gif" width=304 height=199 * ALT="Defining The Reference Pixel"></center> * <p> * {@link #getRefPixelX getRefPixelX()} and {@link #getRefPixelY getRefPixelY()} * can be used to query the location of the reference pixel in the painter's * coordinate system. The developer can also use * {@link #setRefPixelPosition setRefPixelPosition(x,y)} to position the Sprite * so that reference pixel appears at a specific location in the painter's * coordinate system. These methods automatically account for any transforms * applied to the Sprite. * <p> * In this example, the reference pixel's position is set to a point at the end * of a tree branch; the Sprite's location changes so that the reference pixel * appears at this point and the monkey appears to be hanging from the branch: * <p> * <center><img src="doc-files/setrefposition.gif" width=332 height=350 * ALT="Setting The Reference Pixel Position"></center> * <p> * <a name="transforms"></a> * <h3>Sprite Transforms</h3> * Various transforms can be applied to a Sprite. The available transforms * include rotations in multiples of 90 degrees, and mirrored (about * the vertical axis) versions of each of the rotations. A Sprite's transform * is set by calling {@link #setTransform setTransform(transform)}. * <p> * <center><img src="doc-files/transforms.gif" width=355 height=575 * ALT="Transforms"></center> * <br> * When a transform is applied, the Sprite is automatically repositioned * such that the reference pixel appears stationary in the painter's * coordinate system. Thus, the reference pixel effectively becomes the * center of the transform operation. Since the reference pixel does not * move, the values returned by {@link #getRefPixelX()} and * {@link #getRefPixelY()} remain the same; however, the values returned by * {@link #getX getX()} and {@link #getY getY()} may change to reflect the * movement of the Sprite's upper-left corner. * <p> * Referring to the monkey example once again, the position of the * reference pixel remains at (48, 22) when a 90 degree rotation * is applied, thereby making it appear as if the monkey is swinging * from the branch: * <p> * <center><img src="doc-files/transcenter.gif" width=333 height=350 * ALT="Transform Center"></center> * <p> * <h3>Sprite Drawing</h3> * Sprites can be drawn at any time using the {@link #paint(Graphics)} method. * The Sprite will be drawn on the Graphics object according to the current * state information maintained by the Sprite (i.e. position, frame, * visibility). Erasing the Sprite is always the responsibility of code * outside the Sprite class.<p> * <p> * Sprites can be implemented using whatever techniques a manufacturers * wishes to use (e.g hardware acceleration may be used for all Sprites, for * certain sizes of Sprites, or not at all). * <p> * For some platforms, certain Sprite sizes may be more efficient than others; * manufacturers may choose to provide developers with information about * device-specific characteristics such as these. * <p> */ public class Sprite extends Layer { // ----- definitions for the various transformations possible ----- /** * No transform is applied to the Sprite. * This constant has a value of <code>0</code>. */ public static final int TRANS_NONE = 0; /** * Causes the Sprite to appear rotated clockwise by 90 degrees. * This constant has a value of <code>5</code>. */ public static final int TRANS_ROT90 = 5; /** * Causes the Sprite to appear rotated clockwise by 180 degrees. * This constant has a value of <code>3</code>. */ public static final int TRANS_ROT180 = 3; /** * Causes the Sprite to appear rotated clockwise by 270 degrees. * This constant has a value of <code>6</code>. */ public static final int TRANS_ROT270 = 6; /** * Causes the Sprite to appear reflected about its vertical * center. * This constant has a value of <code>2</code>. */ public static final int TRANS_MIRROR = 2; /** * Causes the Sprite to appear reflected about its vertical * center and then rotated clockwise by 90 degrees. * This constant has a value of <code>7</code>. */ public static final int TRANS_MIRROR_ROT90 = 7; /** * Causes the Sprite to appear reflected about its vertical * center and then rotated clockwise by 180 degrees. * This constant has a value of <code>1</code>. */ public static final int TRANS_MIRROR_ROT180 = 1; /** * Causes the Sprite to appear reflected about its vertical * center and then rotated clockwise by 270 degrees. * This constant has a value of <code>4</code>. */ public static final int TRANS_MIRROR_ROT270 = 4; // ----- Constructors ----- /** * Creates a new non-animated Sprite using the provided Image. * This constructor is functionally equivalent to calling * <code>new Sprite(image, image.getWidth(), image.getHeight())</code> * <p> * By default, the Sprite is visible and its upper-left * corner is positioned at (0,0) in the painter's coordinate system. * <br> * @param image the <code>Image</code> to use as the single frame * for the </code>Sprite * @throws NullPointerException if <code>img</code> is <code>null</code> */ public Sprite(Image image) { super(image.getWidth(), image.getHeight()); initializeFrames(image, image.getWidth(), image.getHeight(), false); // initialize collision rectangle initCollisionRectBounds(); // current transformation is TRANS_NONE setTransformImpl(TRANS_NONE); } /** * Creates a new animated Sprite using frames contained in * the provided Image. The frames must be equally sized, with the * dimensions specified by <code>frameWidth</code> and * <code>frameHeight</code>. They may be laid out in the image * horizontally, vertically, or as a grid. The width of the source * image must be an integer multiple of the frame width, and the height * of the source image must be an integer multiple of the frame height. * The values returned by {@link Layer#getWidth} and * {@link Layer#getHeight} will reflect the frame width and frame height * subject to the Sprite's current transform. * <p> * Sprites have a default frame sequence corresponding to the raw frame * numbers, starting with frame 0. The frame sequence may be modified * with {@link #setFrameSequence(int[])}. * <p> * By default, the Sprite is visible and its upper-left corner is * positioned at (0,0) in the painter's coordinate system. * <p> * @param image the <code>Image</code> to use for <code>Sprite</code> * @param frameWidth the <code>width</code>, in pixels, of the * individual raw frames * @param frameHeight the <code>height</code>, in pixels, of the * individual raw frames * @throws NullPointerException if <code>img</code> is <code>null</code> * @throws IllegalArgumentException if <code>frameHeight</code> or * <code>frameWidth</code> is less than <code>1</code> * @throws IllegalArgumentException if the <code>image</code> * width is not an integer multiple of the <code>frameWidth</code> * @throws IllegalArgumentException if the <code>image</code> * height is not an integer multiple of the <code>frameHeight</code> */ public Sprite(Image image, int frameWidth, int frameHeight) { super(frameWidth, frameHeight); // if img is null img.getWidth() will throw NullPointerException if ((frameWidth < 1 || frameHeight < 1) || ((image.getWidth() % frameWidth) != 0) || ((image.getHeight() % frameHeight) != 0)) { throw new IllegalArgumentException(); } // construct the array of images that // we use as "frames" for the sprite. // use default frame , sequence index = 0 initializeFrames(image, frameWidth, frameHeight, false); // initialize collision rectangle initCollisionRectBounds(); // current transformation is TRANS_NONE setTransformImpl(TRANS_NONE); } /** * Creates a new Sprite from another Sprite. <p> * * All instance attributes (raw frames, position, frame sequence, current * frame, reference point, collision rectangle, transform, and visibility) * of the source Sprite are duplicated in the new Sprite. * * @param s the <code>Sprite</code> to create a copy of * @throws NullPointerException if <code>s</code> is <code>null</code> * */ public Sprite(Sprite s) { super(s != null ? s.getWidth() : 0, s != null ? s.getHeight() : 0); if (s == null) { throw new NullPointerException(); } this.sourceImage = Image.createImage(s.sourceImage); this.numberFrames = s.numberFrames; this.frameCoordsX = new int[this.numberFrames]; this.frameCoordsY = new int[this.numberFrames]; System.arraycopy(s.frameCoordsX, 0, this.frameCoordsX, 0, s.getRawFrameCount()); System.arraycopy(s.frameCoordsY, 0, this.frameCoordsY, 0, s.getRawFrameCount()); this.x = s.getX(); this.y = s.getY(); // these fields are set by defining a reference point this.dRefX = s.dRefX; this.dRefY = s.dRefY; // these fields are set when defining a collision rectangle this.collisionRectX = s.collisionRectX; this.collisionRectY = s.collisionRectY; this.collisionRectWidth = s.collisionRectWidth; this.collisionRectHeight = s.collisionRectHeight; // these fields are set when creating a Sprite from an Image this.srcFrameWidth = s.srcFrameWidth; this.srcFrameHeight = s.srcFrameHeight; // the above fields are used in setTransform() // which is why we set them first, then call setTransformImpl() // to set up internally used data structures. setTransformImpl(s.t_currentTransformation); this.setVisible(s.isVisible()); this.frameSequence = new int[s.getFrameSequenceLength()]; this.setFrameSequence(s.frameSequence); this.setFrame(s.getFrame()); this.setRefPixelPosition(s.getRefPixelX(), s.getRefPixelY()); } // ----- public methods ----- /** * Defines the reference pixel for this Sprite. The pixel is * defined by its location relative to the upper-left corner of * the Sprite's un-transformed frame, and it may lay outside of * the frame's bounds. * <p> * When a transformation is applied, the reference pixel is * defined relative to the Sprite's initial upper-left corner * before transformation. This corner may no longer appear as the * upper-left corner in the painter's coordinate system under * current transformation. * <p> * By default, a Sprite's reference pixel is located at (0,0); that is, * the pixel in the upper-left corner of the raw frame. * <p> * Changing the reference pixel does not change the * Sprite's physical position in the painter's coordinate system; * that is, the values returned by {@link #getX getX()} and * {@link #getY getY()} will not change as a result of defining the * reference pixel. However, subsequent calls to methods that * involve the reference pixel will be impacted by its new definition. * * @param inp_x the horizontal location of the reference pixel, relative * to the left edge of the un-transformed frame * @param inp_y the vertical location of the reference pixel, relative * to the top edge of the un-transformed frame * @see #setRefPixelPosition * @see #getRefPixelX * @see #getRefPixelY */ public void defineReferencePixel(int inp_x, int inp_y) { dRefX = inp_x; dRefY = inp_y; } /** * Sets this Sprite's position such that its reference pixel is located * at (x,y) in the painter's coordinate system. * @param inp_x the horizontal location at which to place * the reference pixel * @param inp_y the vertical location at which to place the reference pixel * @see #defineReferencePixel * @see #getRefPixelX * @see #getRefPixelY */ public void setRefPixelPosition(int inp_x, int inp_y) { // update x and y x = inp_x - getTransformedPtX(dRefX, dRefY, t_currentTransformation); y = inp_y - getTransformedPtY(dRefX, dRefY, t_currentTransformation); } /** * Gets the horizontal position of this Sprite's reference pixel * in the painter's coordinate system. * @return the horizontal location of the reference pixel * @see #defineReferencePixel * @see #setRefPixelPosition * @see #getRefPixelY */ public int getRefPixelX() { return (this.x + getTransformedPtX(dRefX, dRefY, this.t_currentTransformation)); } /** * Gets the vertical position of this Sprite's reference pixel * in the painter's coordinate system. * @return the vertical location of the reference pixel * @see #defineReferencePixel * @see #setRefPixelPosition * @see #getRefPixelX */ public int getRefPixelY() { return (this.y + getTransformedPtY(dRefX, dRefY, this.t_currentTransformation)); } /** * Selects the current frame in the frame sequence. <p> * The current frame is rendered when {@link #paint(Graphics)} is called. * <p> * The index provided refers to the desired entry in the frame sequence, * not the index of the actual frame itself. * @param inp_sequenceIndex the index of of the desired entry in the frame * sequence * @throws IndexOutOfBoundsException if <code>frameIndex</code> is * less than<code>0</code> * @throws IndexOutOfBoundsException if <code>frameIndex</code> is * equal to or greater than the length of the current frame * sequence (or the number of raw frames for the default sequence) * @see #setFrameSequence(int[]) * @see #getFrame */ public void setFrame(int inp_sequenceIndex) { if (inp_sequenceIndex < 0 || inp_sequenceIndex >= frameSequence.length) { throw new IndexOutOfBoundsException(); } sequenceIndex = inp_sequenceIndex; } /** * Gets the current index in the frame sequence. <p> * The index returned refers to the current entry in the frame sequence, * not the index of the actual frame that is displayed. * * @return the current index in the frame sequence * @see #setFrameSequence(int[]) * @see #setFrame */ public final int getFrame() { return sequenceIndex; } /** * Gets the number of raw frames for this Sprite. The value returned * reflects the number of frames; it does not reflect the length of the * Sprite's frame sequence. However, these two values will be the same * if the default frame sequence is used. * * @return the number of raw frames for this Sprite * @see #getFrameSequenceLength */ public int getRawFrameCount() { return numberFrames; } /** * Gets the number of elements in the frame sequence. The value returned * reflects the length of the Sprite's frame sequence; it does not reflect * the number of raw frames. However, these two values will be the same * if the default frame sequence is used. * * @return the number of elements in this Sprite's frame sequence * @see #getRawFrameCount */ public int getFrameSequenceLength() { return frameSequence.length; } /** * Selects the next frame in the frame sequence. <p> * * The frame sequence is considered to be circular, i.e. if * {@link #nextFrame} is called when at the end of the sequence, * this method will advance to the first entry in the sequence. * * @see #setFrameSequence(int[]) * @see #prevFrame */ public void nextFrame() { sequenceIndex = (sequenceIndex + 1) % frameSequence.length; } /** * Selects the previous frame in the frame sequence. <p> * * The frame sequence is considered to be circular, i.e. if * {@link #prevFrame} is called when at the start of the sequence, * this method will advance to the last entry in the sequence. * * @see #setFrameSequence(int[]) * @see #nextFrame */ public void prevFrame() { if (sequenceIndex == 0) { sequenceIndex = frameSequence.length - 1; } else { sequenceIndex--; } } /** * Draws the Sprite. * <P> * Draws current frame of Sprite using the provided Graphics object. * The Sprite's upper left corner is rendered at the Sprite's current * position relative to the origin of the Graphics object. The current * position of the Sprite's upper-left corner can be retrieved by * calling {@link #getX()} and {@link #getY()}. * <P> * Rendering is subject to the clip region of the Graphics object. * The Sprite will be drawn only if it is visible. * <p> * If the Sprite's Image is mutable, the Sprite is rendered using the * current contents of the Image. * * @param g the graphics object to draw <code>Sprite</code> on * @throws NullPointerException if <code>g</code> is <code>null</code> * */ public final void paint(Graphics g) { // managing the painting order is the responsibility of // the layermanager, so depth is ignored if (g == null) { throw new NullPointerException(); } if (visible) { // width and height of the source // image is the width and height // of the original frame g.drawRegion(sourceImage, frameCoordsX[frameSequence[sequenceIndex]], frameCoordsY[frameSequence[sequenceIndex]], srcFrameWidth, srcFrameHeight, t_currentTransformation, this.x, this.y, Graphics.TOP | Graphics.LEFT); } } /** * Set the frame sequence for this Sprite. <p> * * All Sprites have a default sequence that displays the Sprites * frames in order. This method allows for the creation of an * arbitrary sequence using the available frames. The current * index in the frame sequence is reset to zero as a result of * calling this method. * <p> * The contents of the sequence array are copied when this method * is called; thus, any changes made to the array after this method * returns have no effect on the Sprite's frame sequence. * <P> * Passing in <code>null</code> causes the Sprite to revert to the * default frame sequence.<p> * * @param sequence an array of integers, where each integer represents * a frame index * * @throws ArrayIndexOutOfBoundsException if seq is non-null and any member * of the array has a value less than <code>0</code> or * greater than or equal to the * number of frames as reported by {@link #getRawFrameCount()} * @throws IllegalArgumentException if the array has less than * <code>1</code> element * @see #nextFrame * @see #prevFrame * @see #setFrame * @see #getFrame * */ public void setFrameSequence(int sequence[]) { if (sequence == null) { // revert to the default sequence sequenceIndex = 0; customSequenceDefined = false; frameSequence = new int[numberFrames]; // copy frames indices into frameSequence for (int i = 0; i < numberFrames; i++) { frameSequence[i] = i; } return; } if (sequence.length < 1) { throw new IllegalArgumentException(); } for (int i = 0; i < sequence.length; i++) { if (sequence[i] < 0 || sequence[i] >= numberFrames) { throw new ArrayIndexOutOfBoundsException(); } } customSequenceDefined = true; frameSequence = new int[sequence.length]; System.arraycopy(sequence, 0, frameSequence, 0, sequence.length); sequenceIndex = 0; } /** * Changes the Image containing the Sprite's frames. * <p> * Replaces the current raw frames of the Sprite with a new set of raw * frames. See the constructor {@link #Sprite(Image, int, int)} for * information on how the frames are created from the image. The * values returned by {@link Layer#getWidth} and {@link Layer#getHeight} * will reflect the new frame width and frame height subject to the * Sprite's current transform. * <p> * Changing the image for the Sprite could change the number of raw * frames. If the new frame set has as many or more raw frames than the * previous frame set, then: * <ul> * <li>The current frame will be unchanged * <li>If a custom frame sequence has been defined (using * {@link #setFrameSequence(int[])}), it will remain unchanged. If no * custom frame sequence is defined (i.e. the default frame * sequence * is in use), the default frame sequence will be updated to * be the default frame sequence for the new frame set. In other * words, the new default frame sequence will include all of the * frames from the new raw frame set, as if this new image had been * used in the constructor. * </ul> * <p> * If the new frame set has fewer frames than the previous frame set, * then: * <ul> * <li>The current frame will be reset to entry 0 * <li>Any custom frame sequence will be discarded and the frame sequence * will revert to the default frame sequence for the new frame * set. * </ul> * <p> * The reference point location is unchanged as a result of calling this * method, both in terms of its defined location within the Sprite and its * position in the painter's coordinate system. However, if the frame * size is changed and the Sprite has been transformed, the position of * the Sprite's upper-left corner may change such that the reference * point remains stationary. * <p> * If the Sprite's frame size is changed by this method, the collision * rectangle is reset to its default value (i.e. it is set to the new * bounds of the untransformed Sprite). * <p> * @param img the <code>Image</code> to use for * <code>Sprite</code> * @param frameWidth the width in pixels of the individual raw frames * @param frameHeight the height in pixels of the individual raw frames * @throws NullPointerException if <code>img</code> is <code>null</code> * @throws IllegalArgumentException if <code>frameHeight</code> or * <code>frameWidth</code> is less than <code>1</code> * @throws IllegalArgumentException if the image width is not an integer * multiple of the <code>frameWidth</code> * @throws IllegalArgumentException if the image height is not an integer * multiple of the <code>frameHeight</code> */ public void setImage(Image img, int frameWidth, int frameHeight) { // if image is null image.getWidth() will throw NullPointerException if ((frameWidth < 1 || frameHeight < 1) || ((img.getWidth() % frameWidth) != 0) || ((img.getHeight() % frameHeight) != 0)) { throw new IllegalArgumentException(); } int noOfFrames = (img.getWidth() / frameWidth)*(img.getHeight() / frameHeight); boolean maintainCurFrame = true; if (noOfFrames < numberFrames) { // use default frame , sequence index = 0 maintainCurFrame = false; customSequenceDefined = false; } if (! ((srcFrameWidth == frameWidth) && (srcFrameHeight == frameHeight))) { // computing is the location // of the reference pixel in the painter's coordinate system. // and then use this to find x and y position of the Sprite int oldX = this.x + getTransformedPtX(dRefX, dRefY, this.t_currentTransformation); int oldY = this.y + getTransformedPtY(dRefX, dRefY, this.t_currentTransformation); setWidthImpl(frameWidth); setHeightImpl(frameHeight); initializeFrames(img, frameWidth, frameHeight, maintainCurFrame); // initialize collision rectangle initCollisionRectBounds(); // set the new x and y position of the Sprite this.x = oldX - getTransformedPtX(dRefX, dRefY, this.t_currentTransformation); this.y = oldY - getTransformedPtY(dRefX, dRefY, this.t_currentTransformation); // Calculate transformed sprites collision rectangle // and transformed width and height computeTransformedBounds(this.t_currentTransformation); } else { // just reinitialize the animation frames. initializeFrames(img, frameWidth, frameHeight, maintainCurFrame); } } /** * Defines the Sprite's bounding rectangle that is used for collision * detection purposes. This rectangle is specified relative to the * un-transformed Sprite's upper-left corner and defines the area that is * checked for collision detection. For pixel-level detection, only those * pixels within the collision rectangle are checked. * * By default, a Sprite's collision rectangle is located at 0,0 as has the * same dimensions as the Sprite. The collision rectangle may be * specified to be larger or smaller than the default rectangle; if made * larger, the pixels outside the bounds of the Sprite are considered to be * transparent for pixel-level collision detection. * * @param inp_x the horizontal location of the collision * rectangle relative to the untransformed Sprite's left edge * @param inp_y the vertical location of the collision rectangle relative to * the untransformed Sprite's top edge * @param width the width of the collision rectangle * @param height the height of the collision rectangle * @throws IllegalArgumentException if the specified * <code>width</code> or <code>height</code> is * less than <code>0</code> */ public void defineCollisionRectangle(int inp_x, int inp_y, int width, int height) { if (width < 0 || height < 0) { throw new IllegalArgumentException(); } collisionRectX = inp_x; collisionRectY = inp_y; collisionRectWidth = width; collisionRectHeight = height; // call set transform with current transformation to // update transformed sprites collision rectangle setTransformImpl(t_currentTransformation); } /** * Sets the transform for this Sprite. Transforms can be * applied to a Sprite to change its rendered appearance. Transforms * are applied to the original Sprite image; they are not cumulative, * nor can they be combined. By default, a Sprite's transform is * {@link #TRANS_NONE}. * <P> * Since some transforms involve rotations of 90 or 270 degrees, their * use may result in the overall width and height of the Sprite * being swapped. As a result, the values returned by * {@link Layer#getWidth} and {@link Layer#getHeight} may change. * <p> * The collision rectangle is also modified by the transform so that * it remains static relative to the pixel data of the Sprite. * Similarly, the defined reference pixel is unchanged by this method, * but its visual location within the Sprite may change as a result. * <P> * This method repositions the Sprite so that the location of * the reference pixel in the painter's coordinate system does not change * as a result of changing the transform. Thus, the reference pixel * effectively becomes the centerpoint for the transform. Consequently, * the values returned by {@link #getRefPixelX} and {@link #getRefPixelY} * will be the same both before and after the transform is applied, but * the values returned by {@link #getX getX()} and {@link #getY getY()} * may change. * <p> * @param transform the desired transform for this <code>Sprite</code> * @throws IllegalArgumentException if the requested * <code>transform</code> is invalid * @see #TRANS_NONE * @see #TRANS_ROT90 * @see #TRANS_ROT180 * @see #TRANS_ROT270 * @see #TRANS_MIRROR * @see #TRANS_MIRROR_ROT90 * @see #TRANS_MIRROR_ROT180 * @see #TRANS_MIRROR_ROT270 * */ public void setTransform(int transform) { setTransformImpl(transform); } /** * Checks for a collision between this Sprite and the specified Sprite. * <P> * If pixel-level detection is used, a collision is detected only if * opaque pixels collide. That is, an opaque pixel in the first * Sprite would have to collide with an opaque pixel in the second * Sprite for a collision to be detected. Only those pixels within * the Sprites' respective collision rectangles are checked. * <P> * If pixel-level detection is not used, this method simply * checks if the Sprites' collision rectangles intersect. * <P> * Any transforms applied to the Sprites are automatically accounted for. * <P> * Both Sprites must be visible in order for a collision to be * detected. * <P> * @param s the <code>Sprite</code> to test for collision with * @param pixelLevel <code>true</code> to test for collision on a * pixel-by-pixel basis, <code>false</code> to test using simple * bounds checking. * @return <code>true</code> if the two Sprites have collided, otherwise * <code>false</code> * @throws NullPointerException if Sprite <code>s</code> is * <code>null</code> */ public final boolean collidesWith(Sprite s, boolean pixelLevel) { // check if either of the Sprite's are not visible if (!(s.visible && this.visible)) { return false; } // these are package private // and can be accessed directly int otherLeft = s.x + s.t_collisionRectX; int otherTop = s.y + s.t_collisionRectY; int otherRight = otherLeft + s.t_collisionRectWidth; int otherBottom = otherTop + s.t_collisionRectHeight; int left = this.x + this.t_collisionRectX; int top = this.y + this.t_collisionRectY; int right = left + this.t_collisionRectWidth; int bottom = top + this.t_collisionRectHeight; // check if the collision rectangles of the two sprites intersect if (intersectRect(otherLeft, otherTop, otherRight, otherBottom, left, top, right, bottom)) { // collision rectangles intersect if (pixelLevel) { // we need to check pixel level collision detection. // use only the coordinates within the Sprite frame if // the collision rectangle is larger than the Sprite // frame if (this.t_collisionRectX < 0) { left = this.x; } if (this.t_collisionRectY < 0) { top = this.y; } if ((this.t_collisionRectX + this.t_collisionRectWidth) > this.width) { right = this.x + this.width; } if ((this.t_collisionRectY + this.t_collisionRectHeight) > this.height) { bottom = this.y + this.height; } // similarly for the other Sprite if (s.t_collisionRectX < 0) { otherLeft = s.x; } if (s.t_collisionRectY < 0) { otherTop = s.y; } if ((s.t_collisionRectX + s.t_collisionRectWidth) > s.width) { otherRight = s.x + s.width; } if ((s.t_collisionRectY + s.t_collisionRectHeight) > s.height) { otherBottom = s.y + s.height; } // recheck if the updated collision area rectangles intersect if (!intersectRect(otherLeft, otherTop, otherRight, otherBottom, left, top, right, bottom)) { // if they don't intersect, return false; return false; } // the updated collision rectangles intersect, // go ahead with collision detection // find intersecting region, // within the collision rectangles int intersectLeft = (left < otherLeft) ? otherLeft : left; int intersectTop = (top < otherTop) ? otherTop : top; // used once, optimize. int intersectRight = (right < otherRight) ? right : otherRight; int intersectBottom = (bottom < otherBottom) ? bottom : otherBottom; int intersectWidth = Math.abs(intersectRight - intersectLeft); int intersectHeight = Math.abs(intersectBottom - intersectTop); // have the coordinates in painter space, // need coordinates of top left and width, height // in source image of Sprite. int thisImageXOffset = getImageTopLeftX(intersectLeft, intersectTop, intersectRight, intersectBottom); int thisImageYOffset = getImageTopLeftY(intersectLeft, intersectTop, intersectRight, intersectBottom); int otherImageXOffset = s.getImageTopLeftX(intersectLeft, intersectTop, intersectRight, intersectBottom); int otherImageYOffset = s.getImageTopLeftY(intersectLeft, intersectTop, intersectRight, intersectBottom); // check if opaque pixels intersect. return doPixelCollision(thisImageXOffset, thisImageYOffset, otherImageXOffset, otherImageYOffset, this.sourceImage, this.t_currentTransformation, s.sourceImage, s.t_currentTransformation, intersectWidth, intersectHeight); } else { // collides! return true; } } return false; } /** * Checks for a collision between this Sprite and the specified * TiledLayer. If pixel-level detection is used, a collision is * detected only if opaque pixels collide. That is, an opaque pixel in * the Sprite would have to collide with an opaque pixel in TiledLayer * for a collision to be detected. Only those pixels within the Sprite's * collision rectangle are checked. * <P> * If pixel-level detection is not used, this method simply checks if the * Sprite's collision rectangle intersects with a non-empty cell in the * TiledLayer. * <P> * Any transform applied to the Sprite is automatically accounted for. * <P> * The Sprite and the TiledLayer must both be visible in order for * a collision to be detected. * <P> * @param t the <code>TiledLayer</code> to test for collision with * @param pixelLevel <code>true</code> to test for collision on a * pixel-by-pixel basis, <code>false</code> to test using simple bounds * checking against non-empty cells. * @return <code>true</code> if this <code>Sprite</code> has * collided with the <code>TiledLayer</code>, otherwise * <code>false</code> * @throws NullPointerException if <code>t</code> is <code>null</code> */ public final boolean collidesWith(TiledLayer t, boolean pixelLevel) { // check if either this Sprite or the TiledLayer is not visible if (!(t.visible && this.visible)) { return false; } // dimensions of tiledLayer, cell, and // this Sprite's collision rectangle // these are package private // and can be accessed directly int tLx1 = t.x; int tLy1 = t.y; int tLx2 = tLx1 + t.width; int tLy2 = tLy1 + t.height; int tW = t.getCellWidth(); int tH = t.getCellHeight(); int sx1 = this.x + this.t_collisionRectX; int sy1 = this.y + this.t_collisionRectY; int sx2 = sx1 + this.t_collisionRectWidth; int sy2 = sy1 + this.t_collisionRectHeight; // number of cells int tNumCols = t.getColumns(); int tNumRows = t.getRows(); // temporary loop variables. int startCol; // = 0; int endCol; // = 0; int startRow; // = 0; int endRow; // = 0; if (!intersectRect(tLx1, tLy1, tLx2, tLy2, sx1, sy1, sx2, sy2)) { // if the collision rectangle of the sprite // does not intersect with the dimensions of the entire // tiled layer return false; } // so there is an intersection // note sx1 < sx2, tLx1 < tLx2, sx2 > tLx1 from intersectRect() // use <= for comparison as this saves us some // computation - the result will be 0 startCol = (sx1 <= tLx1) ? 0 : (sx1 - tLx1)/tW; startRow = (sy1 <= tLy1) ? 0 : (sy1 - tLy1)/tH; // since tLx1 < sx2 < tLx2, the computation will yield // a result between 0 and tNumCols - 1 // subtract by 1 because sx2,sy2 represent // the enclosing bounds of the sprite, not the // locations in the coordinate system. endCol = (sx2 < tLx2) ? ((sx2 - 1 - tLx1)/tW) : tNumCols - 1; endRow = (sy2 < tLy2) ? ((sy2 - 1 - tLy1)/tH) : tNumRows - 1; if (!pixelLevel) { // check for intersection with a non-empty cell, for (int row = startRow; row <= endRow; row++) { for (int col = startCol; col <= endCol; col++) { if (t.getCell(col, row) != 0) { return true; } } } // worst case! we scanned through entire // overlapping region and // all the cells are empty! return false; } else { // do pixel level // we need to check pixel level collision detection. // use only the coordinates within the Sprite frame if // the collision rectangle is larger than the Sprite // frame if (this.t_collisionRectX < 0) { sx1 = this.x; } if (this.t_collisionRectY < 0) { sy1 = this.y; } if ((this.t_collisionRectX + this.t_collisionRectWidth) > this.width) { sx2 = this.x + this.width; } if ((this.t_collisionRectY + this.t_collisionRectHeight) > this.height) { sy2 = this.y + this.height; } if (!intersectRect(tLx1, tLy1, tLx2, tLy2, sx1, sy1, sx2, sy2)) { return (false); } // we have an intersection between the Sprite and // one or more cells of the tiledlayer // note sx1 < sx2, tLx1 < tLx2, sx2 > tLx1 from intersectRect() // use <= for comparison as this saves us some // computation - the result will be 0 startCol = (sx1 <= tLx1) ? 0 : (sx1 - tLx1)/tW; startRow = (sy1 <= tLy1) ? 0 : (sy1 - tLy1)/tH; // since tLx1 < sx2 < tLx2, the computation will yield // a result between 0 and tNumCols - 1 // subtract by 1 because sx2,sy2 represent // the enclosing bounds of the sprite, not the // locations in the coordinate system. endCol = (sx2 < tLx2) ? ((sx2 - 1 - tLx1)/tW) : tNumCols - 1; endRow = (sy2 < tLy2) ? ((sy2 - 1 - tLy1)/tH) : tNumRows - 1; // current cell coordinates int cellTop = startRow * tH + tLy1; int cellBottom = cellTop + tH; // the index of the current tile. int tileIndex; // = 0; for (int row = startRow; row <= endRow; row++, cellTop += tH, cellBottom += tH) { // current cell coordinates int cellLeft = startCol * tW + tLx1; int cellRight = cellLeft + tW; for (int col = startCol; col <= endCol; col++, cellLeft += tW, cellRight += tW) { tileIndex = t.getCell(col, row); if (tileIndex != 0) { // current cell/sprite intersection coordinates // in painter coordinate system. // find intersecting region, int intersectLeft = (sx1 < cellLeft) ? cellLeft : sx1; int intersectTop = (sy1 < cellTop) ? cellTop : sy1; // used once, optimize. int intersectRight = (sx2 < cellRight) ? sx2 : cellRight; int intersectBottom = (sy2 < cellBottom) ? sy2 : cellBottom; if (intersectLeft > intersectRight) { int temp = intersectRight; intersectRight = intersectLeft; intersectLeft = temp; } if (intersectTop > intersectBottom) { int temp = intersectBottom; intersectBottom = intersectTop; intersectTop = temp; } int intersectWidth = intersectRight - intersectLeft; int intersectHeight = intersectBottom - intersectTop; int image1XOffset = getImageTopLeftX(intersectLeft, intersectTop, intersectRight, intersectBottom); int image1YOffset = getImageTopLeftY(intersectLeft, intersectTop, intersectRight, intersectBottom); int image2XOffset = t.tileSetX[tileIndex] + (intersectLeft - cellLeft); int image2YOffset = t.tileSetY[tileIndex] + (intersectTop - cellTop); if (doPixelCollision(image1XOffset, image1YOffset, image2XOffset, image2YOffset, this.sourceImage, this.t_currentTransformation, t.sourceImage, TRANS_NONE, intersectWidth, intersectHeight)) { // intersection found with this tile return true; } } } // end of for col }// end of for row // worst case! we scanned through entire // overlapping region and // no pixels collide! return false; } } /** * Checks for a collision between this Sprite and the specified Image * with its upper left corner at the specified location. If pixel-level * detection is used, a collision is detected only if opaque pixels * collide. That is, an opaque pixel in the Sprite would have to collide * with an opaque pixel in Image for a collision to be detected. Only * those pixels within the Sprite's collision rectangle are checked. * <P> * If pixel-level detection is not used, this method simply checks if the * Sprite's collision rectangle intersects with the Image's bounds. * <P> * Any transform applied to the Sprite is automatically accounted for. * <P> * The Sprite must be visible in order for a collision to be * detected. * <P> * @param image the <code>Image</code> to test for collision * @param inp_x the horizontal location of the <code>Image</code>'s * upper left corner * @param inp_y the vertical location of the <code>Image</code>'s * upper left corner * @param pixelLevel <code>true</code> to test for collision on a * pixel-by-pixel basis, <code>false</code> to test using simple * bounds checking * @return <code>true</code> if this <code>Sprite</code> has * collided with the <code>Image</code>, otherwise * <code>false</code> * @throws NullPointerException if <code>image</code> is * <code>null</code> */ public final boolean collidesWith(Image image, int inp_x, int inp_y, boolean pixelLevel) { // check if this Sprite is not visible if (!(visible)) { return false; } // if image is null // image.getWidth() will throw NullPointerException int otherLeft = inp_x; int otherTop = inp_y; int otherRight = inp_x + image.getWidth(); int otherBottom = inp_y + image.getHeight(); int left = x + t_collisionRectX; int top = y + t_collisionRectY; int right = left + t_collisionRectWidth; int bottom = top + t_collisionRectHeight; // first check if the collision rectangles of the two sprites intersect if (intersectRect(otherLeft, otherTop, otherRight, otherBottom, left, top, right, bottom)) { // collision rectangles intersect if (pixelLevel) { // find intersecting region, // we need to check pixel level collision detection. // use only the coordinates within the Sprite frame if // the collision rectangle is larger than the Sprite // frame if (this.t_collisionRectX < 0) { left = this.x; } if (this.t_collisionRectY < 0) { top = this.y; } if ((this.t_collisionRectX + this.t_collisionRectWidth) > this.width) { right = this.x + this.width; } if ((this.t_collisionRectY + this.t_collisionRectHeight) > this.height) { bottom = this.y + this.height; } // recheck if the updated collision area rectangles intersect if (!intersectRect(otherLeft, otherTop, otherRight, otherBottom, left, top, right, bottom)) { // if they don't intersect, return false; return false; } // within the collision rectangles int intersectLeft = (left < otherLeft) ? otherLeft : left; int intersectTop = (top < otherTop) ? otherTop : top; // used once, optimize. int intersectRight = (right < otherRight) ? right : otherRight; int intersectBottom = (bottom < otherBottom) ? bottom : otherBottom; int intersectWidth = Math.abs(intersectRight - intersectLeft); int intersectHeight = Math.abs(intersectBottom - intersectTop); // have the coordinates in painter space, // need coordinates of top left and width, height // in source image of Sprite. int thisImageXOffset = getImageTopLeftX(intersectLeft, intersectTop, intersectRight, intersectBottom); int thisImageYOffset = getImageTopLeftY(intersectLeft, intersectTop, intersectRight, intersectBottom); int otherImageXOffset = intersectLeft - inp_x; int otherImageYOffset = intersectTop - inp_y; // check if opaque pixels intersect. return doPixelCollision(thisImageXOffset, thisImageYOffset, otherImageXOffset, otherImageYOffset, this.sourceImage, this.t_currentTransformation, image, Sprite.TRANS_NONE, intersectWidth, intersectHeight); } else { // collides! return true; } } return false; } // ----- // ----- private ----- /** * create the Image Array. * * @param image Image to use for Sprite * @param fWidth width, in pixels, of the individual raw frames * @param fHeight height, in pixels, of the individual raw frames * @param maintainCurFrame true if Current Frame is maintained */ private void initializeFrames(Image image, int fWidth, int fHeight, boolean maintainCurFrame) { int imageW = image.getWidth(); int imageH = image.getHeight(); int numHorizontalFrames = imageW / fWidth; int numVerticalFrames = imageH / fHeight; sourceImage = image; srcFrameWidth = fWidth; srcFrameHeight = fHeight; numberFrames = numHorizontalFrames*numVerticalFrames; frameCoordsX = new int[numberFrames]; frameCoordsY = new int[numberFrames]; if (!maintainCurFrame) { sequenceIndex = 0; } if (!customSequenceDefined) { frameSequence = new int[numberFrames]; } int currentFrame = 0; for (int yy = 0; yy < imageH; yy += fHeight) { for (int xx = 0; xx < imageW; xx += fWidth) { frameCoordsX[currentFrame] = xx; frameCoordsY[currentFrame] = yy; if (!customSequenceDefined) { frameSequence[currentFrame] = currentFrame; } currentFrame++; } } } /** * initialize the collision rectangle */ private void initCollisionRectBounds() { // reset x and y of collision rectangle collisionRectX = 0; collisionRectY = 0; // intialize the collision rectangle bounds to that of the sprite collisionRectWidth = this.width; collisionRectHeight = this.height; } /** * Detect rectangle intersection * * @param r1x1 left co-ordinate of first rectangle * @param r1y1 top co-ordinate of first rectangle * @param r1x2 right co-ordinate of first rectangle * @param r1y2 bottom co-ordinate of first rectangle * @param r2x1 left co-ordinate of second rectangle * @param r2y1 top co-ordinate of second rectangle * @param r2x2 right co-ordinate of second rectangle * @param r2y2 bottom co-ordinate of second rectangle * @return True if there is rectangle intersection */ private boolean intersectRect(int r1x1, int r1y1, int r1x2, int r1y2, int r2x1, int r2y1, int r2x2, int r2y2) { if (r2x1 >= r1x2 || r2y1 >= r1y2 || r2x2 <= r1x1 || r2y2 <= r1y1) { return false; } else { return true; } } /** * Detect opaque pixel intersection between regions of two images * * @param image1XOffset left coordinate in the first image * @param image1YOffset top coordinate in the first image * @param image2XOffset left coordinate in the second image * @param image2YOffset top coordinate in the second image * @param image1 first source image * @param transform1 The transform for the first image * @param image2 second source image * @param transform2 transform set on the second image * @param width width of overlapping region, when transformed * @param height height of overlapping region, when transformed * * Clarification required on parameters: * XOffset and YOffset are the offsets from the top left * hand corner of the image. * width, height is the dimensions of the intersecting regions * in the two transformed images. * there fore appropriate conversions have to be made on these * dimensions when using the values, according to the transformation * that has been set. * * @return True if there is a pixel level collision */ private static boolean doPixelCollision(int image1XOffset, int image1YOffset, int image2XOffset, int image2YOffset, Image image1, int transform1, Image image2, int transform2, int width, int height) { // starting point of comparison int startY1; // x and y increments int xIncr1, yIncr1; // .. for image 2 int startY2; int xIncr2, yIncr2; int numPixels = height * width; int[] argbData1 = new int[numPixels]; int[] argbData2 = new int[numPixels]; if (0x0 != (transform1 & INVERTED_AXES)) { // inverted axes // scanlength = height if (0x0 != (transform1 & Y_FLIP)) { xIncr1 = -(height); // - scanlength startY1 = numPixels - height; // numPixels - scanlength } else { xIncr1 = height; // + scanlength startY1 = 0; } if (0x0 != (transform1 & X_FLIP)) { yIncr1 = -1; startY1 += (height - 1); } else { yIncr1 = +1; } image1.getRGB(argbData1, 0, height, // scanlength = height image1XOffset, image1YOffset, height, width); } else { // scanlength = width if (0x0 != (transform1 & Y_FLIP)) { startY1 = numPixels - width; // numPixels - scanlength yIncr1 = -(width); // - scanlength } else { startY1 = 0; yIncr1 = width; // + scanlength } if (0x0 != (transform1 & X_FLIP)) { xIncr1 = -1; startY1 += (width - 1); } else { xIncr1 = +1; } image1.getRGB(argbData1, 0, width, // scanlength = width image1XOffset, image1YOffset, width, height); } if (0x0 != (transform2 & INVERTED_AXES)) { // inverted axes if (0x0 != (transform2 & Y_FLIP)) { xIncr2 = -(height); startY2 = numPixels - height; } else { xIncr2 = height; startY2 = 0; } if (0x0 != (transform2 & X_FLIP)) { yIncr2 = -1; startY2 += height - 1; } else { yIncr2 = +1; } image2.getRGB(argbData2, 0, height, image2XOffset, image2YOffset, height, width); } else { if (0x0 != (transform2 & Y_FLIP)) { startY2 = numPixels - width; yIncr2 = -(width); } else { startY2 = 0; yIncr2 = +width; } if (0x0 != (transform2 & X_FLIP)) { xIncr2 = -1; startY2 += (width - 1); } else { xIncr2 = +1; } image2.getRGB(argbData2, 0, width, image2XOffset, image2YOffset, width, height); } int x1, x2; int xLocalBegin1, xLocalBegin2; // the loop counters int numIterRows; int numIterColumns; for (numIterRows = 0, xLocalBegin1 = startY1, xLocalBegin2 = startY2; numIterRows < height; xLocalBegin1 += yIncr1, xLocalBegin2 += yIncr2, numIterRows++) { for (numIterColumns = 0, x1 = xLocalBegin1, x2 = xLocalBegin2; numIterColumns < width; x1 += xIncr1, x2 += xIncr2, numIterColumns++) { if (((argbData1[x1] & ALPHA_BITMASK) == FULLY_OPAQUE_ALPHA) && ((argbData2[x2] & ALPHA_BITMASK) == FULLY_OPAQUE_ALPHA)) { return true; } } // end for x } // end for y // worst case! couldn't find a single colliding pixel! return false; } /** * Given a rectangle that lies within the sprite * in the painter's coordinates, * find the X coordinate of the top left corner * in the source image of the sprite * * @param x1 the x coordinate of the top left of the rectangle * @param y1 the y coordinate of the top left of the rectangle * @param x2 the x coordinate of the bottom right of the rectangle * @param y2 the y coordinate of the bottom right of the rectangle * * @return the X coordinate in the source image * */ private int getImageTopLeftX(int x1, int y1, int x2, int y2) { int retX = 0; // left = this.x // right = this.x + this.width // top = this.y // bottom = this.y + this.height switch (this.t_currentTransformation) { case TRANS_NONE: case TRANS_MIRROR_ROT180: retX = x1 - this.x; break; case TRANS_MIRROR: case TRANS_ROT180: retX = (this.x + this.width) - x2; break; case TRANS_ROT90: case TRANS_MIRROR_ROT270: retX = y1 - this.y; break; case TRANS_ROT270: case TRANS_MIRROR_ROT90: retX = (this.y + this.height) - y2; break; default: // for safety/completeness. Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI, "Sprite: t_currentTransformation=" + t_currentTransformation); return retX; } retX += frameCoordsX[frameSequence[sequenceIndex]]; return retX; } /** * Given a rectangle that lies within the sprite * in the painter's coordinates, * find the Y coordinate of the top left corner * in the source image of the sprite * * @param x1 the x coordinate of the top left of the rectangle * @param y1 the y coordinate of the top left of the rectangle * @param x2 the x coordinate of the bottom right of the rectangle * @param y2 the y coordinate of the bottom right of the rectangle * * @return the Y coordinate in the source image * */ private int getImageTopLeftY(int x1, int y1, int x2, int y2) { int retY = 0; // left = this.x // right = this.x + this.width // top = this.y // bottom = this.y + this.height switch (this.t_currentTransformation) { case TRANS_NONE: case TRANS_MIRROR: retY = y1 - this.y; break; case TRANS_ROT180: case TRANS_MIRROR_ROT180: retY = (this.y + this.height) - y2; break; case TRANS_ROT270: case TRANS_MIRROR_ROT270: retY = x1 - this.x; break; case TRANS_ROT90: case TRANS_MIRROR_ROT90: retY = (this.x + this.width) - x2; break; default: // for safety/completeness. Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI, "Sprite: t_currentTransformation=" + this.t_currentTransformation); return retY; } retY += frameCoordsY[frameSequence[sequenceIndex]]; return retY; } /** * Sets the transform for this Sprite * * @param transform the desired transform for this Sprite */ private void setTransformImpl(int transform) { // --- // setTransform sets up all transformation related data structures // except transforming the current frame's bitmap. // x, y, width, height, dRefX, dRefY, // collisionRectX, collisionRectY, collisionRectWidth, // collisionRectHeight, t_currentTransformation, // t_bufferImage // The actual transformed frame is drawn at paint time. // --- // update top-left corner position this.x = this.x + getTransformedPtX(dRefX, dRefY, this.t_currentTransformation) - getTransformedPtX(dRefX, dRefY, transform); this.y = this.y + getTransformedPtY(dRefX, dRefY, this.t_currentTransformation) - getTransformedPtY(dRefX, dRefY, transform); // Calculate transformed sprites collision rectangle // and transformed width and height computeTransformedBounds(transform); // set the current transform to be the one requested t_currentTransformation = transform; } /** * Calculate transformed sprites collision rectangle * and transformed width and height * @param transform the desired transform for this <code>Sprite</code> */ private void computeTransformedBounds(int transform) { switch (transform) { case TRANS_NONE: t_collisionRectX = collisionRectX; t_collisionRectY = collisionRectY; t_collisionRectWidth = collisionRectWidth; t_collisionRectHeight = collisionRectHeight; this.width = srcFrameWidth; this.height = srcFrameHeight; break; case TRANS_MIRROR: // flip across vertical // NOTE: top left x and y coordinate must reflect the transformation // performed around the reference point // the X-offset of the reference point from the top left corner // changes. t_collisionRectX = srcFrameWidth - (collisionRectX + collisionRectWidth); t_collisionRectY = collisionRectY; t_collisionRectWidth = collisionRectWidth; t_collisionRectHeight = collisionRectHeight; // the Y-offset of the reference point from the top left corner // remains the same, // top left X-co-ordinate changes this.width = srcFrameWidth; this.height = srcFrameHeight; break; case TRANS_MIRROR_ROT180: // flip across horizontal // NOTE: top left x and y coordinate must reflect the transformation // performed around the reference point // the Y-offset of the reference point from the top left corner // changes t_collisionRectY = srcFrameHeight - (collisionRectY + collisionRectHeight); t_collisionRectX = collisionRectX; t_collisionRectWidth = collisionRectWidth; t_collisionRectHeight = collisionRectHeight; // width and height are as before this.width = srcFrameWidth; this.height = srcFrameHeight; // the X-offset of the reference point from the top left corner // remains the same. // top left Y-co-ordinate changes break; case TRANS_ROT90: // NOTE: top left x and y coordinate must reflect the transformation // performed around the reference point // the bottom-left corner of the rectangle becomes the // top-left when rotated 90. // both X- and Y-offset to the top left corner may change // update the position information for the collision rectangle t_collisionRectX = srcFrameHeight - (collisionRectHeight + collisionRectY); t_collisionRectY = collisionRectX; t_collisionRectHeight = collisionRectWidth; t_collisionRectWidth = collisionRectHeight; // set width and height this.width = srcFrameHeight; this.height = srcFrameWidth; break; case TRANS_ROT180: // NOTE: top left x and y coordinate must reflect the transformation // performed around the reference point // width and height are as before // both X- and Y- offsets from the top left corner may change t_collisionRectX = srcFrameWidth - (collisionRectWidth + collisionRectX); t_collisionRectY = srcFrameHeight - (collisionRectHeight + collisionRectY); t_collisionRectWidth = collisionRectWidth; t_collisionRectHeight = collisionRectHeight; // set width and height this.width = srcFrameWidth; this.height = srcFrameHeight; break; case TRANS_ROT270: // the top-right corner of the rectangle becomes the // top-left when rotated 270. // both X- and Y-offset to the top left corner may change // update the position information for the collision rectangle t_collisionRectX = collisionRectY; t_collisionRectY = srcFrameWidth - (collisionRectWidth + collisionRectX); t_collisionRectHeight = collisionRectWidth; t_collisionRectWidth = collisionRectHeight; // set width and height this.width = srcFrameHeight; this.height = srcFrameWidth; break; case TRANS_MIRROR_ROT90: // both X- and Y- offset from the top left corner may change // update the position information for the collision rectangle t_collisionRectX = srcFrameHeight - (collisionRectHeight + collisionRectY); t_collisionRectY = srcFrameWidth - (collisionRectWidth + collisionRectX); t_collisionRectHeight = collisionRectWidth; t_collisionRectWidth = collisionRectHeight; // set width and height this.width = srcFrameHeight; this.height = srcFrameWidth; break; case TRANS_MIRROR_ROT270: // both X- and Y- offset from the top left corner may change // update the position information for the collision rectangle t_collisionRectY = collisionRectX; t_collisionRectX = collisionRectY; t_collisionRectHeight = collisionRectWidth; t_collisionRectWidth = collisionRectHeight; // set width and height this.width = srcFrameHeight; this.height = srcFrameWidth; break; default: // INVALID TRANSFORMATION! throw new IllegalArgumentException(); } } /** * Given the x and y offsets off a pixel from the top left * corner, in an untransformed sprite, * calculates the x coordinate of the pixel when the same sprite * is transformed, with the coordinates of the top-left pixel * of the transformed sprite as (0,0). * * @param inp_x Horizontal offset within the untransformed sprite * @param inp_y Vertical offset within the untransformed sprite * @param transform transform for the sprite * @return The x-offset, of the coordinates of the pixel, * with the top-left corner as 0 when transformed. */ int getTransformedPtX(int inp_x, int inp_y, int transform) { int t_x = 0; switch (transform) { case TRANS_NONE: t_x = inp_x; break; case TRANS_MIRROR: t_x = srcFrameWidth - inp_x - 1; break; case TRANS_MIRROR_ROT180: t_x = inp_x; break; case TRANS_ROT90: t_x = srcFrameHeight - inp_y - 1; break; case TRANS_ROT180: t_x = srcFrameWidth - inp_x - 1; break; case TRANS_ROT270: t_x = inp_y; break; case TRANS_MIRROR_ROT90: t_x = srcFrameHeight - inp_y - 1; break; case TRANS_MIRROR_ROT270: t_x = inp_y; break; default: // for safety/completeness. Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI, "Sprite: transform=" + transform); break; } return t_x; } /** * Given the x and y offsets off a pixel from the top left * corner, in an untransformed sprite, * calculates the y coordinate of the pixel when the same sprite * is transformed, with the coordinates of the top-left pixel * of the transformed sprite as (0,0). * * @param inp_x Horizontal offset within the untransformed sprite * @param inp_y Vertical offset within the untransformed sprite * @param transform transform for the sprite * @return The y-offset, of the coordinates of the pixel, * with the top-left corner as 0 when transformed. */ int getTransformedPtY(int inp_x, int inp_y, int transform) { int t_y = 0; switch (transform) { case TRANS_NONE: t_y = inp_y; break; case TRANS_MIRROR: t_y = inp_y; break; case TRANS_MIRROR_ROT180: t_y = srcFrameHeight - inp_y - 1; break; case TRANS_ROT90: t_y = inp_x; break; case TRANS_ROT180: t_y = srcFrameHeight - inp_y - 1; break; case TRANS_ROT270: t_y = srcFrameWidth - inp_x - 1; break; case TRANS_MIRROR_ROT90: t_y = srcFrameWidth - inp_x - 1; break; case TRANS_MIRROR_ROT270: t_y = inp_x; break; default: // for safety/completeness. Logging.report(Logging.ERROR, LogChannels.LC_HIGHUI, "Sprite: transform=" + transform); break; } return t_y; } // --- member variables /** * If this bit is set, it denotes that the transform causes the * axes to be interchanged */ private static final int INVERTED_AXES = 0x4; /** * If this bit is set, it denotes that the transform causes the * x axis to be flipped. */ private static final int X_FLIP = 0x2; /** * If this bit is set, it denotes that the transform causes the * y axis to be flipped. */ private static final int Y_FLIP = 0x1; /** * Bit mask for channel value in ARGB pixel. */ private static final int ALPHA_BITMASK = 0xff000000; /** * Alpha channel value for full opacity. */ private static final int FULLY_OPAQUE_ALPHA = 0xff000000; /** * Source image */ Image sourceImage; /** * The number of frames */ int numberFrames; // = 0; /** * list of X coordinates of individual frames */ int[] frameCoordsX; /** * list of Y coordinates of individual frames */ int[] frameCoordsY; /** * Width of each frame in the source image */ int srcFrameWidth; /** * Height of each frame in the source image */ int srcFrameHeight; /** * The sequence in which to display the Sprite frames */ int[] frameSequence; /** * The sequence index */ private int sequenceIndex; // = 0 /** * Set to true if custom sequence is used. */ private boolean customSequenceDefined; // = false; // -- reference point /** * Horizontal offset of the reference point * from the top left of the sprite. */ int dRefX; // =0 /** * Vertical offset of the reference point * from the top left of the sprite. */ int dRefY; // =0 // --- collision rectangle /** * Horizontal offset of the top left of the collision * rectangle from the top left of the sprite. */ int collisionRectX; // =0 /** * Vertical offset of the top left of the collision * rectangle from the top left of the sprite. */ int collisionRectY; // =0 /** * Width of the bounding rectangle for collision detection. */ int collisionRectWidth; /** * Height of the bounding rectangle for collision detection. */ int collisionRectHeight; // --- transformation(s) // --- values that may change on setting transformations // start with t_ /** * The current transformation in effect. */ int t_currentTransformation; /** * Horizontal offset of the top left of the collision * rectangle from the top left of the sprite. */ int t_collisionRectX; /** * Vertical offset of the top left of the collision * rectangle from the top left of the sprite. */ int t_collisionRectY; /** * Width of the bounding rectangle for collision detection, * with the current transformation in effect. */ int t_collisionRectWidth; /** * Height of the bounding rectangle for collision detection, * with the current transformation in effect. */ int t_collisionRectHeight; }