/*
* @(#)Sprite.java 1.110 02/10/01 @(#)
*
* Copyright (c) 2002 Sun Microsystems, Inc. All rights reserved.
* PROPRIETARY/CONFIDENTIAL
* Use is subject to license terms.
*/
package javax.microedition.lcdui.game;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
/**
* 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>
* @since MIDP 2.0
*/
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
this.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
this.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 x the horizontal location of the reference pixel, relative
* to the left edge of the un-transformed frame
* @param 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 x, int y) {
dRefX = x;
dRefY = y;
}
/**
* Sets this Sprite's position such that its reference pixel is located
* at (x,y) in the painter's coordinate system.
* @param x the horizontal location at which to place the reference pixel
* @param y the vertical location at which to place the reference pixel
* @see #defineReferencePixel
* @see #getRefPixelX
* @see #getRefPixelY
*/
public void setRefPixelPosition(int x, int y) {
// update this.x and this.y
this.x = x - getTransformedPtX(dRefX, dRefY,
this.t_currentTransformation);
this.y = y - getTransformedPtY(dRefX, dRefY,
this.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 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 sequenceIndex) {
if (sequenceIndex < 0 || sequenceIndex >= frameSequence.length) {
throw new IndexOutOfBoundsException();
}
this.sequenceIndex = 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 x the horizontal location of the collision rectangle relative to
* the untransformed Sprite's left edge
* @param 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 x, int y, int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException();
}
collisionRectX = x;
collisionRectY = 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 x the horizontal location of the <code>Image</code>'s
* upper left corner
* @param 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 x, int y, boolean pixelLevel) {
// check if this Sprite is not visible
if (!(this.visible)) {
return false;
}
// if image is null
// image.getWidth() will throw NullPointerException
int otherLeft = x;
int otherTop = y;
int otherRight = x + image.getWidth();
int otherBottom = y + image.getHeight();
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;
// 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 - x;
int otherImageYOffset = intersectTop - 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) != 0) &&
((argbData2[x2] & ALPHA_BITMASK) != 0)) {
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;
}
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;
}
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 tranformed 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 x Horizontal offset within the untransformed sprite
* @param 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 x, int y, int transform) {
int t_x = 0;
switch (transform) {
case TRANS_NONE:
t_x = x;
break;
case TRANS_MIRROR:
t_x = srcFrameWidth - x - 1;
break;
case TRANS_MIRROR_ROT180:
t_x = x;
break;
case TRANS_ROT90:
t_x = srcFrameHeight - y - 1;
break;
case TRANS_ROT180:
t_x = srcFrameWidth - x - 1;
break;
case TRANS_ROT270:
t_x = y;
break;
case TRANS_MIRROR_ROT90:
t_x = srcFrameHeight - y - 1;
break;
case TRANS_MIRROR_ROT270:
t_x = y;
break;
default:
// INVALID TRANSFORMATION!
throw new IllegalArgumentException();
}
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 x Horizontal offset within the untransformed sprite
* @param 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 x, int y, int transform) {
int t_y = 0;
switch (transform) {
case TRANS_NONE:
t_y = y;
break;
case TRANS_MIRROR:
t_y = y;
break;
case TRANS_MIRROR_ROT180:
t_y = srcFrameHeight - y - 1;
break;
case TRANS_ROT90:
t_y = x;
break;
case TRANS_ROT180:
t_y = srcFrameHeight - y - 1;
break;
case TRANS_ROT270:
t_y = srcFrameWidth - x - 1;
break;
case TRANS_MIRROR_ROT90:
t_y = srcFrameWidth - x - 1;
break;
case TRANS_MIRROR_ROT270:
t_y = x;
break;
default:
// INVALID TRANSFORMATION!
throw new IllegalArgumentException();
}
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;
/**
* 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;
}
|