From 260562310a7df7989e7ac652f119dd1f93bf8411 Mon Sep 17 00:00:00 2001 From: OLEGSHA Date: Sun, 24 Jan 2021 20:48:38 +0300 Subject: [PATCH] Refactored CROs, removed internal chunk borders. Documented CROs. - Refactored CROs - Renamed CROOpaqueCube to CROSurface (including relevant interfaces) - Optimized CROS - CROS now takes neighbor chunks into account - Refactored default OptimizedSurface implementations - Documented some CRO code --- build.gradle | 2 +- .../client/graphics/model/StaticModel.java | 8 +- .../client/graphics/world/LayerWorld.java | 2 +- .../progressia/client/world/ChunkRender.java | 174 +------- .../client/world/ChunkRenderModel.java | 161 +++++++ .../client/world/ChunkUpdateListener.java | 52 +++ .../client/world/block/BlockRender.java | 4 +- .../client/world/block/BlockRenderNone.java | 4 +- .../world/block/BlockRenderOpaqueCube.java | 5 - .../world/block/BlockRenderTexturedCube.java | 79 +++- .../block/BlockRenderTransparentCube.java | 5 - .../world/cro/ChunkRenderOptimizer.java | 112 ++++- .../world/cro/ChunkRenderOptimizerCube.java | 283 ------------- ...java => ChunkRenderOptimizerRegistry.java} | 31 +- .../cro/ChunkRenderOptimizerSurface.java | 396 ++++++++++++++++++ .../client/world/tile/TileRender.java | 4 +- .../client/world/tile/TileRenderGrass.java | 34 +- .../TileRenderNone.java} | 45 +- .../client/world/tile/TileRenderSurface.java | 63 ++- .../progressia/common/world/Coordinates.java | 4 + .../common/world/block/BlockRelation.java | 6 + .../common/world/generic/GenericChunk.java | 66 +++ .../windcorp/progressia/test/TestContent.java | 3 + .../test/TestEntityRenderHuman.java | 2 +- .../test/TestEntityRenderJavapony.java | 4 +- 25 files changed, 946 insertions(+), 603 deletions(-) create mode 100644 src/main/java/ru/windcorp/progressia/client/world/ChunkRenderModel.java delete mode 100644 src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerCube.java rename src/main/java/ru/windcorp/progressia/client/world/cro/{ChunkRenderOptimizerSupplier.java => ChunkRenderOptimizerRegistry.java} (58%) create mode 100644 src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java rename src/main/java/ru/windcorp/progressia/client/world/{cro/ChunkRenderOptimizers.java => tile/TileRenderNone.java} (50%) diff --git a/build.gradle b/build.gradle index 5adae10..4eddd2f 100644 --- a/build.gradle +++ b/build.gradle @@ -47,7 +47,7 @@ repositories { * Currently used by: * - ru.windcorp.fork.io.github.java-graphics:glm:1.0.1 */ - maven { url 'https://windcorp.ru/./maven' } + maven { url 'http://192.168.0.71/./maven' } } dependencies { diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/model/StaticModel.java b/src/main/java/ru/windcorp/progressia/client/graphics/model/StaticModel.java index 69e5de3..4c489d3 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/model/StaticModel.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/model/StaticModel.java @@ -38,10 +38,6 @@ public class StaticModel extends Model { this.transforms = transforms; } - public StaticModel(Builder builder) { - this(builder.getParts(), builder.getTransforms()); - } - @Override protected Mat4 getTransform(int partIndex) { return transforms[partIndex]; @@ -82,6 +78,10 @@ public class StaticModel extends Model { private Mat4[] getTransforms() { return transforms.toArray(new Mat4[transforms.size()]); } + + public StaticModel build() { + return new StaticModel(getParts(), getTransforms()); + } } diff --git a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java index 0db08f1..317dfa2 100644 --- a/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java +++ b/src/main/java/ru/windcorp/progressia/client/graphics/world/LayerWorld.java @@ -188,7 +188,7 @@ public class LayerWorld extends Layer { ); } - return new StaticModel(b); + return b.build(); } private static final float FRICTION_COEFF = Units.get("1e-5f kg/s"); diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java index a4c10c0..21f6098 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkRender.java @@ -18,35 +18,20 @@ package ru.windcorp.progressia.client.world; -import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.WeakHashMap; -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.ShapeRenderHelper; -import ru.windcorp.progressia.client.graphics.model.StaticModel; -import ru.windcorp.progressia.client.graphics.model.Renderable; -import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder; import ru.windcorp.progressia.client.world.block.BlockRender; -import ru.windcorp.progressia.client.world.block.BlockRenderNone; import ru.windcorp.progressia.client.world.block.BlockRenderRegistry; -import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; -import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSupplier; -import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizers; import ru.windcorp.progressia.client.world.tile.TileRender; import ru.windcorp.progressia.client.world.tile.TileRenderRegistry; import ru.windcorp.progressia.client.world.tile.TileRenderStack; import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.generic.GenericChunk; -import ru.windcorp.progressia.common.world.tile.TileData; import ru.windcorp.progressia.common.world.tile.TileDataStack; public class ChunkRender @@ -55,7 +40,7 @@ public class ChunkRender private final WorldRender world; private final ChunkData data; - private Model model = null; + private final ChunkRenderModel model; private final Map tileRenderLists = Collections .synchronizedMap(new WeakHashMap<>()); @@ -63,6 +48,7 @@ public class ChunkRender public ChunkRender(WorldRender world, ChunkData data) { this.world = world; this.data = data; + this.model = new ChunkRenderModel(this); } @Override @@ -107,165 +93,11 @@ public class ChunkRender } public synchronized void render(ShapeRenderHelper renderer) { - if (model == null) { - return; - } - - renderer.pushTransform().translate( - data.getX() * ChunkData.BLOCKS_PER_CHUNK, - data.getY() * ChunkData.BLOCKS_PER_CHUNK, - data.getZ() * ChunkData.BLOCKS_PER_CHUNK - ); - model.render(renderer); - - renderer.popTransform(); } public synchronized void update() { - Collection optimizers = ChunkRenderOptimizers.getAllSuppliers().stream() - .map(ChunkRenderOptimizerSupplier::createOptimizer) - .collect(Collectors.toList()); - - optimizers.forEach(bro -> bro.startRender(this)); - - StaticModel.Builder builder = StaticModel.builder(); - - Vec3i cursor = new Vec3i(); - for (int x = 0; x < ChunkData.BLOCKS_PER_CHUNK; ++x) { - for (int y = 0; y < ChunkData.BLOCKS_PER_CHUNK; ++y) { - for (int z = 0; z < ChunkData.BLOCKS_PER_CHUNK; ++z) { - cursor.set(x, y, z); - - buildBlock(cursor, optimizers, builder); - buildBlockTiles(cursor, optimizers, builder); - } - } - } - - optimizers.stream() - .map(ChunkRenderOptimizer::endRender) - .filter(Objects::nonNull) - .forEach(builder::addPart); - - model = new StaticModel(builder); - } - - private void buildBlock( - Vec3i cursor, - Collection optimizers, - Builder builder - ) { - BlockRender block = getBlock(cursor); - - if (block instanceof BlockRenderNone) { - return; - } - - forwardBlockToOptimizers(block, cursor, optimizers); - - if (!block.needsOwnRenderable()) { - return; - } - - addBlockRenderable(block, cursor, builder); - } - - private void forwardBlockToOptimizers( - BlockRender block, - Vec3i cursor, - Collection optimizers - ) { - optimizers.forEach(cro -> cro.processBlock(block, cursor)); - } - - private void addBlockRenderable( - BlockRender block, - Vec3i cursor, - Builder builder - ) { - Renderable renderable = block.createRenderable(); - - if (renderable == null) { - renderable = block::render; - } - - builder.addPart( - renderable, - new Mat4().identity().translate(cursor.x, cursor.y, cursor.z) - ); - } - - private void buildBlockTiles( - Vec3i cursor, - Collection optimizers, - Builder builder - ) { - for (BlockFace face : BlockFace.getFaces()) { - buildFaceTiles(cursor, face, optimizers, builder); - } - } - - private void buildFaceTiles( - Vec3i cursor, - BlockFace face, - Collection optimizers, - Builder builder - ) { - List tiles = getData().getTilesOrNull(cursor, face); - - if (tiles == null) { - return; - } - - for (int layer = 0; layer < tiles.size(); ++layer) { - - if (tiles.get(layer) == null) { - System.out.println(tiles.get(layer).getId()); - } - - buildTile( - cursor, - face, - TileRenderRegistry.getInstance().get( - tiles.get(layer).getId() - ), - layer, - optimizers, - builder - ); - } - } - - private void buildTile( - Vec3i cursor, - BlockFace face, - TileRender tile, - int layer, - Collection optimizers, - Builder builder - ) { - // TODO implement - - Vec3 pos = new Vec3(cursor.x, cursor.y, cursor.z); - - optimizers.forEach(cro -> cro.processTile(tile, cursor, face)); - - if (!tile.needsOwnRenderable()) - return; - - Vec3 offset = new Vec3( - face.getVector().x, - face.getVector().y, - face.getVector().z - ); - - pos.add(offset.mul(1f / 64)); - - builder.addPart( - tile.createRenderable(face), - new Mat4().identity().translate(pos) - ); + model.update(); } private class TileRenderStackImpl extends TileRenderStack { diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkRenderModel.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkRenderModel.java new file mode 100644 index 0000000..9653abe --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkRenderModel.java @@ -0,0 +1,161 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * 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 . + */ +package ru.windcorp.progressia.client.world; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +import glm.mat._4.Mat4; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.client.graphics.model.Model; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import ru.windcorp.progressia.client.graphics.model.StaticModel; +import ru.windcorp.progressia.client.graphics.model.StaticModel.Builder; +import ru.windcorp.progressia.client.world.block.BlockRender; +import ru.windcorp.progressia.client.world.block.BlockRenderNone; +import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; +import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry; +import ru.windcorp.progressia.client.world.tile.TileRender; +import ru.windcorp.progressia.client.world.tile.TileRenderNone; +import ru.windcorp.progressia.client.world.tile.TileRenderStack; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.block.BlockFace; + +public class ChunkRenderModel implements Renderable { + + private final ChunkRender chunk; + + private final Collection optimizers = new ArrayList<>(); + private Model model = null; + + public ChunkRenderModel(ChunkRender chunk) { + this.chunk = chunk; + } + + @Override + public void render(ShapeRenderHelper renderer) { + if (model == null) return; + + renderer.pushTransform().translate( + chunk.getX() * ChunkData.BLOCKS_PER_CHUNK, + chunk.getY() * ChunkData.BLOCKS_PER_CHUNK, + chunk.getZ() * ChunkData.BLOCKS_PER_CHUNK + ); + + model.render(renderer); + + renderer.popTransform(); + } + + public void update() { + setupCROs(); + + StaticModel.Builder sink = StaticModel.builder(); + + optimizers.forEach(ChunkRenderOptimizer::startRender); + + chunk.forEachBiC(blockInChunk -> { + processBlockAndTiles(blockInChunk, sink); + }); + + for (ChunkRenderOptimizer optimizer : optimizers) { + Renderable renderable = optimizer.endRender(); + if (renderable != null) { + sink.addPart(renderable); + } + } + + this.model = sink.build(); + this.optimizers.clear(); + } + + private void setupCROs() { + Set ids = ChunkRenderOptimizerRegistry.getInstance().keySet(); + + for (String id : ids) { + ChunkRenderOptimizer optimizer = ChunkRenderOptimizerRegistry.getInstance().create(id); + optimizer.setup(chunk); + this.optimizers.add(optimizer); + } + } + + private void processBlockAndTiles(Vec3i blockInChunk, Builder sink) { + processBlock(blockInChunk, sink); + + for (BlockFace face : BlockFace.getFaces()) { + processTileStack(blockInChunk, face, sink); + } + } + + private void processBlock(Vec3i blockInChunk, Builder sink) { + BlockRender block = chunk.getBlock(blockInChunk); + + if (block instanceof BlockRenderNone) { + return; + } + + if (block.needsOwnRenderable()) { + sink.addPart( + block.createRenderable(chunk.getData(), blockInChunk), + new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z) + ); + } + + processBlockWithCROs(block, blockInChunk); + } + + private void processBlockWithCROs(BlockRender block, Vec3i blockInChunk) { + for (ChunkRenderOptimizer optimizer : optimizers) { + optimizer.addBlock(block, blockInChunk); + } + } + + private void processTileStack(Vec3i blockInChunk, BlockFace face, Builder sink) { + TileRenderStack trs = chunk.getTilesOrNull(blockInChunk, face); + + if (trs == null || trs.isEmpty()) { + return; + } + + trs.forEach(tile -> processTile(tile, blockInChunk, face, sink)); + } + + private void processTile(TileRender tile, Vec3i blockInChunk, BlockFace face, Builder sink) { + if (tile instanceof TileRenderNone) { + return; + } + + if (tile.needsOwnRenderable()) { + sink.addPart( + tile.createRenderable(chunk.getData(), blockInChunk, face), + new Mat4().identity().translate(blockInChunk.x, blockInChunk.y, blockInChunk.z) + ); + } + + processTileWithCROs(tile, blockInChunk, face); + } + + private void processTileWithCROs(TileRender tile, Vec3i blockInChunk, BlockFace face) { + for (ChunkRenderOptimizer optimizer : optimizers) { + optimizer.addTile(tile, blockInChunk, face); + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java index d9e4ca1..32a950f 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java +++ b/src/main/java/ru/windcorp/progressia/client/world/ChunkUpdateListener.java @@ -18,8 +18,12 @@ package ru.windcorp.progressia.client.world; +import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkDataListener; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockFace; +import ru.windcorp.progressia.common.world.tile.TileData; class ChunkUpdateListener implements ChunkDataListener { @@ -33,5 +37,53 @@ class ChunkUpdateListener implements ChunkDataListener { public void onChunkChanged(ChunkData chunk) { world.getChunk(chunk).markForUpdate(); } + + @Override + public void onChunkLoaded(ChunkData chunk) { + Vec3i cursor = new Vec3i(); + for (BlockFace face : BlockFace.getFaces()) { + cursor.set(chunk.getX(), chunk.getY(), chunk.getZ()); + cursor.add(face.getVector()); + world.markChunkForUpdate(cursor); + } + } + + @Override + public void onChunkBlockChanged(ChunkData chunk, Vec3i blockInChunk, BlockData previous, BlockData current) { + onLocationChanged(chunk, blockInChunk); + } + + @Override + public void onChunkTilesChanged( + ChunkData chunk, + Vec3i blockInChunk, + BlockFace face, + TileData tile, + boolean wasAdded + ) { + onLocationChanged(chunk, blockInChunk); + } + + private void onLocationChanged(ChunkData chunk, Vec3i blockInChunk) { + Vec3i chunkPos = new Vec3i(chunk.getPosition()); + + if (blockInChunk.x == 0) { + chunkPos.x -= 1; + } else if (blockInChunk.x == ChunkData.BLOCKS_PER_CHUNK - 1) { + chunkPos.x += 1; + } else if (blockInChunk.y == 0) { + chunkPos.y -= 1; + } else if (blockInChunk.y == ChunkData.BLOCKS_PER_CHUNK - 1) { + chunkPos.y += 1; + } else if (blockInChunk.z == 0) { + chunkPos.z -= 1; + } else if (blockInChunk.z == ChunkData.BLOCKS_PER_CHUNK - 1) { + chunkPos.z += 1; + } else { + return; + } + + world.markChunkForUpdate(chunkPos); + } } diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java index 089da53..d350fa8 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRender.java @@ -20,7 +20,9 @@ package ru.windcorp.progressia.client.world.block; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.generic.GenericBlock; +import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.client.graphics.model.Renderable; public abstract class BlockRender extends Namespaced implements GenericBlock { @@ -35,7 +37,7 @@ public abstract class BlockRender extends Namespaced implements GenericBlock { ); } - public Renderable createRenderable() { + public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) { return null; } diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java index e40b13b..84a2c4b 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderNone.java @@ -18,8 +18,10 @@ package ru.windcorp.progressia.client.world.block; +import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.client.graphics.model.EmptyModel; import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.common.world.ChunkData; public class BlockRenderNone extends BlockRender { @@ -28,7 +30,7 @@ public class BlockRenderNone extends BlockRender { } @Override - public Renderable createRenderable() { + public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) { return EmptyModel.getInstance(); } diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java index 06f2963..e4d8723 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderOpaqueCube.java @@ -65,9 +65,4 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube { return true; } - @Override - public boolean needsOwnRenderable() { - return false; - } - } diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java index fd0c9d9..1042a46 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTexturedCube.java @@ -22,17 +22,27 @@ import static ru.windcorp.progressia.common.world.block.BlockFace.*; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; -import ru.windcorp.progressia.client.graphics.model.Shapes; +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import glm.vec._4.Vec4; +import ru.windcorp.progressia.client.graphics.Colors; +import ru.windcorp.progressia.client.graphics.backend.Usage; +import ru.windcorp.progressia.client.graphics.model.Face; +import ru.windcorp.progressia.client.graphics.model.Faces; import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.Shape; import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; -import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueCube; +import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.BlockOptimizedSurface; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.block.BlockFace; public abstract class BlockRenderTexturedCube extends BlockRender - implements OpaqueCube { + implements BlockOptimizedSurface { private final Map textures = new HashMap<>(); @@ -55,22 +65,61 @@ public abstract class BlockRenderTexturedCube textures.put(WEST, westTexture); } - @Override - public Texture getTexture(BlockFace face) { - return textures.get(face); + public Texture getTexture(BlockFace blockFace) { + return textures.get(blockFace); + } + + public Vec4 getColorMultiplier(BlockFace blockFace) { + return Colors.WHITE; } @Override - public Renderable createRenderable() { - return new Shapes.PppBuilder( + public final void getFaces( + ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, + boolean inner, + Consumer output, + Vec3 offset + ) { + output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset)); + } + + private Face createFace( + ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, + boolean inner, + Vec3 offset + ) { + return Faces.createBlockFace( WorldRenderProgram.getDefault(), - getTexture(TOP), - getTexture(BOTTOM), - getTexture(NORTH), - getTexture(SOUTH), - getTexture(EAST), - getTexture(WEST) - ).create(); + getTexture(blockFace), + getColorMultiplier(blockFace), + offset, + blockFace, + inner + ); + } + + @Override + public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) { + boolean opaque = isBlockOpaque(); + + Face[] faces = new Face[BLOCK_FACE_COUNT + (opaque ? BLOCK_FACE_COUNT : 0)]; + + for (int i = 0; i < BLOCK_FACE_COUNT; ++i) { + faces[i] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), false, Vectors.ZERO_3); + } + + if (!opaque) { + for (int i = 0; i < BLOCK_FACE_COUNT; ++i) { + faces[i + BLOCK_FACE_COUNT] = createFace(chunk, blockInChunk, BlockFace.getFaces().get(i), true, Vectors.ZERO_3); + } + } + + return new Shape(Usage.STATIC, WorldRenderProgram.getDefault(), faces); + } + + @Override + public boolean needsOwnRenderable() { + return false; } } diff --git a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java index 54a94e4..7f3df03 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java +++ b/src/main/java/ru/windcorp/progressia/client/world/block/BlockRenderTransparentCube.java @@ -65,9 +65,4 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube { return false; } - @Override - public boolean needsOwnRenderable() { - return false; - } - } diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java index 9117111..b684c6a 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java +++ b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizer.java @@ -15,31 +15,115 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package ru.windcorp.progressia.client.world.cro; import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.client.graphics.model.Shape; +import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.world.ChunkRender; import ru.windcorp.progressia.client.world.block.BlockRender; import ru.windcorp.progressia.client.world.tile.TileRender; +import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.world.block.BlockFace; -public abstract class ChunkRenderOptimizer { +/** + * Chunk render optimizer (CRO) is an object that produces optimized models for + * chunks. CROs are sequentially given information about the blocks and tiles of + * a particular chunk, after which they are expected to produce a set of + * {@link Renderable}s. As the name suggests, CROs are primarily expected to + * output models that are optimized compared to models of individual blocks and + * tiles. An example of a CRO is {@link ChunkRenderOptimizerSurface}: it removes + * block surfaces and tiles that it knows cannot be seen, thus significantly + * reducing total polygon count. + *

CRO lifecycle

+ * A CRO instance is created by {@link ChunkRenderOptimizerRegistry}. It may + * then be used to work on multiple chunks sequentially. Each chunk is processed + * in the following way: + *
    + *
  1. {@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender} + * instance.
  2. + *
  3. {@link #startRender()} is invoked. The CRO must reset its state.
  4. + *
  5. {@link #addBlock(BlockRender, Vec3i)} and + * {@link #addTile(TileRender, Vec3i, BlockFace)} are invoked for each block and + * tile that this CRO should optimize. {@code addTile} specifies tiles in order + * of ascension within a tile stack.
  6. + *
  7. {@link #endRender()} is invoked. The CRO may perform any pending + * calculations. The result of the optimization is returned.
  8. + *
+ *

+ * Each CRO instance is accessed by a single thread. + */ +public abstract class ChunkRenderOptimizer extends Namespaced { - public abstract void startRender(ChunkRender chunk); + /** + * The chunk that this CRO is currently working on. + */ + protected ChunkRender chunk = null; - public abstract void processBlock( - BlockRender block, - Vec3i posInChunk - ); + /** + * Creates a new CRO instance with the specified ID. + * + * @param id the ID of this CRO + */ + public ChunkRenderOptimizer(String id) { + super(id); + } - public abstract void processTile( - TileRender tile, - Vec3i posInChunk, - BlockFace face - ); + /** + * This method is invoked before a new chunk processing cycle begins to + * specify the chunk. When overriding, {@code super.setup(chunk)} must be + * invoked. + * + * @param chunk the chunk that will be processed next + */ + public void setup(ChunkRender chunk) { + this.chunk = chunk; + } - public abstract Shape endRender(); + /** + * @return the chunk that this CRO is currently working on + */ + public ChunkRender getChunk() { + return chunk; + } + + /** + * Resets this CRO to a state in which a new chunk may be processed. + */ + public abstract void startRender(); + + /** + * Requests that this CRO processes the provided block. This method may only + * be invoked between {@link #startRender()} and {@link #endRender()}. This + * method is only invoked once per block. This method is not necessarily + * invoked for each block. + * + * @param block a {@link BlockRender} instance describing the block. + * It corresponds to + * {@code getChunk().getBlock(blockInChunk)}. + * @param blockInChunk the position of the block + */ + public abstract void addBlock(BlockRender block, Vec3i blockInChunk); + + /** + * Requests that this CRO processes the provided tile. This method may only + * be invoked between {@link #startRender()} and {@link #endRender()}. This + * method is only invoked once per tile. This method is not necessarily + * invoked for each tile. When multiple tiles in a tile stack are requested, + * this method is invoked for lower tiles first. + * + * @param tile a {@link BlockRender} instance describing the tile + * @param blockInChunk the position of the block that the tile belongs to + * @param blockFace the face that the tile belongs to + */ + public abstract void addTile(TileRender tile, Vec3i blockInChunk, BlockFace blockFace); + + /** + * Requests that the CRO assembles and outputs its model. This method may + * only be invoked after {@link #startRender()}. + * + * @return the assembled {@link Renderable}. + */ + public abstract Renderable endRender(); } diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerCube.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerCube.java deleted file mode 100644 index 723549a..0000000 --- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerCube.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Progressia - * Copyright (C) 2020-2021 Wind Corporation and contributors - * - * 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 . - */ - -package ru.windcorp.progressia.client.world.cro; - -import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK; -import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT; -import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Consumer; - -import glm.vec._3.Vec3; -import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.client.graphics.Colors; -import ru.windcorp.progressia.client.graphics.backend.Usage; -import ru.windcorp.progressia.client.graphics.model.Face; -import ru.windcorp.progressia.client.graphics.model.Faces; -import ru.windcorp.progressia.client.graphics.model.Shape; -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.block.BlockRender; -import ru.windcorp.progressia.client.world.tile.TileRender; -import ru.windcorp.progressia.common.world.block.BlockFace; - -public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer { - - public static interface OpaqueCube { - public Texture getTexture(BlockFace face); - - public boolean isOpaque(BlockFace face); - - public boolean isBlockOpaque(); - } - - public static interface OpaqueSurface { - 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 OpaqueSurface[] tiles = new OpaqueSurface[TILES_PER_FACE]; - int tileCount = 0; - } - - 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, Vec3i pos) { - if (!(block instanceof OpaqueCube)) - return; - OpaqueCube opaqueCube = (OpaqueCube) block; - addBlock(pos, opaqueCube); - } - - @Override - public void processTile(TileRender tile, Vec3i pos, BlockFace face) { - if (!(tile instanceof OpaqueSurface)) - return; - OpaqueSurface opaqueTile = (OpaqueSurface) tile; - addTile(pos, face, opaqueTile); - } - - protected void addBlock(Vec3i pos, OpaqueCube cube) { - getBlock(pos).block = cube; - } - - private void addTile(Vec3i pos, BlockFace face, OpaqueSurface 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() { - - Collection shapeFaces = new ArrayList<>( - BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3 - ); - - Vec3i cursor = new Vec3i(); - - 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) { - processInnerFaces(cursor, shapeFaces::add); - processOuterFaces(cursor, shapeFaces::add); - } - } - } - - return new Shape( - Usage.STATIC, - WorldRenderProgram.getDefault(), - shapeFaces.toArray(new Face[shapeFaces.size()]) - ); - } - - private void processOuterFaces( - Vec3i cursor, - Consumer output - ) { - for (BlockFace face : BlockFace.getFaces()) { - if (!shouldRenderOuterFace(cursor, face)) - continue; - - Vec3 faceOrigin = new Vec3(cursor.x, cursor.y, cursor.z); - Vec3 offset = new Vec3(face.getVector().x, face.getVector().y, face.getVector().z).mul(1f / 128); - - 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); - } - } - } - - private void addFace( - Vec3 cursor, - BlockFace face, - Texture texture, - Consumer output - ) { - if (texture == null) - return; - - output.accept( - Faces.createBlockFace( - WorldRenderProgram.getDefault(), - texture, - Colors.WHITE, - new Vec3(cursor), - face, - false - ) - ); - } - - 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).block; - - if (adjacent == null) - return true; - if (adjacent.isOpaque(face)) - return false; - - return true; - - } finally { - cursor.sub(face.getVector()); - } - } - - private void processInnerFaces( - Vec3i cursor, - Consumer 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) && - isInBounds(cursor.y) && - isInBounds(cursor.z); - } - - private boolean isInBounds(int c) { - return c >= 0 && c < BLOCKS_PER_CHUNK; - } - -} diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSupplier.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerRegistry.java similarity index 58% rename from src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSupplier.java rename to src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerRegistry.java index 340ea6d..7e4dc87 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSupplier.java +++ b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerRegistry.java @@ -18,28 +18,17 @@ package ru.windcorp.progressia.client.world.cro; -import com.google.common.base.Supplier; +import ru.windcorp.progressia.common.util.namespaces.NamespacedFactoryRegistry; -import ru.windcorp.progressia.common.util.namespaces.Namespaced; - -public abstract class ChunkRenderOptimizerSupplier extends Namespaced { - - public ChunkRenderOptimizerSupplier(String id) { - super(id); - } - - public abstract ChunkRenderOptimizer createOptimizer(); - - public static ChunkRenderOptimizerSupplier of( - String id, - Supplier supplier - ) { - return new ChunkRenderOptimizerSupplier(id) { - @Override - public ChunkRenderOptimizer createOptimizer() { - return supplier.get(); - } - }; +public class ChunkRenderOptimizerRegistry extends NamespacedFactoryRegistry { + + private static final ChunkRenderOptimizerRegistry INSTANCE = new ChunkRenderOptimizerRegistry(); + + /** + * @return the instance + */ + public static ChunkRenderOptimizerRegistry getInstance() { + return INSTANCE; } } diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java new file mode 100644 index 0000000..488938e --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizerSurface.java @@ -0,0 +1,396 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * 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 . + */ + +package ru.windcorp.progressia.client.world.cro; + +import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK; +import static ru.windcorp.progressia.common.world.block.BlockFace.BLOCK_FACE_COUNT; +import static ru.windcorp.progressia.common.world.generic.GenericTileStack.TILES_PER_FACE; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Consumer; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.client.graphics.backend.Usage; +import ru.windcorp.progressia.client.graphics.model.Face; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.client.graphics.model.Shape; +import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; +import ru.windcorp.progressia.client.world.ChunkRender; +import ru.windcorp.progressia.client.world.block.BlockRender; +import ru.windcorp.progressia.client.world.tile.TileRender; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.block.BlockFace; + +public class ChunkRenderOptimizerSurface extends ChunkRenderOptimizer { + + private static final float OVERLAY_OFFSET = 1 / 128f; + + /** + * A common interface to objects that can provide optimizeable surfaces. + * This is an internal interface; use {@link BlockOptimizedSurface} or + * {@link TileOptimizedSurface} instead. + */ + private static interface OptimizedSurface { + + /** + * Creates and outputs a set of faces that correspond to this surface. + * The coordinates of the face vertices must be in chunk coordinate + * system. + * + * @param chunk the chunk that contains the requested face + * @param blockInChunk the block in chunk + * @param blockFace the requested face + * @param inner whether this face should be visible from inside + * ({@code true}) or outside ({@code false}) + * @param output a consumer that the created faces must be given + * to + * @param offset an additional offset that must be applied to all + * vertices + */ + void getFaces( + ChunkData chunk, + Vec3i blockInChunk, + BlockFace blockFace, + boolean inner, + Consumer output, + Vec3 offset /* kostyl 156% */ + ); + + /** + * Returns the opacity of the surface identified by the provided + * {@link BlockFace}. + * Opaque surfaces prevent surfaces behind them from being included in + * chunk models. + * + * @param blockFace the face to query + * @return {@code true} iff the surface is opaque. + */ + boolean isOpaque(BlockFace blockFace); + } + + /** + * A block that can be optimized by {@link ChunkRenderOptimizerSurface}. + */ + public static interface BlockOptimizedSurface extends OptimizedSurface { + + /** + * Returns the opacity of the block. Opaque blocks do not expect that + * the camera can be inside them. Opaque blocks prevent surfaces that + * face them + * from being included in chunk models. + * + * @return {@code true} iff the block is opaque. + */ + boolean isBlockOpaque(); + } + + /** + * A tile that can be optimized by {@link ChunkRenderOptimizerSurface}. + */ + public static interface TileOptimizedSurface extends OptimizedSurface { + // Empty for now + } + + private static class BlockInfo { + BlockOptimizedSurface block; + final FaceInfo[] faces = new FaceInfo[BLOCK_FACE_COUNT]; + + { + for (int i = 0; i < faces.length; ++i) { + faces[i] = new FaceInfo(this); + } + } + } + + private static class FaceInfo { + static final int BLOCK_LAYER = -1; + + final BlockInfo block; + + int topOpaqueSurface = BLOCK_LAYER; + int bottomOpaqueSurface = Integer.MAX_VALUE; + + final TileOptimizedSurface[] tiles = new TileOptimizedSurface[TILES_PER_FACE]; + int tileCount = 0; + + FaceInfo(BlockInfo block) { + this.block = block; + } + + OptimizedSurface getSurface(int layer) { + return layer == BLOCK_LAYER ? block.block : tiles[layer]; + } + } + + private final BlockInfo[][][] data = new BlockInfo[BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK][BLOCKS_PER_CHUNK]; + + public ChunkRenderOptimizerSurface(String id) { + super(id); + } + + @Override + public void startRender() { + 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 addBlock(BlockRender block, Vec3i pos) { + if (!(block instanceof BlockOptimizedSurface)) + return; + + BlockOptimizedSurface bos = (BlockOptimizedSurface) block; + addBlock(pos, bos); + } + + @Override + public void addTile(TileRender tile, Vec3i pos, BlockFace face) { + if (!(tile instanceof TileOptimizedSurface)) + return; + + TileOptimizedSurface tos = (TileOptimizedSurface) tile; + addTile(pos, face, tos); + } + + protected void addBlock(Vec3i pos, BlockOptimizedSurface block) { + getBlock(pos).block = block; + } + + private void addTile(Vec3i pos, BlockFace face, TileOptimizedSurface tile) { + FaceInfo faceInfo = getFace(pos, face); + + int index = faceInfo.tileCount; + faceInfo.tileCount++; + + faceInfo.tiles[index] = tile; + + if (tile.isOpaque(face)) { + faceInfo.topOpaqueSurface = index; + + if (faceInfo.bottomOpaqueSurface == FaceInfo.BLOCK_LAYER) { + faceInfo.bottomOpaqueSurface = 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 Renderable endRender() { + Collection shapeFaces = new ArrayList<>( + BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3 + ); + + Vec3i cursor = new Vec3i(); + Consumer consumer = shapeFaces::add; + + 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) { + processInnerFaces(cursor, consumer); + processOuterFaces(cursor, consumer); + } + } + } + + if (shapeFaces.isEmpty()) { + return null; + } + + return new Shape( + Usage.STATIC, + WorldRenderProgram.getDefault(), + shapeFaces.toArray(new Face[shapeFaces.size()]) + ); + } + + private void processOuterFaces( + Vec3i blockInChunk, + Consumer output + ) { + for (BlockFace blockFace : BlockFace.getFaces()) { + processOuterFace(blockInChunk, blockFace, output); + } + } + + private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer output) { + if (!shouldRenderOuterFace(blockInChunk, blockFace)) + return; + + FaceInfo info = getFace(blockInChunk, blockFace); + + if (info.tileCount == 0 && info.block.block == null) + return; + + Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z); + Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET); + + for ( + int layer = info.topOpaqueSurface; + layer < info.tileCount; + ++layer + ) { + OptimizedSurface surface = info.getSurface(layer); + if (surface == null) + continue; // layer may be BLOCK_LAYER, then block may be null + + surface.getFaces(chunk.getData(), blockInChunk, blockFace, false, output, faceOrigin); + + faceOrigin.add(offset); + } + } + + private void processInnerFaces( + Vec3i blockInChunk, + Consumer output + ) { + for (BlockFace blockFace : BlockFace.getFaces()) { + processInnerFace(blockInChunk, blockFace, output); + } + } + + private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer output) { + if (!shouldRenderInnerFace(blockInChunk, blockFace)) + return; + + FaceInfo info = getFace(blockInChunk, blockFace); + + if (info.tileCount == 0 && info.block.block == null) + return; + + Vec3 faceOrigin = new Vec3(blockInChunk.x, blockInChunk.y, blockInChunk.z); + Vec3 offset = new Vec3(blockFace.getFloatVector()).mul(OVERLAY_OFFSET); + + for ( + int layer = FaceInfo.BLOCK_LAYER; + layer <= info.bottomOpaqueSurface && layer < info.tileCount; + ++layer + ) { + OptimizedSurface surface = info.getSurface(layer); + if (surface == null) + continue; // layer may be BLOCK_LAYER, then block may be null + + surface.getFaces(chunk.getData(), blockInChunk, blockFace, true, output, faceOrigin); + + faceOrigin.add(offset); + } + } + + private boolean shouldRenderOuterFace(Vec3i blockInChunk, BlockFace face) { + blockInChunk.add(face.getVector()); + try { + return shouldRenderWhenFacing(blockInChunk, face); + } finally { + blockInChunk.sub(face.getVector()); + } + } + + private boolean shouldRenderInnerFace(Vec3i blockInChunk, BlockFace face) { + return shouldRenderWhenFacing(blockInChunk, face); + } + + private boolean shouldRenderWhenFacing(Vec3i blockInChunk, BlockFace face) { + if (chunk.containsBiC(blockInChunk)) { + return shouldRenderWhenFacingLocal(blockInChunk, face); + } else { + return shouldRenderWhenFacingNeighbor(blockInChunk, face); + } + } + + private boolean shouldRenderWhenFacingLocal(Vec3i blockInChunk, BlockFace face) { + BlockOptimizedSurface block = getBlock(blockInChunk).block; + + if (block == null) { + return true; + } + if (block.isOpaque(face)) { + return false; + } + + return true; + } + + private boolean shouldRenderWhenFacingNeighbor(Vec3i blockInLocalChunk, BlockFace face) { + Vec3i blockInChunk = Vectors.grab3i().set(blockInLocalChunk.x, blockInLocalChunk.y, blockInLocalChunk.z); + Vec3i chunkPos = Vectors.grab3i().set(chunk.getX(), chunk.getY(), chunk.getZ()); + + try { + // Determine blockInChunk and chunkPos + if (blockInLocalChunk.x == -1) { + blockInChunk.x = BLOCKS_PER_CHUNK - 1; + chunkPos.x -= 1; + } else if (blockInLocalChunk.x == BLOCKS_PER_CHUNK) { + blockInChunk.x = 0; + chunkPos.x += 1; + } else if (blockInLocalChunk.y == -1) { + blockInChunk.y = BLOCKS_PER_CHUNK - 1; + chunkPos.y -= 1; + } else if (blockInLocalChunk.y == BLOCKS_PER_CHUNK) { + blockInChunk.y = 0; + chunkPos.y += 1; + } else if (blockInLocalChunk.z == -1) { + blockInChunk.z = BLOCKS_PER_CHUNK - 1; + chunkPos.z -= 1; + } else if (blockInLocalChunk.z == BLOCKS_PER_CHUNK) { + blockInChunk.z = 0; + chunkPos.z += 1; + } else { + throw new AssertionError( + "Requested incorrent neighbor (" + + blockInLocalChunk.x + "; " + + blockInLocalChunk.y + "; " + + blockInLocalChunk.z + ")" + ); + } + + ChunkRender chunk = this.chunk.getWorld().getChunk(chunkPos); + if (chunk == null) + return false; + + BlockRender block = chunk.getBlock(blockInChunk); + if (!(block instanceof BlockOptimizedSurface)) + return true; + + BlockOptimizedSurface bos = (BlockOptimizedSurface) block; + if (!bos.isOpaque(face)) + return true; + + return false; + + } finally { + Vectors.release(blockInChunk); + Vectors.release(chunkPos); + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java index 99f3f9a..4f82d86 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRender.java @@ -19,9 +19,11 @@ package ru.windcorp.progressia.client.world.tile; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; +import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; import ru.windcorp.progressia.common.util.namespaces.Namespaced; +import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.generic.GenericTile; @@ -37,7 +39,7 @@ public class TileRender extends Namespaced implements GenericTile { ); } - public Renderable createRenderable(BlockFace face) { + public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) { return null; } diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java index 98c5fa4..c73647b 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderGrass.java @@ -18,19 +18,10 @@ package ru.windcorp.progressia.client.world.tile; -import glm.vec._3.Vec3; -import ru.windcorp.progressia.client.graphics.Colors; -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.Renderable; import ru.windcorp.progressia.client.graphics.texture.Texture; -import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; -import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueSurface; import ru.windcorp.progressia.common.world.block.BlockFace; -public class TileRenderGrass extends TileRender implements OpaqueSurface { +public class TileRenderGrass extends TileRenderSurface { private final Texture topTexture; private final Texture sideTexture; @@ -55,27 +46,4 @@ public class TileRenderGrass extends TileRender implements OpaqueSurface { return face == BlockFace.TOP; } - @Override - public Renderable createRenderable(BlockFace face) { - ShapeRenderProgram program = WorldRenderProgram.getDefault(); - - return new Shape( - Usage.STATIC, - WorldRenderProgram.getDefault(), - Faces.createBlockFace( - program, - getTexture(face), - Colors.WHITE, - new Vec3(0, 0, 0), - face, - false - ) - ); - } - - @Override - public boolean needsOwnRenderable() { - return false; - } - } diff --git a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizers.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderNone.java similarity index 50% rename from src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizers.java rename to src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderNone.java index 442bdf5..8e2a6e4 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/cro/ChunkRenderOptimizers.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderNone.java @@ -15,39 +15,28 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - -package ru.windcorp.progressia.client.world.cro; +package ru.windcorp.progressia.client.world.tile; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.client.graphics.model.EmptyModel; +import ru.windcorp.progressia.client.graphics.model.Renderable; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.block.BlockFace; -public class ChunkRenderOptimizers { +public class TileRenderNone extends TileRender { - private ChunkRenderOptimizers() { + public TileRenderNone(String id) { + super(id); } - - private static final Map SUPPLIERS = new HashMap<>(); - - static { - register( - ChunkRenderOptimizerSupplier.of( - "Default:OpaqueCube", - ChunkRenderOptimizerCube::new - ) - ); + + @Override + public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) { + return EmptyModel.getInstance(); } - - public static ChunkRenderOptimizerSupplier getSupplier(String id) { - return SUPPLIERS.get(id); - } - - public static void register(ChunkRenderOptimizerSupplier supplier) { - SUPPLIERS.put(supplier.getId(), supplier); - } - - public static Collection getAllSuppliers() { - return SUPPLIERS.values(); + + @Override + public boolean needsOwnRenderable() { + return false; } } diff --git a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java index ea942ed..eebcb07 100644 --- a/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java +++ b/src/main/java/ru/windcorp/progressia/client/world/tile/TileRenderSurface.java @@ -18,19 +18,25 @@ package ru.windcorp.progressia.client.world.tile; +import java.util.function.Consumer; + import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import glm.vec._4.Vec4; import ru.windcorp.progressia.client.graphics.Colors; import ru.windcorp.progressia.client.graphics.backend.Usage; +import ru.windcorp.progressia.client.graphics.model.Face; 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.Renderable; import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; -import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerCube.OpaqueSurface; +import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface.TileOptimizedSurface; +import ru.windcorp.progressia.common.util.Vectors; +import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.block.BlockFace; -public abstract class TileRenderSurface extends TileRender implements OpaqueSurface { +public abstract class TileRenderSurface extends TileRender implements TileOptimizedSurface { private final Texture texture; @@ -38,27 +44,52 @@ public abstract class TileRenderSurface extends TileRender implements OpaqueSurf super(id); this.texture = texture; } + + public TileRenderSurface(String id) { + this(id, null); + } - @Override - public Texture getTexture(BlockFace face) { + public Texture getTexture(BlockFace blockFace) { return texture; } + + public Vec4 getColorMultiplier(BlockFace blockFace) { + return Colors.WHITE; + } + + @Override + public final void getFaces( + ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, + boolean inner, + Consumer output, + Vec3 offset + ) { + output.accept(createFace(chunk, blockInChunk, blockFace, inner, offset)); + } + + private Face createFace( + ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace, + boolean inner, + Vec3 offset + ) { + return Faces.createBlockFace( + WorldRenderProgram.getDefault(), + getTexture(blockFace), + getColorMultiplier(blockFace), + offset, + blockFace, + inner + ); + } @Override - public Renderable createRenderable(BlockFace face) { - ShapeRenderProgram program = WorldRenderProgram.getDefault(); - + public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) { return new Shape( Usage.STATIC, WorldRenderProgram.getDefault(), - Faces.createBlockFace( - program, - getTexture(face), - Colors.WHITE, - new Vec3(0, 0, 0), - face, - false - ) + + createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3), + createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3) ); } diff --git a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java index 55fc4bc..55f2076 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java +++ b/src/main/java/ru/windcorp/progressia/common/world/Coordinates.java @@ -158,5 +158,9 @@ public class Coordinates { return output; } + + public static boolean isOnChunkBorder(int blockInChunk) { + return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1; + } } diff --git a/src/main/java/ru/windcorp/progressia/common/world/block/BlockRelation.java b/src/main/java/ru/windcorp/progressia/common/world/block/BlockRelation.java index 02e1c2e..659a5fa 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/block/BlockRelation.java +++ b/src/main/java/ru/windcorp/progressia/common/world/block/BlockRelation.java @@ -27,10 +27,12 @@ import glm.vec._3.i.Vec3i; public class BlockRelation { private final Vec3i vector = new Vec3i(); + private final Vec3 floatVector = new Vec3(); private final Vec3 normalized = new Vec3(); public BlockRelation(int x, int y, int z) { vector.set(x, y, z); + floatVector.set(x, y, z); normalized.set(x, y, z).normalize(); } @@ -41,6 +43,10 @@ public class BlockRelation { public Vec3i getVector() { return vector; } + + public Vec3 getFloatVector() { + return floatVector; + } public Vec3 getNormalized() { return normalized; diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java index c6a1b32..656304f 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java @@ -91,6 +91,72 @@ public interface GenericChunk, B exten Vectors.release(v); return result; } + + default boolean isSurfaceBiC(Vec3i blockInChunk) { + int hits = 0; + + if (Coordinates.isOnChunkBorder(blockInChunk.x)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.y)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.z)) hits++; + + return hits >= 1; + } + + default boolean isSurfaceBiW(Vec3i blockInWorld) { + Vec3i v = Vectors.grab3i(); + + v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); + v = blockInWorld.sub(v, v); + + boolean result = isSurfaceBiC(v); + + Vectors.release(v); + return result; + } + + default boolean isEdgeBiC(Vec3i blockInChunk) { + int hits = 0; + + if (Coordinates.isOnChunkBorder(blockInChunk.x)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.y)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.z)) hits++; + + return hits >= 2; + } + + default boolean isEdgeBiW(Vec3i blockInWorld) { + Vec3i v = Vectors.grab3i(); + + v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); + v = blockInWorld.sub(v, v); + + boolean result = isEdgeBiC(v); + + Vectors.release(v); + return result; + } + + default boolean isVertexBiC(Vec3i blockInChunk) { + int hits = 0; + + if (Coordinates.isOnChunkBorder(blockInChunk.x)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.y)) hits++; + if (Coordinates.isOnChunkBorder(blockInChunk.z)) hits++; + + return hits == 3; + } + + default boolean isVertexBiW(Vec3i blockInWorld) { + Vec3i v = Vectors.grab3i(); + + v = Coordinates.getInWorld(getPosition(), Vectors.ZERO_3i, v); + v = blockInWorld.sub(v, v); + + boolean result = isVertexBiC(v); + + Vectors.release(v); + return result; + } default void forEachBiC(Consumer action) { VectorUtil.iterateCuboid( diff --git a/src/main/java/ru/windcorp/progressia/test/TestContent.java b/src/main/java/ru/windcorp/progressia/test/TestContent.java index ed64c13..9fabd96 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestContent.java +++ b/src/main/java/ru/windcorp/progressia/test/TestContent.java @@ -39,6 +39,8 @@ import ru.windcorp.progressia.client.graphics.input.KeyEvent; import ru.windcorp.progressia.client.graphics.input.KeyMatcher; import ru.windcorp.progressia.client.graphics.world.Selection; import ru.windcorp.progressia.client.world.block.*; +import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerRegistry; +import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizerSurface; import ru.windcorp.progressia.client.world.entity.*; import ru.windcorp.progressia.client.world.tile.*; import ru.windcorp.progressia.common.collision.AABB; @@ -420,6 +422,7 @@ public class TestContent { private static void registerMisc() { ChunkIO.registerCodec(new TestChunkCodec()); + ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new); } } diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java index c6b9a97..388d89c 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderHuman.java @@ -200,7 +200,7 @@ public class TestEntityRenderHuman extends EntityRender { ).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create() ); - return new StaticModel(b); + return b.build(); } @Override diff --git a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderJavapony.java b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderJavapony.java index bcc1bd1..aefa1c6 100644 --- a/src/main/java/ru/windcorp/progressia/test/TestEntityRenderJavapony.java +++ b/src/main/java/ru/windcorp/progressia/test/TestEntityRenderJavapony.java @@ -386,7 +386,7 @@ public class TestEntityRenderJavapony extends EntityRender { ).setOrigin(16, -4, 8).setSize(4, 8, 4).create() ); - return new StaticModel(b); + return b.build(); } private static Renderable createLeg( @@ -427,7 +427,7 @@ public class TestEntityRenderJavapony extends EntityRender { ).setOrigin(-8, -8, -32).setSize(16, 16, 32).create() ); - return new StaticModel(b); + return b.build(); } @Override