mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-03 23:17:48 +01:00
Large framebuffers for more than 128x128 rendering
This commit is contained in:
parent
a7139d19b6
commit
6856904905
137
src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java
Normal file
137
src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java
Normal file
@ -0,0 +1,137 @@
|
||||
package net.minestom.demo.largeframebuffers;
|
||||
|
||||
import fr.themode.demo.MainDemo;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.entity.GameMode;
|
||||
import net.minestom.server.entity.type.decoration.EntityItemFrame;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.metadata.MapMeta;
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
import net.minestom.server.map.framebuffers.LargeDirectFramebuffer;
|
||||
import net.minestom.server.map.framebuffers.LargeGLFWFramebuffer;
|
||||
import net.minestom.server.map.framebuffers.LargeGraphics2DFramebuffer;
|
||||
import net.minestom.server.network.packet.server.play.MapDataPacket;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
public class Demo {
|
||||
|
||||
public static void main(String[] args) {
|
||||
MainDemo.main(args); // used to avoid code duplication
|
||||
initDemo();
|
||||
}
|
||||
|
||||
private static void initDemo() {
|
||||
MinecraftServer.getConnectionManager().addPlayerInitialization(player -> {
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
});
|
||||
|
||||
InstanceManager instances = MinecraftServer.getInstanceManager();
|
||||
Instance instance = instances.getInstances().stream().findAny().get();
|
||||
|
||||
LargeDirectFramebuffer directFramebuffer = new LargeDirectFramebuffer(512, 512);
|
||||
LargeGraphics2DFramebuffer graphics2DFramebuffer = new LargeGraphics2DFramebuffer(512, 512);
|
||||
LargeGLFWFramebuffer glfwFramebuffer = new LargeGLFWFramebuffer(512, 512);
|
||||
|
||||
renderingLoop(0, directFramebuffer, Demo::directRendering);
|
||||
renderingLoop(101, graphics2DFramebuffer, Demo::graphics2DRendering);
|
||||
renderingLoop(201, glfwFramebuffer, f -> {});
|
||||
|
||||
glfwFramebuffer.setupRenderLoop(30, TimeUnit.MILLISECOND, Demo::openGLRendering);
|
||||
|
||||
for (int x = -2; x <= 2; x++) {
|
||||
for (int z = -2; z <= 2; z++) {
|
||||
instance.loadChunk(x, z);
|
||||
}
|
||||
}
|
||||
setupMaps(instance, 0, 10);
|
||||
setupMaps(instance, 101, 20);
|
||||
setupMaps(instance, 201, 30);
|
||||
}
|
||||
|
||||
private static void createFrame(Instance instance, int id, int x, int y, int z) {
|
||||
EntityItemFrame itemFrame = new EntityItemFrame(new Position(x, y, z), EntityItemFrame.ItemFrameOrientation.NORTH);
|
||||
itemFrame.getPosition().setYaw(180f);
|
||||
ItemStack map = new ItemStack(Material.FILLED_MAP, (byte)1);
|
||||
map.setItemMeta(new MapMeta(id));
|
||||
itemFrame.setItemStack(map);
|
||||
itemFrame.setInstance(instance);
|
||||
itemFrame.setCustomNameVisible(true);
|
||||
itemFrame.setCustomName(ColoredText.of("MapID: "+id));
|
||||
}
|
||||
|
||||
private static void setupMaps(Instance instance, int mapIDStart, int zCoordinate) {
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 4; x++) {
|
||||
createFrame(instance, mapIDStart+y*4+x, 2-x, 45-y, zCoordinate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends LargeFramebuffer> void renderingLoop(int mapIDStart, T framebuffer, Consumer<T> renderingCode) {
|
||||
final Framebuffer[] subviews = new Framebuffer[4*4];
|
||||
for (int i = 0; i < subviews.length; i++) {
|
||||
int x = (i % 4)*128;
|
||||
int y = (i / 4)*128;
|
||||
subviews[i] = framebuffer.createSubView(x, y);
|
||||
}
|
||||
MinecraftServer.getSchedulerManager().buildTask(() -> {
|
||||
renderingCode.accept(framebuffer);
|
||||
for (int i = 0; i < subviews.length; i++) {
|
||||
final MapDataPacket packet = new MapDataPacket();
|
||||
packet.mapId = mapIDStart + i;
|
||||
|
||||
Framebuffer f = subviews[i];
|
||||
f.preparePacket(packet);
|
||||
sendPacket(packet);
|
||||
}
|
||||
}).repeat(30, TimeUnit.MILLISECOND).schedule();
|
||||
}
|
||||
|
||||
private static void sendPacket(MapDataPacket packet) {
|
||||
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(p -> p.getPlayerConnection().sendPacket(packet));
|
||||
}
|
||||
|
||||
private static void directRendering(LargeDirectFramebuffer framebuffer) {
|
||||
Arrays.fill(framebuffer.getColors(), 0, 512*40+128, MapColors.COLOR_CYAN.baseColor());
|
||||
Arrays.fill(framebuffer.getColors(), 512*40+128, framebuffer.getColors().length, MapColors.COLOR_RED.baseColor());
|
||||
}
|
||||
|
||||
private static void graphics2DRendering(LargeGraphics2DFramebuffer framebuffer) {
|
||||
Graphics2D renderer = framebuffer.getRenderer();
|
||||
renderer.setColor(Color.BLACK);
|
||||
renderer.clearRect(0,0,512,512);
|
||||
renderer.setColor(Color.WHITE);
|
||||
renderer.drawString("Here's a very very long string that needs multiple maps to fit", 0, 100);
|
||||
}
|
||||
|
||||
private static void openGLRendering() {
|
||||
glClearColor(0f, 0f, 0f, 1f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glBegin(GL_TRIANGLES);
|
||||
|
||||
glVertex2f(0, -0.75f);
|
||||
glColor3f(1f, 0f, 0f);
|
||||
|
||||
glVertex2f(0.75f, 0.75f);
|
||||
glColor3f(0f, 1f, 0f);
|
||||
|
||||
glVertex2f(-0.75f, 0.75f);
|
||||
glColor3f(0f, 0f, 1f);
|
||||
|
||||
glEnd();
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
import net.minestom.server.timer.Task;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.glfw.GLFW.glfwTerminate;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
abstract class GLFWCapableBuffer {
|
||||
|
||||
protected final byte[] colors;
|
||||
private final ByteBuffer pixels;
|
||||
private final long glfwWindow;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
protected GLFWCapableBuffer(int width, int height) {
|
||||
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the framebuffer and initializes a new context
|
||||
*/
|
||||
protected GLFWCapableBuffer(int width, int height, int apiContext, int clientAPI) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colors = new byte[width*height];
|
||||
this.pixels = BufferUtils.createByteBuffer(width*height*4);
|
||||
if(!glfwInit()) {
|
||||
throw new RuntimeException("Failed to init GLFW");
|
||||
}
|
||||
|
||||
GLFWErrorCallback.createPrint().set();
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_CREATION_API, apiContext);
|
||||
glfwWindowHint(GLFW_CLIENT_API, clientAPI);
|
||||
|
||||
this.glfwWindow = glfwCreateWindow(width, height, "", 0L, 0L);
|
||||
if(glfwWindow == 0L) {
|
||||
try(var stack = MemoryStack.stackPush()) {
|
||||
PointerBuffer desc = stack.mallocPointer(1);
|
||||
int errcode = glfwGetError(desc);
|
||||
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GLFWCapableBuffer unbindContextFromThread() {
|
||||
glfwMakeContextCurrent(0L);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void changeRenderingThreadToCurrent() {
|
||||
glfwMakeContextCurrent(glfwWindow);
|
||||
GL.createCapabilities();
|
||||
}
|
||||
|
||||
public Task setupRenderLoop(long period, TimeUnit unit, Runnable rendering) {
|
||||
return MinecraftServer.getSchedulerManager()
|
||||
.buildTask(new Runnable() {
|
||||
private boolean first = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if(first) {
|
||||
changeRenderingThreadToCurrent();
|
||||
first = false;
|
||||
}
|
||||
render(rendering);
|
||||
}
|
||||
})
|
||||
.repeat(period, unit)
|
||||
.schedule();
|
||||
}
|
||||
|
||||
public void render(Runnable rendering) {
|
||||
rendering.run();
|
||||
glfwSwapBuffers(glfwWindow);
|
||||
prepareMapColors();
|
||||
}
|
||||
|
||||
// TODO: provide shader that performs the conversion automatically, would be a lot faster
|
||||
/**
|
||||
* Called in render after glFlush to read the pixel buffer contents and convert it to map colors.
|
||||
* Only call if you do not use {@link #render(Runnable)} nor {@link #setupRenderLoop(long, TimeUnit, Runnable)}
|
||||
*/
|
||||
public void prepareMapColors() {
|
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int i = Framebuffer.index(x, y, width)*4;
|
||||
int red = pixels.get(i) & 0xFF;
|
||||
int green = pixels.get(i+1) & 0xFF;
|
||||
int blue = pixels.get(i+2) & 0xFF;
|
||||
int alpha = pixels.get(i+3) & 0xFF;
|
||||
int argb = (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
colors[Framebuffer.index(x, y, width)] = MapColors.closestColor(argb).getIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
public long getGLFWWindow() {
|
||||
return glfwWindow;
|
||||
}
|
||||
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
}
|
@ -1,20 +1,13 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
import net.minestom.server.timer.Task;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_NATIVE_CONTEXT_API;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_API;
|
||||
|
||||
/**
|
||||
* GLFW-based framebuffer.
|
||||
@ -33,101 +26,20 @@ import static org.lwjgl.opengl.GL11.*;
|
||||
*
|
||||
* This framebuffer is meant to render to a single map (ie it is only compatible with 128x128 rendering)
|
||||
*/
|
||||
public class GLFWFramebuffer implements Framebuffer {
|
||||
public class GLFWFramebuffer extends GLFWCapableBuffer implements Framebuffer {
|
||||
|
||||
private final byte[] colors = new byte[WIDTH*HEIGHT];
|
||||
private final ByteBuffer pixels = BufferUtils.createByteBuffer(WIDTH*HEIGHT*4);
|
||||
private final long glfwWindow;
|
||||
|
||||
public GLFWFramebuffer() {
|
||||
this(GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the framebuffer and initializes a new EGL context
|
||||
* Creates the framebuffer and initializes a new context
|
||||
*/
|
||||
public GLFWFramebuffer(int apiContext, int clientAPI) {
|
||||
if(!glfwInit()) {
|
||||
throw new RuntimeException("Failed to init GLFW");
|
||||
}
|
||||
|
||||
GLFWErrorCallback.createPrint().set();
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_CREATION_API, apiContext);
|
||||
glfwWindowHint(GLFW_CLIENT_API, clientAPI);
|
||||
|
||||
this.glfwWindow = glfwCreateWindow(WIDTH, HEIGHT, "", 0L, 0L);
|
||||
if(glfwWindow == 0L) {
|
||||
try(var stack = MemoryStack.stackPush()) {
|
||||
PointerBuffer desc = stack.mallocPointer(1);
|
||||
int errcode = glfwGetError(desc);
|
||||
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GLFWFramebuffer unbindContextFromThread() {
|
||||
glfwMakeContextCurrent(0L);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void changeRenderingThreadToCurrent() {
|
||||
glfwMakeContextCurrent(glfwWindow);
|
||||
GL.createCapabilities();
|
||||
}
|
||||
|
||||
public Task setupRenderLoop(long period, TimeUnit unit, Runnable rendering) {
|
||||
return MinecraftServer.getSchedulerManager()
|
||||
.buildTask(new Runnable() {
|
||||
private boolean first = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if(first) {
|
||||
changeRenderingThreadToCurrent();
|
||||
first = false;
|
||||
}
|
||||
render(rendering);
|
||||
}
|
||||
})
|
||||
.repeat(period, unit)
|
||||
.schedule();
|
||||
}
|
||||
|
||||
public void render(Runnable rendering) {
|
||||
rendering.run();
|
||||
glfwSwapBuffers(glfwWindow);
|
||||
prepareMapColors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in render after glFlush to read the pixel buffer contents and convert it to map colors.
|
||||
* Only call if you do not use {@link #render(Runnable)} nor {@link #setupRenderLoop(long, TimeUnit, Runnable)}
|
||||
*/
|
||||
public void prepareMapColors() {
|
||||
glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
for (int x = 0; x < WIDTH; x++) {
|
||||
int i = Framebuffer.index(x, y)*4;
|
||||
int red = pixels.get(i) & 0xFF;
|
||||
int green = pixels.get(i+1) & 0xFF;
|
||||
int blue = pixels.get(i+2) & 0xFF;
|
||||
int alpha = pixels.get(i+3) & 0xFF;
|
||||
int argb = (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
colors[Framebuffer.index(x, y)] = MapColors.closestColor(argb).getIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
public long getGLFWWindow() {
|
||||
return glfwWindow;
|
||||
super(WIDTH, HEIGHT, apiContext, clientAPI);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_NATIVE_CONTEXT_API;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_API;
|
||||
|
||||
public class LargeGLFWFramebuffer extends GLFWCapableBuffer implements LargeFramebuffer {
|
||||
public LargeGLFWFramebuffer(int width, int height) {
|
||||
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||||
}
|
||||
|
||||
public LargeGLFWFramebuffer(int width, int height, int apiContext, int clientAPI) {
|
||||
super(width, height, apiContext, clientAPI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Framebuffer createSubView(int left, int top) {
|
||||
return new LargeFramebufferDefaultView(this, left, top);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMapColor(int x, int y) {
|
||||
return colors[Framebuffer.index(x, y, width())];
|
||||
}
|
||||
}
|
@ -22,6 +22,8 @@ public class EntityItemFrame extends ObjectEntity {
|
||||
super(EntityType.ITEM_FRAME, spawnPosition);
|
||||
this.orientation = orientation;
|
||||
this.rotation = Rotation.NONE;
|
||||
setNoGravity(true);
|
||||
setGravity(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
48
src/main/java/net/minestom/server/map/LargeFramebuffer.java
Normal file
48
src/main/java/net/minestom/server/map/LargeFramebuffer.java
Normal file
@ -0,0 +1,48 @@
|
||||
package net.minestom.server.map;
|
||||
|
||||
import net.minestom.server.network.packet.server.play.MapDataPacket;
|
||||
|
||||
/**
|
||||
* Framebuffer that is meant to be split in sub-framebuffers. Contrary to Framebuffer, LargeFramebuffer supports sizes over 128x128 pixels.
|
||||
*/
|
||||
public interface LargeFramebuffer {
|
||||
|
||||
int width();
|
||||
int height();
|
||||
|
||||
/**
|
||||
* Returns a new Framebuffer that represent a 128x128 sub-view of this framebuffer.
|
||||
* Implementations are free (but not guaranteed) to throw exceptions if left & top produces out-of-bounds coordinates.
|
||||
* @param left
|
||||
* @param top
|
||||
* @return
|
||||
*/
|
||||
Framebuffer createSubView(int left, int top);
|
||||
|
||||
byte getMapColor(int x, int y);
|
||||
|
||||
/**
|
||||
* Prepares the packet to render a 128x128 sub view of this framebuffer
|
||||
* @param packet
|
||||
* @param left
|
||||
* @param top
|
||||
*/
|
||||
default void preparePacket(MapDataPacket packet, int left, int top) {
|
||||
byte[] colors = new byte[width()*height()];
|
||||
int width = Math.min(width(), left+Framebuffer.WIDTH) - left;
|
||||
int height = Math.min(height(), top+Framebuffer.HEIGHT) - top;
|
||||
for (int y = top; y < height; y++) {
|
||||
for (int x = left; x < width; x++) {
|
||||
byte color = getMapColor(left, top);
|
||||
colors[Framebuffer.index(x-left, y-top, width)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
packet.columns = (short) width;
|
||||
packet.rows = (short) height;
|
||||
packet.icons = new MapDataPacket.Icon[0];
|
||||
packet.x = 0;
|
||||
packet.z = 0;
|
||||
packet.data = colors;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
import java.awt.*;
|
||||
|
@ -0,0 +1,65 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
/**
|
||||
* Large framebuffer with direct access to the colors array.
|
||||
*
|
||||
* This implementation does not throw errors when accessing out-of-bounds coordinates through sub-views, and will instead
|
||||
* use {@link MapColors#NONE}. This is only the case for sub-views, access through {@link #setMapColor(int, int, byte)}
|
||||
* and {@link #getMapColor(int, int)} will throw an exception if out-of-bounds coordinates are inputted.
|
||||
*/
|
||||
public class LargeDirectFramebuffer implements LargeFramebuffer {
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final byte[] colors;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LargeDirectFramebuffer} with the desired size
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public LargeDirectFramebuffer(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colors = new byte[width*height];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Framebuffer createSubView(int left, int top) {
|
||||
return new LargeFramebufferDefaultView(this, left, top);
|
||||
}
|
||||
|
||||
public LargeDirectFramebuffer setMapColor(int x, int y, byte color) {
|
||||
if(!bounds(x, y)) throw new IndexOutOfBoundsException("Invalid x;y coordinate: "+x+";"+y);
|
||||
colors[y*width+x] = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMapColor(int x, int y) {
|
||||
if(!bounds(x, y)) throw new IndexOutOfBoundsException("Invalid x;y coordinate: "+x+";"+y);
|
||||
return colors[y*width+x];
|
||||
}
|
||||
|
||||
private boolean bounds(int x, int y) {
|
||||
return x >= 0 && x < width && y >= 0 && y < height;
|
||||
}
|
||||
|
||||
public byte[] getColors() {
|
||||
return colors;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
public class LargeFramebufferDefaultView implements Framebuffer {
|
||||
private final LargeFramebuffer parent;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final byte[] colors = new byte[WIDTH*HEIGHT];
|
||||
|
||||
public LargeFramebufferDefaultView(LargeFramebuffer parent, int x, int y) {
|
||||
this.parent = parent;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
private boolean bounds(int x, int y) {
|
||||
return x >= 0 && x < parent.width() && y >= 0 && y < parent.height();
|
||||
}
|
||||
|
||||
private byte colorOrNone(int x, int y) {
|
||||
if(!bounds(x, y)) return MapColors.NONE.baseColor();
|
||||
return parent.getMapColor(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toMapColors() {
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
for (int x = 0; x < WIDTH; x++) {
|
||||
colors[Framebuffer.index(x, y)] = colorOrNone(x+this.x, y+this.y);
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
|
||||
/**
|
||||
* LargeFramebuffer that embeds a BufferedImage, allowing for rendering directly via Graphics2D or its pixel array
|
||||
*/
|
||||
public class LargeGraphics2DFramebuffer implements LargeFramebuffer {
|
||||
|
||||
private final byte[] colors;
|
||||
private final BufferedImage backingImage;
|
||||
private final Graphics2D renderer;
|
||||
private final int[] pixels;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public LargeGraphics2DFramebuffer(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
colors = new byte[width*height];
|
||||
backingImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
renderer = backingImage.createGraphics();
|
||||
pixels = ((DataBufferInt)backingImage.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
|
||||
public Graphics2D getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public BufferedImage getBackingImage() {
|
||||
return backingImage;
|
||||
}
|
||||
|
||||
public int get(int x, int z) {
|
||||
return pixels[x+z*width]; // stride is always the width of the image
|
||||
}
|
||||
|
||||
public LargeGraphics2DFramebuffer set(int x, int z, int rgb) {
|
||||
pixels[x+z*width] = rgb;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Framebuffer createSubView(int left, int top) {
|
||||
return new LargeFramebufferDefaultView(this, left, top);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMapColor(int x, int y) {
|
||||
return MapColors.closestColor(get(x, y)).getIndex();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user