Refactored CROs and CROCube, Faces.createBlockFace and Camera

- Multiple ChunkRenderOptimizers can now optimize one block if desired
  - CROs require that an interface is implemented, not class extended
- ChunkRenderOptimizerCube has been refactored:
  - Blocks that a not opaque are now optimized
  - Blocks have per-face opacity
  - Blocks can render inner faces
    - Example: test glass
  - Code is now readable
- Faces was rewritten to support flipping
  - .createBlockFace is now readable
- Camera now manipulates coordinate systems in a more obvious way
  - Camera now uses world coordinate system
    - Z is up
    - Yaw 0 looks to the North (+X)
    - Positive pitch is up
  - Improved control code readability somewhat
  - Fixed test UI compass
This commit is contained in:
OLEGSHA 2020-08-26 01:34:32 +03:00
parent bdb458b911
commit a9896fa3e1
13 changed files with 309 additions and 209 deletions

View File

@ -73,7 +73,7 @@ public class LayerTestUI extends AssembledFlatLayer {
target.addCustomRenderer(new LambdaModel(LambdaModel.lambdaBuilder()
.addDynamicPart(
target.createRectagle(0, 0, texSize, texSize, 0xFFFFFF, compassFg),
mat -> mat.translate(texSize/2, texSize/2, 0).rotateZ(-LayerWorld.tmp_the_camera.getYaw()).translate(-texSize/2, -texSize/2, 0)
mat -> mat.translate(texSize/2, texSize/2, 0).rotateZ(LayerWorld.tmp_the_camera.getYaw()).translate(-texSize/2, -texSize/2, 0)
)
));
target.popTransform();

View File

@ -230,7 +230,8 @@ public class RenderTarget {
createVectorFromRGBInt(color),
new Vec3(x, y, depth),
new Vec3(width, 0, 0),
new Vec3(0, height, 0)
new Vec3(0, height, 0),
false
);
}

View File

