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
This commit is contained in:
OLEGSHA 2021-01-24 20:48:38 +03:00
parent 85edc07c75
commit 260562310a
Signed by: OLEGSHA
GPG Key ID: E57A4B08D64AFF7A
25 changed files with 946 additions and 603 deletions

View File

@ -47,7 +47,7 @@ repositories {
* Currently used by: * Currently used by:
* - ru.windcorp.fork.io.github.java-graphics:glm:1.0.1 * - 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 { dependencies {

View File

@ -38,10 +38,6 @@ public class StaticModel extends Model {
this.transforms = transforms; this.transforms = transforms;
} }
public StaticModel(Builder builder) {
this(builder.getParts(), builder.getTransforms());
}
@Override @Override
protected Mat4 getTransform(int partIndex) { protected Mat4 getTransform(int partIndex) {
return transforms[partIndex]; return transforms[partIndex];
@ -83,6 +79,10 @@ public class StaticModel extends Model {
return transforms.toArray(new Mat4[transforms.size()]); return transforms.toArray(new Mat4[transforms.size()]);
} }
public StaticModel build() {
return new StaticModel(getParts(), getTransforms());
}
} }
} }

View File

@ -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"); private static final float FRICTION_COEFF = Units.get("1e-5f kg/s");

View File

@ -18,35 +18,20 @@
package ru.windcorp.progressia.client.world; package ru.windcorp.progressia.client.world;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap; 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 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.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.BlockRender;
import ru.windcorp.progressia.client.world.block.BlockRenderNone;
import ru.windcorp.progressia.client.world.block.BlockRenderRegistry; 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.TileRender;
import ru.windcorp.progressia.client.world.tile.TileRenderRegistry; import ru.windcorp.progressia.client.world.tile.TileRenderRegistry;
import ru.windcorp.progressia.client.world.tile.TileRenderStack; import ru.windcorp.progressia.client.world.tile.TileRenderStack;
import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.block.BlockFace; import ru.windcorp.progressia.common.world.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericChunk; import ru.windcorp.progressia.common.world.generic.GenericChunk;
import ru.windcorp.progressia.common.world.tile.TileData;
import ru.windcorp.progressia.common.world.tile.TileDataStack; import ru.windcorp.progressia.common.world.tile.TileDataStack;
public class ChunkRender public class ChunkRender
@ -55,7 +40,7 @@ public class ChunkRender
private final WorldRender world; private final WorldRender world;
private final ChunkData data; private final ChunkData data;
private Model model = null; private final ChunkRenderModel model;
private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections private final Map<TileDataStack, TileRenderStackImpl> tileRenderLists = Collections
.synchronizedMap(new WeakHashMap<>()); .synchronizedMap(new WeakHashMap<>());
@ -63,6 +48,7 @@ public class ChunkRender
public ChunkRender(WorldRender world, ChunkData data) { public ChunkRender(WorldRender world, ChunkData data) {
this.world = world; this.world = world;
this.data = data; this.data = data;
this.model = new ChunkRenderModel(this);
} }
@Override @Override
@ -107,165 +93,11 @@ public class ChunkRender
} }
public synchronized void render(ShapeRenderHelper renderer) { 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); model.render(renderer);
renderer.popTransform();
} }
public synchronized void update() { public synchronized void update() {
Collection<ChunkRenderOptimizer> optimizers = ChunkRenderOptimizers.getAllSuppliers().stream() model.update();
.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<ChunkRenderOptimizer> 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<ChunkRenderOptimizer> 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<ChunkRenderOptimizer> optimizers,
Builder builder
) {
for (BlockFace face : BlockFace.getFaces()) {
buildFaceTiles(cursor, face, optimizers, builder);
}
}
private void buildFaceTiles(
Vec3i cursor,
BlockFace face,
Collection<ChunkRenderOptimizer> optimizers,
Builder builder
) {
List<TileData> 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<ChunkRenderOptimizer> 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)
);
} }
private class TileRenderStackImpl extends TileRenderStack { private class TileRenderStackImpl extends TileRenderStack {

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ChunkRenderOptimizer> 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<String> 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);
}
}
}

View File

@ -18,8 +18,12 @@
package ru.windcorp.progressia.client.world; 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.ChunkData;
import ru.windcorp.progressia.common.world.ChunkDataListener; 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 { class ChunkUpdateListener implements ChunkDataListener {
@ -34,4 +38,52 @@ class ChunkUpdateListener implements ChunkDataListener {
world.getChunk(chunk).markForUpdate(); 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);
}
} }

