package net.sf.jiga.xtended.impl;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.*;
import java.lang.ref.ReferenceQueue;
import java.net.URISyntaxException;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.List;
import java.util.Map.Entry;
import java.util.*;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.event.IIOWriteProgressListener;
import javax.imageio.event.IIOWriteWarningListener;
import javax.media.jai.PerspectiveTransform;
import javax.swing.JComponent;
import javax.swing.Timer;
import net.sf.jiga.xtended.JXAException;
import net.sf.jiga.xtended.impl.game.Custom;
import net.sf.jiga.xtended.impl.game.RenderingScene;
import net.sf.jiga.xtended.impl.game.RenderingSceneComponent;
import net.sf.jiga.xtended.impl.game.RenderingSceneListener;
import net.sf.jiga.xtended.impl.game.gl.AnimationGLHandler;
import net.sf.jiga.xtended.impl.game.gl.GLFX;
import net.sf.jiga.xtended.impl.game.gl.GLHandler;
import net.sf.jiga.xtended.impl.game.gl.GLObjectHandler;
import net.sf.jiga.xtended.impl.game.gl.RenderingSceneGL;
import net.sf.jiga.xtended.impl.game.gl.geom.GLGeom;
import net.sf.jiga.xtended.impl.system.BufferIO;
import net.sf.jiga.xtended.kernel.*;
/**
* Animation of Sprite's The implemented timer is the most important part of an
* animation design pattern. It must implement a real time iterator to avoid
* visual artefacts while drawing the animation on screen. Yet correctly
* Threaded, the timer, a.k.a animator, calls a small timing framework to mesure
* a informal framerate and to retrieve next animation frame. Thereby nothing is
* done until the first paint-frame call occurs, whereas the buffer/cache is
* call-back to get the most current frame from the image file cache.
*
* @see RenderingSceneListener#reset()
*
* @see Sprite
* @author www.b23prodtm.info
*/
public class Animation implements Debugger, CompositeCapable, Resource, RenderingSceneComponent, Sf3Renderable, Externalizable, Threaded, AnimationHandler<Sprite> {
/**
* the DataFlavor used for the mime-type by this Animation class @default DataFlavor.javaSerializedObjectMimeType
*/
public static final String _MIME_TYPE = DataFlavor.javaSerializedObjectMimeType;
/**
* the file extensions supported by this Animation class @default mc2 MC2
*/
public static final String[] _MIME_EXT = new String[]{"mc2", "MC2"};
/**
* the DataFlavor used for identifying a Animation instance
*/
public static final DataFlavor _dataFlavor = new DataFlavor(Animation.class, "sf3jswing Animation");
static {
Custom._storeMIMETYPES(_MIME_TYPE, _MIME_EXT);
}
/**
* serial version UID to identify serialize operations
*/
private static final long serialVersionUID = 2323;
/**
* starting frame n
*/
protected int startingFrame;
/**
* @return returns the starting frame number
*/
@Override
public int getStartingFrame() {
return startingFrame;
}
/**
* prefix to each frame file
*/
protected String prefix;
/**
* @return returns the prefix to each frame file
*/
public String getPrefix() {
return prefix;
}
/**
* suffix to each frame file
*/
protected String suffix;
/**
* @return returns the suffix to each frame file
*/
public String getSuffix() {
return suffix;
}
/**
* current frame index
*/
protected int animator;
/**
* reverse playing @default false protected boolean reverse = false;
*/
/**
* timer
*/
protected transient Timer timer;
/**
* frames sorted map
*/
protected transient SortedMap<Integer, Sprite> frames;
/**
* cache map
*/
protected SpritesCacheManager<Integer, Sprite> spm;
/**
* returns the current cache instance to permit external access. NOTICE : as
* of SpritesCacheManager specs, the returned map may not equal sizes for
* keySet(),size() and values(). <i>To obtain the full size of the cache
* (living and swapped), please use {@linkplain Map#keySet()}.</i>
*
* @return the current cache instance to permit external access to the cache
* methods.
* @see #accessSynchCache()
*/
public SpritesCacheManager<Integer, Sprite> accessCache() {
return spm;
}
/**
* returns a synchronized Collections-view of the current cache instance.
* NOTICE : as of SpritesCacheManager specs, the returned map may not equal
* sizes for keySet(),size() and values(). <i>To obtain the full size of the
* cache (living and swapped), please use {@linkplain Map#keySet()}.</i>
*
* @return a synchronized Collections-view of the currrent cache instance.
*/
public SortedMap<Integer, Sprite> accessSynchCache() {
return frames;
}
/**
* image file cache map
*/
protected final SortedMap<Integer, File> imageFiles = Collections.synchronizedSortedMap(new TreeMap<Integer, File>());
/**
* image file cache map protected final SortedMap<Integer, File> texFiles =
* Collections.synchronizedSortedMap(new TreeMap<Integer, File>());
*/
/**
* sound fx
*/
protected transient SoundInput sfx;
public SoundInput getSfx() {
return sfx;
}
public int getSfx_frame() {
return sfx_frame;
}
public String getSfx_dir() {
return sfx_dir;
}
public String getSfx_path() {
return sfx_path;
}
public boolean isSfxIsRsrc() {
return sfxIsRsrc;
}
/**
* sound fx file path
*/
protected String sfx_path;
/**
* sound directory path
*/
protected String sfx_dir = "cache" + File.separator + "soundIO";
/**
* start time stamp (nanos)
*/
protected long start = 0;
/**
* last timer tick time stamp (ms)
*/
protected long lastTick = 0;
private final static BitStack bits = new BitStack();
/**
* paused state
*/
public final static int PAUSED = bits._newBitRange();
/**
* playing state
*/
public final static int PLAYING = bits._newBitRange();
/**
* stopped state
*/
public final static int STOPPED = bits._newBitRange();
/**
*
*/
public final static int STATE_ACTIVE_RENDERING = bits._newBitRange();
/**
*
*/
public final static int STATE_COMPRESSION = bits._newBitRange();
/**
*
*/
public final static int STATE_SWAP = bits._newBitRange();
/**
*
*/
public final static int STATE_REVERSED = bits._newBitRange();
/**
*
*/
public final static int STATE_JLAYER = bits._newBitRange();
/**
*
*/
public final static int STATE_TRANSFORM = bits._newBitRange();
/**
* resource LOADED state
*/
public final static int LOADED = bits._newBitRange();
/**
* resource CLEARED state
*/
public final static int CLEARED = bits._newBitRange();
/**
* resource LOADING state
*/
public final static int LOADING = bits._newBitRange();
/**
* current state
*
* @see #PLAYING
* @see #STOPPED
* @see #PAUSED
* @see #playerStatus()
*/
protected int statusID = CLEARED | STOPPED | STATE_ACTIVE_RENDERING | STATE_SWAP | STATE_TRANSFORM;
/**
* frames amount count
*/
public int length;
/**
* current framerate mesured in nanos, default is 24FPS
*
* @see #timer
*/
public long frameRate = Math.round(1000000000. / 24.); /*
* nanoseconds
*/
/**
* last drawn frame index
*/
protected int lastFramePosition = 0;
/**
* next constant value, used for iterating
*
* @see #getAnimatorValue(int, int)
*/
public static final int NEXT = 0;
/**
* previous constant value, used for iterating
*
* @see #getAnimatorValue(int, int)
*/
public static final int PREVIOUS = 1;
/**
* sound inner-resource dis/enabled
*/
protected boolean sfxIsRsrc;
public Animation() {
this(1, "image/x-png", SpriteIO._WRITE_TILES_DIMENSION);
}
/**
* Initializes an animation with the specified disk base link and frame
* indexes.
*
* @see net.sf.jiga.sf3.system.Resource
* @see Sprite#mime
* @see Sprite#size @discussion (comprehensive description)
* @param startingFrame starting frame number that appears in frame sprites
* file names
* @param endingFrame ending frame number that appears in frame sprites file
* names
* @param baseLink string path to base directory of the frame sprites, if
* rsrcMode is true, then only reachable resources paths may be available.
* @param prefix string prefix to every frame sprites filenames
* @param suffix string suffix to every frame sprites filenames (usually
* file .extension)
* @param format image mime type of Sprites
* @param size desired image size of Sprites
* @param rsrcMode inner-resource mode for frame sprites files dis/enabled
* @throws java.net.URISyntaxException if the base path does not exist
*/
public Animation(String baseLink, boolean rsrcMode, int startingFrame, int endingFrame, String prefix, String suffix, String format, Dimension size) throws URISyntaxException {
renderableImpl = new Sprite(false, baseLink, rsrcMode, format, size, true);
sfxIsRsrc = rsrcMode;
this.length = endingFrame - startingFrame + 1;
frames = Collections.synchronizedSortedMap(spm = new SpritesCacheManager<Integer, Sprite>(Math.max(1, length)));
spm.setCompressionEnabled(isCompressedCacheEnabled());
spm.setSwapDiskEnabled(true);
this.startingFrame = startingFrame;
this.prefix = prefix;
this.suffix = suffix;
animator = (isReverseEnabled()) ? length - 1 : 0;
stop();
}
@Override
public void setUseIIOCacheEnabled(boolean b) {
renderableImpl.setUseIIOCacheEnabled(b);
}
@Override
public boolean isUseIIOCacheEnabled() {
return renderableImpl.isUseIIOCacheEnabled();
}
/**
* Initializes the animation with blank frames that can be drawn on using
* the getImage() method on each Sprite.
*
* @see #getImage(Component)
*
* @param length amount of frames to put in this Animation
* @param size desired dimension of each sprite image
* @param format sprite image mime type to use (e.g. image/jpeg)
* @see #loadBlank(int) @discussion (comprehensive description)
*/
public Animation(int length, String format, Dimension size) {
renderableImpl = new Sprite(false, SpriteIO.createBufferedImage(size, Sprite.DEFAULT_TYPE), format, size);
renderableImpl.setUseIIOCacheEnabled(false);
sfxIsRsrc = renderableImpl.innerResource;
this.length = length;
this.frames = Collections.synchronizedSortedMap((spm = new SpritesCacheManager<Integer, Sprite>(Math.max(1, length))));
spm.setCompressionEnabled(isCompressedCacheEnabled());
spm.setSwapDiskEnabled(true);
animator = (isReverseEnabled()) ? length - 1 : 0;
stop();
}
/**
* will automatically switch to {@linkplain #setActiveRenderingEnabled(boolean) passive rendering}
* when the returned component is added to a layout Container. NOTICE : {@link #play() call to play}
* after the component's been added to a container !
*/
public static JComponent _makeAnimationJComponent(final Animation anim) {
JComponent render = new JComponent() {
@Override
public void addNotify() {
super.addNotify();
anim.setActiveRenderingEnabled(false);
}
@Override
public void removeNotify() {
super.removeNotify();
anim.setActiveRenderingEnabled(true);
}
@Override
protected void paintComponent(Graphics g) {
anim.paintComponentImpl(g, this);
}
};
render.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
/*
* to avoid crappy affect if the component is stretched out*
* super.componentResized(e); anim.clearResource();-- already
* clearresource (at draw())
*/
}
});
anim.renderableImpl.setObs(render);
render.setMinimumSize(anim.getSize());
render.setPreferredSize(anim.getSize());
return render;
}
/**
* method that clears the resources on finalization.
*
* @see #clearResource()
* @throws java.lang.Throwable thrown by the super class
*/
@Override
public void finalize() throws Throwable {
stop();
frames.clear();
super.finalize();
}
private void writeFilesMap(SortedMap<Integer, File> map, ObjectOutput out) throws IOException {
synchronized (map) {
for (Iterator<Integer> i = map.keySet().iterator(); i.hasNext();) {
int key;
File f = map.get(key = i.next());
out.writeInt(key);
out.writeObject(f);
if (f.exists()) {
RandomAccessFile raf = new RandomAccessFile(f, "r");
out.writeLong(raf.length());
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int read = 0;
while ((read = raf.read(b)) != -1) {
out.write(b, 0, read);
}
raf.close();
} else {
out.writeLong(0L);
}
}
}
}
private void readFilesMap(SortedMap<Integer, File> map, ObjectInput in) throws IOException, ClassNotFoundException {
synchronized (map) {
for (Iterator<Integer> i = map.keySet().iterator(); i.hasNext();) {
int key, rkey;
File f, rf;
f = map.get(key = i.next());
rkey = in.readInt();
rf = (File) in.readObject();
if (key != rkey) {
throw new JXAException("wrong key in Animation " + renderableImpl.base + " image files base : " + key + " read : " + rkey);
}
if (!f.equals(rf)) {
throw new JXAException("wrong file in Animation " + renderableImpl.base + " image files base : " + f + " read : " + rf);
}
long len;
boolean skipped = false;
if ((len = in.readLong()) > 0) {
if (!f.exists() || f.length() != len) {
if (!f.exists()) {
if ((f = new File(FileHelper._TMPDIRECTORY.getPath() + File.separator + SpriteIO.image_dir + File.separator + f.getName())).isFile()) {
skipped = true;
}
}
if (!skipped) {
f.getParentFile().mkdirs();
FileHelper._makeWritable(f.getParentFile());
RandomAccessFile raf = new RandomAccessFile(f, "rw");
raf.setLength(len);
FileOutputStream fos = new FileOutputStream(raf.getFD());
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int readBytes = 0;
int rBytes;
boolean fread = true;
do {
if (readBytes + b.length > len) {
in.readFully(b, 0, rBytes = ((int) len) - readBytes);
} else {
rBytes = in.read(b);
}
if (rBytes != -1) {
fos.write(b, 0, rBytes);
readBytes += rBytes;
} else {
fread = false;
}
if (readBytes >= len) {
fread = false;
}
} while (fread);
fos.close();
raf.close();
}
} else {
skipped = true;
}
if (skipped) {
int skip = in.skipBytes((int) len);
}
f.deleteOnExit();
}
}
}
}
private void prepareWrite(ObjectOutput out) throws IOException {
stop();
/*
* setSwapDiskCacheEnabled(true);
*/
loadResource();
/**
* storing mode
*/
List<Integer> removals = Collections.synchronizedList(new ArrayList<Integer>());
synchronized (frames) {
for (int s : frames.keySet()) {
Sprite sp = frames.get(s);
if (sp instanceof Sprite) {
refreshFiles(sp, s);
} else {
removals.add(s);
}
}
for (Integer s : removals) {
frames.remove(s);
}
}
}
private void endWrite(ObjectOutput out) throws IOException {
if ((getStoreMode() & MODE_JAVA2D) != 0) {
writeFilesMap(imageFiles, out);
}
/*
* if ((getStoreMode() & MODE_TEXTURE) != 0) { writeFilesMap(texFiles,
* out);
}
*/
if (sfx.isResourceLoaded()) {
out.writeBoolean(true);
FileInputStream fis = null;
BufferedInputStream bis = (sfxIsRsrc) ? new BufferedInputStream(getClass().getResourceAsStream(sfx_path)) : new BufferedInputStream(fis = new FileInputStream(sfx_path));
File d = new File(FileHelper._TMPDIRECTORY.getPath() + File.separator + sfx_dir);
d.mkdirs();
FileHelper._makeWritable(d);
File sfxFile = FileHelper._createTempFile("sfx_" + hashCode(), d, true);
out.writeObject(sfxFile);
RandomAccessFile raf = new RandomAccessFile(sfxFile, "rw");
raf.setLength(bis.available());
FileOutputStream fos = new FileOutputStream(raf.getFD());
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int read = 0;
while ((read = bis.read(b)) != -1) {
fos.write(b, 0, read);
}
fos.close();
bis.close();
raf.close();
raf = new RandomAccessFile(sfxFile, "r");
out.writeLong(sfxFile.length());
b = new byte[FileHelper._SMALLBUFFFER_SIZE];
read = 0;
while ((read = raf.read(b)) != -1) {
out.write(b, 0, read);
}
raf.close();
if (fis instanceof FileInputStream) {
fis.close();
}
} else {
out.writeBoolean(false);
}
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
if (isDebugEnabled()) {
System.out.println("* Animation's serializing (Externally)...");
}
prepareWrite(out);
/*
* super.writeExternal(out);
*/
/*
*
*/
out.writeObject(renderableImpl);
out.writeInt(animator);
out.writeLong(frameRate);
synchronized (imageFiles) {
out.writeInt(imageFiles.size());
for (Iterator<Entry<Integer, File>> it = imageFiles.entrySet().iterator(); it.hasNext();) {
Entry<Integer, File> e = it.next();
out.writeInt(e.getKey());
out.writeObject(e.getValue());
}
}
out.writeInt(lastFramePosition);
out.writeLong(lastTick);
out.writeInt(length);
out.writeUTF(prefix);
out.writeBoolean(sfxIsRsrc);
if (sfx_dir != null) {
out.writeBoolean(true);
out.writeUTF(sfx_dir);
} else {
out.writeBoolean(false);
}
out.writeInt(sfx_frame);
if (sfx_path != null) {
out.writeBoolean(true);
out.writeUTF(sfx_path);
} else {
out.writeBoolean(false);
}
out.writeObject(spm);
out.writeLong(start);
out.writeInt(startingFrame);
out.writeInt(statusID);
out.writeUTF(suffix);
endWrite(out);
if (isDebugEnabled()) {
System.out.println("* Animation's been serialized (Externally).");
}
out.flush();
Thread.currentThread().setPriority(pty);
}
private void endRead(ObjectInput in) throws IOException, ClassNotFoundException {
sfx_played = false;
frames = Collections.synchronizedSortedMap(spm);
if ((getStoreMode() & MODE_JAVA2D) != 0) {
readFilesMap(imageFiles, in);
}
/*
* if ((getStoreMode() & MODE_TEXTURE) != 0) { readFilesMap(texFiles,
* in);
}
*/
if (in.readBoolean()) {
File dir = new File(FileHelper._TMPDIRECTORY.getPath() + File.separator + sfx_dir);
dir.mkdirs();
FileHelper._makeWritable(dir);
File _sfx = (File) in.readObject();
if (_sfx == null) {
_sfx = FileHelper._createTempFile("sfx_" + hashCode(), dir, true);
}
long len = in.readLong();
boolean skipped = false;
if (!_sfx.exists() || _sfx.length() != len) {
if (!_sfx.exists()) {
if ((_sfx = new File(FileHelper._TMPDIRECTORY.getPath() + File.separator + sfx_dir + File.separator + _sfx.getName())).isFile()) {
skipped = true;
}
}
if (!skipped) {
_sfx.getParentFile().mkdirs();
FileHelper._makeWritable(_sfx.getParentFile());
RandomAccessFile raf = new RandomAccessFile(_sfx, "rw");
raf.setLength(len);
FileOutputStream fos = new FileOutputStream(raf.getFD());
byte[] b = new byte[FileHelper._SMALLBUFFFER_SIZE];
int readBytes = 0;
int rBytes;
boolean fread = true;
do {
if (readBytes + b.length > len) {
in.readFully(b, 0, rBytes = ((int) len) - readBytes);
} else {
rBytes = in.read(b);
}
if (rBytes != -1) {
fos.write(b, 0, rBytes);
readBytes += rBytes;
} else {
fread = false;
}
if (readBytes >= len) {
fread = false;
}
} while (fread);
fos.close();
raf.close();
if (isDebugEnabled()) {
System.out.println("Animation serialized sfx path: " + sfx_path);
}
}
} else {
skipped = true;
}
if (skipped) {
in.skipBytes((int) len);
}
_sfx.deleteOnExit();
sfx_path = _sfx.getAbsolutePath();
setSfx(sfx_path, false, sfx_frame);
}
goLoadState(CLEARED);
if (isDebugEnabled()) {
System.out.println("* Animation's been deserialized (Externally).");
}
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
if (isDebugEnabled()) {
System.out.println("* Animation is deserializing...");
}
renderableImpl = (Sprite) in.readObject();
if (isDebugEnabled()) {
System.out.println("* Animation " + renderableImpl.base + " is deserializing...");
}
animator = in.readInt();
frameRate = in.readLong();
int s = in.readInt();
for (int i = 0; i < s; i++) {
imageFiles.put(in.readInt(), (File) in.readObject());
}
lastFramePosition = in.readInt();
lastTick = in.readLong();
length = in.readInt();
prefix = in.readUTF();
sfxIsRsrc = in.readBoolean();
if (in.readBoolean()) {
sfx_dir = in.readUTF();
}
sfx_frame = in.readInt();
if (in.readBoolean()) {
sfx_path = in.readUTF();
}
spm = (SpritesCacheManager<Integer, Sprite>) in.readObject();
start = in.readLong();
startingFrame = in.readInt();
statusID = in.readInt();
suffix = in.readUTF();
endRead(in);
Thread.currentThread().setPriority(pty);
}
/**
* sets the animation duration which modifies the current framerate, e.g.
* 3000 ms for an amount 3 frames will set the framerate to 1FPS for this
* animation.
*
* @param millis the desired duration in ms
*/
@Override
public void setRealTimeLength(long millis) {
frameRate = Math.round((double) millis * 1000000. / (double) length);
}
/**
* returns duration based on framerate and frame length of the animation.
*
* @return current duration in millis
*/
@Override
public long realTimeLength() {
return Math.round(realTimeLengthNanos() / 1000000.);
}
/**
*
*/
private long realTimeLengthNanos() {
return frameRate * length;
}
/**
*
*/
/**
* elapsed period of frames since the animator player started
*
* @return elapsed amount of frames (always positive or zero)
* @see #elapsedTime()
*/
private int elapsedFrames() {
return Math.round((float) elapsedTime() / (float) realTimeLengthNanos() * (float) length);
}
/**
* elapsed period of time since the last tick occured on animator timer
*
* @return elapsed time in ms since last tick of the timer
* @see #lastTick
*/
private long elapsedTickTime() {
long t = System.nanoTime() - lastTick;
if (playerStatus() != PLAYING) {
t = 0;
}
return t;
}
/**
* elapsed frames since last tick occured on animator timer
*
* @return elapsed amount of frames since last tick of the timer
* @see #elapsedTickTime()
*/
private int elapsedTickFrames() {
return Math.round((float) elapsedTickTime() / (float) realTimeLengthNanos() * (float) length);
}
/**
* re-adjusts startTime to the animation current timeframe if needed,
* thereby the animator can loop. But use play() to make loops is the
* correct manner.
*
* @return new start time in ms if current is to old
* @see #start
* @see #play()
*/
private long adjustStartTime() {
if (elapsedTime() >= realTimeLengthNanos() || playerStatus() != PLAYING) {
lastTick = System.nanoTime();
start = lastTick - getTimeFramePositionNanos();
}
return start;
}
/**
* loads and returns one blank frame with the specified index with the
* current settings (size, mime type, etc.)
*
* @param i frame index (it is painted on the background of the sprite)
* @return frame Sprite
*/
private Sprite loadBlank(int i) {
if (isDebugEnabled()) {
System.out.println("Animation : loading blank sprite at frame " + i);
}
Image img = (!renderableImpl.isBufferedImageEnabled()) ? (Image) Sprite.createVolatileImage(renderableImpl.getSize(), Sprite._getTransparency(renderableImpl._type)) : SpriteIO.createBufferedImage(renderableImpl.getSize(), renderableImpl._type);
Graphics dataG = Sprite._createImageGraphics(img);
Graphics2D g = Sprite.wrapRendering(dataG);
g.setFont(new Font("Arial", Font.ITALIC, 20));
g.setColor(Color.BLACK);
g.fillRect(0, 0, renderableImpl.getBounds().width, renderableImpl.getBounds().height);
g.setColor(Color.GRAY);
g.drawString("(" + (i + 1) + ")", (int) ((float) renderableImpl.getBounds().width / 2.0f), (int) ((float) renderableImpl.getBounds().height / 2.0f));
dataG.dispose();
Sprite sp = (!renderableImpl.isBufferedImageEnabled()) ? new Sprite((VolatileImage) img, renderableImpl.mime, renderableImpl.getSize()) : new Sprite((BufferedImage) img, renderableImpl.mime, renderableImpl.getSize());
return sp;
}
/**
* marks the FPS monitor to mesure the actual framerate
*
* @return actual fps
*/
protected double markFPS() {
int newFramePosition = 0;
double fps = 0;
if ((newFramePosition = getPosition()) != lastFramePosition) {
fps = renderableImpl.markFPS();
lastFramePosition = newFramePosition;
}
return fps;
}
/**
* instances a new Sound interface
*
* @return one sound interface
* @see Sound
*/
private SoundInput initSound() {
return initSound(sfx_path, sfxIsRsrc);
}
private SoundInput initSound(String sfx_path, boolean sfxIsRsrc) {
return sfx = new SoundInput(this.sfx_path = sfx_path, this.sfxIsRsrc = sfxIsRsrc);
}
/**
* computes the hashcode of the animation
*
* @return the hashcode
*/
@Override
public int hashCode() {
return renderableImpl.hashCode() + ((frames != null) ? frames.hashCode() : 0);
}
/**
* checks for equality with the specified object
*
* @return true or false
* @param o the specified object to check for equality
*/
@Override
public boolean equals(Object o) {
return o != null ? hashCode() == o.hashCode() : false;
}
/**
* gets the playerStatus of the animation
*
* @return playerStatus id
* @see #STOPPED
* @see #PLAYING
* @see #PAUSED
* @see #STATE_ACTIVE_RENDERING
* @see #state
*/
protected int playerStatus() {
return statusID & (STOPPED | PLAYING | PAUSED);
}
/**
* player state
*
* @param state go to the specified state (private use only, this is not
* safe)
* @see #PLAYING
* @see #PAUSED
* @see #STOPPED
*/
private void goPlayerState(int state) {
statusID = (statusID & ~(PLAYING | PAUSED | STOPPED)) | state;
}
/**
* resource loader state
*
* @param state go to the specified LOAD state (private use only, this is
* not safe)
* @see #LOADED
* @see #CLEARED
* @see #LOADING
*/
private void goLoadState(int state) {
statusID = (statusID & ~(LOADING | LOADED | CLEARED)) | state;
}
/**
* same as getState() but the result is not a bitwise-OR combination.
*
* @return {@linkplain #PLAYING}, {@linkplain #STOPPED} or {@linkplain #PAUSED}
* @see #getState()
*/
@Override
public int getPlayerStatus() {
return playerStatus();
}
/**
* activates compressed cache map
*
* @param b compressed cachee enabled
* @see SpritesCacheManager#setCompressionEnabled(boolean)
*/
public void setCompressedCacheEnabled(boolean b) {
statusID = b ? statusID | STATE_COMPRESSION : statusID & ~STATE_COMPRESSION;
spm.setCompressionEnabled(b);
}
/**
* @return true or false, whether cache is compressed or not, resp.
*/
public boolean isCompressedCacheEnabled() {
return (statusID & STATE_COMPRESSION) != 0;
}
/**
* gets the state of the current animation
*
* @return current state, a bitwise-OR combination of the player
* playerStatus and rendering mode.
* ({@link #PLAYING}^{@link #PAUSED}^{@link #STOPPED}) | {@link #STATE_ACTIVE_RENDERING}
* | ...
* @see #playerStatus()
*/
public int getState() {
return statusID;
}
/**
* gets the current allocation size of cache
*
* @return current allocation size in percent
* @see SpritesCacheManager#allocSize()
*/
public double allocSize() {
return spm.allocSize();
}
/**
* gets the animation length
*
* @return frames amount of this animation
* @see #length
*/
@Override
public int length() {
return length;
}
/**
* positions the animator to the corresponding frame index
*
* @param i frame index position
* @return timeframe position in ms
* @see #getPosition()
*/
@Override
public long position(int i) {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
assert (i < length && i >= 0) : getClass().getCanonicalName() + " iterator is not in the correct interval!";
animator = i;
}
adjustStartTime();
return getTimeFramePosition();
}
/**
* returns the current position of the animator
*
* @return current animator value corresponding to frame index
*/
@Override
public int getPosition() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
return animator;
}
}
public boolean isValid() {
return isResourceLoaded();
}
public void invalidate() {
clearResource();
}
/**
* refreshes the animation and the cache map to current params and prints
* current iterator value. if activerendering is activated, it also runs the
* player thread to retrieve the animator index value to paint. it must be
* called each time refreshing the current frame image is required.
* @discussion (comprehensive description)
*
* @see #spm
* @see #setZoomEnabled(boolean, double)
* @see #setFlipEnabled(boolean, int)
* @see Sprite#runValidate()
*/
@Override
public Animation runValidate() {
spm.setCompressionEnabled(isCompressedCacheEnabled());
spm.setSwapDiskEnabled(true);
setZoomEnabled(isTransformEnabled(), renderableImpl.zoom);
renderableImpl.setFlipEnabled(isTransformEnabled(), renderableImpl.mirror);
if (!isResourceLoaded()) {
loadResource();
}
try {
final Monitor monitor0 = renderableImpl.renderableImpl.paintMonitor;
synchronized (monitor0) {
if (sfx instanceof Sound) {
if (!sfx.isResourceLoaded()) {
sfx.loadResource();
}
} else {
initSound().loadResource();
}
while (renderableImpl.painting) {
if (isDebugEnabled()) {
System.out.print(".");
}
monitor0.wait(1000);
}
renderableImpl.validating = true;
final Monitor monitor2 = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor2) {
if (playerStatus() == PLAYING) {
if (elapsedTime() >= realTimeLengthNanos()) {
stop();
} else {
if (isReverseEnabled()) {
animator -= elapsedTickFrames();
} else {
animator += elapsedTickFrames();
}
lastTick = System.nanoTime();
}
}
animator = Math.min(animator, length - 1);
animator = Math.max(animator, 0);
if (playerStatus() == PLAYING) {
if ((isReverseEnabled() ? sfx_frame >= animator : sfx_frame <= animator) && sfx.isResourceLoaded()) {
if (!sfx_played) {
sfx_played = playSfx();
}
} else {
sfx_played = false;
}
}
}
}
} catch (InterruptedException e) {
if (isDebugEnabled()) {
e.printStackTrace();
}
} finally {
final Monitor monitor1 = renderableImpl.renderableImpl.validateMonitor;
synchronized (monitor1) {
renderableImpl.validating = false;
monitor1.notifyAll();
}
return this;
}
}
/**
* returns the current frame sprite that the animator is pointing on
*
* @see Sprite
* @return current frame sprite
*/
@Override
public Sprite getCurrentSprite() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
return getSprite(animator);
}
}
/**
* returns the desired sprite instance at index
*
* @param index the specified index of the animation Sprite to return [start
* index; end index]
* @return the Sprite at the specified index
*/
@Override
public Sprite getSprite(int index) {
Sprite sp = null;
if ((sp = frames.get(index)) == null) {
sp = loadFrame(index);
}
return refreshSpriteData(sp, (isReverseEnabled()) ? length - 1 - index : index);
}
/**
* refreshes the specified Sprite instance to the current settings
*
* @param sp Sprite instance to refresh
* @param id the id on which the MediaTracker will track
* @return the refreshed Sprite instance
*/
private Sprite refreshSpriteData(Sprite sp, int id) {
synchronized (renderableImpl.renderableImpl.io.rProgList) {
for (IIOReadProgressListener iiorp : renderableImpl.renderableImpl.io.rProgList) {
sp.getIO().addIIOReadProgressListener(iiorp);
}
}
synchronized (renderableImpl.renderableImpl.io.rWarnList) {
for (IIOReadWarningListener iiorw : renderableImpl.renderableImpl.io.rWarnList) {
sp.getIO().addIIOReadWarningListener(iiorw);
}
}
synchronized (renderableImpl.renderableImpl.io.wProgList) {
for (IIOWriteProgressListener iiowp : renderableImpl.renderableImpl.io.wProgList) {
sp.getIO().addIIOWriteProgressListener(iiowp);
}
}
synchronized (renderableImpl.renderableImpl.io.wWarnList) {
for (IIOWriteWarningListener iioww : renderableImpl.renderableImpl.io.wWarnList) {
sp.getIO().addIIOWriteWarningListener(iioww);
}
}
sp.setBufferedType(renderableImpl._type);
sp.setRenderingScene(getRenderingScene());
Sf3RenderableImpl.modeCopy(this, sp);
sp.setTexPty(renderableImpl.getTexPty());
sp.setMt(renderableImpl.renderableImpl.io.mt, renderableImpl.renderableImpl.io.obs);
sp.setZoomEnabled(isTransformEnabled(), renderableImpl.zoom);
sp.setFlipEnabled(isTransformEnabled(), renderableImpl.mirror);
if (isTransformEnabled()) {
sp.setTX(renderableImpl.transforming);
sp.setPTX(renderableImpl.perspectiveTransforming);
}
/**
* avoid resizing after a transform has completed
*/
try {
if (renderableImpl._nextTransformSize(sp, sp.renderableImpl.bounds.getSize()).equals(sp.renderableImpl.bounds.getSize()) && (sp.isZoomEnabled() || (isTransformEnabled() && (!renderableImpl.transforming.isIdentity() || !renderableImpl.perspectiveTransforming.isIdentity())))) {
/*
* transform was performed, no resize to bounds is allowed
*/
} else {
sp.renderableImpl.bounds.setSize(renderableImpl.getSize());
sp.invalidate();
}
} catch (NoninvertibleTransformException ex) {
if (isDebugEnabled()) {
ex.printStackTrace();
}
} finally {
sp.setOpaque(isOpaque());
sp.setCompositeEnabled(renderableImpl.compositeEnabled);
sp.setComposite(renderableImpl.cps);
sp.setPaint(renderableImpl.pnt);
sp.setColor(renderableImpl.clr);
sp.setTrackerPty(id);
sp.setSPM(spm);
return sp;
}
}
private static SortedMap<Integer, SortedMap<Integer, Integer>> spritesHashes = Collections.synchronizedSortedMap(new TreeMap<Integer, SortedMap<Integer, Integer>>());
/**
*
*/
public static SortedMap<Integer, Integer> _GLgetSpritesHashes(int animHash) {
if (!spritesHashes.containsKey(animHash)) {
spritesHashes.put(animHash, new TreeMap<Integer, Integer>());
}
return spritesHashes.get(animHash);
}
@Override
public boolean isResourceLoaded() {
return (statusID & LOADED) != 0;
}
/**
* @param a an Animation instance or its hashcode
*/
public static boolean _GLIsLoaded(Object a) {
return RenderingSceneGL._GLgetLoadState(a) == RenderingSceneGL._GLLOADSTATE_Loaded;
}
/**
* refresh sprite on swap and cache files
*/
private void refreshFiles(Sprite sp, int i) {
sp = refreshSpriteData(sp, (isReverseEnabled()) ? length - 1 - i : i);
frames.put(i, sp);
if (sp.src instanceof File) {
imageFiles.put(i, (File) sp.src);
}
/*
* if (sp.texSrc instanceof File) { texFiles.put(i, (File) sp.texSrc);
}
*/
}
/**
* loads the frame sprites indexed by the specified number
*
* @param i index of the frame
* @return frame sprite loaded at index i
*/
private Sprite loadFrame(int i) {
Sprite sp = null;
if (!frames.containsKey(i)) {
if (isDebugEnabled()) {
System.out.println("Animation load " + i + "...");
}
if (renderableImpl.base != null) {
String path = renderableImpl.src + ((renderableImpl.innerResource) ? "/" : File.separator) + prefix + (int) (startingFrame + i) + suffix;
if (isDebugEnabled()) {
System.out.println("loading frame: " + ((renderableImpl.innerResource) ? getClass().getResource(path) : path));
}
try {
sp = new Sprite(path, renderableImpl.innerResource, renderableImpl.mime, renderableImpl.getSize(), renderableImpl.isBufferedImageEnabled());
} catch (Exception ex) {
ex.printStackTrace();
sp = loadError(i);
}
} else {
sp = loadBlank(i);
}
refreshFiles(sp, i);
} else {
sp = frames.get(i);
sp = refreshSpriteData(sp, (isReverseEnabled()) ? length - 1 - i : i);
}
sp.loadResource();
/*
* SpriteGLHandler spHdr = (SpriteGLHandler)
* Sprite._GLHandlers.getHandler(sp);
* spHdr.setrCoord(Sf3Texture3D._3DTexLayersAmount * (float) i);
*/
_GLgetSpritesHashes(hashCode()).put(i, sp.hashCode());/*
* spHdr);
*/
return sp;
}
/**
* loads and returns a frame sprite instance for error at index i
*
* @param i the index to give error at
* @return frame sprite instance
*/
private Sprite loadError(int i) {
Image img = (!renderableImpl.isBufferedImageEnabled()) ? (Image) Sprite.createVolatileImage(renderableImpl.getSize(), Sprite._getTransparency(renderableImpl._type)) : SpriteIO.createBufferedImage(renderableImpl.getSize(), renderableImpl._type);
Graphics imgG = Sprite._createImageGraphics(img);
Graphics2D g = Sprite.wrapRendering(imgG);
g.setFont(new Font("Arial", Font.ITALIC, 20));
g.setColor(Color.RED);
g.fillRect(0, 0, renderableImpl.getBounds().width, renderableImpl.getBounds().height);
g.setColor(Color.GRAY);
g.drawString("error_" + i, (int) ((float) renderableImpl.getBounds().width / 2.0f), (int) ((float) renderableImpl.getBounds().height / 2.0f));
imgG.dispose();
Sprite sp = (!renderableImpl.isBufferedImageEnabled()) ? new Sprite((VolatileImage) img, renderableImpl.mime, renderableImpl.getSize()) : new Sprite((BufferedImage) img, renderableImpl.mime, renderableImpl.getSize());
return sp;
}
/**
* unloads the frame sprite instance indexed with the argument. this sprite
* instance can be recovered if the isSwapDiskCacheEnabled() returns true.
* @discussion (comprehensive description)
*
* @param i index of the frame sprite instance
*/
protected void unloadFrame(int i) {
if (frames.containsKey(i)) {
Sprite sp = frames.get(i);
sp.clearResource();
try {
spm.memorySensitiveCallback("memoryClear", spm, new Object[]{sp}, new Class[]{Object.class});
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
/**
* elapsed period of time since the animator timer started; the playerStatus
* is changed to stopped-state upon the end of the Animation if it is
* reached or passed.
*
* @return elapsed time since started in nanos
* @see #realTimeLengthNanos()
*/
private long elapsedTime() {
long t = System.nanoTime() - start;
if (playerStatus() != PLAYING) {
t = 0;
}
return t;
}
/**
* current time-frame position
*
* @return current time-frame position in ms
* @see #getPosition()
* @see #realTimeLength()
*/
@Override
public long getTimeFramePosition() {
return Math.round(getTimeFramePositionNanos() / 1000000.);
}
private long getTimeFramePositionNanos() {
long posTime = length > 1 ? (long) Math.round(((double) getPosition() / ((double) length - 1.)) * (double) realTimeLengthNanos()) : 0;
return isReverseEnabled() ? realTimeLengthNanos() - posTime : posTime;
}
/**
* positions the animator to the corresponding time-frame
*
* @return corresponding new positioned frame index value
* @see #position(int)
* @param timeFrame time frame position in ms (must be positive and less
* than the total real length time)
* @see #realTimeLength()
*/
@Override
public int position(long timeFrame) {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
if (isReverseEnabled()) {
timeFrame = realTimeLength() - timeFrame;
}
animator = Math.min(length - 1, Math.max(0, (int) Math.round(((float) timeFrame / (float) realTimeLength()) * (float) length)));
position(animator);
return animator;
}
}
/**
* draws with the specified AffineTransform and PerspectiveTransform
* instances
*
* @return true or false whether the drawing's completed or not (this value
* cannot be used for synchronization)
* @param g2 graphics instance to draw on
* @param tx transform instance
* @param ptx perspective transform instance
* @param obs the component that is observing this animation
*/
@Override
public boolean draw(Component obs, Graphics2D g2, AffineTransform tx, PerspectiveTransform ptx) {
try {
return __draw(obs, g2, tx, ptx, 0, new Point(0, 0), null);
} catch (Exception ex) {
if (isDebugEnabled()) {
ex.printStackTrace();
}
return false;
}
}
/**
* draws with the specified OR-bitwise-combination FX
*
* @return true or false whether the drawing's completed or not (this value
* cannot be used for synchronization)
* @param g2 graphics instance to draw on
* @param fx the FX OR-bitwise-combination to use, or 0 for NONE
* @param fx_loc the FX translation to add to the location, or new Point(0,
* 0) for NONE
* @param fx_color the FX color or null for NONE
* @param obs the component that is observing this animation
* @throws java.lang.InterruptedException if the current Thread gets
* interrupted
* @see #__draw(Component, Graphics2D, AffineTransform,
* PerspectiveTransform, int, Point, Color)
* @see Sprite#draw(Component, Graphics2D, int, Point, Color)
*/
@Override
public boolean draw(Component obs, Graphics2D g2, int fx, Point fx_loc, Color fx_color) {
try {
return __draw(obs, g2, null, null, fx, fx_loc, fx_color);
} catch (Exception ex) {
if (isDebugEnabled()) {
ex.printStackTrace();
}
return false;
}
}
/**
* draws the animation on the specified graphics and component context
*
* @return true or false whether the drawing's completed or not (this value
* cannot be used for synchronization)
* @see #draw(Component, Graphics2D, AffineTransform, PerspectiveTransform)
* @param g2 graphics instance to draw on
* @param obs the component that is observing this animation
*/
@Override
public boolean draw(Component obs, Graphics2D g2) {
try {
return __draw(obs, g2, null, null, 0, new Point(0, 0), null);
} catch (Exception ex) {
if (isDebugEnabled()) {
ex.printStackTrace();
}
return false;
}
}
/**
* the current sprite instance that this animation is using
*/
private transient Sprite currentSprite = null;
/**
* sprites in animation will show blitting if this is false (due to some
* unidentified incorrect buffering issues) @default true (can be changed
* on-the-fly, or with net.sf.jiga.xtended.impl.game.rendering.properties
* FIXclearBlitSprite=false property)
*/
public static boolean FIXclearBlitSprite = Boolean.parseBoolean(RenderingScene.rb.getString("FIXclearBlitSprite"));
/**
* draws with the specified AffineTransform and PerspectiveTransform
* instances
*
* @return true or false whether the drawing's completed or not (this value
* cannot be used for synchronization)
* @param g2 graphics instance to draw on
* @param tx transform instance
* @param ptx perspective transform instance
* @param obs the component that is observing this animation
* @param fx a {@linkplain Sprite#gfx} constant or 0 for none
* @param fx_loc an x,y Point location for the fx (depends on the fx used)
* or set new Point(0,0) for null
* @param fx_color a Color for the fx or null
* @throws java.lang.InterruptedException if the current Thread gets
* interrupted
*/
private boolean __draw(Component obs, Graphics2D g2, AffineTransform tx, PerspectiveTransform ptx, int fx, Point fx_loc, Color fx_color) throws InterruptedException {
boolean interrupt_ = false;
boolean complete = true;
try {
currentSprite = getCurrentSprite();
/**
* no bounds because of transform issues (see refreshSprite)
*/
currentSprite.setLocation(getLocation());
currentSprite.runValidate();
final Monitor monitor0 = renderableImpl.renderableImpl.validateMonitor;
synchronized (monitor0) {
while (renderableImpl.validating) {
if (isDebugEnabled()) {
System.out.print(".");
}
monitor0.wait(1000);
}
renderableImpl.painting = true;
if (isDebugEnabled()) {
System.out.println("Animation render...");
}
if (fx == GLFX.gfx.FX_NONE) {
complete = (Boolean) spm.memorySensitiveCallback("draw", currentSprite, new Object[]{obs, g2, tx, ptx}, new Class[]{Component.class, Graphics2D.class, AffineTransform.class, PerspectiveTransform.class}) && complete;
} else {
complete = (Boolean) spm.memorySensitiveCallback("draw", currentSprite, new Object[]{obs, g2, fx, fx_loc, fx_color}, new Class[]{Component.class, Graphics2D.class, int.class, Point.class, Color.class}) && complete;
}
markFPS();
}
} catch (Throwable t) {
if (isDebugEnabled()) {
t.printStackTrace();
}
interrupt_ = true;
} finally {
if (FIXclearBlitSprite) {
currentSprite.clearResource(); /*
* or BLITTTING!!!
*/
}
if (isDebugEnabled()) {
System.out.println(complete && !interrupt_ ? "...render OK" : "...render ERROR!");
}
final Monitor monitor1 = renderableImpl.renderableImpl.paintMonitor;
synchronized (monitor1) {
renderableImpl.painting = false;
monitor1.notifyAll();
}
if (interrupt_) {
throw new JXAException("Animation " + renderableImpl.base + " caught an interruption.");
}
return complete;
}
}
/**
* sets the animation SFX to play.
*
* @see #sfx
* @see #initSound()
* @param frameMark frame index telling when to play the sfx
* @param sfx_path full resource path to the sound file (currently
* supporting mpeg-layer-3 or PCM wave files)
* @param rsrcMode specifies the inner-resource mode dis/enabled
* @see net.sf.jiga.sf3.system.Resource
*/
public void setSfx(String sfx_path, boolean rsrcMode, int frameMark) {
assert ((frameMark < length || frameMark == 0) && frameMark >= 0) : getClass().getCanonicalName() + " frame mark for sfx is not in the correct interval!";
sfx_frame = frameMark;
initSound(sfx_path, rsrcMode);
sfx.setUseJLayerEnabled(isSfxUseJLayerEnabled());
sfx.loadResource();
}
/**
*
* @param b dis/enables the mp3 layer for the associated sound fx of this
* Animation
*/
public void setSfxUseJLayerEnabled(boolean b) {
statusID = b ? statusID | STATE_JLAYER : statusID & ~STATE_JLAYER;
}
/**
*
* @return true or false, whether the mp3 layer is enabled or not, resp. for
* the sound fx associated with this Animation
*/
public boolean isSfxUseJLayerEnabled() {
return (statusID & STATE_JLAYER) != 0;
}
/**
* sets whether to reverse or not this animation when playing it
*
* @param b true to reverse animation
*/
@Override
public void setReverseEnabled(boolean b) {
statusID = b ? statusID | STATE_REVERSED : statusID & ~STATE_REVERSED;
}
/**
* @return true or false, whether the reverse play switch is enabled or not,
* resp.
*/
@Override
public boolean isReverseEnabled() {
return (statusID & STATE_REVERSED) != 0;
}
/**
* tells which frame index to use to play the associated sound FX
*
* @see #sfx
* @see #initSound()
*/
protected int sfx_frame = 0;
/**
* sound has been played at least once
*/
protected transient boolean sfx_played = false;
/**
* returns the current frame image instance, seen by the current Component
* observer.
*
* @return the current frame image instance
* @see #obs
* @param obs the component that will observe the image
*/
public Image getImage(
Component obs) {
return getCurrentSprite().getImage(obs);
}
/**
* returns the synchronized frames map of this animation
*
* @return a synchronized frames map view
*/
@Override
public SortedMap<Integer, Sprite> getFrames() {
return frames;
}
/**
* tells whether mirror transform is enabled
*
* @see #getMirrorOrientation()
* @return true or false
*/
@Override
public boolean isMirrored() {
return (renderableImpl.mirror == renderableImpl.NONE) ? false : true;
}
/**
* returns the current mirror orientation or
*
* @see #NONE
* @see #HORIZONTAL
* @see #VERTICAL
* @see #mirror
* @return mirror orientation
*/
@Override
public int getMirrorOrientation() {
return renderableImpl.mirror;
}
/**
* tells whether the zoom transform is enabled
*
* @see #getZoomValue()
* @return true or false
*/
@Override
public boolean isZoomed() {
return (renderableImpl.zoom == 1.0) ? false : true;
}
/**
* returns current zoom value
*
* @return zoom value (1.0 is no zoom)
*/
@Override
public double getZoomValue() {
return renderableImpl.zoom;
}
@Override
public void setFrameRate(long delay) {
if (delay < 0) {
throw new JXAException("framerate delay must be different from Zero!");
}
frameRate = delay * 1000000;
}
/**
* returns the timestamp used by the animator timer when it has started
*
*
* @return the timestamp got when started playing
* @see #start
* @see #play()
*/
@Override
public long getStartTime() {
return System.currentTimeMillis() - Math.round(elapsedTime() / 1000000.);
}
/**
* dis/enables active rendering as defined by the Swing API.
*
* @param b dis/enables active rendering for this Animation
*/
public void setActiveRenderingEnabled(boolean b) {
if ((statusID & PLAYING) != 0 && b) {
if (timer instanceof Timer) {
timer.stop();
}
}
statusID = b ? statusID | STATE_ACTIVE_RENDERING : statusID & ~STATE_ACTIVE_RENDERING;
}
/**
* returns true or false whether active rendering is enabled or not, resp.
*
* @return true or false, whether the active rendering is enabled or not,
* resp.
*/
public boolean isActiveRenderingEnabled() {
return (statusID & STATE_ACTIVE_RENDERING) != 0;
}
/**
* starts playing this animation. Actually the animator is incremented or
* decreased, depending on reverse playerStatus. if it is called more than
* once it will safely manage to get it to continue (start time will be
* updated !) even if it has been stopped or paused. The sound FX will be
* played when the animator comes to set frame index for the sound if any
* available.
*
* @see #sfx_frame
* @see #setSfx(String, boolean, int)
*/
@Override
public void play() {
int status = playerStatus();
if (status == PAUSED) {
}
if (status == STOPPED) {
rewind();
if (!isActiveRenderingEnabled()) {
if (!(timer instanceof Timer)) {
timer = new Timer(Math.round(frameRate / 1000000f), new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!isActiveRenderingEnabled()) {
if (renderableImpl.getObs() instanceof JComponent) {
Sprite._quickPaintImmediately((JComponent) Animation.this.renderableImpl.getObs());
} else if (renderableImpl.getObs() instanceof Component) {
renderableImpl.getObs().repaint();
}
}
}
});
}
timer.start();
}
}
start = adjustStartTime();
goPlayerState(PLAYING);
}
/**
* pauses this animation. animator timer is stopped. next time play() is
* called it will restart at the current animator index.
*/
@Override
public void pause() {
if (timer != null) {
timer.stop();
}
goPlayerState(PAUSED);
}
/**
* stops playing the animation. the animator will be reset. the animator
* timer is cancelled.
*/
@Override
public void stop() {
pause();
goPlayerState(STOPPED);
timer = null;
}
/**
* returns the animator for the specified operation-mode
*
* @param mode operation-mode
* @see #NEXT
* @see #PREVIOUS
* @param position where to get the iterator from
* @return the animator requested value (current animator won't be modified)
*/
private int getAnimatorValue(int mode, int position) {
int i = position;
switch (mode) {
case NEXT:
if (isReverseEnabled()) {
if (i > 0) {
i--;
} else {
i = length - 1;
}
} else {
if (i < length - 1) {
i++;
} else {
i = 0;
}
}
break;
case PREVIOUS:
if (isReverseEnabled()) {
if (i < length - 1) {
i++;
} else {
i = 0;
}
} else {
if (i > 0) {
i--;
} else {
i = length - 1;
}
}
break;
default:
break;
}
return i;
}
/**
* moves the animator to the next frame index and returns the associated
* Sprite instance.
*
* @return the next frame sprite instance
* @see #getAnimatorValue(int, int)
*/
@Override
public Sprite next() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
pause();
animator = getAnimatorValue(NEXT, animator);
return getCurrentSprite();
}
}
/**
* moves the animator to the previous frame index and returns the associated
* Sprite instance.
*
* @return previous frame sprite
* @see #getAnimatorValue(int, int)
*/
@Override
public Sprite previous() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
pause();
animator = getAnimatorValue(PREVIOUS, animator);
return getCurrentSprite();
}
}
/**
* rewinds to the animator first index value.
*/
@Override
public void rewind() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
pause();
if (isReverseEnabled()) {
animator = length - 1;
} else {
animator = 0;
}
sfx_played = false;
}
}
/**
* Forwards to the animator end index value.
*/
@Override
public void end() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
if (!isReverseEnabled()) {
animator = length - 1;
} else {
animator = 0;
}
}
}
/**
* clears the animation and cache resources. resources can be recovered with
* loadResource(). a new thread is registered in the buffer threads map.
* animation is paused. the Timer (if any) lives.
*
* @return Thread id of that command
* @see #loadResource()
*/
@Override
public Object clearResource() {
Object ret = renderableImpl.clearResource();
pause();
goLoadState(LOADING);
currentSprite = null;
spm.clear();
spm.cleanup();
goLoadState(CLEARED);
return ret;
}
/**
* caches the resources of this animation. a new thread is registered in the
* buffer threads map.
*
* @return Thread id of that command
* @see #clearResource()
*/
@Override
public Object loadResource() {
Object ret = renderableImpl.loadResource();
goLoadState(LOADING);
stop();
for (int i = 0; i < length; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Loading of Animation is interrupted. returning...");
return clearResource();
}
loadFrame(i).clearResource();
}
currentSprite = getCurrentSprite();
initSound();
sfx.loadResource();
goLoadState(LOADED);
return ret;
}
/**
* cancels the animation Timer instance if any and clears the resource.
*
* @deprecated clearResouce() is better.
*/
public void cancel() {
int pty = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
timer = null;
clearResource();
Thread.currentThread().setPriority(pty);
}
/**
* removes current frame sprite instance from mapping, it cannot be
* recovered.
*
* @deprecated functional but usually unused
*/
@Override
public void remove() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
frames.remove(animator);
}
}
/**
* checks for next frame sprite instance availability
*
* @return true or false
*/
@Override
public boolean hasNext() {
final Monitor monitor = renderableImpl.renderableImpl.imageSynch;
synchronized (monitor) {
int next = (isReverseEnabled()) ? -1 : +1;
return frames.containsKey(animator + next);
}
}
/**
* change cache capacity to allow more frames to be mapped
*
* @param n the new cache capacity
* @see SpritesCacheManager#setListCapacity(int)
* @deprecated
*/
public void setCacheCapacity(int n) {
spm.setListCapacity(n);
}
/**
* returns the cache capacity
*
* @return the cache capacity
* @deprecated
*/
public int getCacheCapacity() {
return spm.getListCapacity();
}
/**
* returns the cache ReferenceQueue that repeatly polls to free resource of
* unused sprites instance.
*
* @return the ReferenceQueue that polls for sprites instance to free
* resources
* @see SpritesCacheManager#_cacheBack
* @deprecated null is returned
*/
public ReferenceQueue getCacheBack() {
return null;
}
/**
* dis/enables transform. if it is disabled no transform will be made.
*
* @see #setFlipEnabled(boolean, int)
* @see #setZoomEnabled(boolean, double)
* @param transform dis/enable
*/
@Override
public void setTransformEnabled(boolean transform) {
statusID = transform ? statusID | STATE_TRANSFORM : statusID & ~STATE_TRANSFORM;
}
/**
* @return true or false, whether the transform is enabled or not, resp.
*/
@Override
public boolean isTransformEnabled() {
return (statusID & STATE_TRANSFORM) != 0;
}
/**
* plays the associated Sound in this Animation
*
* @return true or false, whether the sound played or not
* @see #setSfx(String, boolean, int)
*/
@Override
public boolean playSfx() {
if (sfx instanceof Sound) {
sfx.play();
return true;
} else {
return false;
}
}
/**
* renders the Animation within an OpenGL context
*
* @param gld the RenderingSceneGL instance that is to be rendered onto
* @param anim the Animation instance to render
* @param bounds 2D Rectangle bounds for the rendering (0,0 is the upper
* left corner)
* @param z the z-depth to render at
*/
public static void _GLRenderAnimation(RenderingSceneGL gld, AnimationGLHandler anim, Rectangle bounds, double z) {
int transform = 0;
DoubleBuffer scale = null;
DoubleBuffer translate = null;
if (anim.isTransformEnabled()) {
if (scale == null) {
scale = BufferIO._wrapd(new double[]{1, 1});
}
if (translate == null) {
translate = BufferIO._wrapd(new double[]{0, 0});
}
if (anim.isZoomed()) {
scale.put(0, scale.get(0) * anim.getZoomValue());
scale.put(1, scale.get(1) * anim.getZoomValue());
transform |= GLHandler._GL_TRANSFORM_SCALE_BIT;
}
if (anim.isMirrored()) {
switch (anim.getMirrorOrientation()) {
case Sprite.HORIZONTAL:
transform |= GLHandler._GL_TRANSFORM_FLIP_HORIZONTAL_BIT;
break;
case Sprite.VERTICAL:
transform |= GLHandler._GL_TRANSFORM_FLIP_VERTICAL_BIT;
break;
default:
break;
}
}
}
if ((anim.getLoadState() & RenderingSceneGL._GLLOADSTATE_Loaded) != 0) {
if (DebugMap._getInstance().isDebugLevelEnabled(Sprite.DBUG_RENDER_LOW)) {
System.out.println(">>>>>>>>>>>>>>>>GL render Animation " + anim.hashLinkToGLObject() + "...");
}
Sprite._GLRenderSprite(gld, anim.getCurrentSprite(), bounds, z, 0, null, null, transform, scale, null, translate);
}
}
/**
* renders the Animation within an OpenGL context
*
* @param gld the RenderingSceneGL that is to be rendered onto
* @param anim the Animation to render
* @param bounds 2D Rectangle bounds to render at
* @param z the z-depth to render at
* @param fx a {@linkplain Sprite#gfx} or 0 for none
* @param fx_loc a x,y Point location for the fx, use Point(0,0) for none
* @param fx_color a Color for the fx or null
*
*/
public static void _GLRenderAnimation(RenderingSceneGL gld, AnimationGLHandler anim, Rectangle bounds, double z, int fx, Point fx_loc, Color fx_color, int transform, DoubleBuffer scale, DoubleBuffer rotate, DoubleBuffer translate) {
if (anim.isTransformEnabled()) {
if (scale == null) {
scale = BufferIO._wrapd(new double[]{1, 1});
}
if (translate == null) {
translate = BufferIO._wrapd(new double[]{0, 0});
}
if (anim.isZoomed()) {
scale.put(0, scale.get(0) * anim.getZoomValue());
scale.put(1, scale.get(1) * anim.getZoomValue());
transform |= GLHandler._GL_TRANSFORM_SCALE_BIT;
}
if (anim.isMirrored()) {
switch (anim.getMirrorOrientation()) {
case Sprite.HORIZONTAL:
transform |= GLHandler._GL_TRANSFORM_FLIP_HORIZONTAL_BIT;
break;
case Sprite.VERTICAL:
transform |= GLHandler._GL_TRANSFORM_FLIP_VERTICAL_BIT;
break;
default:
break;
}
}
}
if ((anim.getLoadState() & RenderingSceneGL._GLLOADSTATE_Loaded) != 0) {
if (DebugMap._getInstance().isDebugLevelEnabled(Sprite.DBUG_RENDER_LOW)) {
System.out.println(">>>>>>>>>>>>>>>>GL render Animation " + anim.hashLinkToGLObject() + "...");
}
Sprite._GLRenderSprite(gld, anim.getCurrentSprite(), bounds, z, fx, fx_loc, fx_color, transform, scale, rotate, translate);
}
}
/**
* renders the Animation within an OpenGL context
*
* @param gld the RenderingSceneGL that is to be rendered onto
* @param anim the Animation to render
* @param bounds 2D Rectangle bounds to render at
* @param z z-depth to render at
* @param transform a Rendering transform bit ({@linkplain RenderingScene#_GL_TRANSFORM_ROTATE_BIT}, {@linkplain RenderingScene#_GL_TRANSFORM_SCALE_BIT}, {@linkplain RenderingScene#_GL_TRANSFORM_TRANSLATE_BIT})
* transform bitwise-OR combination or 0 for no transform
* @param scaleArgs double array with the scaling args (scaleX, scaleY)
* @param rotateArgs double array with the rotation args (rotationDEGREES,
* centerX, centerY)
* @param translateArgs double array with the translation args (translateX,
* translateY)
* @param colorBlend float array with a 3- or 4-components (RGB(A)) color
* for blending or null for none
*
*/
public static void _GLRenderAnimation(RenderingSceneGL gld, AnimationGLHandler anim, Rectangle bounds, double z, int transform, DoubleBuffer scaleArgs, DoubleBuffer rotateArgs, DoubleBuffer translateArgs, FloatBuffer colorBlend) {
DoubleBuffer scale = scaleArgs;
DoubleBuffer translate = translateArgs;
if (anim.isTransformEnabled()) {
if (scale == null) {
scale = BufferIO._wrapd(new double[]{1, 1});
}
if (translate == null) {
translate = BufferIO._wrapd(new double[]{0, 0});
}
if (anim.isZoomed()) {
scale.put(0, scale.get(0) * anim.getZoomValue());
scale.put(1, scale.get(1) * anim.getZoomValue());
transform |= GLHandler._GL_TRANSFORM_SCALE_BIT;
}
if (anim.isMirrored()) {
switch (anim.getMirrorOrientation()) {
case Sprite.HORIZONTAL:
transform |= GLHandler._GL_TRANSFORM_FLIP_HORIZONTAL_BIT;
break;
case Sprite.VERTICAL:
transform |= GLHandler._GL_TRANSFORM_FLIP_VERTICAL_BIT;
break;
default:
break;
}
}
}
if ((anim.getLoadState() & RenderingSceneGL._GLLOADSTATE_Loaded) != 0) {
if (DebugMap._getInstance().isDebugLevelEnabled(Sprite.DBUG_RENDER_LOW)) {
System.out.println(">>>>>>>>>>>>>>>>GL render Animation " + anim.hashLinkToGLObject() + "...");
}
Sprite._GLRenderSprite(gld, anim.getCurrentSprite(), bounds, z, 0, null, GLGeom.getColorFromComponentsArray(colorBlend), transform, scaleArgs, rotateArgs, translateArgs);
}
}
public static GLObjectHandler<AnimationGLHandler> _GLHandlers = RenderingSceneGL._GLAnimations;
@Override
public void setMirrorEnabled(boolean b, int mirror) {
renderableImpl.setFlipEnabled(b, mirror);
}
private Sprite renderableImpl;
/**
* Animation Sprite's behaviours-capabilities can be controlled with this
* function.
*
* @return a single Sprite instance that is handling Sprite capabilities
*/
public Sprite accessSpriteCaps() {
return renderableImpl;
}
public boolean isDebugEnabled() {
return renderableImpl.isDebugEnabled();
}
public void setDebugEnabled(boolean bln) {
renderableImpl.setDebugEnabled(bln);
}
@Override
public boolean isCompositeEnabled() {
return isCompositeEnabled();
}
@Override
public void setCompositeEnabled(boolean b) {
renderableImpl.setCompositeEnabled(b);
}
@Override
public Composite getComposite() {
return renderableImpl.getComposite();
}
@Override
public void setComposite(Composite cps) {
renderableImpl.setComposite(cps);
}
@Override
public Paint getPaint() {
return renderableImpl.getPaint();
}
@Override
public void setPaint(Paint pnt) {
renderableImpl.setPaint(pnt);
}
@Override
public Color getColor() {
return renderableImpl.getColor();
}
@Override
public void setColor(Color clr) {
renderableImpl.setColor(clr);
}
@Override
public boolean isOpaque() {
return renderableImpl.isOpaque();
}
@Override
public void setOpaque(boolean b) {
renderableImpl.setOpaque(b);
}
@Override
public RenderingScene getRenderingScene() {
return renderableImpl.getRenderingScene();
}
@Override
public void setRenderingScene(RenderingScene renderingScene) {
renderableImpl.setRenderingScene(renderingScene);
}
/** does nothing */
@Override
public void setHardwareAccel(boolean b) {
}
@Override
public boolean isHardwareAccel() {
return renderableImpl.isHardwareAccel();
}
@Override
public int getStoreMode() {
return renderableImpl.getStoreMode();
}
@Override
public boolean isJava2DModeEnabled() {
return renderableImpl.isJava2DModeEnabled();
}
@Override
public boolean isTextureModeEnabled() {
return renderableImpl.isTextureModeEnabled();
}
@Override
public boolean isTileModeEnabled() {
return renderableImpl.isTileModeEnabled();
}
@Override
public void setJava2DModeEnabled(boolean b) {
renderableImpl.setJava2DModeEnabled(b);
}
@Override
public void setStoreMode(int mode) {
renderableImpl.setStoreMode(mode);
}
@Override
public void setTextureModeEnabled(boolean b) {
renderableImpl.setTextureModeEnabled(b);
}
@Override
public void setTileModeEnabled(boolean b) {
renderableImpl.setTileModeEnabled(b);
}
@Override
public Rectangle getBounds() {
return renderableImpl.getBounds();
}
@Override
public Rectangle getBounds(Rectangle rv) {
return renderableImpl.getBounds(rv);
}
@Override
public Point getLocation() {
return renderableImpl.getLocation();
}
@Override
public Dimension getSize() {
return renderableImpl.getSize();
}
@Override
public void setBounds(Rectangle r) {
renderableImpl.setBounds(r);
}
@Override
public void setBounds(int x, int y, int width, int height) {
renderableImpl.setBounds(x, y, width, height);
}
@Override
public void setLocation(Point p) {
renderableImpl.setLocation(p);
}
@Override
public void setLocation(int x, int y) {
renderableImpl.setLocation(x, y);
}
@Override
public void setSize(Dimension size) {
renderableImpl.setSize(size);
}
@Override
public void setSize(int width, int height) {
renderableImpl.setSize(width, height);
}
@Override
public boolean isMultiThreadingEnabled() {
return renderableImpl.isMultiThreadingEnabled();
}
@Override
public void setMultiThreadingEnabled(boolean bln) {
renderableImpl.setMultiThreadingEnabled(bln);
}
@Override
public Monitor[] getGroupMonitor() {
return renderableImpl.getGroupMonitor();
}
@Override
public void setGroupMonitor(Monitor... mntrs) {
renderableImpl.setGroupMonitor(mntrs);
}
@Override
public void setZoomEnabled(boolean b, double zoom) {
renderableImpl.setZoomEnabled(b, zoom);
}
/**
* repaints the component with the Sprite data. used by Swing EDT when
* called
*
* @see JComponent#repaint()
* @see JComponent#update(Graphics)
* @param g1 Graphics instance
*/
public void paintComponentImpl(Graphics g1, JComponent comp) {
renderableImpl.setJava2DModeEnabled(true);
boolean myOpaque = renderableImpl.isOpaque();
setOpaque(comp.isOpaque());
Component myObs = renderableImpl.renderableImpl.io.obs;
renderableImpl.setObs(comp);
Rectangle myBounds = renderableImpl.getBounds();
setBounds(comp.getBounds());
Graphics2D g = renderableImpl.wrapRendering(g1);
Shape clip = g.getClip();
g.translate(-renderableImpl.renderableImpl.bounds.x, -renderableImpl.renderableImpl.bounds.y);
g.clip(getBounds());
if (isOpaque()) {
Color c = getColor();
g.setColor(g.getBackground());
g.fill(getBounds());
g.setColor(c);
}
runValidate();
draw(renderableImpl.renderableImpl.io.obs, g);
g.translate(renderableImpl.renderableImpl.bounds.x, renderableImpl.renderableImpl.bounds.y);
g.setClip(clip);
}
@Override
public int getWidth() {
return renderableImpl.getWidth();
}
@Override
public int getHeight() {
return renderableImpl.getHeight();
}
}