@ -142,7 +142,7 @@ public abstract class SpriteTypeface extends Typeface {
Face createFace() {
return Faces.createRectangle(
getProgram(),
texture, color, origin, width, height
texture, color, origin, width, height, false
);
}

View File

@ -0,0 +1,112 @@
package ru.windcorp.progressia.client.graphics.model;
import static ru.windcorp.progressia.common.block.BlockFace.*;
import com.google.common.collect.ImmutableMap;
import glm.vec._3.Vec3;
import ru.windcorp.progressia.common.block.BlockFace;
class BlockFaceVectors {
private static BlockFaceVectors createInner(BlockFaceVectors outer) {
ImmutableMap.Builder<BlockFace, Vec3> originBuilder =
ImmutableMap.builderWithExpectedSize(count());
ImmutableMap.Builder<BlockFace, Vec3> widthBuilder =
ImmutableMap.builderWithExpectedSize(count());
ImmutableMap.Builder<BlockFace, Vec3> heightBuilder =
ImmutableMap.builderWithExpectedSize(count());
for (BlockFace face : getFaces()) {
Vec3 width = outer.getWidth(face);
Vec3 height = outer.getHeight(face);
originBuilder.put(face,
new Vec3(outer.getOrigin(face))
);
widthBuilder.put(face, new Vec3(width));
heightBuilder.put(face, new Vec3(height));
}
return new BlockFaceVectors(
originBuilder.build(),
widthBuilder.build(),
heightBuilder.build()
);
}
private static final BlockFaceVectors OUTER;
private static final BlockFaceVectors INNER;
static {
OUTER = new BlockFaceVectors(
ImmutableMap.<BlockFace, Vec3>builderWithExpectedSize(count())
.put(TOP, new Vec3(-0.5f, +0.5f, +0.5f))
.put(BOTTOM, new Vec3(-0.5f, -0.5f, -0.5f))
.put(NORTH, new Vec3(+0.5f, -0.5f, -0.5f))
.put(SOUTH, new Vec3(-0.5f, +0.5f, -0.5f))
.put(WEST, new Vec3(+0.5f, +0.5f, -0.5f))
.put(EAST, new Vec3(-0.5f, -0.5f, -0.5f))
.build(),
ImmutableMap.<BlockFace, Vec3>builderWithExpectedSize(count())
.put(TOP, new Vec3( 0, -1, 0))
.put(BOTTOM, new Vec3( 0, +1, 0))
.put(NORTH, new Vec3( 0, +1, 0))
.put(SOUTH, new Vec3( 0, -1, 0))
.put(WEST, new Vec3(-1, 0, 0))
.put(EAST, new Vec3(+1, 0, 0))
.build(),
ImmutableMap.<BlockFace, Vec3>builderWithExpectedSize(count())
.put(TOP, new Vec3(+1, 0, 0))
.put(BOTTOM, new Vec3(+1, 0, 0))
.put(NORTH, new Vec3( 0, 0, +1))
.put(SOUTH, new Vec3( 0, 0, +1))
.put(WEST, new Vec3( 0, 0, +1))
.put(EAST, new Vec3( 0, 0, +1))
.build()
);
INNER = createInner(OUTER);
}
public static BlockFaceVectors get(boolean inner) {
return inner ? INNER : OUTER;
}
private final ImmutableMap<BlockFace, Vec3> origins;
private final ImmutableMap<BlockFace, Vec3> widths;
private final ImmutableMap<BlockFace, Vec3> heights;
public BlockFaceVectors(
ImmutableMap<BlockFace, Vec3> origins,
ImmutableMap<BlockFace, Vec3> widths,
ImmutableMap<BlockFace, Vec3> heights
) {
this.origins = origins;
this.widths = widths;
this.heights = heights;
}
public Vec3 getOrigin(BlockFace face) {
return origins.get(face);
}
public Vec3 getWidth(BlockFace face) {
return widths.get(face);
}
public Vec3 getHeight(BlockFace face) {
return heights.get(face);
}
}

View File

@ -35,7 +35,8 @@ public class Faces {
Vec3 colorMultiplier,
Vec3 origin,
Vec3 width,
Vec3 height
Vec3 height,
boolean flip
) {
VertexBuilder builder = program.getVertexBuilder();
@ -60,13 +61,18 @@ public class Faces {
texCoords.set(1, 1)
);
ShortBuffer buffer = flip ? ShortBuffer.wrap(new short[] {
0, 1, 3,
0, 3, 2
}) : ShortBuffer.wrap(new short[] {
3, 1, 0,
2, 3, 0
});
return new Face(
texture,
builder.assemble(),
ShortBuffer.wrap(new short[] {
3, 1, 0,
2, 3, 0
})
buffer
);
}
@ -75,59 +81,20 @@ public class Faces {
Texture texture,
Vec3 colorMultiplier,
Vec3 blockCenter,
BlockFace face
BlockFace face,
boolean inner
) {
if (face == BlockFace.TOP) {
return createRectangle(
program,
texture, colorMultiplier,
blockCenter.add(-0.5f, +0.5f, +0.5f),
new Vec3( 0, -1, 0),
new Vec3(+1, 0, 0)
);
} else if (face == BlockFace.BOTTOM) {
return createRectangle(
program,
texture, colorMultiplier,
blockCenter.add(-0.5f, -0.5f, -0.5f),
new Vec3( 0, +1, 0),
new Vec3(+1, 0, 0)
);
} else if (face == BlockFace.NORTH) {
return createRectangle(
program,
texture, colorMultiplier,
blockCenter.add(+0.5f, -0.5f, -0.5f),
new Vec3( 0, +1, 0),
new Vec3( 0, 0, +1)
);
} else if (face == BlockFace.SOUTH) {
return createRectangle(
program,
texture, colorMultiplier,
blockCenter.add(-0.5f, +0.5f, -0.5f),
new Vec3( 0, -1, 0),
new Vec3( 0, 0, +1)
);
} else if (face == BlockFace.EAST) {
return createRectangle(
program,
texture, colorMultiplier,
blockCenter.add(-0.5f, -0.5f, -0.5f),
new Vec3(+1, 0, 0),
new Vec3( 0, 0, +1)
);
} else if (face == BlockFace.WEST) {
return createRectangle(
program,
texture, colorMultiplier,
blockCenter.add(+0.5f, +0.5f, -0.5f),
new Vec3(-1, 0, 0),
new Vec3( 0, 0, +1)
);
} else {
throw new NullPointerException("face");
}
BlockFaceVectors vectors = BlockFaceVectors.get(inner);
Vec3 origin = new Vec3(blockCenter).add(vectors.getOrigin(face));
Vec3 width = vectors.getWidth(face);
Vec3 height = vectors.getHeight(face);
return createRectangle(
program, texture, colorMultiplier,
origin, width, height,
inner
);
}
}

View File