View File

@ -20,7 +20,9 @@ package ru.windcorp.progressia.client.world.block;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.ChunkData;
import ru.windcorp.progressia.common.world.generic.GenericBlock; import ru.windcorp.progressia.common.world.generic.GenericBlock;
import glm.vec._3.i.Vec3i;
import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.Renderable;
public abstract class BlockRender extends Namespaced implements GenericBlock { 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; return null;
} }

View File

@ -18,8 +18,10 @@
package ru.windcorp.progressia.client.world.block; 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.EmptyModel;
import ru.windcorp.progressia.client.graphics.model.Renderable; import ru.windcorp.progressia.client.graphics.model.Renderable;
import ru.windcorp.progressia.common.world.ChunkData;
public class BlockRenderNone extends BlockRender { public class BlockRenderNone extends BlockRender {
@ -28,7 +30,7 @@ public class BlockRenderNone extends BlockRender {
} }
@Override @Override
public Renderable createRenderable() { public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk) {
return EmptyModel.getInstance(); return EmptyModel.getInstance();
} }

View File

@ -65,9 +65,4 @@ public class BlockRenderOpaqueCube extends BlockRenderTexturedCube {
return true; return true;
} }
@Override
public boolean needsOwnRenderable() {
return false;
}
} }

View File

@ -22,17 +22,27 @@ import static ru.windcorp.progressia.common.world.block.BlockFace.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.Renderable;
import ru.windcorp.progressia.client.graphics.model.Shape;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; 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; import ru.windcorp.progressia.common.world.block.BlockFace;
public abstract class BlockRenderTexturedCube public abstract class BlockRenderTexturedCube
extends BlockRender extends BlockRender
implements OpaqueCube { implements BlockOptimizedSurface {
private final Map<BlockFace, Texture> textures = new HashMap<>(); private final Map<BlockFace, Texture> textures = new HashMap<>();
@ -55,22 +65,61 @@ public abstract class BlockRenderTexturedCube
textures.put(WEST, westTexture); textures.put(WEST, westTexture);
} }
@Override public Texture getTexture(BlockFace blockFace) {
public Texture getTexture(BlockFace face) { return textures.get(blockFace);
return textures.get(face); }
public Vec4 getColorMultiplier(BlockFace blockFace) {
return Colors.WHITE;
} }
@Override @Override
public Renderable createRenderable() { public final void getFaces(
return new Shapes.PppBuilder( ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Consumer<Face> 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(), WorldRenderProgram.getDefault(),
getTexture(TOP), getTexture(blockFace),
getTexture(BOTTOM), getColorMultiplier(blockFace),
getTexture(NORTH), offset,
getTexture(SOUTH), blockFace,
getTexture(EAST), inner
getTexture(WEST) );
).create(); }
@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;
} }
} }

View File

@ -65,9 +65,4 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
return false; return false;
} }
@Override
public boolean needsOwnRenderable() {
return false;
}
} }

View File

