diff --git a/README.md b/README.md index d35cba7..d20ecdd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # freeworld A 3D sandbox game. + +## Additions + +Math codes are from [JOML](https://github.com/JOML-CI/JOML). + +Block textures are from [Squareful](https://www.curseforge.com/minecraft/texture-packs/xekr-square-pattern) by XeKr. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7d817a4..3d6827b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,13 +4,13 @@ # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# License as published by the Free Software Foundation; +# only version 2.1 of the License. # distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 8aff732..358c912 100644 --- a/gradlew +++ b/gradlew @@ -6,8 +6,8 @@ # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# License as published by the Free Software Foundation; +# only version 2.1 of the License. # ############################################################################## diff --git a/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java b/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java index 4537144..7bf05ec 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java @@ -19,7 +19,8 @@ import freeworld.client.render.screen.ingame.PauseScreen; import freeworld.client.render.screen.Screen; import freeworld.client.render.world.HitResult; -import freeworld.core.registry.BuiltinRegistries; +import freeworld.client.render.world.WorldRenderer; +import freeworld.core.registry.Registries; import freeworld.math.Vector2d; import freeworld.math.Vector3d; import freeworld.util.Direction; @@ -46,6 +47,7 @@ import java.lang.foreign.MemorySegment; import java.lang.invoke.MethodHandles; +import java.util.Random; /** * Client logic @@ -94,6 +96,8 @@ public final class Freeworld implements AutoCloseable { BlockTypes.AIR, BlockTypes.AIR }; + private int gameTick = 0; + private int spaceTick = 0; private Freeworld() { this.glfw = GLFW.INSTANCE; @@ -140,15 +144,17 @@ public void start() { } BlockTypes.bootstrap(); - BuiltinRegistries.BLOCK_TYPE.freeze(); + Registries.BLOCK_TYPE.freeze(); EntityTypes.bootstrap(); - BuiltinRegistries.ENTITY_TYPE.freeze(); + Registries.ENTITY_TYPE.freeze(); blockModelManager = new BlockModelManager(); blockModelManager.bootstrap(); - world = new World("New world"); - player = world.createEntity(EntityTypes.PLAYER, new Vector3d(0.0, 0.0, 0.0)); + world = new World("New world", new Random().nextLong()); + player = world.createEntity(EntityTypes.PLAYER, new Vector3d(0.0, 128.0, 0.0)); + + World.forEachChunk(player, WorldRenderer.RENDER_RADIUS, (x, y, z) -> world.getOrCreateChunk(x, y, z)); initGL(); run(); @@ -168,16 +174,6 @@ private void onKey(int key, int scancode, int action, int mods) { } case GLFW.PRESS -> { switch (key) { - case GLFW.KEY_1 -> hotBarSelection = 0; - case GLFW.KEY_2 -> hotBarSelection = 1; - case GLFW.KEY_3 -> hotBarSelection = 2; - case GLFW.KEY_4 -> hotBarSelection = 3; - case GLFW.KEY_5 -> hotBarSelection = 4; - case GLFW.KEY_6 -> hotBarSelection = 5; - case GLFW.KEY_7 -> hotBarSelection = 6; - case GLFW.KEY_8 -> hotBarSelection = 7; - case GLFW.KEY_9 -> hotBarSelection = 8; - case GLFW.KEY_0 -> hotBarSelection = 9; case GLFW.KEY_ESCAPE -> { if (screen != null) { if (screen.escapeCanClose()) { @@ -191,7 +187,27 @@ private void onKey(int key, int scancode, int action, int mods) { if (screen == null) { if (world != null) { switch (key) { + case GLFW.KEY_1 -> hotBarSelection = 0; + case GLFW.KEY_2 -> hotBarSelection = 1; + case GLFW.KEY_3 -> hotBarSelection = 2; + case GLFW.KEY_4 -> hotBarSelection = 3; + case GLFW.KEY_5 -> hotBarSelection = 4; + case GLFW.KEY_6 -> hotBarSelection = 5; + case GLFW.KEY_7 -> hotBarSelection = 6; + case GLFW.KEY_8 -> hotBarSelection = 7; + case GLFW.KEY_9 -> hotBarSelection = 8; + case GLFW.KEY_0 -> hotBarSelection = 9; case GLFW.KEY_E -> openScreen(new CreativeTabScreen(this, null)); + case GLFW.KEY_SPACE -> { + if (gameTick - spaceTick < 5) { + if (player.hasComponent(EntityComponents.FLYING)) { + player.removeComponent(EntityComponents.FLYING); + } else { + player.addComponent(EntityComponents.FLYING); + } + } + spaceTick = gameTick; + } } } } else { @@ -253,17 +269,35 @@ private void tick() { camera.preUpdate(); if (screen == null) { final boolean onGround = player.hasComponent(EntityComponents.ON_GROUND); - double speed = onGround ? 0.1 : 0.02; + final boolean flying = player.hasComponent(EntityComponents.FLYING); + double speed; + if (onGround) { + speed = 0.1; + } else if (flying) { + speed = 0.5; + } else { + speed = 0.02; + } if (glfw.getKey(window, GLFW.KEY_LEFT_CONTROL) == GLFW.PRESS) speed *= 2.0; double xo = 0.0; + double yo = 0.0; + boolean changedYo = false; double zo = 0.0; if (glfw.getKey(window, GLFW.KEY_W) == GLFW.PRESS) zo -= 1.0; if (glfw.getKey(window, GLFW.KEY_S) == GLFW.PRESS) zo += 1.0; if (glfw.getKey(window, GLFW.KEY_A) == GLFW.PRESS) xo -= 1.0; if (glfw.getKey(window, GLFW.KEY_D) == GLFW.PRESS) xo += 1.0; - if (onGround && glfw.getKey(window, GLFW.KEY_SPACE) == GLFW.PRESS) { - final Vector3d value = player.getComponent(EntityComponents.VELOCITY); - player.setComponent(EntityComponents.VELOCITY, new Vector3d(value.x(), 0.5, value.z())); + if ((onGround || flying) && glfw.getKey(window, GLFW.KEY_SPACE) == GLFW.PRESS) { + yo += 0.5; + changedYo = true; + } + if (flying && glfw.getKey(window, GLFW.KEY_LEFT_SHIFT) == GLFW.PRESS) { + yo -= 0.5; + changedYo = true; + } + if (changedYo) { + double finalYo = yo; + player.withComponent(EntityComponents.VELOCITY, v -> v.withY(finalYo)); } player.setComponent(EntityComponents.ACCELERATION, MathUtil.moveRelative(xo, 0.0, zo, player.getComponent(EntityComponents.ROTATION).y(), speed)); @@ -299,6 +333,8 @@ private void tick() { world.tick(); } gameRenderer.tick(); + + gameTick++; } private void initGL() { diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/gui/HudRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/gui/HudRenderer.java index 41ec7de..df17205 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/gui/HudRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/gui/HudRenderer.java @@ -14,14 +14,12 @@ import freeworld.client.render.GameRenderer; import freeworld.client.render.RenderSystem; import freeworld.client.render.Tessellator; -import freeworld.client.render.animation.Animation; import freeworld.client.render.gl.GLDrawMode; import freeworld.client.render.gl.GLStateMgr; import freeworld.client.render.texture.TextureAtlas; import freeworld.client.render.texture.TextureManager; import freeworld.core.Identifier; -import freeworld.core.registry.BuiltinRegistries; -import freeworld.math.Maths; +import freeworld.core.registry.Registries; import freeworld.math.Matrix4f; import freeworld.world.block.BlockType; import overrungl.opengl.GL10C; @@ -35,11 +33,6 @@ public final class HudRenderer { public static final Identifier HOT_BAR_TEXTURE = Identifier.ofBuiltin("gui/hotbar"); public static final Identifier HOT_BAR_SELECTED_TEXTURE = Identifier.ofBuiltin("gui/hotbar_selected"); private final GameRenderer gameRenderer; - private final Animation hotBarSelectorAnimation = new Animation<>( - hotBarSelectorX(0), - (start, end, progress) -> (float) Maths.lerp(start, end, progress) - ); - private int prevHotBarSelection = 0; private float width = 0f; private float height = 0f; @@ -97,7 +90,7 @@ private void renderHotBar(GuiGraphics graphics, GLStateMgr gl, double partialTic ); graphics.drawSprite( atlas.getRegion(HOT_BAR_SELECTED_TEXTURE), - (float) Maths.lerp(hotBarSelectorAnimation.previous(), hotBarSelectorAnimation.current(), partialTick), + hotBarSelectorX(gameRenderer.client().hotBarSelection()), -height * 0.5f, 0.0f, 0.0f @@ -123,7 +116,7 @@ private void renderHotBarItems(GLStateMgr gl) { .rotateY((float) Math.toRadians(45.0)) .scale(10)); tessellator.begin(GLDrawMode.TRIANGLES); - gameRenderer.blockRenderer().renderBlockModel(tessellator, client.blockModelManager().get(BuiltinRegistries.BLOCK_TYPE.getId(blockType)), 0, 0, 0, _ -> false); + gameRenderer.blockRenderer().renderBlockModel(tessellator, client.blockModelManager().get(Registries.BLOCK_TYPE.getId(blockType)), 0, 0, 0, _ -> false); tessellator.end(gl); i++; } @@ -135,14 +128,5 @@ private float hotBarSelectorX(int selection) { } public void tick() { - final int selection = gameRenderer.client().hotBarSelection(); - if (prevHotBarSelection != selection) { - hotBarSelectorAnimation.reset( - hotBarSelectorX(selection), - 1 - ); - prevHotBarSelection = selection; - } - hotBarSelectorAnimation.tick(); } } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/model/block/BlockModelManager.java b/modules/freeworld.client/src/main/java/freeworld/client/render/model/block/BlockModelManager.java index c770284..acd5da8 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/model/block/BlockModelManager.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/model/block/BlockModelManager.java @@ -12,7 +12,7 @@ import freeworld.client.render.texture.TextureAtlas; import freeworld.core.Identifier; -import freeworld.core.registry.BuiltinRegistries; +import freeworld.core.registry.Registries; import freeworld.core.registry.DefaultedRegistry; import freeworld.core.registry.Registry; import freeworld.world.block.BlockType; @@ -57,7 +57,7 @@ public void register(Identifier identifier, BlockModel blockModel) { } public void register(BlockType blockType, BlockModel blockModel) { - register(BuiltinRegistries.BLOCK_TYPE.getId(blockType), blockModel); + register(Registries.BLOCK_TYPE.getId(blockType), blockModel); } public void bootstrap() { diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java index c9a06d5..1d16b00 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java @@ -36,38 +36,45 @@ public BlockRenderer(TextureManager textureManager) { } private void emitVertices(VertexBuilder builder, Vector3f from, Vector3f to, Vector2f uvFrom, Vector2f uvTo, Direction direction) { + // TODO: 2024/7/6 squid233: color switch (direction) { case WEST -> { + builder.color(0.7f, 0.7f, 0.7f); builder.position(from.x(), to.y(), from.z()).texCoord(uvFrom.x(), uvFrom.y()).emit(); builder.position(from.x(), from.y(), from.z()).texCoord(uvFrom.x(), uvTo.y()).emit(); builder.position(from.x(), from.y(), to.z()).texCoord(uvTo.x(), uvTo.y()).emit(); builder.position(from.x(), to.y(), to.z()).texCoord(uvTo.x(), uvFrom.y()).emit(); } case EAST -> { + builder.color(1.0f, 1.0f, 1.0f); builder.position(to.x(), to.y(), to.z()).texCoord(uvFrom.x(), uvFrom.y()).emit(); builder.position(to.x(), from.y(), to.z()).texCoord(uvFrom.x(), uvTo.y()).emit(); builder.position(to.x(), from.y(), from.z()).texCoord(uvTo.x(), uvTo.y()).emit(); builder.position(to.x(), to.y(), from.z()).texCoord(uvTo.x(), uvFrom.y()).emit(); } case DOWN -> { + builder.color(0.6f, 0.6f, 0.6f); builder.position(from.x(), from.y(), to.z()).texCoord(uvFrom.x(), uvFrom.y()).emit(); builder.position(from.x(), from.y(), from.z()).texCoord(uvFrom.x(), uvTo.y()).emit(); builder.position(to.x(), from.y(), from.z()).texCoord(uvTo.x(), uvTo.y()).emit(); builder.position(to.x(), from.y(), to.z()).texCoord(uvTo.x(), uvFrom.y()).emit(); } case UP -> { + builder.color(0.9f, 0.9f, 0.9f); builder.position(from.x(), to.y(), from.z()).texCoord(uvFrom.x(), uvFrom.y()).emit(); builder.position(from.x(), to.y(), to.z()).texCoord(uvFrom.x(), uvTo.y()).emit(); builder.position(to.x(), to.y(), to.z()).texCoord(uvTo.x(), uvTo.y()).emit(); builder.position(to.x(), to.y(), from.z()).texCoord(uvTo.x(), uvFrom.y()).emit(); } case NORTH -> { + builder.color(0.8f, 0.8f, 0.8f); builder.position(to.x(), to.y(), from.z()).texCoord(uvFrom.x(), uvFrom.y()).emit(); builder.position(to.x(), from.y(), from.z()).texCoord(uvFrom.x(), uvTo.y()).emit(); builder.position(from.x(), from.y(), from.z()).texCoord(uvTo.x(), uvTo.y()).emit(); builder.position(from.x(), to.y(), from.z()).texCoord(uvTo.x(), uvFrom.y()).emit(); } case SOUTH -> { + builder.color(0.8f, 0.8f, 0.8f); builder.position(from.x(), to.y(), to.z()).texCoord(uvFrom.x(), uvFrom.y()).emit(); builder.position(from.x(), from.y(), to.z()).texCoord(uvFrom.x(), uvTo.y()).emit(); builder.position(to.x(), from.y(), to.z()).texCoord(uvTo.x(), uvTo.y()).emit(); diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java index 04734f0..874136b 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java @@ -13,7 +13,7 @@ import freeworld.client.render.builder.VertexBuilder; import freeworld.client.render.model.block.BlockModel; import freeworld.client.render.model.block.BlockModelManager; -import freeworld.core.registry.BuiltinRegistries; +import freeworld.core.registry.Registries; import freeworld.world.chunk.Chunk; import freeworld.world.chunk.ChunkPos; @@ -45,7 +45,7 @@ public static ChunkVertexData compile( int finalX = x; int finalY = y; int finalZ = z; - final BlockModel model = blockModelManager.get(BuiltinRegistries.BLOCK_TYPE.getId(chunk.getBlockType(x, y, z))); + final BlockModel model = blockModelManager.get(Registries.BLOCK_TYPE.getId(chunk.getBlockType(x, y, z))); blockRenderer.renderBlockModel( vertexBuilder, model, @@ -61,9 +61,10 @@ public static ChunkVertexData compile( final int absNz = ChunkPos.relativeToAbsolute(cz, nz); final boolean shouldRender = (chunk.isInBound(nx, ny, nz) && - chunk.getBlockType(nx, ny, nz).air()) || + chunk.getBlockType(nx, ny, nz).nonOpaque()) || (chunk.world().isBlockLoaded(absNx, absNy, absNz) && - chunk.world().getBlockType(absNx, absNy, absNz).air()); + chunk.world().getBlockType(absNx, absNy, absNz).nonOpaque()) || + !chunk.world().isBlockLoaded(absNx, absNy, absNz) /* TODO: add method world::tryLoading() */; return !shouldRender; } ); diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java index b320843..8240aaa 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java @@ -18,10 +18,7 @@ import freeworld.client.render.model.vertex.VertexLayouts; import freeworld.client.world.chunk.ClientChunk; import freeworld.core.math.AABBox; -import freeworld.math.FrustumIntersection; -import freeworld.math.FrustumRayBuilder; -import freeworld.math.Intersectiond; -import freeworld.math.Vector3f; +import freeworld.math.*; import freeworld.util.Direction; import freeworld.util.Logging; import freeworld.world.World; @@ -41,7 +38,6 @@ import java.time.Duration; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -63,36 +59,38 @@ public final class WorldRenderer implements GLResource, WorldListener { .buildPool(); private final Map chunks = new ConcurrentHashMap<>(RENDER_CHUNK_COUNT); private final Disposable chunkGC; + private int playerChunkX = 0; + private int playerChunkY = 0; + private int playerChunkZ = 0; public WorldRenderer(GameRenderer gameRenderer, World world) { this.gameRenderer = gameRenderer; this.world = world; world.addListener(this); - this.chunkGC = Flux.interval(Duration.ofSeconds(60)) - .subscribe(_ -> { - final List list = new ArrayList<>(RENDER_CHUNK_COUNT); - World.forEachChunk(gameRenderer.client().player(), RENDER_RADIUS, (x, y, z) -> list.add(new ChunkPos(x, y, z))); - final var it = chunks.entrySet().iterator(); - while (it.hasNext()) { - final var e = it.next(); - if (!list.contains(e.getKey())) { - e.getValue().close(); - it.remove(); - } - } - }); + this.chunkGC = Flux.interval(Duration.ofSeconds(45)) + .subscribe(_ -> uninstallChunks()); } private static DefaultVertexBuilder createVertexBuilder() { return new DefaultVertexBuilder(VertexLayouts.POSITION_COLOR_TEX, 30000, 45000); } + private void uninstallChunks() { + final List list = new ArrayList<>(RENDER_CHUNK_COUNT); + World.forEachChunk(gameRenderer.client().player(), RENDER_RADIUS, (x, y, z) -> list.add(new ChunkPos(x, y, z))); + final var it = chunks.entrySet().iterator(); + while (it.hasNext()) { + final var e = it.next(); + if (!list.contains(e.getKey())) { + e.getValue().close(); + it.remove(); + } + } + } + public List renderingChunks(Entity player) { final List chunks = new ArrayList<>(RENDER_CHUNK_COUNT); World.forEachChunk(player, RENDER_RADIUS, (x, y, z) -> chunks.add(getChunkOrCreate(x, y, z))); - chunks.sort(Comparator - .comparingDouble(o -> o.yDistanceToPlayer(player)) - .thenComparingDouble(o -> o.xzDistanceToPlayerSquared(player))); return chunks; } @@ -103,6 +101,20 @@ public void compileChunks(List renderingChunks) { } public void renderChunks(GLStateMgr gl, List renderingChunks) { + Vector3d playerPos = gameRenderer.client().player().getComponent(EntityComponents.POSITION); + int playerChunkX = ChunkPos.absoluteToChunk((int) Math.floor(playerPos.x())); + int playerChunkY = ChunkPos.absoluteToChunk((int) Math.floor(playerPos.y())); + int playerChunkZ = ChunkPos.absoluteToChunk((int) Math.floor(playerPos.z())); + if (playerChunkX != this.playerChunkX || + playerChunkY != this.playerChunkY || + playerChunkZ != this.playerChunkZ) { + this.playerChunkX = playerChunkX; + this.playerChunkY = playerChunkY; + this.playerChunkZ = playerChunkZ; + uninstallChunks(); + } + + int builtChunkCount = 0; FrustumIntersection frustumIntersection = new FrustumIntersection(RenderSystem.projectionViewMatrix()); for (ClientChunk chunk : renderingChunks) { if (frustumIntersection.testAab( @@ -113,6 +125,10 @@ public void renderChunks(GLStateMgr gl, List renderingChunks) { chunk.toY(), chunk.toZ() )) { + if (builtChunkCount < 8 && chunk.shouldBuildBuffer()) { + builtChunkCount++; + chunk.buildBuffer(gl); + } chunk.render(gl); } } @@ -398,12 +414,12 @@ public GameRenderer gameRenderer() { @Override public void close(GLStateMgr gl) { logger.info("Closing world renderer"); - scheduler.dispose(); - vertexBuilderPool.dispose(); - chunkGC.dispose(); for (ClientChunk chunk : chunks.values()) { chunk.close(); } chunks.clear(); + scheduler.dispose(); + vertexBuilderPool.dispose(); + chunkGC.dispose(); } } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java b/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java index 2e7681d..f619f0d 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java @@ -25,9 +25,9 @@ import freeworld.world.entity.EntityComponents; import org.slf4j.Logger; import overrungl.opengl.GL15C; +import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.pool.PoolShutdownException; import java.lang.foreign.MemorySegment; import java.lang.ref.Cleaner; @@ -43,6 +43,7 @@ public final class ClientChunk extends Chunk implements AutoCloseable { private final Cleaner.Cleanable cleanable; private final State state; private final Flux dataFlux; + private Disposable subscribed; /** * Is this chunk changed? */ @@ -56,13 +57,18 @@ public ClientChunk(World world, WorldRenderer worldRenderer, int x, int y, int z this.state = new State(gameRenderer.client().gl()); this.cleanable = CLEANER.register(this, state); this.dataFlux = worldRenderer.vertexBuilderPool() - .withPoolable(vertexBuilder -> Mono.fromSupplier(() -> ChunkCompiler.compile( + .withPoolable(vertexBuilder -> Mono.fromSupplier(() -> { + final Chunk chunk = world().getOrCreateChunk(x(), y(), z()); + if (chunk != null) { + copyFrom(chunk); + } + return ChunkCompiler.compile( vertexBuilder, gameRenderer.blockRenderer(), gameRenderer.client().blockModelManager(), this - )) - ) + ); + })) .onBackpressureBuffer() .subscribeOn(worldRenderer.scheduler()); } @@ -90,24 +96,26 @@ public void compile() { if (!dirty) { return; } - final Chunk chunk = world().getOrCreateChunk(x(), y(), z()); - if (chunk != null) { - copyFrom(chunk); + if (subscribed != null) { + subscribed.dispose(); } - dataFlux.subscribe(state.dataRef::set, throwable -> { - if (!(throwable instanceof PoolShutdownException)) { - logger.error(STR."Error thrown compiling client chunk \{x()}, \{y()}, \{z()}", throwable); - } - }); + subscribed = dataFlux.subscribe(state.dataRef::set); dirty = false; } - public void render(GLStateMgr gl) { + public boolean shouldBuildBuffer() { + return state.dataRef.get() != null; + } + + public void buildBuffer(GLStateMgr gl) { final ChunkVertexData data = state.dataRef.get(); if (data != null) { buildBuffer(gl, data); state.dataRef.set(null); } + } + + public void render(GLStateMgr gl) { if (state.vao != 0) { gl.setVertexArrayBinding(state.vao); gl.drawElements(GLStateMgr.TRIANGLES, indexCount, GLStateMgr.UNSIGNED_INT, MemorySegment.NULL); @@ -165,6 +173,7 @@ public void markDirty() { @Override public void close() { + subscribed.dispose(); cleanable.clean(); } } diff --git a/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/dirt.png b/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/dirt.png index 8558a91..bdfb235 100644 Binary files a/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/dirt.png and b/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/dirt.png differ diff --git a/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/grass_block.png b/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/grass_block.png index 9800912..e3436f4 100644 Binary files a/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/grass_block.png and b/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/grass_block.png differ diff --git a/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/stone.png b/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/stone.png index 9acf144..ed3d698 100644 Binary files a/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/stone.png and b/modules/freeworld.client/src/main/resources/assets/freeworld/texture/block/stone.png differ diff --git a/modules/freeworld.core/src/main/java/freeworld/core/registry/BuiltinRegistries.java b/modules/freeworld.core/src/main/java/freeworld/core/registry/Registries.java similarity index 77% rename from modules/freeworld.core/src/main/java/freeworld/core/registry/BuiltinRegistries.java rename to modules/freeworld.core/src/main/java/freeworld/core/registry/Registries.java index bd29eba..b547180 100644 --- a/modules/freeworld.core/src/main/java/freeworld/core/registry/BuiltinRegistries.java +++ b/modules/freeworld.core/src/main/java/freeworld/core/registry/Registries.java @@ -4,8 +4,8 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * License as published by the Free Software Foundation; + * only version 2.1 of the License. */ package freeworld.core.registry; @@ -19,10 +19,10 @@ * @author squid233 * @since 0.1.0 */ -public final class BuiltinRegistries { +public final class Registries { public static final DefaultedRegistry BLOCK_TYPE = new DefaultedRegistry<>(Identifier.ofBuiltin("block_type"), () -> BlockTypes.AIR); public static final MappedRegistry ENTITY_TYPE = new MappedRegistry<>(Identifier.ofBuiltin("entity_type")); - private BuiltinRegistries() { + private Registries() { } } diff --git a/modules/freeworld.core/src/main/java/freeworld/core/registry/Registry.java b/modules/freeworld.core/src/main/java/freeworld/core/registry/Registry.java index cfd2594..8a5eb40 100644 --- a/modules/freeworld.core/src/main/java/freeworld/core/registry/Registry.java +++ b/modules/freeworld.core/src/main/java/freeworld/core/registry/Registry.java @@ -22,10 +22,6 @@ * @since 0.1.0 */ public interface Registry extends Iterable> { - static T register(MutableRegistry registry, Identifier id, int rawId, T entry) { - return registry.set(id, rawId, entry); - } - static T register(MutableRegistry registry, Identifier id, T entry) { return registry.add(id, entry); } diff --git a/modules/freeworld.core/src/main/java/freeworld/util/math/SimplexNoiseUtil.java b/modules/freeworld.core/src/main/java/freeworld/util/math/SimplexNoiseUtil.java new file mode 100644 index 0000000..aa48c12 --- /dev/null +++ b/modules/freeworld.core/src/main/java/freeworld/util/math/SimplexNoiseUtil.java @@ -0,0 +1,44 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.util.math; + +import freeworld.math.SimplexNoise; + +/** + * Original code from Christian Maher + * + * @author squid233 + * @since 0.1.0 + */ +public final class SimplexNoiseUtil { + public static float sumOctave(int numIterations, float x, float y, float z, float w, float persistence, float scale, float low, float high) { + float maxAmp = 0; + float amp = 1; + float freq = scale; + float noise = 0; + + // add successively smaller, higher-frequency terms + for (int i = 0; i < numIterations; ++i) { + noise += SimplexNoise.noise(x * freq, y * freq, z * freq, w * freq) * amp; + maxAmp += amp; + amp *= persistence; + freq *= 2; + } + + // take the average value of the iterations + noise /= maxAmp; + + // normalize the result + noise = noise * (high - low) * 0.5f + (high + low) * 0.5f; + + return noise; + } +} diff --git a/modules/freeworld.core/src/main/java/freeworld/world/World.java b/modules/freeworld.core/src/main/java/freeworld/world/World.java index e79b982..ba301c7 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/World.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/World.java @@ -22,10 +22,7 @@ import freeworld.world.entity.EntityComponents; import freeworld.world.entity.system.MotionSystem; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -40,8 +37,10 @@ public final class World { private final List entities = new ArrayList<>(); private final MotionSystem motionSystem = new MotionSystem(); private final List listeners = new ArrayList<>(); + private final long seed; - public World(String name) { + public World(String name, long seed) { + this.seed = seed; } public static void forEachChunk(Entity player, int chunkRadius, Int3Consumer consumer) { @@ -137,4 +136,8 @@ public void setBlockType(int x, int y, int z, BlockType blockType) { } } } + + public long seed() { + return seed; + } } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java b/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java index 11cd230..43c23cc 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java @@ -18,24 +18,36 @@ */ public class BlockType { // must be an identity class private final boolean air; + private final boolean nonOpaque; public BlockType(Settings settings) { this.air = settings.air; + this.nonOpaque = settings.nonOpaque; } public static final class Settings { private boolean air = false; + private boolean nonOpaque = false; public Settings air() { this.air = true; return this; } + + public Settings nonOpaque() { + this.nonOpaque = true; + return this; + } } public boolean air() { return air; } + public boolean nonOpaque() { + return nonOpaque; + } + public AABBox outlineShape() { return AABBox.FULL_CUBE; } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/block/BlockTypes.java b/modules/freeworld.core/src/main/java/freeworld/world/block/BlockTypes.java index 0d8455e..559e99a 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/block/BlockTypes.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/block/BlockTypes.java @@ -11,7 +11,7 @@ package freeworld.world.block; import freeworld.core.Identifier; -import freeworld.core.registry.BuiltinRegistries; +import freeworld.core.registry.Registries; import freeworld.core.registry.Registry; /** @@ -19,16 +19,16 @@ * @since 0.1.0 */ public final class BlockTypes { - public static final BlockType AIR = register("air", 0, new AirBlockType(new BlockType.Settings().air())); - public static final BlockType GRASS_BLOCK = register("grass_block", 1, new BlockType(new BlockType.Settings())); - public static final BlockType DIRT = register("dirt", 2, new BlockType(new BlockType.Settings())); - public static final BlockType STONE = register("stone", 3, new BlockType(new BlockType.Settings())); + public static final BlockType AIR = register("air", new AirBlockType(new BlockType.Settings().air().nonOpaque())); + public static final BlockType GRASS_BLOCK = register("grass_block", new BlockType(new BlockType.Settings())); + public static final BlockType DIRT = register("dirt", new BlockType(new BlockType.Settings())); + public static final BlockType STONE = register("stone", new BlockType(new BlockType.Settings())); private BlockTypes() { } - private static BlockType register(String name, int rawId, BlockType blockType) { - return Registry.register(BuiltinRegistries.BLOCK_TYPE, Identifier.ofBuiltin(name), rawId, blockType); + private static BlockType register(String name, BlockType blockType) { + return Registry.register(Registries.BLOCK_TYPE, Identifier.ofBuiltin(name), blockType); } public static void bootstrap() { diff --git a/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java b/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java index 6bda60a..7012cac 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java @@ -4,12 +4,13 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * License as published by the Free Software Foundation; + * only version 2.1 of the License. */ package freeworld.world.chunk; +import freeworld.util.math.SimplexNoiseUtil; import freeworld.world.World; import freeworld.world.block.BlockType; import freeworld.world.block.BlockTypes; @@ -23,7 +24,7 @@ * @since 0.1.0 */ public class Chunk { - public static final int SIZE = 32; + public static final int SIZE = 16; private final World world; private final int x; private final int y; @@ -60,13 +61,16 @@ public Chunk(World world, int x, int y, int z) { public void generateTerrain() { for (int bx = 0; bx < width; bx++) { for (int bz = 0; bz < depth; bz++) { + final int absX = ChunkPos.relativeToAbsolute(x, bx); + final int absZ = ChunkPos.relativeToAbsolute(z, bz); + final float heightmap = SimplexNoiseUtil.sumOctave(8, absX, absZ, world.seed() & 0xff, (world.seed() >> 8) & 0xff, 0.2f, 0.003f, -64.0f, 64.0f); for (int by = 0; by < height; by++) { final int absY = ChunkPos.relativeToAbsolute(y, by); - if (absY < -4) { + if (absY < heightmap - 3) { setBlockType(bx, by, bz, BlockTypes.STONE); - } else if (absY < -1) { + } else if (absY < heightmap - 1) { setBlockType(bx, by, bz, BlockTypes.DIRT); - } else if (absY == -1) { + } else if (absY < heightmap) { setBlockType(bx, by, bz, BlockTypes.GRASS_BLOCK); } } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java index 5045106..5319b8d 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.function.UnaryOperator; /** * @author squid233 @@ -52,6 +53,10 @@ public void setComponent(ComponentKey key, T component) { componentMap.put(key, component); } + public void withComponent(ComponentKey key, UnaryOperator component) { + setComponent(key, component.apply(getComponent(key))); + } + public void removeComponent(ComponentKey id) { componentMap.remove(id); } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java index b002165..5cd3b1b 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java @@ -24,10 +24,12 @@ */ public final class EntityComponents { private static final Supplier zeroVec3 = () -> Vector3d.ZERO; + private static final Supplier object = () -> Object.class; public static final ComponentKey ACCELERATION = of("acceleration", zeroVec3); public static final ComponentKey BOUNDING_BOX = of("bounding_box", () -> AABBox.EMPTY); public static final ComponentKey EYE_POSITION = of("eye_position", () -> new Vector3d(0.0, 0.5, 0.0)); - public static final ComponentKey ON_GROUND = of("on_ground", () -> Object.class); + public static final ComponentKey FLYING = of("flying", object); + public static final ComponentKey ON_GROUND = of("on_ground", object); public static final ComponentKey POSITION = of("position", zeroVec3); public static final ComponentKey ROTATION = of("rotation", () -> Vector2d.ZERO); public static final ComponentKey VELOCITY = of("velocity", zeroVec3); diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityTypes.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityTypes.java index 7cffb1f..7a3a58b 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityTypes.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityTypes.java @@ -11,7 +11,7 @@ package freeworld.world.entity; import freeworld.core.Identifier; -import freeworld.core.registry.BuiltinRegistries; +import freeworld.core.registry.Registries; import freeworld.core.registry.Registry; import freeworld.math.Vector3d; import freeworld.world.World; @@ -22,7 +22,7 @@ */ public final class EntityTypes { public static final Vector3d PLAYER_EYE_POSITION = new Vector3d(0.0, 1.62, 0.0); - public static final EntityType PLAYER = register(1, "player", EntityTypes::setupComponentPlayer); + public static final EntityType PLAYER = register("player", EntityTypes::setupComponentPlayer); private EntityTypes() { } @@ -36,8 +36,8 @@ private static void setupComponentPlayer(World world, Entity entity, Vector3d po entity.addComponent(EntityComponents.VELOCITY); } - private static EntityType register(int rawId, String name, EntityType.Initializer initializer) { - return Registry.register(BuiltinRegistries.ENTITY_TYPE, Identifier.ofBuiltin(name), rawId, new EntityType(initializer)); + private static EntityType register(String name, EntityType.Initializer initializer) { + return Registry.register(Registries.ENTITY_TYPE, Identifier.ofBuiltin(name), new EntityType(initializer)); } public static void bootstrap() { diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java index 99b26b3..01e3e9a 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java @@ -38,7 +38,11 @@ public void process(World world, List entities) { Vector3d position = entity.getComponent(EntityComponents.POSITION); Vector3d velocity = entity.getComponent(EntityComponents.VELOCITY); - velocity = velocity.add(acceleration.x(), acceleration.y() - 0.08, acceleration.z()); + double g = 0.08; + if (entity.hasComponent(EntityComponents.FLYING)) { + g = 0.0; + } + velocity = velocity.add(acceleration.x(), acceleration.y() - g, acceleration.z()); AABBox boundingBox = entity.getComponent(EntityComponents.BOUNDING_BOX); @@ -113,7 +117,11 @@ public void process(World world, List entities) { entity.setComponent(EntityComponents.POSITION, position); entity.setComponent(EntityComponents.BOUNDING_BOX, computeBox(boundingBox, position)); - velocity = velocity.mul(0.91, 0.98, 0.91); + if (!entity.hasComponent(EntityComponents.FLYING)) { + velocity = velocity.mul(0.91, 0.98, 0.91); + } else { + velocity = Vector3d.ZERO; + } if (entity.hasComponent(EntityComponents.ON_GROUND)) { final double fiction = 0.7; velocity = velocity.mul(fiction, 1.0, fiction); diff --git a/modules/freeworld.math/src/main/java/freeworld/math/SimplexNoise.java b/modules/freeworld.math/src/main/java/freeworld/math/SimplexNoise.java new file mode 100644 index 0000000..77ccaee --- /dev/null +++ b/modules/freeworld.math/src/main/java/freeworld/math/SimplexNoise.java @@ -0,0 +1,452 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.math; + +/** + * @author JOML + * @author squid233 + * @since 0.1.0 + */ +public final class SimplexNoise { + private static final class Vector3b { + byte x, y, z; + + Vector3b(int x, int y, int z) { + super(); + this.x = (byte) x; + this.y = (byte) y; + this.z = (byte) z; + } + } + + private static final class Vector4b { + byte x, y, z, w; + + Vector4b(int x, int y, int z, int w) { + super(); + this.x = (byte) x; + this.y = (byte) y; + this.z = (byte) z; + this.w = (byte) w; + } + } + + private static final Vector3b[] grad3 = {new Vector3b(1, 1, 0), new Vector3b(-1, 1, 0), new Vector3b(1, -1, 0), new Vector3b(-1, -1, 0), + new Vector3b(1, 0, 1), new Vector3b(-1, 0, 1), new Vector3b(1, 0, -1), new Vector3b(-1, 0, -1), new Vector3b(0, 1, 1), new Vector3b(0, -1, 1), + new Vector3b(0, 1, -1), new Vector3b(0, -1, -1)}; + private static final Vector4b[] grad4 = {new Vector4b(0, 1, 1, 1), new Vector4b(0, 1, 1, -1), new Vector4b(0, 1, -1, 1), new Vector4b(0, 1, -1, -1), + new Vector4b(0, -1, 1, 1), new Vector4b(0, -1, 1, -1), new Vector4b(0, -1, -1, 1), new Vector4b(0, -1, -1, -1), new Vector4b(1, 0, 1, 1), + new Vector4b(1, 0, 1, -1), new Vector4b(1, 0, -1, 1), new Vector4b(1, 0, -1, -1), new Vector4b(-1, 0, 1, 1), new Vector4b(-1, 0, 1, -1), + new Vector4b(-1, 0, -1, 1), new Vector4b(-1, 0, -1, -1), new Vector4b(1, 1, 0, 1), new Vector4b(1, 1, 0, -1), new Vector4b(1, -1, 0, 1), + new Vector4b(1, -1, 0, -1), new Vector4b(-1, 1, 0, 1), new Vector4b(-1, 1, 0, -1), new Vector4b(-1, -1, 0, 1), new Vector4b(-1, -1, 0, -1), + new Vector4b(1, 1, 1, 0), new Vector4b(1, 1, -1, 0), new Vector4b(1, -1, 1, 0), new Vector4b(1, -1, -1, 0), new Vector4b(-1, 1, 1, 0), + new Vector4b(-1, 1, -1, 0), new Vector4b(-1, -1, 1, 0), new Vector4b(-1, -1, -1, 0)}; + private static final byte[] p = {-105, -96, -119, 91, 90, 15, -125, 13, -55, 95, 96, 53, -62, -23, 7, -31, -116, 36, 103, 30, 69, -114, 8, 99, 37, -16, + 21, 10, 23, -66, 6, -108, -9, 120, -22, 75, 0, 26, -59, 62, 94, -4, -37, -53, 117, 35, 11, 32, 57, -79, 33, 88, -19, -107, 56, 87, -82, 20, 125, + -120, -85, -88, 68, -81, 74, -91, 71, -122, -117, 48, 27, -90, 77, -110, -98, -25, 83, 111, -27, 122, 60, -45, -123, -26, -36, 105, 92, 41, 55, 46, + -11, 40, -12, 102, -113, 54, 65, 25, 63, -95, 1, -40, 80, 73, -47, 76, -124, -69, -48, 89, 18, -87, -56, -60, -121, -126, 116, -68, -97, 86, -92, + 100, 109, -58, -83, -70, 3, 64, 52, -39, -30, -6, 124, 123, 5, -54, 38, -109, 118, 126, -1, 82, 85, -44, -49, -50, 59, -29, 47, 16, 58, 17, -74, + -67, 28, 42, -33, -73, -86, -43, 119, -8, -104, 2, 44, -102, -93, 70, -35, -103, 101, -101, -89, 43, -84, 9, -127, 22, 39, -3, 19, 98, 108, 110, + 79, 113, -32, -24, -78, -71, 112, 104, -38, -10, 97, -28, -5, 34, -14, -63, -18, -46, -112, 12, -65, -77, -94, -15, 81, 51, -111, -21, -7, 14, -17, + 107, 49, -64, -42, 31, -75, -57, 106, -99, -72, 84, -52, -80, 115, 121, 50, 45, 127, 4, -106, -2, -118, -20, -51, 93, -34, 114, 67, 29, 24, 72, + -13, -115, -128, -61, 78, 66, -41, 61, -100, -76}; + // To remove the need for index wrapping, float the permutation table length + private static final byte[] perm = new byte[512]; + private static final byte[] permMod12 = new byte[512]; + + static { + for (int i = 0; i < 512; i++) { + perm[i] = p[i & 255]; + permMod12[i] = (byte) ((perm[i] & 0xFF) % 12); + } + } + + // Skewing and unskewing factors for 2, 3, and 4 dimensions + private static final float F2 = 0.3660254037844386f; // <- (float) (0.5f * (Math.sqrt(3.0f) - 1.0f)); + private static final float G2 = 0.21132486540518713f; // <- (float) ((3.0f - Math.sqrt(3.0f)) / 6.0f); + private static final float F3 = 1.0f / 3.0f; + private static final float G3 = 1.0f / 6.0f; + private static final float F4 = 0.30901699437494745f; // <- (float) ((Math.sqrt(5.0f) - 1.0f) / 4.0f); + private static final float G4 = 0.1381966011250105f; // <- (float) ((5.0f - Math.sqrt(5.0f)) / 20.0f); + + // This method is a *lot* faster than using (int)Math.floor(x) + private static int fastfloor(float x) { + int xi = (int) x; + return x < xi ? xi - 1 : xi; + } + + private static float dot(Vector3b g, float x, float y) { + return g.x * x + g.y * y; + } + + private static float dot(Vector3b g, float x, float y, float z) { + return g.x * x + g.y * y + g.z * z; + } + + private static float dot(Vector4b g, float x, float y, float z, float w) { + return g.x * x + g.y * y + g.z * z + g.w * w; + } + + /** + * Compute 2D simplex noise for the given input vector (x, y). + *

