/*******************************************************************************
* Copyright (c) 2015
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.graphics.image;
import go.graphics.GLDrawContext;
import go.graphics.TextureHandle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import jsettlers.common.resources.ResourceManager;
import jsettlers.graphics.map.draw.GLPreloadTask;
import jsettlers.graphics.map.draw.ImageProvider;
import jsettlers.graphics.reader.AdvancedDatFileReader;
import jsettlers.graphics.reader.DatBitmapReader;
import jsettlers.graphics.reader.ImageArrayProvider;
import jsettlers.graphics.reader.ImageMetadata;
import jsettlers.graphics.reader.bytereader.ByteReader;
import jsettlers.graphics.sequence.ArraySequence;
import jsettlers.graphics.sequence.Sequence;
/**
* This is a map of multiple images of one sequence. It always contains the settler image and the torso. This class allows packing the settler images
* to a single, big texture.
*
* @author Michael Zangl
*/
public class MultiImageMap implements ImageArrayProvider, GLPreloadTask {
private final int width;
private final int height;
private int drawx = 0; // x coordinate of free space
private int linetop = 0;
private int linebottom = 0;
private int drawpointer = 0;
private boolean drawEnabled = false;
private boolean textureValid = false;
private TextureHandle texture = null;
private ShortBuffer buffers;
private ByteBuffer byteBuffer;
private final File cacheFile;
/**
* Creates a new {@link MultiImageMap}.
*
* @param width
* The width of the base image.
* @param height
* The height of the base image.
* @param id
* The id of the map.
* @see #addSequences(AdvancedDatFileReader, int[], Sequence[])
*/
public MultiImageMap(int width, int height, String id) {
this.width = width;
this.height = height;
File root = new File(ResourceManager.getResourcesDirectory(), "cache");
cacheFile = new File(root, "cache-" + id);
}
private void allocateBuffers() {
byteBuffer = ByteBuffer.allocateDirect(width * height * 2);
byteBuffer.order(ByteOrder.nativeOrder());
buffers = byteBuffer.asShortBuffer();
}
/**
* Adds a list of textures to this file. The images can be referenced by the image handles added to addTo.
*
* @param dfr
* The reader to read the textures from.
* @param sequenceIndexes
* The indexes where the sequences start.
* @param addTo
* The image sequence to add image references to the newly added images to.
* @throws IOException
* If the file could not be read.
*/
public synchronized void addSequences(AdvancedDatFileReader dfr, int[] sequenceIndexes,
Sequence<Image>[] addTo) throws IOException {
allocateBuffers();
ImageMetadata settlermeta = new ImageMetadata();
ImageMetadata reusableTorsometa = new ImageMetadata();
for (int seqindex : sequenceIndexes) {
long[] settlers = dfr.getSettlerPointers(seqindex);
long[] torsos = dfr.getTorsoPointers(seqindex);
Image[] images = new Image[settlers.length];
for (int i = 0; i < settlers.length; i++) {
// System.out.println("Processing seq + " + seqindex +
// ", image " + i + ":");
ByteReader reader;
reader = dfr.getReaderForPointer(settlers[i]);
DatBitmapReader.uncompressImage(reader,
dfr.getSettlerTranslator(), settlermeta,
this);
int settlerx = drawx - settlermeta.width;
int settlery = linetop;
int torsox = 0;
int torsoy = 0;
ImageMetadata torsometa;
if (torsos != null) {
torsometa = reusableTorsometa;
reader = dfr.getReaderForPointer(torsos[i]);
if (reader != null) {
DatBitmapReader.uncompressImage(reader,
dfr.getTorsoTranslator(),
torsometa, this);
torsox = drawx - torsometa.width;
torsoy = linetop;
}
} else {
torsometa = null;
}
images[i] =
new MultiImageImage(this, settlermeta, settlerx,
settlery, torsometa,
torsox, torsoy);
}
addTo[seqindex] = new ArraySequence<Image>(images);
}
// request a opengl rerender, or do it ourselves on the next image
textureValid = false;
ImageProvider.getInstance().addPreloadTask(this);
}
/**
* Forces the regeneration of the cache file.
*/
public synchronized void writeCache() {
FileOutputStream out = null;
try {
cacheFile.getParentFile().mkdirs();
cacheFile.delete();
File tempFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".tmp");
out = new FileOutputStream(tempFile);
try {
byte[] line = new byte[this.width * 2];
byteBuffer.rewind();
while (byteBuffer.hasRemaining()) {
byteBuffer.get(line);
out.write(line);
}
} finally {
out.close();
}
tempFile.renameTo(cacheFile);
buffers = null;
byteBuffer = null;
} catch (IOException e) {
if (out != null) {
try {
out.close();
} catch (IOException e1) {
}
}
e.printStackTrace();
}
}
/**
* Checks if this image map is can be loaded from the cache instead of regenerating it.
*
* @return <code>true</code> iff this file is cached.
*/
public synchronized boolean hasCache() {
return cacheFile.isFile();
}
@Override
public void startImage(int imageWidth, int imageHeight) throws IOException {
if (this.width < drawx + imageWidth) {
if (linebottom + imageHeight <= this.height) {
linetop = linebottom;
drawx = 0;
} else {
drawEnabled = false;
System.err.println("Error adding image to texture: "
+ "there is no space to open a new row");
return;
}
}
if (linetop + imageHeight < this.height) {
drawEnabled = true;
textureValid = false;
drawpointer = drawx + linetop * this.width;
drawx += imageWidth;
linebottom = Math.max(linebottom, linetop + imageHeight);
} else {
System.err.println("Error adding image to texture: "
+ "Line to low");
drawEnabled = false;
return;
}
}
@Override
public void writeLine(short[] data, int length) throws IOException {
if (drawEnabled) {
int dp = drawpointer;
buffers.position(dp);
buffers.put(data, 0, length);
drawpointer = dp + this.width;
}
}
/**
* Gets the width of the underlying texture.
*
* @return The width.
*/
public int getWidth() {
return width;
}
/**
* Gets the height of the underlying texture.
*
* @return The height.
*/
public int getHeight() {
return height;
}
/**
* Gets the texture handle.
*
* @param gl
* The gl context to use when creating the texutre.
* @return A valid texture handle.
*/
public TextureHandle getTexture(GLDrawContext gl) {
if (!textureValid || !texture.isValid()) {
if (texture != null) {
texture.delete();
}
try {
loadTexture(gl);
} catch (IOException e) {
e.printStackTrace();
}
}
return texture;
}
private synchronized void loadTexture(GLDrawContext gl) throws IOException,
IOException {
if (buffers == null) {
allocateBuffers();
FileInputStream in = new FileInputStream(cacheFile);
try {
byte[] line = new byte[this.width * 2];
while (in.available() > 0) {
if (in.read(line) <= 0) {
throw new IOException();
}
byteBuffer.put(line);
}
byteBuffer.rewind();
} finally {
in.close();
}
}
buffers.rewind();
texture = gl.generateTexture(width, height, buffers);
System.out.println("opengl Texture: " + texture
+ ", thread: " + Thread.currentThread().toString());
if (texture != null) {
textureValid = true;
}
buffers = null;
byteBuffer = null;
}
@Override
public void run(GLDrawContext context) {
getTexture(context);
}
}