@ -19,27 +19,111 @@
package ru.windcorp.progressia.client.world.cro; package ru.windcorp.progressia.client.world.cro;
import glm.vec._3.i.Vec3i; 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.ChunkRender;
import ru.windcorp.progressia.client.world.block.BlockRender; import ru.windcorp.progressia.client.world.block.BlockRender;
import ru.windcorp.progressia.client.world.tile.TileRender; import ru.windcorp.progressia.client.world.tile.TileRender;
import ru.windcorp.progressia.common.util.namespaces.Namespaced;
import ru.windcorp.progressia.common.world.block.BlockFace; 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.
* <h3>CRO lifecycle</h3>
* 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:
* <ol>
* <li>{@link #setup(ChunkRender)} is invoked to provide the {@link ChunkRender}
* instance.</li>
* <li>{@link #startRender()} is invoked. The CRO must reset its state.</li>
* <li>{@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.</li>
* <li>{@link #endRender()} is invoked. The CRO may perform any pending
* calculations. The result of the optimization is returned.</li>
* </ol>
* <p>
* 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, * Creates a new CRO instance with the specified ID.
Vec3i posInChunk *
); * @param id the ID of this CRO
*/
public ChunkRenderOptimizer(String id) {
super(id);
}
public abstract void processTile( /**
TileRender tile, * This method is invoked before a new chunk processing cycle begins to
Vec3i posInChunk, * specify the chunk. When overriding, {@code super.setup(chunk)} must be
BlockFace face * 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();
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Face> 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<Face> 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<Face> 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<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) &&
isInBounds(cursor.y) &&
isInBounds(cursor.z);
}
private boolean isInBounds(int c) {
return c >= 0 && c < BLOCKS_PER_CHUNK;
}
}

View File

@ -18,28 +18,17 @@
package ru.windcorp.progressia.client.world.cro; 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 class ChunkRenderOptimizerRegistry extends NamespacedFactoryRegistry<ChunkRenderOptimizer> {
public abstract class ChunkRenderOptimizerSupplier extends Namespaced { private static final ChunkRenderOptimizerRegistry INSTANCE = new ChunkRenderOptimizerRegistry();
public ChunkRenderOptimizerSupplier(String id) { /**
super(id); * @return the instance
} */
public static ChunkRenderOptimizerRegistry getInstance() {
public abstract ChunkRenderOptimizer createOptimizer(); return INSTANCE;
public static ChunkRenderOptimizerSupplier of(
String id,
Supplier<ChunkRenderOptimizer> supplier
) {
return new ChunkRenderOptimizerSupplier(id) {
@Override
public ChunkRenderOptimizer createOptimizer() {
return supplier.get();
}
};
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Face> 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<Face> shapeFaces = new ArrayList<>(
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
);
Vec3i cursor = new Vec3i();
Consumer<Face> 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<Face> output
) {
for (BlockFace blockFace : BlockFace.getFaces()) {
processOuterFace(blockInChunk, blockFace, output);
}
}
private void processOuterFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> 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<Face> output
) {
for (BlockFace blockFace : BlockFace.getFaces()) {
processInnerFace(blockInChunk, blockFace, output);
}
}
private void processInnerFace(Vec3i blockInChunk, BlockFace blockFace, Consumer<Face> 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);
}
}
}

View File

@ -19,9 +19,11 @@
package ru.windcorp.progressia.client.world.tile; package ru.windcorp.progressia.client.world.tile;
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper; 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.graphics.model.Renderable;
import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer; import ru.windcorp.progressia.client.world.cro.ChunkRenderOptimizer;
import ru.windcorp.progressia.common.util.namespaces.Namespaced; 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.block.BlockFace;
import ru.windcorp.progressia.common.world.generic.GenericTile; 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; return null;
} }

View File

@ -18,19 +18,10 @@
package ru.windcorp.progressia.client.world.tile; 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.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; 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 topTexture;
private final Texture sideTexture; private final Texture sideTexture;
@ -55,27 +46,4 @@ public class TileRenderGrass extends TileRender implements OpaqueSurface {
return face == BlockFace.TOP; 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;
}
} }

View File

@ -15,39 +15,28 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package ru.windcorp.progressia.client.world.tile;
package ru.windcorp.progressia.client.world.cro; 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;
import java.util.Collection; public class TileRenderNone extends TileRender {
import java.util.HashMap;
import java.util.Map;
public class ChunkRenderOptimizers { public TileRenderNone(String id) {
super(id);
private ChunkRenderOptimizers() {
} }
private static final Map<String, ChunkRenderOptimizerSupplier> SUPPLIERS = new HashMap<>(); @Override
public Renderable createRenderable(ChunkData chunk, Vec3i blockInChunk, BlockFace face) {
static { return EmptyModel.getInstance();
register(
ChunkRenderOptimizerSupplier.of(
"Default:OpaqueCube",
ChunkRenderOptimizerCube::new
)
);
} }
public static ChunkRenderOptimizerSupplier getSupplier(String id) { @Override
return SUPPLIERS.get(id); public boolean needsOwnRenderable() {
} return false;
public static void register(ChunkRenderOptimizerSupplier supplier) {
SUPPLIERS.put(supplier.getId(), supplier);
}
public static Collection<ChunkRenderOptimizerSupplier> getAllSuppliers() {
return SUPPLIERS.values();
} }
} }

View File

