Optimized tile render and added CRO tile support

- CROs now support tiles
  - CROCube refactored
- Test content:
  - Added sand and flower tiles
  - Removed grass blocks, added grass tiles
This commit is contained in:
OLEGSHA 2020-08-27 20:58:27 +03:00
parent 60fbfa9578
commit 79c7aa91f8
17 changed files with 312 additions and 105 deletions

View File

@ -19,13 +19,13 @@ package ru.windcorp.progressia.client.world;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Model;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.StaticModel;
import ru.windcorp.progressia.client.graphics.model.WorldRenderable;
@ -114,12 +114,10 @@ public class ChunkRender {
}
}
for (ChunkRenderOptimizer optimizer : optimizers) {
Shape result = optimizer.endRender();
if (result != null) {
builder.addPart(result);
}
}
optimizers.stream()
.map(ChunkRenderOptimizer::endRender)
.filter(Objects::nonNull)
.forEach(builder::addPart);
model = new StaticModel(builder);
needsUpdate = false;
@ -131,55 +129,41 @@ public class ChunkRender {
Builder builder
) {
BlockRender block = getBlock(cursor);
int x = cursor.x;
int y = cursor.y;
int z = cursor.z;
if (block instanceof BlockRenderNone) {
return;
}
forwardBlockToOptimizers(block, x, y, z, optimizers);
forwardBlockToOptimizers(block, cursor, optimizers);
if (!block.needsOwnRenderable()) {
return;
}
if (tryToCreateBlockRenderable(block, x, y, z, builder)) {
return;
}
addBlockRenderAsRenderable(block, x, y, z, builder);
addBlockRenderable(block, cursor, builder);
}
private void forwardBlockToOptimizers(
BlockRender block, int x, int y, int z,
BlockRender block, Vec3i cursor,
Collection<ChunkRenderOptimizer> optimizers
) {
optimizers.forEach(bro -> bro.processBlock(block, x, y, z));
optimizers.forEach(cro -> cro.processBlock(block, cursor));
}
private boolean tryToCreateBlockRenderable(
BlockRender block, int x, int y, int z,
private void addBlockRenderable(
BlockRender block,
Vec3i cursor,
Builder builder
) {
WorldRenderable renderable = block.createRenderable();
if (renderable == null) {
return false;
renderable = block::render;
}
builder.addPart(renderable, new Mat4().identity().translate(x, y, z));
return true;
}
private void addBlockRenderAsRenderable(
BlockRender block, int x, int y, int z,
Builder builder
) {
builder.addPart(
block::render,
new Mat4().identity().translate(x, y, z)
renderable,
new Mat4().identity().translate(cursor.x, cursor.y, cursor.z)
);
}
@ -230,6 +214,13 @@ public class ChunkRender {
Vec3 pos = Vectors.grab3().set(cursor.x, cursor.y, cursor.z);
optimizers.forEach(cro -> cro.processTile(tile, cursor, face));
if (!tile.needsOwnRenderable()) {
Vectors.release(pos);
return;
}
Vec3 offset = Vectors.grab3().set(
face.getVector().x, face.getVector().y, face.getVector().z
);

View File

@ -19,7 +19,6 @@ package ru.windcorp.progressia.client.world.renders;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.client.graphics.model.WorldRenderable;
import ru.windcorp.progressia.client.world.renders.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.Namespaced;
public abstract class BlockRender extends Namespaced {
@ -38,10 +37,6 @@ public abstract class BlockRender extends Namespaced {
return null;
}
public boolean canBeOptimized(ChunkRenderOptimizer optimizer) {
return true;
}
public boolean needsOwnRenderable() {
return true;
}

View File

@ -34,9 +34,7 @@ public class BlockRenders {
private static final AtlasGroup BLOCKS_ATLAS_GROUP =
new AtlasGroup("Blocks", 1 << 12);
private static Texture grassTop = getTexture("grass_top");
private static Texture grassSide = getTexture("grass_side");
private static Texture dirt = getTexture("grass_bottom");
private static Texture dirt = getTexture("dirt");
private static Texture stone = getTexture("stone");
private static Texture glass = getTexture("glass_clear");
private static Texture compass = getTexture("compass");
@ -44,7 +42,6 @@ public class BlockRenders {
private BlockRenders() {}
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));

View File

@ -0,0 +1,65 @@
package ru.windcorp.progressia.client.world.renders;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.client.graphics.backend.Usage;
import ru.windcorp.progressia.client.graphics.model.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
import ru.windcorp.progressia.client.graphics.model.WorldRenderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.renders.cro.ChunkRenderOptimizerCube.OpaqueTile;
import ru.windcorp.progressia.common.block.BlockFace;
import ru.windcorp.progressia.common.util.Vectors;
public class TileRenderGrass extends TileRender implements OpaqueTile {
private final Texture topTexture;
private final Texture sideTexture;
public TileRenderGrass(
String namespace, String name,
Texture top, Texture side
) {
super(namespace, name);
this.topTexture = top;
this.sideTexture = side;
}
@Override
public Texture getTexture(BlockFace face) {
return (face == BlockFace.TOP) ? topTexture : sideTexture;
}
@Override
public boolean isOpaque(BlockFace face) {
return face == BlockFace.TOP;
}
@Override
public WorldRenderable createRenderable(BlockFace face) {
ShapeRenderProgram program = WorldRenderProgram.getDefault();
Vec3 color = Vectors.grab3().set(1, 1, 1);
Vec3 center = Vectors.grab3().set(0, 0, 0);
try {
return new Shape(
Usage.STATIC, WorldRenderProgram.getDefault(),
Faces.createBlockFace(
program, getTexture(face), color,
center, face, false
)
);
} finally {
Vectors.release(color);
Vectors.release(center);
}
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -8,10 +8,11 @@ import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
import ru.windcorp.progressia.client.graphics.model.WorldRenderable;
import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.renders.cro.ChunkRenderOptimizerCube.OpaqueTile;
import ru.windcorp.progressia.common.block.BlockFace;
import ru.windcorp.progressia.common.util.Vectors;
public class TileRenderSimple extends TileRender {
public class TileRenderSimple extends TileRender implements OpaqueTile {
private final Texture texture;
@ -20,10 +21,16 @@ public class TileRenderSimple extends TileRender {
this.texture = texture;
}
public Texture getTexture() {
@Override
public Texture getTexture(BlockFace face) {
return texture;
}
@Override
public boolean isOpaque(BlockFace face) {
return false;
}
@Override
public WorldRenderable createRenderable(BlockFace face) {
ShapeRenderProgram program = WorldRenderProgram.getDefault();
@ -35,7 +42,8 @@ public class TileRenderSimple extends TileRender {
return new Shape(
Usage.STATIC, WorldRenderProgram.getDefault(),
Faces.createBlockFace(
program, texture, color, center, face, false
program, getTexture(face), color,
center, face, false
)
);
} finally {
@ -44,4 +52,9 @@ public class TileRenderSimple extends TileRender {
}
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -37,7 +37,11 @@ public class TileRenders {
private TileRenders() {}
public static void registerTest() {
register(new TileRenderGrass("Test", "Grass", getTexture("grass_top"), getTexture("grass_side")));
register(new TileRenderSimple("Test", "Stones", getTexture("stones")));
register(new TileRenderSimple("Test", "YellowFlowers", getTexture("yellow_flowers")));
register(new TileRenderSimple("Test", "Sand", getTexture("sand")));
}
public static TileRender get(String name) {

View File

@ -17,15 +17,26 @@
*******************************************************************************/
package ru.windcorp.progressia.client.world.renders.cro;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.renders.BlockRender;
import ru.windcorp.progressia.client.world.renders.TileRender;
import ru.windcorp.progressia.common.block.BlockFace;
public abstract class ChunkRenderOptimizer {
public abstract void startRender(ChunkRender chunk);
public abstract void processBlock(BlockRender block, int x, int y, int z);
public abstract void processBlock(
BlockRender block,
Vec3i posInChunk
);
public abstract void processTile(
TileRender tile,
Vec3i posInChunk, BlockFace face
);
public abstract Shape endRender();

View File

@ -17,7 +17,9 @@
*******************************************************************************/
package ru.windcorp.progressia.client.world.renders.cro;
import static ru.windcorp.progressia.common.block.BlockFace.BLOCK_FACE_COUNT;
import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
import static ru.windcorp.progressia.common.world.ChunkData.TILES_PER_FACE;
import java.util.ArrayList;
import java.util.Collection;
@ -33,7 +35,9 @@ import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.renders.BlockRender;
import ru.windcorp.progressia.client.world.renders.TileRender;
import ru.windcorp.progressia.common.block.BlockFace;
import ru.windcorp.progressia.common.util.Vectors;
public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
@ -43,33 +47,103 @@ public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
public boolean isBlockOpaque();
}
public static interface OpaqueTile {
public Texture getTexture(BlockFace face);
public boolean isOpaque(BlockFace face);
}
private static class BlockInfo {
OpaqueCube block;
final FaceInfo[] faces = new FaceInfo[BLOCK_FACE_COUNT];
{
for (int i = 0; i < faces.length; ++i) {
faces[i] = new FaceInfo();
}
}
}
private static class FaceInfo {
static final int NO_OPAQUE_TILES = -1;
int topOpaqueTile = NO_OPAQUE_TILES;
final OpaqueTile[] tiles = new OpaqueTile[TILES_PER_FACE];
int tileCount = 0;
}
private static final Vec3 COLOR_MULTIPLIER = new Vec3(1, 1, 1);
private final OpaqueCube[][][] data =
new OpaqueCube[BLOCKS_PER_CHUNK]
// private final OpaqueCube[][][] blocks =
// new OpaqueCube[BLOCKS_PER_CHUNK]
// [BLOCKS_PER_CHUNK]
// [BLOCKS_PER_CHUNK];
//
// private final OpaqueTile[][][][][] tiles =
// new OpaqueTile[BLOCKS_PER_CHUNK]
// [BLOCKS_PER_CHUNK]
// [BLOCKS_PER_CHUNK]
// [BLOCK_FACE_COUNT]
// [TILES_PER_FACE];
private final BlockInfo[][][] data =
new BlockInfo[BLOCKS_PER_CHUNK]
[BLOCKS_PER_CHUNK]
[BLOCKS_PER_CHUNK];
{
for (int x = 0; x < BLOCKS_PER_CHUNK; ++x) {
for (int y = 0; y < BLOCKS_PER_CHUNK; ++y) {
for (int z = 0; z < BLOCKS_PER_CHUNK; ++z) {
data[x][y][z] = new BlockInfo();
}
}
}
}
@Override
public void startRender(ChunkRender chunk) {
// Do nothing
}
@Override
public void processBlock(BlockRender block, int x, int y, int z) {
public void processBlock(BlockRender block, Vec3i pos) {
if (!(block instanceof OpaqueCube)) return;
OpaqueCube opaqueCube = (OpaqueCube) block;
addBlock(x, y, z, opaqueCube);
addBlock(pos, opaqueCube);
}
protected void addBlock(int x, int y, int z, OpaqueCube cube) {
data[x][y][z] = cube;
@Override
public void processTile(TileRender tile, Vec3i pos, BlockFace face) {
if (!(tile instanceof OpaqueTile)) return;
OpaqueTile opaqueTile = (OpaqueTile) tile;
addTile(pos, face, opaqueTile);
}
protected OpaqueCube getBlock(Vec3i cursor) {
protected void addBlock(Vec3i pos, OpaqueCube cube) {
getBlock(pos).block = cube;
}
private void addTile(Vec3i pos, BlockFace face, OpaqueTile opaqueTile) {
FaceInfo faceInfo = getFace(pos, face);
int index = faceInfo.tileCount;
faceInfo.tileCount++;
faceInfo.tiles[index] = opaqueTile;
if (opaqueTile.isOpaque(face)) {
faceInfo.topOpaqueTile = index;
}
}
protected BlockInfo getBlock(Vec3i cursor) {
return data[cursor.x][cursor.y][cursor.z];
}
protected FaceInfo getFace(Vec3i cursor, BlockFace face) {
return getBlock(cursor).faces[face.getId()];
}
@Override
public Shape endRender() {
@ -82,12 +156,8 @@ public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
for (cursor.x = 0; cursor.x < BLOCKS_PER_CHUNK; ++cursor.x) {
for (cursor.y = 0; cursor.y < BLOCKS_PER_CHUNK; ++cursor.y) {
for (cursor.z = 0; cursor.z < BLOCKS_PER_CHUNK; ++cursor.z) {
OpaqueCube block = getBlock(cursor);
if (block == null) continue;
processInnerFaces(block, cursor, shapeFaces::add);
processOuterFaces(block, cursor, shapeFaces::add);
processInnerFaces(cursor, shapeFaces::add);
processOuterFaces(cursor, shapeFaces::add);
}
}
}
@ -99,62 +169,82 @@ public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
);
}
private void processInnerFaces(
OpaqueCube block,
Vec3i cursor,
Consumer<Face> output
) {
if (block.isBlockOpaque()) return;
for (BlockFace face : BlockFace.getFaces()) {
Texture texture = block.getTexture(face);
if (texture == null) continue;
output.accept(Faces.createBlockFace(
WorldRenderProgram.getDefault(),
texture,
COLOR_MULTIPLIER,
new Vec3(cursor.x, cursor.y, cursor.z),
face,
true
));
}
}
private void processOuterFaces(
OpaqueCube block,
Vec3i cursor,
Consumer<Face> output
) {
Vec3 faceOrigin = Vectors.grab3();
Vec3 offset = Vectors.grab3();
for (BlockFace face : BlockFace.getFaces()) {
if (!shouldRenderOuterFace(cursor, face)) continue;
Texture texture = block.getTexture(face);
if (texture == null) continue;
faceOrigin.set(cursor.x, cursor.y, cursor.z);
offset
.set(face.getVector().x, face.getVector().y, face.getVector().z)
.mul(1f / 128);
if (!shouldRenderFace(cursor, face)) continue;
FaceInfo info = getFace(cursor, face);
if (info.topOpaqueTile == FaceInfo.NO_OPAQUE_TILES) {
OpaqueCube block = getBlock(cursor).block;
if (block != null) {
addFace(
faceOrigin, face,
getBlock(cursor).block.getTexture(face),
output
);
faceOrigin.add(offset);
}
}
int startLayer = info.topOpaqueTile;
if (startLayer == FaceInfo.NO_OPAQUE_TILES) {
startLayer = 0;
}
for (int layer = startLayer; layer < info.tileCount; ++layer) {
addFace(
faceOrigin, face,
info.tiles[layer].getTexture(face),
output
);
faceOrigin.add(offset);
}
}
Vectors.release(offset);
Vectors.release(faceOrigin);
}
private void addFace(
Vec3 cursor, BlockFace face,
Texture texture,
Consumer<Face> output
) {
if (texture == null) return;
output.accept(Faces.createBlockFace(
WorldRenderProgram.getDefault(),
texture,
COLOR_MULTIPLIER,
new Vec3(cursor.x, cursor.y, cursor.z),
new Vec3(cursor),
face,
false
));
}
}
private boolean shouldRenderFace(Vec3i cursor, BlockFace face) {
private boolean shouldRenderOuterFace(Vec3i cursor, BlockFace face) {
cursor.add(face.getVector());
try {
// TODO handle neighboring chunks properly
if (!isInBounds(cursor)) return true;
OpaqueCube adjacent = getBlock(cursor);
OpaqueCube adjacent = getBlock(cursor).block;
if (adjacent == null) return true;
if (adjacent.isOpaque(face)) return false;
@ -166,6 +256,29 @@ public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
}
}
private void processInnerFaces(
Vec3i cursor,
Consumer<Face> output
) {
// if (block.isBlockOpaque()) return;
//
// for (BlockFace face : BlockFace.getFaces()) {
//
// Texture texture = block.getTexture(face);
// if (texture == null) continue;
//
// output.accept(Faces.createBlockFace(
// WorldRenderProgram.getDefault(),
// texture,
// COLOR_MULTIPLIER,
// new Vec3(cursor.x, cursor.y, cursor.z),
// face,
// true
// ));
//
// }
}
private boolean isInBounds(Vec3i cursor) {
return
isInBounds(cursor.x) &&

View File

@ -25,7 +25,6 @@ public class BlockDataRegistry {
private static final Map<String, BlockData> REGISTRY = new HashMap<>();
static {
register(new BlockData("Test", "Grass"));
register(new BlockData("Test", "Dirt"));
register(new BlockData("Test", "Stone"));
register(new BlockData("Test", "Air"));

View File

@ -25,7 +25,10 @@ public class TileDataRegistry {
private static final Map<String, TileData> REGISTRY = new HashMap<>();
static {
register(new TileData("Test", "Grass"));
register(new TileData("Test", "Stones"));
register(new TileData("Test", "YellowFlowers"));
register(new TileData("Test", "Sand"));
}
public static TileData get(String name) {

View File

@ -59,12 +59,14 @@ public class ChunkData {
}
private void tmp_generate() {
BlockData grass = BlockDataRegistry.get("Test:Grass");
BlockData dirt = BlockDataRegistry.get("Test:Dirt");
BlockData stone = BlockDataRegistry.get("Test:Stone");
BlockData air = BlockDataRegistry.get("Test:Air");
TileData grass = TileDataRegistry.get("Test:Grass");
TileData stones = TileDataRegistry.get("Test:Stones");
TileData flowers = TileDataRegistry.get("Test:YellowFlowers");
TileData sand = TileDataRegistry.get("Test:Sand");
Vec3i aPoint = new Vec3i(5, 0, BLOCKS_PER_CHUNK + BLOCKS_PER_CHUNK/2);
Vec3i pos = new Vec3i();
@ -95,12 +97,26 @@ public class ChunkData {
for (pos.z = BLOCKS_PER_CHUNK - 1; pos.z >= 0 && getBlock(pos) == air; --pos.z);
setBlock(pos, grass);
getTiles(pos, BlockFace.TOP).add(grass);
for (BlockFace face : BlockFace.getFaces()) {
if (face.getVector().z != 0) continue;
getTiles(pos, face).add(grass);
}
int hash = x*x * 13 ^ y*y * 37 ^ pos.z*pos.z * 129;
int hash = x*x * 19 ^ y*y * 41 ^ pos.z*pos.z * 147;
if (hash % 5 == 0) {
getTiles(pos, BlockFace.TOP).add(sand);
}
hash = x*x * 13 ^ y*y * 37 ^ pos.z*pos.z * 129;
if (hash % 5 == 0) {
getTiles(pos, BlockFace.TOP).add(stones);
}
hash = x*x * 17 ^ y*y * 39 ^ pos.z*pos.z * 131;
if (hash % 9 == 0) {
getTiles(pos, BlockFace.TOP).add(flowers);
}
}
}
}
@ -218,8 +234,8 @@ public class ChunkData {
return
(blockInChunk.x == min && face == SOUTH ) ||
(blockInChunk.x == max && face == NORTH ) ||
(blockInChunk.y == min && face == WEST ) ||
(blockInChunk.y == max && face == EAST ) ||
(blockInChunk.y == min && face == EAST ) ||
(blockInChunk.y == max && face == WEST ) ||
(blockInChunk.z == min && face == BOTTOM) ||
(blockInChunk.z == max && face == TOP );
}

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B