/******************************************************************************* * 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.GeometryHandle; import go.graphics.IllegalBufferException; import go.graphics.TextureHandle; import java.nio.ShortBuffer; import jsettlers.common.Color; import jsettlers.graphics.map.draw.DrawBuffer; import jsettlers.graphics.reader.ImageMetadata; /** * This is the base for all images that are directly loaded from the image file. * <p> * This class interprets the image data in 5-5-5-1-Format. To change the interpretation, it is possible to subclass this class. * * @author Michael Zangl */ public class SingleImage extends Image implements ImageDataPrivider { protected ShortBuffer data; protected final int width; protected final int height; protected int textureWidth = 0; protected int textureHeight = 0; protected final int offsetX; protected final int offsetY; private TextureHandle texture = null; private GeometryHandle geometryhandle = null; /** * Creates a new image by the given buffer. * * @param data * The data buffer for the image with an unspecified color format. * @param width * The width. * @param height * The height. * @param offsetX * The x offset of the image. * @param offsetY * The y offset of the image. */ protected SingleImage(ShortBuffer data, int width, int height, int offsetX, int offsetY) { this.data = data; this.width = width; this.height = height; this.offsetX = offsetX; this.offsetY = offsetY; } /** * Creates a new image by linking this images data to the data of the provider. * * @param metadata * The mata data to use. * @param data * The data to use. */ protected SingleImage(ImageMetadata metadata, short[] data) { this.data = ShortBuffer.wrap(data); this.width = metadata.width; this.height = metadata.height; this.offsetX = metadata.offsetX; this.offsetY = metadata.offsetY; } @Override public int getWidth() { return this.width; } @Override public int getHeight() { return this.height; } @Override public int getOffsetX() { return this.offsetX; } @Override public int getOffsetY() { return this.offsetY; } /** * Converts the current data to match the power of two size. */ protected void adaptDataToTextureSize() { if (width == 0 || height == 0) { return; } this.data.rewind(); short[] newData = new short[textureHeight * textureWidth]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { newData[y * textureWidth + x] = data.get(y * width + x); } for (int x = width; x < textureWidth; x++) { newData[y * textureWidth + x] = newData[y * textureWidth + width - 1]; } } for (int y = height; y < textureHeight; y++) { for (int x = 0; x < textureWidth; x++) { newData[y * textureWidth + x] = newData[(height - 1) * textureWidth + x]; } } data = ShortBuffer.wrap(newData); } /** * Generates the texture, if needed, and returns the index of that texutre. * * @param gl * The gl context to use to generate the image. * @return The gl handle or <code>null</code> if the texture is not allocated. */ public TextureHandle getTextureIndex(GLDrawContext gl) { if (texture == null || !texture.isValid()) { if (textureWidth == 0) { textureWidth = gl.makeWidthValid(width); textureHeight = gl.makeHeightValid(height); if (textureWidth != width || textureHeight != height) { adaptDataToTextureSize(); } data.position(0); } texture = gl.generateTexture(textureWidth, textureHeight, this.data); } return this.texture; } private static float[] tempBuffer = new float[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; @Override public void drawImageAtRect(GLDrawContext gl, float left, float bottom, float right, float top) { try { TextureHandle textureHandle = getTextureIndex(gl); tempBuffer[0] = left; tempBuffer[1] = top; tempBuffer[5] = left; tempBuffer[6] = bottom; tempBuffer[9] = (float) height / textureHeight; tempBuffer[10] = right; tempBuffer[11] = bottom; tempBuffer[13] = (float) width / textureWidth; tempBuffer[14] = (float) height / textureHeight; tempBuffer[15] = right; tempBuffer[16] = top; tempBuffer[18] = (float) width / textureWidth; gl.drawQuadWithTexture(textureHandle, tempBuffer); } catch (IllegalBufferException e) { handleIllegalBufferException(e); } } /* * (non-Javadoc) * * @see jsettlers.graphics.image.Image#drawAt(go.graphics.GLDrawContext, float, float) */ @Override public void drawAt(GLDrawContext gl, float x, float y) { drawAt(gl, x, y, null); } /* * (non-Javadoc) * * @see jsettlers.graphics.image.Image#drawAt(go.graphics.GLDrawContext, float, float, go.graphics.Color) */ @Override public void drawAt(GLDrawContext gl, float x, float y, Color color) { gl.glPushMatrix(); gl.glTranslatef(x, y, 0); draw(gl, color); gl.glPopMatrix(); } @Override public ShortBuffer getData() { return this.data; } /* * (non-Javadoc) * * @see jsettlers.graphics.image.Image#draw(go.graphics.GLDrawContext, go.graphics.Color) */ @Override public void draw(GLDrawContext gl, Color color) { try { if (color == null) { gl.color(1, 1, 1, 1); } else { gl.color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); } TextureHandle textureIndex = getTextureIndex(gl); GeometryHandle geometry = getGeometry(gl); gl.drawTrianglesWithTexture(textureIndex, geometry, 2); } catch (IllegalBufferException e) { handleIllegalBufferException(e); } } @Override public void draw(GLDrawContext gl, Color color, float multiply) { try { if (color == null) { gl.color(multiply, multiply, multiply, 1); } else { gl.color(color.getRed() * multiply, color.getGreen() * multiply, color.getBlue() * multiply, color.getAlpha()); } TextureHandle textureIndex = getTextureIndex(gl); GeometryHandle geometryIndex2 = getGeometry(gl); gl.drawTrianglesWithTexture(textureIndex, geometryIndex2, 2); } catch (IllegalBufferException e) { handleIllegalBufferException(e); } } private float[] getGeometry() { int left = getOffsetX(); int top = -getOffsetY(); return new float[] { // bottom right left + this.width, top, 0, (float) width / textureWidth, 0, // top left left, top, 0, 0, 0, // top right left + this.width, top - this.height, 0, (float) width / textureWidth, (float) height / textureHeight, // top right left + this.width, top - this.height, 0, (float) width / textureWidth, (float) height / textureHeight, // bottom left left, top, 0, 0, 0, // top left left, top - this.height, 0, 0, (float) height / textureHeight, }; } protected void setGeometry(GeometryHandle geometry) { geometryhandle = geometry; } protected GeometryHandle getGeometry(GLDrawContext context) { if (geometryhandle == null || !geometryhandle.isValid()) { geometryhandle = context.storeGeometry(getGeometry()); } return geometryhandle; } public float getTextureScaleX() { return (float) width / textureWidth; } public float getTextureScaleY() { return (float) height / textureHeight; } @Override public void drawAt(GLDrawContext gl, DrawBuffer buffer, float viewX, float viewY, int iColor) { try { TextureHandle textureIndex = getTextureIndex(gl); buffer.addImage(textureIndex, viewX + getOffsetX(), viewY - getOffsetY(), viewX + getOffsetX() + width, viewY - getOffsetY() - height, 0, 0, getTextureScaleX(), getTextureScaleY(), iColor); } catch (IllegalBufferException e) { handleIllegalBufferException(e); } } protected float convertU(float relativeU) { return relativeU * getTextureScaleX(); } protected float convertV(float relativeV) { return relativeV * getTextureScaleY(); } /** * Draws a triangle part of this image on the image buffer. * * @param gl * The context to use * @param buffer * The buffer to draw on. * @param viewX * Image center x coordinate * @param viewY * Image center y coordinate * @param u1 * @param v1 * @param u2 * @param v2 * @param u3 * @param v3 * @param activeColor */ public void drawTriangle(GLDrawContext gl, DrawBuffer buffer, float viewX, float viewY, float u1, float v1, float u2, float v2, float u3, float v3, int activeColor) { try { DrawBuffer.Buffer buffer2 = buffer.getBuffer(getTextureIndex(gl)); float left = getOffsetX() + viewX; float top = -getOffsetY() + viewY; // In the draw process sub-integer coordinates can be rounded in unexpected ways that is particularly noticeable when redrawing the // growing // image of a building in the construction phase. By aligning to the nearest integer images can be placed in a more predictable and // controlled // manner. u1 = (float) Math.round(u1 * width) / width; u2 = (float) Math.round(u2 * width) / width; u3 = (float) Math.round(u3 * width) / width; v1 = (float) Math.round(v1 * height) / height; v2 = (float) Math.round(v2 * height) / height; v3 = (float) Math.round(v3 * height) / height; buffer2.addTriangle( (left + u1 * width), (top - v1 * height), (left + u2 * width), (top - v2 * height), (left + u3 * width), (top - v3 * height), convertU(u1), convertV(v1), convertU(u2), convertV(v2), convertU(u3), convertV(v3), activeColor); } catch (IllegalBufferException e) { handleIllegalBufferException(e); } } }