diff --git a/src/main/java/ru/windcorp/progressia/common/util/ArrayFloatRangeMap.java b/src/main/java/ru/windcorp/progressia/common/util/ArrayFloatRangeMap.java new file mode 100644 index 0000000..4eea3f6 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/ArrayFloatRangeMap.java @@ -0,0 +1,225 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.common.util; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; + +public class ArrayFloatRangeMap implements FloatRangeMap { + + protected static class Node implements Comparable> { + public float pos; + public E value; + + public Node(float pos, E value) { + this.pos = pos; + this.value = value; + } + + @Override + public int compareTo(Node o) { + return Float.compare(pos, o.pos); + } + } + + /* + * Expects a random-access list + */ + protected final List> nodes; + protected int ranges = 0; + + protected static final int DEFAULT_CAPACITY = 16; + + public ArrayFloatRangeMap(int capacity) { + this.nodes = new ArrayList<>(2 * capacity); + } + + public ArrayFloatRangeMap() { + this(DEFAULT_CAPACITY); + } + + @Override + public int size() { + return this.ranges; + } + + @Override + public Iterator iterator() { + return new Iterator() { + + private int nextIndex = 0; + + { + assert nodes.isEmpty() || nodes.get(nextIndex).value != null; + } + + private void findNext() { + while (nextIndex < nodes.size()) { + nextIndex++; + Node node = nodes.get(nextIndex); + if (node.value != null) return; + } + } + + @Override + public boolean hasNext() { + return nextIndex < nodes.size(); + } + + @Override + public E next() { + E result = nodes.get(nextIndex).value; + findNext(); + return result; + } + }; + } + + /** + * Returns an index of the smallest {@link Node} larger than or exactly at + * {@code position}. + * + * @param position the position to look up + * @return an index in the {@link #nodes} list containing the first + * {@link Node} whose {@link Node#pos} is not smaller than + * {@code position}, or {@code nodes.size()} if no such index exists + */ + protected int findCeiling(float position) { + + /* + * Implementation based on OpenJDK's + * Collections.indexedBinarySearch(List, Comparator) + */ + + int low = 0; + int high = nodes.size() - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + float midVal = nodes.get(mid).pos; + int cmp = Float.compare(midVal, position); + + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + + return low; // the insertion point is the desired index + } + + /** + * Returns an index of the largest {@link Node} smaller than or exactly at + * {@code position}. + * + * @param position the position to look up + * @return an index in the {@link #nodes} list containing the last + * {@link Node} whose {@link Node#pos} is not greater than + * {@code position}, or {@code -1} if no such index exists + */ + protected int findFloor(float position) { + + /* + * Implementation based on OpenJDK's + * Collections.indexedBinarySearch(List, Comparator) + */ + + int low = 0; + int high = nodes.size() - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + float midVal = nodes.get(mid).pos; + int cmp = Float.compare(midVal, position); + + if (cmp < 0) + low = mid + 1; + else if (cmp > 0) + high = mid - 1; + else + return mid; // key found + } + + return low - 1; // the insertion point immediately follows the desired index + } + + protected Node getEffectiveNode(float at) { + int effectiveNodeIndex = findFloor(at); + if (effectiveNodeIndex < 0) return null; + return nodes.get(effectiveNodeIndex); + } + + @Override + public E get(float at) { + Node effectiveNode = getEffectiveNode(at); + return effectiveNode == null ? null : effectiveNode.value; + } + + @Override + public void put(float min, float max, E element) { + Objects.requireNonNull(element, "element"); + + if (!(max > min)) // This funky construction also deals with NaNs since NaNs always fail any comparison + { + throw new IllegalArgumentException(max + " is not greater than " + min); + } + + int indexOfInsertionOfMin = findCeiling(min); + + nodes.add(indexOfInsertionOfMin, new Node(min, element)); + ranges++; + + ListIterator> it = nodes.listIterator(indexOfInsertionOfMin + 1); + E elementEffectiveImmediatelyAfterInsertedRange = null; + + if (indexOfInsertionOfMin > 0) { + elementEffectiveImmediatelyAfterInsertedRange = nodes.get(indexOfInsertionOfMin - 1).value; + } + + while (it.hasNext()) { + Node node = it.next(); + + if (node.pos >= max) { + break; + } + + elementEffectiveImmediatelyAfterInsertedRange = node.value; + if (elementEffectiveImmediatelyAfterInsertedRange != null) { + // Removing an actual range + ranges--; + } + it.remove(); + } + + if (max != Float.POSITIVE_INFINITY) { + nodes.add(indexOfInsertionOfMin + 1, new Node(max, elementEffectiveImmediatelyAfterInsertedRange)); + + if (elementEffectiveImmediatelyAfterInsertedRange != null) { + // We might have added one right back + ranges++; + } + } + + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/FloatRangeMap.java b/src/main/java/ru/windcorp/progressia/common/util/FloatRangeMap.java new file mode 100644 index 0000000..303d6a8 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/common/util/FloatRangeMap.java @@ -0,0 +1,36 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.common.util; + +public interface FloatRangeMap extends Iterable { + + void put(float min, float max, E element); + E get(float at); + + int size(); + + default boolean defines(float position) { + return get(position) != null; + } + + default E getOrDefault(float at, E def) { + E result = get(at); + return result == null ? def : result; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java index 6bef350..f25368f 100644 --- a/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java +++ b/src/main/java/ru/windcorp/progressia/common/util/VectorUtil.java @@ -290,6 +290,88 @@ public class VectorUtil { ); return output; } + + public static Vec3i sort(Vec3i input, Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + int ax = input.x, ay = input.y, az = input.z; + + if (ax > ay) { + if (ax > az) { + output.x = ax; + output.y = ay > az ? ay : az; + output.z = ay > az ? az : ay; + } else { + output.x = az; + output.y = ax; + output.z = ay; + } + } else { + if (ay > az) { + output.x = ay; + output.y = ax > az ? ax : az; + output.z = ax > az ? az : ax; + } else { + output.x = az; + output.y = ay; + output.z = ax; + } + } + + return output; + } + + public static Vec3 sort(Vec3 input, Vec3 output) { + if (output == null) { + output = new Vec3(); + } + + float ax = input.x, ay = input.y, az = input.z; + + if (ax > ay) { + if (ax > az) { + output.x = ax; + output.y = ay > az ? ay : az; + output.z = ay > az ? az : ay; + } else { + output.x = az; + output.y = ax; + output.z = ay; + } + } else { + if (ay > az) { + output.x = ay; + output.y = ax > az ? ax : az; + output.z = ax > az ? az : ax; + } else { + output.x = az; + output.y = ay; + output.z = ax; + } + } + + return output; + } + + public static Vec3i sortAfterAbs(Vec3i input, Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + input.abs(output); + return sort(output, output); + } + + public static Vec3 sortAfterAbs(Vec3 input, Vec3 output) { + if (output == null) { + output = new Vec3(); + } + + input.abs(output); + return sort(output, output); + } public static float get(Vec2 v, Axis a) { switch (a) { diff --git a/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java index 9f35d4c..d7dffc1 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java +++ b/src/main/java/ru/windcorp/progressia/common/world/BlockRay.java @@ -90,11 +90,7 @@ public class BlockRay { VectorUtil.set(block, axis, VectorUtil.get(block, axis) + (int) signum(VectorUtil.get(direction, axis))); // position += direction * tMin - VectorUtil.linearCombination(position, 1, direction, tMin, position); // position - // += - // direction - // * - // tMin + VectorUtil.linearCombination(position, 1, direction, tMin, position); distance += tMin; // position.(axis) = round(position.(axis)) diff --git a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java index 95c18be..fa80269 100644 --- a/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java +++ b/src/main/java/ru/windcorp/progressia/common/world/generic/GenericChunk.java @@ -20,6 +20,7 @@ package ru.windcorp.progressia.common.world.generic; import java.util.function.Consumer; +import glm.Glm; import glm.vec._3.i.Vec3i; import ru.windcorp.progressia.common.util.VectorUtil; import ru.windcorp.progressia.common.util.Vectors; @@ -118,6 +119,64 @@ public interface GenericChunk, B exten default int getMaxZ() { return Coordinates.getInWorld(getZ(), BLOCKS_PER_CHUNK - 1); } + + default Vec3i getMinBIW(Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + output.set(getMinX(), getMinY(), getMinZ()); + + return output; + } + + default Vec3i getMaxBIW(Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + output.set(getMaxX(), getMaxY(), getMaxZ()); + + return output; + } + + default Vec3i getMinBIWRel(Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + Vec3i absMin = getMinBIW(Vectors.grab3i()); + Vec3i absMax = getMaxBIW(Vectors.grab3i()); + + AxisRotations.relativize(absMin, getUp(), absMin); + AxisRotations.relativize(absMax, getUp(), absMax); + + Glm.min(absMin, absMax, output); + + Vectors.release(absMax); + Vectors.release(absMin); + + return output; + } + + default Vec3i getMaxBIWRel(Vec3i output) { + if (output == null) { + output = new Vec3i(); + } + + Vec3i absMin = getMinBIW(Vectors.grab3i()); + Vec3i absMax = getMaxBIW(Vectors.grab3i()); + + AxisRotations.relativize(absMin, getUp(), absMin); + AxisRotations.relativize(absMax, getUp(), absMax); + + Glm.max(absMin, absMax, output); + + Vectors.release(absMax); + Vectors.release(absMin); + + return output; + } default boolean containsBiC(Vec3i blockInChunk) { return blockInChunk.x >= 0 && blockInChunk.x < BLOCKS_PER_CHUNK && diff --git a/src/main/java/ru/windcorp/progressia/server/ChunkManager.java b/src/main/java/ru/windcorp/progressia/server/ChunkManager.java index b0b0777..ede6d70 100644 --- a/src/main/java/ru/windcorp/progressia/server/ChunkManager.java +++ b/src/main/java/ru/windcorp/progressia/server/ChunkManager.java @@ -128,7 +128,7 @@ public class ChunkManager { private void processQueues() { toUnload.forEach(this::unloadChunk); toUnload.clear(); - toLoad.forEach(this::loadChunk); + toLoad.forEach(this::loadOrGenerateChunk); toLoad.clear(); visions.forEach((p, v) -> { @@ -140,15 +140,26 @@ public class ChunkManager { return createIfMissing ? visions.computeIfAbsent(player, k -> new PlayerVision()) : visions.get(player); } - public void loadChunk(Vec3i chunkPos) { + public void loadOrGenerateChunk(Vec3i chunkPos) { + + boolean chunkLoadedFromDisk = loadChunk(chunkPos); + + if (!chunkLoadedFromDisk) { + getServer().getWorld().generate(chunkPos); + } + + } + + public boolean loadChunk(Vec3i chunkPos) { WorldData world = getServer().getWorld().getData(); ChunkData chunk = TestWorldDiskIO.tryToLoad(chunkPos, world, getServer()); if (chunk != null) { world.addChunk(chunk); + return true; } else { - getServer().getWorld().generate(chunkPos); + return false; } } diff --git a/src/main/java/ru/windcorp/progressia/server/Server.java b/src/main/java/ru/windcorp/progressia/server/Server.java index fcdee16..ceafa47 100644 --- a/src/main/java/ru/windcorp/progressia/server/Server.java +++ b/src/main/java/ru/windcorp/progressia/server/Server.java @@ -31,6 +31,7 @@ import ru.windcorp.progressia.server.world.WorldLogic; import ru.windcorp.progressia.server.world.tasks.WorldAccessor; import ru.windcorp.progressia.server.world.ticking.Change; import ru.windcorp.progressia.server.world.ticking.Evaluation; +import ru.windcorp.progressia.test.gen.planet.Planet; import ru.windcorp.progressia.test.gen.planet.TestPlanetGenerator; public class Server { @@ -60,7 +61,7 @@ public class Server { private final TickingSettings tickingSettings = new TickingSettings(); public Server(WorldData world) { - this.world = new WorldLogic(world, this, w -> new TestPlanetGenerator("Test:PlanetGenerator", Units.get("48 m"), w)); + this.world = new WorldLogic(world, this, w -> new TestPlanetGenerator("Test:PlanetGenerator", new Planet(4, 16f, 9.8f, 16f), w)); this.serverThread = new ServerThread(this); this.clientManager = new ClientManager(this); diff --git a/src/main/java/ru/windcorp/progressia/test/gen/TerrainLayer.java b/src/main/java/ru/windcorp/progressia/test/gen/TerrainLayer.java new file mode 100644 index 0000000..74e3335 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/TerrainLayer.java @@ -0,0 +1,30 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen; + +import java.util.Random; + +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.block.BlockData; + +@FunctionalInterface +public interface TerrainLayer { + + BlockData get(float north, float west, float depth, Random random, ChunkData chunk); + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/planet/Planet.java b/src/main/java/ru/windcorp/progressia/test/gen/planet/Planet.java new file mode 100644 index 0000000..795c85f --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/planet/Planet.java @@ -0,0 +1,82 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen.planet; + +import ru.windcorp.progressia.common.world.ChunkData; + +public class Planet { + + private final int radiusInChunks; + + private final float curvature; + private final float surfaceGravitationalAcceleration; + private final float innerGravityRadius; + + public Planet( + int radiusInChunks, + float curvature, + float surfaceGravitationalAcceleration, + float innerGravityRadius + ) { + this.radiusInChunks = radiusInChunks; + this.curvature = curvature; + this.surfaceGravitationalAcceleration = surfaceGravitationalAcceleration; + this.innerGravityRadius = innerGravityRadius; + } + + /** + * @return the radiusInChunks + */ + public int getRadiusInChunks() { + return radiusInChunks; + } + + public float getRadius() { + return radiusInChunks * ChunkData.BLOCKS_PER_CHUNK + ChunkData.CHUNK_RADIUS; + } + + public int getDiameterInChunks() { + return radiusInChunks * 2 + 1; + } + + public float getDiameter() { + return getDiameterInChunks() * ChunkData.BLOCKS_PER_CHUNK; + } + + /** + * @return the curvature + */ + public float getCurvature() { + return curvature; + } + + /** + * @return the innerGravityRadius + */ + public float getInnerGravityRadius() { + return innerGravityRadius; + } + + /** + * @return the surfaceGravitationalAcceleration + */ + public float getSurfaceGravitationalAcceleration() { + return surfaceGravitationalAcceleration; + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/planet/PlanetScatterGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/planet/PlanetScatterGenerator.java new file mode 100644 index 0000000..da0f8d3 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/planet/PlanetScatterGenerator.java @@ -0,0 +1,41 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen.planet; + +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.test.gen.surface.SurfaceScatterGenerator; + +public class PlanetScatterGenerator { + + private final TestPlanetGenerator parent; + private final SurfaceScatterGenerator surfaceGenerator; + + public PlanetScatterGenerator(TestPlanetGenerator generator) { + this.parent = generator; + this.surfaceGenerator = new SurfaceScatterGenerator(); + } + + public TestPlanetGenerator getGenerator() { + return parent; + } + + public void generateScatter(ChunkData chunk) { + surfaceGenerator.generateScatter(chunk); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/planet/PlanetTerrainGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/planet/PlanetTerrainGenerator.java new file mode 100644 index 0000000..d59984a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/planet/PlanetTerrainGenerator.java @@ -0,0 +1,107 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen.planet; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.ArrayFloatRangeMap; +import ru.windcorp.progressia.common.util.FloatRangeMap; +import ru.windcorp.progressia.common.util.VectorUtil; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.Coordinates; +import ru.windcorp.progressia.common.world.WorldData; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.block.BlockDataRegistry; +import ru.windcorp.progressia.test.gen.TerrainLayer; +import ru.windcorp.progressia.test.gen.surface.SurfaceFloatField; +import ru.windcorp.progressia.test.gen.surface.SurfaceTerrainGenerator; + +class PlanetTerrainGenerator { + + private final TestPlanetGenerator parent; + private final SurfaceTerrainGenerator surfaceGenerator; + + public PlanetTerrainGenerator(TestPlanetGenerator generator) { + this.parent = generator; + + SurfaceFloatField heightMap = new TestHeightMap( + generator.getPlanet().getRadius() - ChunkData.BLOCKS_PER_CHUNK, + generator.getPlanet().getRadius() / 4, + 5, + 6 + ); + + FloatRangeMap layers = new ArrayFloatRangeMap<>(); + BlockData granite = BlockDataRegistry.getInstance().get("Test:GraniteMonolith"); + BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); + layers.put(Float.NEGATIVE_INFINITY, 0, (n, w, d, r, c) -> air); + layers.put(0, Float.POSITIVE_INFINITY, (n, w, d, r, c) -> granite); + + this.surfaceGenerator = new SurfaceTerrainGenerator((f, n, w) -> heightMap.get(f, n, w) + generator.getPlanet().getRadius(), layers); + } + + public TestPlanetGenerator getGenerator() { + return parent; + } + + public ChunkData generateTerrain(Vec3i chunkPos, WorldData world) { + ChunkData chunk = new ChunkData(chunkPos, world); + + if (isOrdinaryChunk(chunkPos)) { + generateOrdinaryTerrain(chunk); + } else { + generateBorderTerrain(chunk); + } + + return chunk; + } + + private boolean isOrdinaryChunk(Vec3i chunkPos) { + Vec3i sorted = VectorUtil.sortAfterAbs(chunkPos, null); + return sorted.x != sorted.y; + } + + private void generateOrdinaryTerrain(ChunkData chunk) { + surfaceGenerator.generateTerrain(chunk); + } + + private void generateBorderTerrain(ChunkData chunk) { + BlockData stone = BlockDataRegistry.getInstance().get("Test:Stone"); + BlockData air = BlockDataRegistry.getInstance().get("Test:Air"); + + float radius = parent.getPlanet().getRadius(); + + Vec3 biw = new Vec3(); + + chunk.forEachBiC(bic -> { + + biw.set( + Coordinates.getInWorld(chunk.getX(), bic.x), + Coordinates.getInWorld(chunk.getY(), bic.y), + Coordinates.getInWorld(chunk.getZ(), bic.z) + ); + + biw.sub(ChunkData.CHUNK_RADIUS - 0.5f); + VectorUtil.sortAfterAbs(biw, biw); + + chunk.setBlock(bic, biw.x <= radius ? stone : air, false); + + }); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/planet/TestHeightMap.java b/src/main/java/ru/windcorp/progressia/test/gen/planet/TestHeightMap.java new file mode 100644 index 0000000..2141c63 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/planet/TestHeightMap.java @@ -0,0 +1,70 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen.planet; + +import ru.windcorp.progressia.common.world.rels.AbsFace; +import ru.windcorp.progressia.test.gen.surface.SurfaceFloatField; + +public class TestHeightMap implements SurfaceFloatField { + + private final float cutoffPoint; + private final float cutoffDistance; + private final float amplitude; + private final float characteristicSize; + + public TestHeightMap( + float cutoffPoint, + float cutoffDistance, + float amplitude, + float characteristicSize + ) { + this.cutoffPoint = cutoffPoint; + this.cutoffDistance = cutoffDistance; + this.amplitude = amplitude; + this.characteristicSize = characteristicSize; + } + + @Override + public float get(AbsFace face, float north, float west) { + double cutoffCoefficient = 1; + cutoffCoefficient *= cutoffFunction(cutoffPoint - north); + cutoffCoefficient *= cutoffFunction(cutoffPoint + north); + cutoffCoefficient *= cutoffFunction(cutoffPoint - west); + cutoffCoefficient *= cutoffFunction(cutoffPoint + west); + + if (cutoffCoefficient == 0) { + return 0; + } + + double base = Math.sin(north / characteristicSize) * Math.sin(west / characteristicSize); + base *= amplitude; + + return (float) (base * cutoffCoefficient); + } + + private double cutoffFunction(float distanceToCutoffPoint) { + if (distanceToCutoffPoint < 0) { + return 0; + } else if (distanceToCutoffPoint < cutoffDistance) { + return (1 - Math.cos(Math.PI * distanceToCutoffPoint / cutoffDistance)) / 2; + } else { + return 1; + } + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/planet/TestPlanetGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/planet/TestPlanetGenerator.java index ea45d1e..b21d985 100644 --- a/src/main/java/ru/windcorp/progressia/test/gen/planet/TestPlanetGenerator.java +++ b/src/main/java/ru/windcorp/progressia/test/gen/planet/TestPlanetGenerator.java @@ -20,38 +20,39 @@ package ru.windcorp.progressia.test.gen.planet; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.util.Arrays; - import glm.vec._3.Vec3; import glm.vec._3.i.Vec3i; -import ru.windcorp.progressia.common.util.VectorUtil; import ru.windcorp.progressia.common.world.ChunkData; import ru.windcorp.progressia.common.world.DecodingException; import ru.windcorp.progressia.common.world.WorldData; -import ru.windcorp.progressia.common.world.block.BlockData; -import ru.windcorp.progressia.common.world.block.BlockDataRegistry; -import ru.windcorp.progressia.common.world.rels.RelFace; -import ru.windcorp.progressia.common.world.tile.TileData; -import ru.windcorp.progressia.common.world.tile.TileDataRegistry; import ru.windcorp.progressia.server.world.WorldLogic; import ru.windcorp.progressia.server.world.generation.AbstractWorldGenerator; public class TestPlanetGenerator extends AbstractWorldGenerator { - private final int surfaceLevel; + private final Planet planet; + + private final PlanetTerrainGenerator terrainGenerator; + private final PlanetScatterGenerator scatterGenerator; - public TestPlanetGenerator(String id, float planetRadius, WorldLogic world) { + public TestPlanetGenerator(String id, Planet planet, WorldLogic world) { super(id, Boolean.class, "Test:PlanetGravityModel"); + this.planet = planet; - this.surfaceLevel = (int) (planetRadius / ChunkData.BLOCKS_PER_CHUNK); - if (surfaceLevel < 2) { - throw new IllegalArgumentException("planetRadius too small, must be at least 32 m"); - } + this.terrainGenerator = new PlanetTerrainGenerator(this); + this.scatterGenerator = new PlanetScatterGenerator(this); + } + + /** + * @return the planet + */ + public Planet getPlanet() { + return planet; } @Override public Vec3 suggestSpawnLocation() { - return new Vec3(0, 0, 66); + return new Vec3(7f, 7f, getPlanet().getRadius() + 10); } @Override @@ -71,116 +72,9 @@ public class TestPlanetGenerator extends AbstractWorldGenerator { @Override public ChunkData generate(Vec3i chunkPos, WorldData world) { - ChunkData chunk = new ChunkData(chunkPos, world); - - generate(chunk); - chunk.setGenerationHint(true); - + ChunkData chunk = terrainGenerator.generateTerrain(chunkPos, world); + scatterGenerator.generateScatter(chunk); return chunk; } - - private enum ChunkType { - SURFACE, UNDERGROUND, EDGE_SURFACE, EDGE_UNDERGROUND, CORE, AIR; - } - - private void generate(ChunkData chunk) { - switch (getChunkType(chunk.getPosition())) { - case SURFACE: - fillSurface(chunk); - break; - case UNDERGROUND: - fillUndeground(chunk); - break; - case EDGE_SURFACE: - fillEdgeSurface(chunk); - break; - case EDGE_UNDERGROUND: - fillEdgeUnderground(chunk); - break; - case CORE: - fillCore(chunk); - break; - case AIR: - fillAir(chunk); - break; - } - } - - private void fillSurface(ChunkData chunk) { - final int bpc = ChunkData.BLOCKS_PER_CHUNK; - - BlockData dirt = BlockDataRegistry.getInstance().get("Test:Dirt"); - BlockData granite = BlockDataRegistry.getInstance().get("Test:GraniteMonolith"); - - TileData grass = TileDataRegistry.getInstance().get("Test:Grass"); - - chunk.forEachBiC(bic -> { - - BlockData block; - - if (bic.z > bpc - 4) { - block = dirt; - } else { - block = granite; - } - - chunk.setBlockRel(bic, block, false); - - }); - - VectorUtil.iterateCuboid(0, 0, bpc - 1, bpc, bpc, bpc, bic -> { - chunk.getTilesRel(bic, RelFace.UP).add(grass); - }); - } - - private void fillUndeground(ChunkData chunk) { - fill(chunk, BlockDataRegistry.getInstance().get("Test:GraniteMonolith")); - } - - private void fillEdgeSurface(ChunkData chunk) { - fill(chunk, BlockDataRegistry.getInstance().get("Test:Stone")); - } - - private void fillEdgeUnderground(ChunkData chunk) { - fill(chunk, BlockDataRegistry.getInstance().get("Test:Stone")); - } - - private void fillCore(ChunkData chunk) { - fill(chunk, BlockDataRegistry.getInstance().get("Test:Stone")); - } - - private void fillAir(ChunkData chunk) { - fill(chunk, BlockDataRegistry.getInstance().get("Test:Air")); - } - - private void fill(ChunkData chunk, BlockData block) { - chunk.forEachBiC(bic -> chunk.setBlock(bic, block, false)); - } - - private ChunkType getChunkType(Vec3i pos) { - int[] abs = pos.abs_().toIA_(); - Arrays.sort(abs); - - int medium = abs[1]; - int largest = abs[2]; - - int level = largest; - - if (level == 0) { - return ChunkType.CORE; - } - - if (largest > surfaceLevel) { - return ChunkType.AIR; - } - - boolean isSurface = largest == surfaceLevel; - - if (medium == largest) { - return isSurface ? ChunkType.EDGE_SURFACE : ChunkType.EDGE_UNDERGROUND; - } else { - return isSurface ? ChunkType.SURFACE : ChunkType.UNDERGROUND; - } - } } diff --git a/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceFloatField.java b/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceFloatField.java new file mode 100644 index 0000000..4b368d4 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceFloatField.java @@ -0,0 +1,27 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen.surface; + +import ru.windcorp.progressia.common.world.rels.AbsFace; + +@FunctionalInterface +public interface SurfaceFloatField { + + float get(AbsFace face, float north, float west); + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceScatterGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceScatterGenerator.java new file mode 100644 index 0000000..a9f08d9 --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceScatterGenerator.java @@ -0,0 +1,28 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen.surface; + +import ru.windcorp.progressia.common.world.ChunkData; + +public class SurfaceScatterGenerator { + + public void generateScatter(ChunkData chunk) { + chunk.setGenerationHint(true); + } + +} diff --git a/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceTerrainGenerator.java b/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceTerrainGenerator.java new file mode 100644 index 0000000..b05df5a --- /dev/null +++ b/src/main/java/ru/windcorp/progressia/test/gen/surface/SurfaceTerrainGenerator.java @@ -0,0 +1,74 @@ +/* + * Progressia + * Copyright (C) 2020-2021 Wind Corporation and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ru.windcorp.progressia.test.gen.surface; + +import java.util.Random; + +import glm.vec._3.Vec3; +import glm.vec._3.i.Vec3i; +import ru.windcorp.progressia.common.util.CoordinatePacker; +import ru.windcorp.progressia.common.util.FloatRangeMap; +import ru.windcorp.progressia.common.world.ChunkData; +import ru.windcorp.progressia.common.world.block.BlockData; +import ru.windcorp.progressia.common.world.rels.AxisRotations; +import ru.windcorp.progressia.test.gen.TerrainLayer; + +public class SurfaceTerrainGenerator { + + private final SurfaceFloatField heightMap; + private final FloatRangeMap layers; + + public SurfaceTerrainGenerator(SurfaceFloatField heightMap, FloatRangeMap layers) { + this.heightMap = heightMap; + this.layers = layers; + } + + public void generateTerrain(ChunkData chunk) { + + Vec3i relBIC = new Vec3i(); + + Vec3 offset = new Vec3(chunk.getMinX(), chunk.getMinY(), chunk.getMinZ()); + AxisRotations.relativize(offset, chunk.getUp(), offset); + offset.sub(ChunkData.CHUNK_RADIUS - 0.5f); + + Random random = new Random(CoordinatePacker.pack3IntsIntoLong(chunk.getPosition()) /* ^ seed*/); + + for (relBIC.x = 0; relBIC.x < ChunkData.BLOCKS_PER_CHUNK; ++relBIC.x) { + for (relBIC.y = 0; relBIC.y < ChunkData.BLOCKS_PER_CHUNK; ++relBIC.y) { + generateColumn(chunk, relBIC, offset, random); + } + } + + } + + public void generateColumn(ChunkData chunk, Vec3i relBIC, Vec3 offset, Random random) { + + float north = relBIC.x + offset.x; + float west = relBIC.y + offset.y; + + float relSurface = heightMap.get(chunk.getUp(), north, west) - offset.z; + + for (relBIC.z = 0; relBIC.z < ChunkData.BLOCKS_PER_CHUNK; ++relBIC.z) { + float depth = relSurface - relBIC.z; + BlockData block = layers.get(depth).get(north, west, depth, random, chunk); + chunk.setBlockRel(relBIC, block, false); + } + + } + +}