@ -50,7 +50,8 @@ public class Shapes {
topTexture, colorMultiplier,
faceOrigin.set(origin).add(height).add(width),
faceWidth.set(width).negate(),
depth
depth,
false
);
Face bottom = Faces.createRectangle(
@ -58,7 +59,8 @@ public class Shapes {
bottomTexture, colorMultiplier,
origin,
width,
depth
depth,
false
);
Face north = Faces.createRectangle(
@ -66,7 +68,8 @@ public class Shapes {
northTexture, colorMultiplier,
faceOrigin.set(origin).add(depth),
width,
height
height,
false
);
Face south = Faces.createRectangle(
@ -74,7 +77,8 @@ public class Shapes {
southTexture, colorMultiplier,
faceOrigin.set(origin).add(width),
faceWidth.set(width).negate(),
height
height,
false
);
Face east = Faces.createRectangle(
@ -82,7 +86,8 @@ public class Shapes {
eastTexture, colorMultiplier,
origin,
depth,
height
height,
false
);
Face west = Faces.createRectangle(
@ -90,7 +95,8 @@ public class Shapes {
westTexture, colorMultiplier,
faceOrigin.set(origin).add(width).add(depth),
faceWidth.set(depth).negate(),
height
height,
false
);
Shape result = new Shape(

View File

@ -43,20 +43,37 @@ public class Camera {
public Camera() {}
public void apply(WorldRenderHelper helper) {
applyPerspective(helper);
rotateCoordinateSystem(helper);
applyDirection(helper);
applyPosition(helper);
}
private void applyPerspective(WorldRenderHelper helper) {
Mat4 previous = helper.getViewTransform();
Glm.perspective(
computeFovY(),
GraphicsInterface.getAspectRatio(),
0.01f, 10000.0f,
helper.pushViewTransform()
).mul(previous);
helper.pushViewTransform().rotateX(pitch).rotateY(yaw);
}
private void rotateCoordinateSystem(WorldRenderHelper helper) {
helper.pushViewTransform().rotateX(-PI / 2).rotateZ(PI / 2);
}
private void applyDirection(WorldRenderHelper helper) {
helper.pushViewTransform().rotateY(pitch).rotateZ(yaw);
}
private void applyPosition(WorldRenderHelper helper) {
helper.pushViewTransform().translate(position.negate());
position.negate();
}
private float computeFovY() {
float widthOverHeight = GraphicsInterface.getAspectRatio();

View File

@ -38,8 +38,8 @@ public class LayerWorld extends Layer {
public static Camera tmp_the_camera;
private final Camera camera = new Camera(
new Vec3(8, 8, 8),
0, 0,
new Vec3(-6, -6, 20),
(float) Math.toRadians(-40), (float) Math.toRadians(-45),
(float) Math.toRadians(70)
);
@ -47,10 +47,10 @@ public class LayerWorld extends Layer {
private final Vec3 tmp = new Vec3();
private final Mat3 angMat = new Mat3();
private int movementX = 0;
private int movementY = 0;
private int movementZ = 0;
private int movementForward = 0;
private int movementRight = 0;
private int movementUp = 0;
private static final boolean I_WANT_TO_THROW_UP = false;
private float shakeParam = 0;
@ -81,12 +81,12 @@ public class LayerWorld extends Layer {
renderWorld();
helper.reset();
angMat.set().rotateY(-camera.getYaw());
angMat.set().rotateZ(-camera.getYaw());
tmp.set(movementX, 0, movementZ);
if (movementX != 0 && movementZ != 0) tmp.normalize();
tmp.set(movementForward, -movementRight, 0);
if (movementForward != 0 && movementRight != 0) tmp.normalize();
angMat.mul_(tmp); // bug in jglm
tmp.y = movementY;
tmp.z = movementUp;
tmp.mul(0.1f);
tmp.sub(velocity);
tmp.mul(0.1f);
@ -141,22 +141,22 @@ public class LayerWorld extends Layer {
switch (event.getKey()) {
case GLFW.GLFW_KEY_W:
movementZ += -1 * multiplier;
movementForward += +1 * multiplier;
break;
case GLFW.GLFW_KEY_S:
movementZ += +1 * multiplier;
movementForward += -1 * multiplier;
break;
case GLFW.GLFW_KEY_A:
movementX += -1 * multiplier;
movementRight += -1 * multiplier;
break;
case GLFW.GLFW_KEY_D:
movementX += +1 * multiplier;
movementRight += +1 * multiplier;
break;
case GLFW.GLFW_KEY_SPACE:
movementY += +1 * multiplier;
movementUp += +1 * multiplier;
break;
case GLFW.GLFW_KEY_LEFT_SHIFT:
movementY += -1 * multiplier;
movementUp += -1 * multiplier;
break;
case GLFW.GLFW_KEY_ESCAPE:
@ -190,7 +190,7 @@ public class LayerWorld extends Layer {
if (!flag) return;
final float yawScale = 0.002f;
final float pitchScale = -yawScale;
final float pitchScale = yawScale;
camera.turn(
(float) (event.getChangeY() * pitchScale),

View File

@ -56,8 +56,6 @@ public class WorldRender {
}
public void render(ShapeRenderHelper renderer) {
renderer.pushTransform().rotateX(-Math.PI / 2);
for (ChunkRender chunk : getChunks()) {
chunk.render(renderer);
}

View File

@ -57,5 +57,10 @@ public class BlockRenderTransparentCube extends BlockRenderTexturedCube {
public boolean isBlockOpaque() {
return false;
}
@Override
public boolean needsOwnRenderable() {
return false;
}
}

View File

@ -21,8 +21,10 @@ import static ru.windcorp.progressia.common.world.ChunkData.BLOCKS_PER_CHUNK;
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.Faces;
@ -32,7 +34,6 @@ import ru.windcorp.progressia.client.graphics.world.WorldRenderProgram;
import ru.windcorp.progressia.client.world.ChunkRender;
import ru.windcorp.progressia.client.world.renders.BlockRender;
import ru.windcorp.progressia.common.block.BlockFace;
import ru.windcorp.progressia.common.world.ChunkData;
public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
@ -42,122 +43,51 @@ public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
public boolean isBlockOpaque();
}
private static final int BLOCK_MASK = 1 << 7;
private static final BlockFace[] GOOD_FACES = new BlockFace[] {
BlockFace.TOP, BlockFace.NORTH, BlockFace.WEST
};
private static final Vec3 COLOR_MULTIPLIER = new Vec3(1, 1, 1);
private final byte[][][] data = new byte[BLOCKS_PER_CHUNK + 1]
[BLOCKS_PER_CHUNK + 1]
[BLOCKS_PER_CHUNK + 1];
private ChunkRender chunk;
private final Vec3 blockCenter = new Vec3();
private final OpaqueCube[][][] data =
new OpaqueCube[BLOCKS_PER_CHUNK]
[BLOCKS_PER_CHUNK]
[BLOCKS_PER_CHUNK];
@Override
public void startRender(ChunkRender chunk) {
this.chunk = chunk;
// Do nothing
}
@Override
public void processBlock(BlockRender block, int x, int y, int z) {
if (!(block instanceof OpaqueCube)) return;
OpaqueCube opaqueCube = (OpaqueCube) block;
if (!opaqueCube.isBlockOpaque()) return; // FIXME
addFace(x, y, z, BlockFace.TOP);
addFace(x, y, z, BlockFace.BOTTOM);
addFace(x, y, z, BlockFace.NORTH);
addFace(x, y, z, BlockFace.SOUTH);
addFace(x, y, z, BlockFace.EAST);
addFace(x, y, z, BlockFace.WEST);
addBlock(x, y, z);
addBlock(x, y, z, opaqueCube);
}
protected void addFace(int x, int y, int z, BlockFace face) {
if (face == BlockFace.BOTTOM) {
z -= 1;
face = BlockFace.TOP;
} else if (face == BlockFace.SOUTH) {
x -= 1;
face = BlockFace.NORTH;
} else if (face == BlockFace.EAST) {
y -= 1;
face = BlockFace.WEST;
}
data[x + 1][y + 1][z + 1] ^= 1 << getBit(face);
protected void addBlock(int x, int y, int z, OpaqueCube cube) {
data[x][y][z] = cube;
}
protected void addBlock(int x, int y, int z) {
data[x + 1][y + 1][z + 1] |= BLOCK_MASK;
}
protected boolean hasFace(int x, int y, int z, BlockFace face) {
if (face == BlockFace.BOTTOM) {
z -= 1;
face = BlockFace.TOP;
} else if (face == BlockFace.SOUTH) {
x -= 1;
face = BlockFace.NORTH;
} else if (face == BlockFace.EAST) {
y -= 1;
face = BlockFace.WEST;
}
return (data[x + 1][y + 1][z + 1] & 1 << getBit(face)) != 0;
}
protected boolean hasBlock(int x, int y, int z) {
return (data[x + 1][y + 1][z + 1] & BLOCK_MASK) != 0;
protected OpaqueCube getBlock(Vec3i cursor) {
return data[cursor.x][cursor.y][cursor.z];
}
@Override
public Shape endRender() {
Collection<Face> shapeFaces = new ArrayList<>(
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3 +
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * BLOCKS_PER_CHUNK * 3
);
for (int x = -1; x < ChunkData.BLOCKS_PER_CHUNK; ++x) {
for (int y = -1; y < ChunkData.BLOCKS_PER_CHUNK; ++y) {
for (int z = -1; z < ChunkData.BLOCKS_PER_CHUNK; ++z) {
for (BlockFace face : GOOD_FACES) {
if (!hasFace(x, y, z, face)) continue;
Face shapeFace = null;
if (!hasBlock(x, y, z)) {
if (face == BlockFace.TOP) {
shapeFace = createFace(
x, y, z + 1,
BlockFace.BOTTOM
);
} else if (face == BlockFace.NORTH) {
shapeFace = createFace(
x + 1, y, z,
BlockFace.SOUTH
);
} else if (face == BlockFace.WEST) {
shapeFace = createFace(
x, y + 1, z,
BlockFace.EAST
);
}
} else {
shapeFace = createFace(x, y, z, face);
}
shapeFaces.add(shapeFace);
}
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) {
OpaqueCube block = getBlock(cursor);
if (block == null) continue;
processInnerFaces(block, cursor, shapeFaces::add);
processOuterFaces(block, cursor, shapeFaces::add);
}
}
}
@ -169,29 +99,82 @@ public class ChunkRenderOptimizerCube extends ChunkRenderOptimizer {
);
}
private Face createFace(int x, int y, int z, BlockFace face) {
OpaqueCube blockRender =
(OpaqueCube) chunk.getBlock(x, y, z);
Texture texture = blockRender.getTexture(face);
private void processInnerFaces(
OpaqueCube block,
Vec3i cursor,
Consumer<Face> output
) {
if (block.isBlockOpaque()) return;
return Faces.createBlockFace(
WorldRenderProgram.getDefault(),
texture,
COLOR_MULTIPLIER,
blockCenter.set(x, y, z),
face
);
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
));
}
}
// TODO refactor
private static int getBit(BlockFace face) {
if (face == BlockFace.TOP) return 0;
if (face == BlockFace.BOTTOM) return 1;
if (face == BlockFace.NORTH) return 2;
if (face == BlockFace.SOUTH) return 3;
if (face == BlockFace.WEST) return 4;
if (face == BlockFace.EAST) return 5;
return -1;
private void processOuterFaces(
OpaqueCube block,
Vec3i cursor,
Consumer<Face> output
) {
for (BlockFace face : BlockFace.getFaces()) {
Texture texture = block.getTexture(face);
if (texture == null) continue;
if (!shouldRenderFace(cursor, face)) continue;
output.accept(Faces.createBlockFace(
WorldRenderProgram.getDefault(),
texture,
COLOR_MULTIPLIER,
new Vec3(cursor.x, cursor.y, cursor.z),
face,
false
));
}
}
private boolean shouldRenderFace(Vec3i cursor, BlockFace face) {
cursor.add(face.getVector());
try {
// TODO handle neighboring chunks properly
if (!isInBounds(cursor)) return true;
OpaqueCube adjacent = getBlock(cursor);
if (adjacent == null) return true;
if (adjacent.isOpaque(face)) return false;
return true;
} finally {
cursor.sub(face.getVector());
}
}
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

@ -26,14 +26,25 @@ public final class BlockFace extends BlockRelation {
BOTTOM = new BlockFace( 0, 0, -1, false),
NORTH = new BlockFace(+1, 0, 0, true),
SOUTH = new BlockFace(-1, 0, 0, false),
WEST = new BlockFace( 0, +1, 0, true),
EAST = new BlockFace( 0, -1, 0, false);
WEST = new BlockFace( 0, +1, 0, false),
EAST = new BlockFace( 0, -1, 0, true);
private static final ImmutableList<BlockFace> ALL_VALUES =
private static final ImmutableList<BlockFace> ALL_FACES =
ImmutableList.of(TOP, BOTTOM, NORTH, SOUTH, WEST, EAST);
public static ImmutableList<BlockFace> getValues() {
return ALL_VALUES;
private static final ImmutableList<BlockFace> PRIMARY_FACES =
ImmutableList.of(TOP, NORTH, WEST);
public static ImmutableList<BlockFace> getFaces() {
return ALL_FACES;
}
public static ImmutableList<BlockFace> getPrimaryFaces() {
return PRIMARY_FACES;
}
public static int count() {
return ALL_FACES.size();
}
static {

View File

@ -3,7 +3,7 @@
varying vec3 varyingNormals;
void applyShading() {
vec3 light = normalize(vec3(0.5, 1.0, 0.2));
vec3 light = normalize(vec3(0.5, -0.2, 1.0));
vec3 normal = varyingNormals;
float angleCos = dot(normal, light);