+ * The result is in the range [-1..+1]. + * + * @param x the x coordinate + * @param y the y coordinate + * @return the noise value (within [-1..+1]) + */ + public static float noise(float x, float y) { + float n0, n1, n2; // Noise contributions from the three corners + // Skew the input space to determine which simplex cell we're in + float s = (x + y) * F2; // Hairy factor for 2D + int i = fastfloor(x + s); + int j = fastfloor(y + s); + float t = (i + j) * G2; + float X0 = i - t; // Unskew the cell origin back to (x,y) space + float Y0 = j - t; + float x0 = x - X0; // The x,y distances from the cell origin + float y0 = y - Y0; + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + float x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + float y1 = y0 - j1 + G2; + float x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords + float y2 = y0 - 1.0f + 2.0f * G2; + // Work out the hashed gradient indices of the three simplex corners + int ii = i & 255; + int jj = j & 255; + int gi0 = permMod12[ii + perm[jj] & 0xFF] & 0xFF; + int gi1 = permMod12[ii + i1 + perm[jj + j1] & 0xFF] & 0xFF; + int gi2 = permMod12[ii + 1 + perm[jj + 1] & 0xFF] & 0xFF; + // Calculate the contribution from the three corners + float t0 = 0.5f - x0 * x0 - y0 * y0; + if (t0 < 0.0f) + n0 = 0.0f; + else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient + } + float t1 = 0.5f - x1 * x1 - y1 * y1; + if (t1 < 0.0f) + n1 = 0.0f; + else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + float t2 = 0.5f - x2 * x2 - y2 * y2; + if (t2 < 0.0f) + n2 = 0.0f; + else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0f * (n0 + n1 + n2); + } + + /** + * Compute 3D simplex noise for the given input vector (x, y, z). + *

+ * The result is in the range [-1..+1]. + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @return the noise value (within [-1..+1]) + */ + public static float noise(float x, float y, float z) { + float n0, n1, n2, n3; // Noise contributions from the four corners + // Skew the input space to determine which simplex cell we're in + float s = (x + y + z) * F3; // Very nice and simple skew factor for 3D + int i = fastfloor(x + s); + int j = fastfloor(y + s); + int k = fastfloor(z + s); + float t = (i + j + k) * G3; + float X0 = i - t; // Unskew the cell origin back to (x,y,z) space + float Y0 = j - t; + float Z0 = k - t; + float x0 = x - X0; // The x,y,z distances from the cell origin + float y0 = y - Y0; + float z0 = z - Z0; + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } // Z X Y order + } else { // x0(x, y, z, w). + *

+ * The result is in the range [-1..+1]. + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param w the w coordinate + * @return the noise value (within [-1..+1]) + */ + public static float noise(float x, float y, float z, float w) { + float n0, n1, n2, n3, n4; // Noise contributions from the five corners + // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in + float s = (x + y + z + w) * F4; // Factor for 4D skewing + int i = fastfloor(x + s); + int j = fastfloor(y + s); + int k = fastfloor(z + s); + int l = fastfloor(w + s); + float t = (i + j + k + l) * G4; // Factor for 4D unskewing + float X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space + float Y0 = j - t; + float Z0 = k - t; + float W0 = l - t; + float x0 = x - X0; // The x,y,z,w distances from the cell origin + float y0 = y - Y0; + float z0 = z - Z0; + float w0 = w - W0; + // For the 4D case, the simplex is a 4D shape I won't even try to describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // Six pair-wise comparisons are performed between each possible pair + // of the four coordinates, and the results are used to rank the numbers. + int rankx = 0; + int ranky = 0; + int rankz = 0; + int rankw = 0; + if (x0 > y0) + rankx++; + else + ranky++; + if (x0 > z0) + rankx++; + else + rankz++; + if (x0 > w0) + rankx++; + else + rankw++; + if (y0 > z0) + ranky++; + else + rankz++; + if (y0 > w0) + ranky++; + else + rankw++; + if (z0 > w0) + rankz++; + else + rankw++; + int i1, j1, k1, l1; // The integer offsets for the second simplex corner + int i2, j2, k2, l2; // The integer offsets for the third simplex corner + int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. + // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0; + j1 = ranky >= 3 ? 1 : 0; + k1 = rankz >= 3 ? 1 : 0; + l1 = rankw >= 3 ? 1 : 0; + // Rank 2 denotes the second largest coordinate. + i2 = rankx >= 2 ? 1 : 0; + j2 = ranky >= 2 ? 1 : 0; + k2 = rankz >= 2 ? 1 : 0; + l2 = rankw >= 2 ? 1 : 0; + // Rank 1 denotes the second smallest coordinate. + i3 = rankx >= 1 ? 1 : 0; + j3 = ranky >= 1 ? 1 : 0; + k3 = rankz >= 1 ? 1 : 0; + l3 = rankw >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to compute that. + float x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords + float y1 = y0 - j1 + G4; + float z1 = z0 - k1 + G4; + float w1 = w0 - l1 + G4; + float x2 = x0 - i2 + 2.0f * G4; // Offsets for third corner in (x,y,z,w) coords + float y2 = y0 - j2 + 2.0f * G4; + float z2 = z0 - k2 + 2.0f * G4; + float w2 = w0 - l2 + 2.0f * G4; + float x3 = x0 - i3 + 3.0f * G4; // Offsets for fourth corner in (x,y,z,w) coords + float y3 = y0 - j3 + 3.0f * G4; + float z3 = z0 - k3 + 3.0f * G4; + float w3 = w0 - l3 + 3.0f * G4; + float x4 = x0 - 1.0f + 4.0f * G4; // Offsets for last corner in (x,y,z,w) coords + float y4 = y0 - 1.0f + 4.0f * G4; + float z4 = z0 - 1.0f + 4.0f * G4; + float w4 = w0 - 1.0f + 4.0f * G4; + // Work out the hashed gradient indices of the five simplex corners + int ii = i & 255; + int jj = j & 255; + int kk = k & 255; + int ll = l & 255; + int gi0 = (perm[ii + perm[jj + perm[kk + perm[ll] & 0xFF] & 0xFF] & 0xFF] & 0xFF) % 32; + int gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1] & 0xFF] & 0xFF] & 0xFF] & 0xFF) % 32; + int gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2] & 0xFF] & 0xFF] & 0xFF] & 0xFF) % 32; + int gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3] & 0xFF] & 0xFF] & 0xFF] & 0xFF) % 32; + int gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1] & 0xFF] & 0xFF] & 0xFF] & 0xFF) % 32; + // Calculate the contribution from the five corners + float t0 = 0.6f - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 < 0.0f) + n0 = 0.0f; + else { + t0 *= t0; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + float t1 = 0.6f - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 < 0.0f) + n1 = 0.0f; + else { + t1 *= t1; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + float t2 = 0.6f - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 < 0.0f) + n2 = 0.0f; + else { + t2 *= t2; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + float t3 = 0.6f - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 < 0.0f) + n3 = 0.0f; + else { + t3 *= t3; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + float t4 = 0.6f - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 < 0.0f) + n4 = 0.0f; + else { + t4 *= t4; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + // Sum up and scale the result to cover the range [-1,1] + return 27.0f * (n0 + n1 + n2 + n3 + n4); + } +} diff --git a/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java b/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java index 16c8f1d..8129418 100644 --- a/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java +++ b/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java @@ -4,8 +4,8 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * License as published by the Free Software Foundation; + * only version 2.1 of the License. */ package freeworld.math; @@ -40,4 +40,16 @@ public Vector3d lerp(Vector3d v, double t) { Maths.fma(v.z() - z, t, z) ); } + + public Vector3d withX(double x) { + return new Vector3d(x, y(), z()); + } + + public Vector3d withY(double y) { + return new Vector3d(x(), y, z()); + } + + public Vector3d withZ(double z) { + return new Vector3d(x(), y(), z); + } }