Archived
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/
This commit is contained in:
1 parent
aeed34d105
commit
6d2adb0f31
25 files changed
+661
-397
No files matched your search
@@ -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);
|
||||
@@ -143,17 +140,4 @@ public class LayerTestUI extends AssembledFlatLayer {
|
||||
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() {}
|
||||
|
||||
}
|
||||
+2
-2
@@ -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 int handle = NOT_LOADED;
|
||||
private Pixels pixels;
|
||||
private static int[] currentlyBound = new int[32];
|
||||
static {
|
||||
Arrays.fill(currentlyBound, NOT_LOADED);
|
||||
}
|
||||
|
||||
public TexturePrimitive(Pixels pixels) {
|
||||
private int handle = NOT_LOADED;
|
||||
private TextureData 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() {
|
||||
|
||||
@@ -23,4 +23,10 @@ public class ResourceManager {
|
||||
return new Resource(name);
|
||||
}
|
||||
|
||||
public static Resource getTextureResource(String name) {
|
||||
return getResource("assets/textures/" + name + ".png");
|
||||
}
|
||||
|
||||
private ResourceManager() {}
|
||||
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user