@ -18,19 +18,25 @@
package ru.windcorp.progressia.client.world.tile; package ru.windcorp.progressia.client.world.tile;
import java.util.function.Consumer;
import glm.vec._3.Vec3; 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.Colors;
import ru.windcorp.progressia.client.graphics.backend.Usage; 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.Faces;
import ru.windcorp.progressia.client.graphics.model.Shape; 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.model.Renderable;
import ru.windcorp.progressia.client.graphics.texture.Texture; import ru.windcorp.progressia.client.graphics.texture.Texture;
import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram; 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; 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; private final Texture texture;
@ -39,26 +45,51 @@ public abstract class TileRenderSurface extends TileRender implements OpaqueSurf
this.texture = texture; this.texture = texture;
} }
@Override public TileRenderSurface(String id) {
public Texture getTexture(BlockFace face) { this(id, null);
}
public Texture getTexture(BlockFace blockFace) {
return texture; return texture;
} }
@Override public Vec4 getColorMultiplier(BlockFace blockFace) {
public Renderable createRenderable(BlockFace face) { return Colors.WHITE;
ShapeRenderProgram program = WorldRenderProgram.getDefault(); }
@Override
public final void getFaces(
ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace,
boolean inner,
Consumer<Face> 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(ChunkData chunk, Vec3i blockInChunk, BlockFace blockFace) {
return new Shape( return new Shape(
Usage.STATIC, Usage.STATIC,
WorldRenderProgram.getDefault(), WorldRenderProgram.getDefault(),
Faces.createBlockFace(
program, createFace(chunk, blockInChunk, blockFace, false, Vectors.ZERO_3),
getTexture(face), createFace(chunk, blockInChunk, blockFace, true, Vectors.ZERO_3)
Colors.WHITE,
new Vec3(0, 0, 0),
face,
false
)
); );
} }

View File

@ -159,4 +159,8 @@ public class Coordinates {
return output; return output;
} }
public static boolean isOnChunkBorder(int blockInChunk) {
return blockInChunk == 0 || blockInChunk == BLOCKS_PER_CHUNK - 1;
}
} }

View File

@ -27,10 +27,12 @@ import glm.vec._3.i.Vec3i;
public class BlockRelation { public class BlockRelation {
private final Vec3i vector = new Vec3i(); private final Vec3i vector = new Vec3i();
private final Vec3 floatVector = new Vec3();
private final Vec3 normalized = new Vec3(); private final Vec3 normalized = new Vec3();
public BlockRelation(int x, int y, int z) { public BlockRelation(int x, int y, int z) {
vector.set(x, y, z); vector.set(x, y, z);
floatVector.set(x, y, z);
normalized.set(x, y, z).normalize(); normalized.set(x, y, z).normalize();
} }
@ -42,6 +44,10 @@ public class BlockRelation {
return vector; return vector;
} }
public Vec3 getFloatVector() {
return floatVector;
}
public Vec3 getNormalized() { public Vec3 getNormalized() {
return normalized; return normalized;
} }

View File

@ -92,6 +92,72 @@ public interface GenericChunk<Self extends GenericChunk<Self, B, T, TS>, B exten
return result; 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<? super Vec3i> action) { default void forEachBiC(Consumer<? super Vec3i> action) {
VectorUtil.iterateCuboid( VectorUtil.iterateCuboid(
0, 0,

View File

@ -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.input.KeyMatcher;
import ru.windcorp.progressia.client.graphics.world.Selection; import ru.windcorp.progressia.client.graphics.world.Selection;
import ru.windcorp.progressia.client.world.block.*; 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.entity.*;
import ru.windcorp.progressia.client.world.tile.*; import ru.windcorp.progressia.client.world.tile.*;
import ru.windcorp.progressia.common.collision.AABB; import ru.windcorp.progressia.common.collision.AABB;
@ -420,6 +422,7 @@ public class TestContent {
private static void registerMisc() { private static void registerMisc() {
ChunkIO.registerCodec(new TestChunkCodec()); ChunkIO.registerCodec(new TestChunkCodec());
ChunkRenderOptimizerRegistry.getInstance().register("Core:SurfaceOptimizer", ChunkRenderOptimizerSurface::new);
} }
} }

View File

@ -200,7 +200,7 @@ public class TestEntityRenderHuman extends EntityRender {
).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create() ).setOrigin(ox, oy, oz).setSize(sx, sy, sz).create()
); );
return new StaticModel(b); return b.build();
} }
@Override @Override

View File

@ -386,7 +386,7 @@ public class TestEntityRenderJavapony extends EntityRender {
).setOrigin(16, -4, 8).setSize(4, 8, 4).create() ).setOrigin(16, -4, 8).setSize(4, 8, 4).create()
); );
return new StaticModel(b); return b.build();
} }
private static Renderable createLeg( private static Renderable createLeg(
@ -427,7 +427,7 @@ public class TestEntityRenderJavapony extends EntityRender {
).setOrigin(-8, -8, -32).setSize(16, 16, 32).create() ).setOrigin(-8, -8, -32).setSize(16, 16, 32).create()
); );
return new StaticModel(b); return b.build();
} }
@Override @Override