Refactored texture loading system to allow runtime editing and atlases
- Removed TextureManager, use TextureLoader instead - Removed PngLoader, AWT image compat moved to TextureUtils - Pixels renamed to TextureData - Added TextureDataEditor for runtime editing of TextureData - Added SimpleTextures to load random, one-off textures - Added Atlases to load textures into runtime-compiled atlases - BlockRenders now contains a method to load a block texture properly - Moved block textures into assets/textures/blocks/
@ -24,8 +24,10 @@ import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue;
|
||||
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.flat.LayerTestUI;
|
||||
import ru.windcorp.progressia.client.graphics.gui.LayerTestGUI;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Atlases;
|
||||
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
||||
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
|
||||
import ru.windcorp.progressia.client.world.renders.BlockRenders;
|
||||
|
||||
public class ClientProxy implements Proxy {
|
||||
|
||||
@ -40,6 +42,9 @@ public class ClientProxy implements Proxy {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
BlockRenders.registerTest();
|
||||
Atlases.loadAllAtlases();
|
||||
|
||||
GUI.addBottomLayer(new LayerWorld());
|
||||
GUI.addTopLayer(new LayerTestUI());
|
||||
GUI.addTopLayer(new LayerTestGUI());
|
||||
|
@ -27,11 +27,8 @@ import ru.windcorp.progressia.client.graphics.backend.GraphicsInterface;
|
||||
import ru.windcorp.progressia.client.graphics.input.KeyEvent;
|
||||
import ru.windcorp.progressia.client.graphics.input.bus.Input;
|
||||
import ru.windcorp.progressia.client.graphics.model.LambdaModel;
|
||||
import ru.windcorp.progressia.client.graphics.texture.SimpleTexture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Sprite;
|
||||
import ru.windcorp.progressia.client.graphics.texture.SimpleTextures;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TextureManager;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
|
||||
import ru.windcorp.progressia.client.graphics.world.LayerWorld;
|
||||
|
||||
public class LayerTestUI extends AssembledFlatLayer {
|
||||
@ -67,8 +64,8 @@ public class LayerTestUI extends AssembledFlatLayer {
|
||||
|
||||
target.pushTransform(new Mat4().identity().translate(x + 2*BORDER, y + 2*BORDER, 0));
|
||||
|
||||
final Texture compassBg = qtex("compass_icon");
|
||||
final Texture compassFg = qtex("compass_icon_arrow");
|
||||
final Texture compassBg = SimpleTextures.get("compass_icon");
|
||||
final Texture compassFg = SimpleTextures.get("compass_icon_arrow");
|
||||
|
||||
target.drawTexture(texShadow, texShadow, texSize, texSize, Colors.BLACK, compassBg);
|
||||
target.drawTexture(0, 0, texSize, texSize, compassBg);
|
||||
@ -142,18 +139,5 @@ public class LayerTestUI extends AssembledFlatLayer {
|
||||
flag = event.isPress();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/*
|
||||
* TMP texture loader
|
||||
*/
|
||||
|
||||
private static final TextureSettings TEXTURE_SETTINGS =
|
||||
new TextureSettings(false);
|
||||
|
||||
private static Texture qtex(String name) {
|
||||
return new SimpleTexture(new Sprite(
|
||||
TextureManager.load(name, TEXTURE_SETTINGS)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,211 @@
|
||||
package ru.windcorp.progressia.client.graphics.texture;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
|
||||
import glm.vec._2.Vec2;
|
||||
import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue;
|
||||
import ru.windcorp.progressia.common.resource.Resource;
|
||||
import ru.windcorp.progressia.common.util.BinUtil;
|
||||
import ru.windcorp.progressia.common.util.Named;
|
||||
|
||||
public class Atlases {
|
||||
|
||||
public static class AtlasGroup extends Named {
|
||||
private final int atlasSize;
|
||||
|
||||
public AtlasGroup(String name, int atlasSize) {
|
||||
super(name);
|
||||
this.atlasSize = atlasSize;
|
||||
|
||||
if (!BinUtil.isPowerOf2(atlasSize)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Atlas size " + atlasSize
|
||||
+ " is not a power of 2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public int getAtlasSize() {
|
||||
return atlasSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Atlas {
|
||||
private final AtlasGroup group;
|
||||
|
||||
private final TextureDataEditor editor;
|
||||
private int nextX, nextY;
|
||||
private int rowHeight;
|
||||
|
||||
private final TexturePrimitive primitive;
|
||||
|
||||
public Atlas(AtlasGroup group) {
|
||||
this.group = group;
|
||||
int size = group.getAtlasSize();
|
||||
|
||||
this.editor = new TextureDataEditor(size, size, size, size, SETTINGS);
|
||||
this.primitive = new TexturePrimitive(editor.createSnapshot());
|
||||
}
|
||||
|
||||
public Sprite addSprite(TextureDataEditor data) {
|
||||
int width = data.getContentWidth();
|
||||
int height = data.getContentHeight();
|
||||
|
||||
selectPosition(width, height);
|
||||
|
||||
editor.draw(data, nextX, nextY);
|
||||
|
||||
Sprite result = new Sprite(
|
||||
getPrimitive(),
|
||||
toPrimitiveCoords(nextX, nextY),
|
||||
toPrimitiveCoords(width, height)
|
||||
);
|
||||
|
||||
nextX += width;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void selectPosition(int width, int height) {
|
||||
if (nextX + width > getSize()) {
|
||||
// Wrapping
|
||||
nextY += rowHeight; // Move to next row
|
||||
rowHeight = height; // Next row is at least 'height' high
|
||||
nextX = 0; // Start the row over
|
||||
} else {
|
||||
// Not wrapping
|
||||
|
||||
// Update rowHeight if necessary
|
||||
if (rowHeight < height) {
|
||||
rowHeight = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vec2 toPrimitiveCoords(int x, int y) {
|
||||
return new Vec2(
|
||||
toPrimitiveCoord(x),
|
||||
toPrimitiveCoord(y)
|
||||
);
|
||||
}
|
||||
|
||||
private float toPrimitiveCoord(int c) {
|
||||
return c / (float) getSize();
|
||||
}
|
||||
|
||||
public boolean canAddSprite(TextureDataEditor data) {
|
||||
int width = data.getContentWidth();
|
||||
int height = data.getContentHeight();
|
||||
|
||||
// Try to fit without wrapping
|
||||
|
||||
if (nextY + height > getSize())
|
||||
// Does not fit vertically
|
||||
return false;
|
||||
|
||||
if (nextX + width <= getSize())
|
||||
// Can place at (nextX; nextY)
|
||||
return true;
|
||||
|
||||
// Try wrapping
|
||||
|
||||
if (width > getSize())
|
||||
// GTFO. We couldn't fit if if we tried
|
||||
return false;
|
||||
|
||||
if (nextY + rowHeight + height > getSize())
|
||||
// Does not fit vertically when wrapped
|
||||
return false;
|
||||
|
||||
// Can place at (0; nextY + rowHeight)
|
||||
return true;
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
editor.createSnapshot(primitive.getData());
|
||||
editor.close();
|
||||
}
|
||||
|
||||
public AtlasGroup getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public TexturePrimitive getPrimitive() {
|
||||
return primitive;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return editor.getBufferWidth();
|
||||
}
|
||||
}
|
||||
|
||||
private static final TextureSettings SETTINGS = new TextureSettings(false);
|
||||
|
||||
private static final Map<Resource, Sprite> LOADED =
|
||||
new HashMap<>();
|
||||
private static final Multimap<AtlasGroup, Atlas> ATLASES =
|
||||
MultimapBuilder.hashKeys().arrayListValues().build();
|
||||
|
||||
public static Sprite getSprite(Resource resource, AtlasGroup group) {
|
||||
return LOADED.computeIfAbsent(resource, k -> loadSprite(k, group));
|
||||
}
|
||||
|
||||
private static Sprite loadSprite(Resource resource, AtlasGroup group) {
|
||||
try (
|
||||
TextureDataEditor data =
|
||||
TextureLoader.loadPixels(resource, SETTINGS)
|
||||
) {
|
||||
|
||||
Atlas atlas = getReadyAtlas(group, data);
|
||||
return atlas.addSprite(data);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Atlas getReadyAtlas(AtlasGroup group, TextureDataEditor data) {
|
||||
List<Atlas> atlases = (List<Atlas>) ATLASES.get(group);
|
||||
|
||||
if (
|
||||
atlases.isEmpty() ||
|
||||
!(atlases.get(atlases.size() - 1).canAddSprite(data))
|
||||
) {
|
||||
Atlas newAtlas = new Atlas(group);
|
||||
|
||||
if (!newAtlas.canAddSprite(data)) {
|
||||
throw new RuntimeException("Could not fit tex into atlas of size " + newAtlas.getSize());
|
||||
}
|
||||
|
||||
atlases.add(newAtlas);
|
||||
}
|
||||
|
||||
return atlases.get(atlases.size() - 1);
|
||||
}
|
||||
|
||||
public static void loadAllAtlases() {
|
||||
ATLASES.forEach((group, atlas) -> {
|
||||
atlas.flush();
|
||||
RenderTaskQueue.invokeLater(atlas.getPrimitive()::load);
|
||||
});
|
||||
}
|
||||
|
||||
private Atlases() {}
|
||||
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Progressia
|
||||
* Copyright (C) 2020 Wind Corporation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
package ru.windcorp.progressia.client.graphics.texture;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Hashtable;
|
||||
import javax.imageio.ImageIO;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import ru.windcorp.progressia.common.util.BinUtil;
|
||||
|
||||
class PngLoader {
|
||||
|
||||
private static final ColorModel COLOR_MODEL = new ComponentColorModel(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB), // Use RGB
|
||||
new int[] {8, 8, 8, 8}, // Use every bit
|
||||
true, // Has alpha
|
||||
false, // Not premultiplied
|
||||
ComponentColorModel.TRANSLUCENT, // Can have any alpha
|
||||
DataBuffer.TYPE_BYTE // Alpha is one byte
|
||||
);
|
||||
|
||||
private static final Hashtable<?, ?> CANVAS_PROPERTIES = new Hashtable<>();
|
||||
|
||||
private static final java.awt.Color CANVAS_BACKGROUND = new java.awt.Color(0, 0, 0, 0);
|
||||
|
||||
public static Pixels loadPngImage(
|
||||
InputStream pngStream,
|
||||
TextureSettings settings
|
||||
) throws IOException {
|
||||
|
||||
BufferedImage readResult = ImageIO.read(pngStream);
|
||||
|
||||
int width = readResult.getWidth();
|
||||
int height = readResult.getHeight();
|
||||
|
||||
int bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
|
||||
int bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
|
||||
|
||||
WritableRaster raster = createRaster(bufferWidth, bufferHeight);
|
||||
|
||||
BufferedImage canvas = createCanvas(raster);
|
||||
|
||||
Graphics2D g = canvas.createGraphics();
|
||||
try {
|
||||
g.setColor(CANVAS_BACKGROUND);
|
||||
g.fillRect(0, 0, width, height);
|
||||
g.drawImage(
|
||||
readResult,
|
||||
0, 0, width, height,
|
||||
0, height, width, 0, // Flip the image
|
||||
null
|
||||
);
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
return new Pixels(
|
||||
extractBytes(raster),
|
||||
bufferWidth, bufferHeight,
|
||||
width, height,
|
||||
settings
|
||||
);
|
||||
}
|
||||
|
||||
private static WritableRaster createRaster(
|
||||
int bufferWidth,
|
||||
int bufferHeight
|
||||
) {
|
||||
return Raster.createInterleavedRaster(
|
||||
DataBuffer.TYPE_BYTE, // Storage model
|
||||
bufferWidth, // Buffer width
|
||||
bufferHeight, // Buffer height
|
||||
4, // RGBA
|
||||
null // Location (here (0; 0))
|
||||
);
|
||||
}
|
||||
|
||||
private static BufferedImage createCanvas(WritableRaster raster) {
|
||||
return new BufferedImage(
|
||||
COLOR_MODEL, // Color model
|
||||
raster, // Backing raster
|
||||
false, // Raster is not premultipied
|
||||
CANVAS_PROPERTIES // Properties
|
||||
);
|
||||
}
|
||||
|
||||
private static ByteBuffer extractBytes(WritableRaster raster) {
|
||||
byte[] data = (
|
||||
(DataBufferByte) raster.getDataBuffer()
|
||||
).getData();
|
||||
|
||||
ByteBuffer buffer = BufferUtils.createByteBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package ru.windcorp.progressia.client.graphics.texture;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.windcorp.progressia.common.resource.Resource;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
|
||||
public class SimpleTextures {
|
||||
|
||||
private static final TextureSettings SETTINGS = new TextureSettings(false);
|
||||
|
||||
private static final Map<Resource, Texture> TEXTURES = new HashMap<>();
|
||||
|
||||
public static Texture get(Resource resource) {
|
||||
return TEXTURES.computeIfAbsent(resource, SimpleTextures::load);
|
||||
}
|
||||
|
||||
public static Texture get(String textureName) {
|
||||
return get(ResourceManager.getTextureResource(textureName));
|
||||
}
|
||||
|
||||
private static Texture load(Resource resource) {
|
||||
try (
|
||||
TextureDataEditor data =
|
||||
TextureLoader.loadPixels(resource, SETTINGS)
|
||||
) {
|
||||
return new SimpleTexture(
|
||||
new Sprite(
|
||||
new TexturePrimitive(
|
||||
data.toStatic()
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private SimpleTextures() {}
|
||||
|
||||
}
|
@ -22,7 +22,7 @@ import static org.lwjgl.opengl.GL12.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
class Pixels {
|
||||
class TextureData {
|
||||
|
||||
private final ByteBuffer data;
|
||||
|
||||
@ -34,7 +34,7 @@ class Pixels {
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public Pixels(
|
||||
public TextureData(
|
||||
ByteBuffer data,
|
||||
int bufferWidth, int bufferHeight,
|
||||
int width, int height,
|
@ -0,0 +1,185 @@
|
||||
package ru.windcorp.progressia.client.graphics.texture;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class TextureDataEditor implements AutoCloseable {
|
||||
|
||||
protected final BufferedImage image;
|
||||
protected Graphics2D graphics;
|
||||
|
||||
private final int contentWidth;
|
||||
private final int contentHeight;
|
||||
private final TextureSettings settings;
|
||||
|
||||
protected TextureDataEditor(
|
||||
BufferedImage image,
|
||||
int contentWidth, int contentHeight,
|
||||
TextureSettings settings
|
||||
) {
|
||||
this.image = image;
|
||||
this.contentWidth = contentWidth;
|
||||
this.contentHeight = contentHeight;
|
||||
this.settings = settings;
|
||||
|
||||
startEditing();
|
||||
}
|
||||
|
||||
public TextureDataEditor(
|
||||
int bufferWidth, int bufferHeight,
|
||||
int contentWidth, int contentHeight,
|
||||
TextureSettings settings
|
||||
) {
|
||||
this(
|
||||
TextureUtils.createCanvas(
|
||||
TextureUtils.createRaster(bufferWidth, bufferHeight)
|
||||
),
|
||||
contentWidth, contentHeight,
|
||||
settings
|
||||
);
|
||||
}
|
||||
|
||||
public TextureDataEditor(TextureData buffer) {
|
||||
this(
|
||||
createImage(buffer),
|
||||
buffer.getContentWidth(),
|
||||
buffer.getContentHeight(),
|
||||
buffer.getSettings()
|
||||
);
|
||||
}
|
||||
|
||||
private static BufferedImage createImage(TextureData buffer) {
|
||||
return TextureUtils.createCanvas(
|
||||
TextureUtils.createRaster(
|
||||
buffer.getData(),
|
||||
buffer.getBufferWidth(),
|
||||
buffer.getBufferHeight()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public void startEditing() {
|
||||
if (isEditable()) {
|
||||
throw new IllegalStateException("This object is already editable");
|
||||
}
|
||||
|
||||
this.graphics = this.image.createGraphics();
|
||||
|
||||
graphics.setColor(TextureUtils.CANVAS_BACKGROUND);
|
||||
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
|
||||
|
||||
graphics.clipRect(0, 0, contentWidth, contentHeight);
|
||||
}
|
||||
|
||||
public void finishEditing() {
|
||||
checkEditability();
|
||||
this.graphics.dispose();
|
||||
this.graphics = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (isEditable()) {
|
||||
finishEditing();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEditable() {
|
||||
return this.graphics != null;
|
||||
}
|
||||
|
||||
protected void checkEditability() {
|
||||
if (!isEditable()) {
|
||||
throw new IllegalStateException("This object is not editable");
|
||||
}
|
||||
}
|
||||
|
||||
public TextureData createSnapshot() {
|
||||
return new TextureData(
|
||||
TextureUtils.extractBytes(image.getRaster()),
|
||||
image.getWidth(), image.getHeight(),
|
||||
contentWidth, contentHeight,
|
||||
settings
|
||||
);
|
||||
}
|
||||
|
||||
public TextureData createSnapshot(TextureData output) {
|
||||
TextureUtils.extractBytes(image.getRaster(), output.getData());
|
||||
return output;
|
||||
}
|
||||
|
||||
public TextureData toStatic() {
|
||||
close();
|
||||
return createSnapshot();
|
||||
}
|
||||
|
||||
public TextureData toStatic(TextureData data) {
|
||||
close();
|
||||
return createSnapshot(data);
|
||||
}
|
||||
|
||||
public int getBufferWidth() {
|
||||
return image.getWidth();
|
||||
}
|
||||
|
||||
public int getBufferHeight() {
|
||||
return image.getHeight();
|
||||
}
|
||||
|
||||
public int getContentWidth() {
|
||||
return contentWidth;
|
||||
}
|
||||
|
||||
public int getContentHeight() {
|
||||
return contentHeight;
|
||||
}
|
||||
|
||||
public TextureSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void draw(
|
||||
BufferedImage source,
|
||||
int srcX, int srcY,
|
||||
int dstX, int dstY,
|
||||
int width, int height
|
||||
) {
|
||||
checkEditability();
|
||||
|
||||
graphics.drawImage(
|
||||
source,
|
||||
dstX, dstY,
|
||||
dstX + width, dstY + height,
|
||||
srcX, srcY,
|
||||
srcX + width, srcY + height,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public void draw(
|
||||
BufferedImage source,
|
||||
int dstX, int dstY
|
||||
) {
|
||||
draw(source, 0, 0, dstX, dstY, source.getWidth(), source.getHeight());
|
||||
}
|
||||
|
||||
public void draw(
|
||||
TextureDataEditor source,
|
||||
int srcX, int srcY,
|
||||
int dstX, int dstY,
|
||||
int width, int height
|
||||
) {
|
||||
draw(source.image, srcX, srcY, dstX, dstY, width, height);
|
||||
}
|
||||
|
||||
public void draw(
|
||||
TextureDataEditor source,
|
||||
int dstX, int dstY
|
||||
) {
|
||||
draw(
|
||||
source, 0, 0, dstX, dstY,
|
||||
source.getContentWidth(), source.getContentHeight()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package ru.windcorp.progressia.client.graphics.texture;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import ru.windcorp.progressia.common.resource.Resource;
|
||||
import ru.windcorp.progressia.common.util.BinUtil;
|
||||
|
||||
public class TextureLoader {
|
||||
|
||||
public static TextureDataEditor loadPixels(
|
||||
InputStream compressed, TextureSettings settings
|
||||
) throws IOException {
|
||||
BufferedImage readResult = ImageIO.read(compressed);
|
||||
|
||||
int width = readResult.getWidth();
|
||||
int height = readResult.getHeight();
|
||||
|
||||
int bufferWidth = BinUtil.roundToGreaterPowerOf2(width);
|
||||
int bufferHeight = BinUtil.roundToGreaterPowerOf2(height);
|
||||
|
||||
TextureDataEditor result = new TextureDataEditor(
|
||||
bufferWidth, bufferHeight, width, height, settings
|
||||
);
|
||||
|
||||
Graphics2D g = result.graphics;
|
||||
g.drawImage(
|
||||
readResult,
|
||||
0, 0, width, height,
|
||||
0, height, width, 0, // Flip the image
|
||||
null
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TextureDataEditor loadPixels(
|
||||
Resource resource, TextureSettings settings
|
||||
) throws IOException {
|
||||
return loadPixels(resource.getInputStream(), settings);
|
||||
}
|
||||
|
||||
private TextureLoader() {}
|
||||
|
||||
}
|
@ -1,218 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Progressia
|
||||
* Copyright (C) 2020 Wind Corporation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*******************************************************************************/
|
||||
package ru.windcorp.progressia.client.graphics.texture;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.RenderTaskQueue;
|
||||
import ru.windcorp.progressia.common.resource.Resource;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
import ru.windcorp.progressia.common.util.ByteBufferInputStream;
|
||||
|
||||
public class TextureManager {
|
||||
|
||||
private static final String TEXTURE_ASSETS_PREFIX = "assets/textures/";
|
||||
|
||||
private static final Map<String, Pixels> LOADED_PIXELS =
|
||||
new HashMap<>();
|
||||
|
||||
private static final Map<String, TexturePrimitive> LOADED_PRIMITIVES =
|
||||
new HashMap<>();
|
||||
|
||||
private static Pixels getCachedPixels(String name) {
|
||||
return LOADED_PIXELS.get(name);
|
||||
}
|
||||
|
||||
private static Pixels getCachedPixels(Resource resource) {
|
||||
return getCachedPixels(resource.getName());
|
||||
}
|
||||
|
||||
private static Pixels cachePixels(Pixels pixels, String name) {
|
||||
LOADED_PIXELS.put(name, pixels);
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private static Pixels cachePixels(Pixels pixels, Resource resource) {
|
||||
return cachePixels(pixels, resource.getName());
|
||||
}
|
||||
|
||||
private static TexturePrimitive getCachedPrimitive(String name) {
|
||||
return LOADED_PRIMITIVES.get(name);
|
||||
}
|
||||
|
||||
private static TexturePrimitive getCachedPrimitive(Resource resource) {
|
||||
return getCachedPrimitive(resource.getName());
|
||||
}
|
||||
|
||||
private static TexturePrimitive cachePrimitive(
|
||||
TexturePrimitive primitive,
|
||||
String name
|
||||
) {
|
||||
LOADED_PRIMITIVES.put(name, primitive);
|
||||
return primitive;
|
||||
}
|
||||
|
||||
private static TexturePrimitive cachePrimitive(
|
||||
TexturePrimitive primitive,
|
||||
Resource resource
|
||||
) {
|
||||
return cachePrimitive(primitive, resource.getName());
|
||||
}
|
||||
|
||||
private static Resource getResource(String textureName) {
|
||||
return ResourceManager.getResource(
|
||||
TEXTURE_ASSETS_PREFIX + textureName + ".png"
|
||||
);
|
||||
}
|
||||
|
||||
public static Pixels createOpenGLBuffer(
|
||||
InputStream stream,
|
||||
TextureSettings settings
|
||||
) {
|
||||
try {
|
||||
return PngLoader.loadPngImage(stream, settings);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("u stupid. refresh ur project");
|
||||
}
|
||||
}
|
||||
|
||||
public static Pixels createOpenGLBuffer(
|
||||
Resource resource,
|
||||
TextureSettings settings
|
||||
) {
|
||||
Pixels cache = getCachedPixels(resource);
|
||||
if (cache != null) return cache;
|
||||
return cachePixels(
|
||||
createOpenGLBuffer(resource.getInputStream(), settings),
|
||||
resource
|
||||
);
|
||||
}
|
||||
|
||||
public static Pixels createOpenGLBuffer(
|
||||
String textureName,
|
||||
TextureSettings settings
|
||||
) {
|
||||
Pixels cache = getCachedPixels(textureName);
|
||||
if (cache != null) return cache;
|
||||
return cachePixels(
|
||||
createOpenGLBuffer(getResource(textureName), settings),
|
||||
textureName
|
||||
);
|
||||
}
|
||||
|
||||
public static Pixels createOpenGLBuffer(
|
||||
ByteBuffer bytes,
|
||||
TextureSettings settings
|
||||
) {
|
||||
bytes.mark();
|
||||
try {
|
||||
return createOpenGLBuffer(
|
||||
new ByteBufferInputStream(bytes), settings
|
||||
);
|
||||
} finally {
|
||||
bytes.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public static TexturePrimitive createTexturePrimitive(
|
||||
InputStream stream,
|
||||
TextureSettings settings
|
||||
) {
|
||||
Pixels pixels = createOpenGLBuffer(stream, settings);
|
||||
TexturePrimitive result = new TexturePrimitive(pixels);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TexturePrimitive createTexturePrimitive(
|
||||
Resource resource,
|
||||
TextureSettings settings
|
||||
) {
|
||||
TexturePrimitive primitive = getCachedPrimitive(resource);
|
||||
if (primitive != null) return primitive;
|
||||
return cachePrimitive(
|
||||
createTexturePrimitive(resource.getInputStream(), settings),
|
||||
resource
|
||||
);
|
||||
}
|
||||
|
||||
public static TexturePrimitive createTexturePrimitive(
|
||||
String textureName,
|
||||
TextureSettings settings
|
||||
) {
|
||||
TexturePrimitive primitive = getCachedPrimitive(textureName);
|
||||
if (primitive != null) return primitive;
|
||||
return cachePrimitive(
|
||||
createTexturePrimitive(getResource(textureName), settings),
|
||||
textureName
|
||||
);
|
||||
}
|
||||
|
||||
public static TexturePrimitive createTexturePrimitive(
|
||||
ByteBuffer bytes,
|
||||
TextureSettings settings
|
||||
) {
|
||||
bytes.mark();
|
||||
try {
|
||||
return createTexturePrimitive(
|
||||
new ByteBufferInputStream(bytes), settings
|
||||
);
|
||||
} finally {
|
||||
bytes.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public static TexturePrimitive load(
|
||||
InputStream stream,
|
||||
TextureSettings settings
|
||||
) {
|
||||
TexturePrimitive result = createTexturePrimitive(stream, settings);
|
||||
if (!result.isLoaded()) RenderTaskQueue.invokeLater(result::load);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TexturePrimitive load(
|
||||
Resource resource,
|
||||
TextureSettings settings
|
||||
) {
|
||||
return load(resource.getInputStream(), settings);
|
||||
}
|
||||
|
||||
public static TexturePrimitive load(
|
||||
String textureName,
|
||||
TextureSettings settings
|
||||
) {
|
||||
return load(getResource(textureName), settings);
|
||||
}
|
||||
|
||||
public static TexturePrimitive load(
|
||||
ByteBuffer bytes,
|
||||
TextureSettings settings
|
||||
) {
|
||||
bytes.mark();
|
||||
try {
|
||||
return load(new ByteBufferInputStream(bytes), settings);
|
||||
} finally {
|
||||
bytes.reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -20,6 +20,8 @@ package ru.windcorp.progressia.client.graphics.texture;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL20.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker;
|
||||
import ru.windcorp.progressia.client.graphics.backend.OpenGLObjectTracker.OpenGLDeletable;
|
||||
|
||||
@ -27,13 +29,22 @@ public class TexturePrimitive implements OpenGLDeletable {
|
||||
|
||||
private static final int NOT_LOADED = -1;
|
||||
|
||||
private static int[] currentlyBound = new int[32];
|
||||
static {
|
||||
Arrays.fill(currentlyBound, NOT_LOADED);
|
||||
}
|
||||
|
||||
private int handle = NOT_LOADED;
|
||||
private Pixels pixels;
|
||||
private TextureData pixels;
|
||||
|
||||
public TexturePrimitive(Pixels pixels) {
|
||||
public TexturePrimitive(TextureData pixels) {
|
||||
this.pixels = pixels;
|
||||
}
|
||||
|
||||
public TextureData getData() {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
public int getBufferWidth() {
|
||||
return pixels.getBufferWidth();
|
||||
}
|
||||
@ -59,10 +70,16 @@ public class TexturePrimitive implements OpenGLDeletable {
|
||||
load();
|
||||
}
|
||||
|
||||
if (currentlyBound[slot] == handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
int code = GL_TEXTURE0 + slot;
|
||||
|
||||
glActiveTexture(code);
|
||||
glBindTexture(GL_TEXTURE_2D, handle);
|
||||
|
||||
currentlyBound[slot] = handle;
|
||||
}
|
||||
|
||||
protected void load() {
|
||||
|
@ -0,0 +1,108 @@
|
||||
package ru.windcorp.progressia.client.graphics.texture;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
public class TextureUtils {
|
||||
|
||||
public static final Color CANVAS_BACKGROUND = new Color(0, true);
|
||||
|
||||
public static final ColorModel COLOR_MODEL = new ComponentColorModel(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB), // Use RGB
|
||||
new int[] {8, 8, 8, 8}, // Use every bit
|
||||
true, // Has alpha
|
||||
false, // Not premultiplied
|
||||
Transparency.TRANSLUCENT, // Can have any alpha
|
||||
DataBuffer.TYPE_BYTE // Alpha is one byte
|
||||
);
|
||||
|
||||
private static final Hashtable<?, ?> BUFFERED_IMAGE_PROPERTIES =
|
||||
new Hashtable<>();
|
||||
|
||||
public static WritableRaster createRaster(
|
||||
int bufferWidth,
|
||||
int bufferHeight
|
||||
) {
|
||||
return Raster.createInterleavedRaster(
|
||||
DataBuffer.TYPE_BYTE, // Storage model
|
||||
bufferWidth, // Buffer width
|
||||
bufferHeight, // Buffer height
|
||||
4, // RGBA
|
||||
null // Location (here (0; 0))
|
||||
);
|
||||
}
|
||||
|
||||
public static WritableRaster createRaster(
|
||||
ByteBuffer buffer,
|
||||
int bufferWidth,
|
||||
int bufferHeight
|
||||
) {
|
||||
final int bands = 4; // RGBA
|
||||
|
||||
byte[] bytes = new byte[bufferWidth * bufferHeight * bands];
|
||||
|
||||
buffer.get(bytes);
|
||||
buffer.position(buffer.position() - bytes.length);
|
||||
|
||||
DataBufferByte dataBuffer = new DataBufferByte(bytes, bytes.length);
|
||||
|
||||
return Raster.createInterleavedRaster(
|
||||
dataBuffer, // The buffer
|
||||
bufferWidth, // Buffer width
|
||||
bufferHeight, // Buffer height
|
||||
bands * bufferWidth, // Scanline stride
|
||||
bands, // Pixel stride
|
||||
new int[] {0, 1, 2, 3}, // Band offsets
|
||||
null // Location (here (0; 0))
|
||||
);
|
||||
}
|
||||
|
||||
public static BufferedImage createCanvas(WritableRaster raster) {
|
||||
return new BufferedImage(
|
||||
COLOR_MODEL, // Color model
|
||||
raster, // Backing raster
|
||||
false, // Raster is not premultipied
|
||||
BUFFERED_IMAGE_PROPERTIES // Properties
|
||||
);
|
||||
}
|
||||
|
||||
public static ByteBuffer extractBytes(WritableRaster raster) {
|
||||
byte[] data = (
|
||||
(DataBufferByte) raster.getDataBuffer()
|
||||
).getData();
|
||||
|
||||
ByteBuffer buffer = BufferUtils.createByteBuffer(data.length);
|
||||
buffer.put(data);
|
||||
buffer.flip();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static ByteBuffer extractBytes(
|
||||
WritableRaster raster, ByteBuffer output
|
||||
) {
|
||||
byte[] data = (
|
||||
(DataBufferByte) raster.getDataBuffer()
|
||||
).getData();
|
||||
|
||||
output.put(data);
|
||||
output.flip();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private TextureUtils() {}
|
||||
|
||||
}
|
@ -20,38 +20,39 @@ package ru.windcorp.progressia.client.world.renders;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ru.windcorp.progressia.client.graphics.texture.Atlases;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Atlases.AtlasGroup;
|
||||
import ru.windcorp.progressia.client.graphics.texture.SimpleTexture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Sprite;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TextureManager;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
|
||||
import ru.windcorp.progressia.client.world.renders.bro.BlockRenderOpaqueCube;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
|
||||
public class BlockRenders {
|
||||
|
||||
private static final Map<String, BlockRender> BLOCK_RENDERS =
|
||||
new HashMap<>();
|
||||
|
||||
private static final TextureSettings TEXTURE_SETTINGS =
|
||||
new TextureSettings(false);
|
||||
private static final AtlasGroup BLOCKS_ATLAS_GROUP =
|
||||
new AtlasGroup("Blocks", 1 << 6);
|
||||
|
||||
private static Texture grassTop = qtex("grass_top");
|
||||
private static Texture grassSide = qtex("grass_side");
|
||||
private static Texture dirtT = qtex("grass_bottom");
|
||||
private static Texture stoneT = qtex("stone");
|
||||
private static Texture glassT = qtex("glass_clear");
|
||||
private static Texture grassTop = getTexture("grass_top");
|
||||
private static Texture grassSide = getTexture("grass_side");
|
||||
private static Texture dirt = getTexture("grass_bottom");
|
||||
private static Texture stone = getTexture("stone");
|
||||
private static Texture glass = getTexture("glass_clear");
|
||||
private static Texture compass = getTexture("compass");
|
||||
|
||||
private BlockRenders() {}
|
||||
|
||||
static {
|
||||
register(new BlockRenderOpaqueCube("Test", "Grass", grassTop, dirtT, grassSide, grassSide, grassSide, grassSide));
|
||||
register(new BlockRenderOpaqueCube("Test", "Dirt", dirtT, dirtT, dirtT, dirtT, dirtT, dirtT));
|
||||
register(new BlockRenderOpaqueCube("Test", "Stone", stoneT, stoneT, stoneT, stoneT, stoneT, stoneT));
|
||||
public static void registerTest() {
|
||||
register(new BlockRenderOpaqueCube("Test", "Grass", grassTop, dirt, grassSide, grassSide, grassSide, grassSide));
|
||||
register(new BlockRenderOpaqueCube("Test", "Dirt", dirt, dirt, dirt, dirt, dirt, dirt));
|
||||
register(new BlockRenderOpaqueCube("Test", "Stone", stone, stone, stone, stone, stone, stone));
|
||||
|
||||
register(new BlockRenderOpaqueCube("Test", "Compass", qtex("compass"), qtex("compass"), qtex("side_north"), qtex("side_south"), qtex("side_east"), qtex("side_west")));
|
||||
register(new BlockRenderOpaqueCube("Test", "Compass", compass, compass, getTexture("side_north"), getTexture("side_south"), getTexture("side_east"), getTexture("side_west")));
|
||||
|
||||
register(new BlockRenderNone("Test", "Air"));
|
||||
register(new BlockRenderTransparentCube("Test", "Glass", glassT, glassT, glassT, glassT, glassT, glassT));
|
||||
register(new BlockRenderTransparentCube("Test", "Glass", glass, glass, glass, glass, glass, glass));
|
||||
}
|
||||
|
||||
public static BlockRender get(String name) {
|
||||
@ -62,10 +63,13 @@ public class BlockRenders {
|
||||
BLOCK_RENDERS.put(blockRender.getId(), blockRender);
|
||||
}
|
||||
|
||||
private static Texture qtex(String name) {
|
||||
return new SimpleTexture(new Sprite(
|
||||
TextureManager.load(name, TEXTURE_SETTINGS)
|
||||
));
|
||||
public static Texture getTexture(String name) {
|
||||
return new SimpleTexture(
|
||||
Atlases.getSprite(
|
||||
ResourceManager.getTextureResource("blocks/" + name),
|
||||
BLOCKS_ATLAS_GROUP
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,22 +25,17 @@ import java.io.Reader;
|
||||
import com.google.common.io.CharStreams;
|
||||
|
||||
import ru.windcorp.progressia.Progressia;
|
||||
import ru.windcorp.progressia.common.util.Named;
|
||||
|
||||
public class Resource {
|
||||
|
||||
private final String name;
|
||||
public class Resource extends Named {
|
||||
|
||||
public Resource(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
super(name);
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
// TODO Do proper resource lookup
|
||||
return Progressia.class.getClassLoader().getResourceAsStream(name);
|
||||
return Progressia.class.getClassLoader().getResourceAsStream(getName());
|
||||
}
|
||||
|
||||
public Reader getReader() {
|
||||
|
@ -22,5 +22,11 @@ public class ResourceManager {
|
||||
public static Resource getResource(String name) {
|
||||
return new Resource(name);
|
||||
}
|
||||
|
||||
public static Resource getTextureResource(String name) {
|
||||
return getResource("assets/textures/" + name + ".png");
|
||||
}
|
||||
|
||||
private ResourceManager() {}
|
||||
|
||||
}
|
||||
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 256 B |
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 644 B |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 588 B |
Before Width: | Height: | Size: 609 B After Width: | Height: | Size: 609 B |
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |