/******************************************************************************* * Copyright 2015 Maximilian Stark | Dakror <mail@dakror.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package de.dakror.vloxlands.generate; import java.io.ByteArrayOutputStream; import com.badlogic.gdx.math.Bezier; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; import de.dakror.vloxlands.game.query.VoxelPos; import de.dakror.vloxlands.game.query.VoxelStats; import de.dakror.vloxlands.game.voxel.Voxel; import de.dakror.vloxlands.game.world.Chunk; import de.dakror.vloxlands.game.world.Island; import de.dakror.vloxlands.game.world.World; public abstract class Generator { public abstract void generate(WorldGenerator worldGen, Island island); public static Array<Byte> getNaturalTypes() { Array<Byte> naturalVoxels = new Array<Byte>(); naturalVoxels.add(Voxel.get("SANDSTONE").getId()); naturalVoxels.add(Voxel.get("STONE").getId()); naturalVoxels.add(Voxel.get("DIRT").getId()); return naturalVoxels; } public static byte[] createRatio(byte[] keys, int[] vals) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i = 0; i < keys.length; i++) { for (int j = 0; j < vals[i]; j++) { baos.write(keys[i]); } } return baos.toByteArray(); } public static void fillHorizontalCircle(Island island, int x, int y, int z, float radius, byte[] b, boolean force) { int rad = (int) Math.ceil(radius); for (int i = -rad; i <= rad; i++) { for (int j = -rad; j <= rad; j++) { int x1 = x + i; int z1 = z + j; if (Vector2.dst(i, j, 0, 0) < radius) island.set(x1, y, z1, b[((int) (MathUtils.random() * b.length))], force, false); } } } public static void generateBezier(Island island, float[] c, int x, int z, int radius, int off, int h, byte[] b, boolean force) { Vector2 p0 = new Vector2(c[0], c[1]); Vector2 p1 = new Vector2(c[2], c[3]); Vector2 p2 = new Vector2(c[4], c[5]); Vector2 p3 = new Vector2(c[6], c[7]); for (int i = 0; i < h; i++) { float t = i / (float) h; float rad = (float) Math.floor(radius * Bezier.cubic(new Vector2(), t, p0, p1, p2, p3, new Vector2()).y); fillHorizontalCircle(island, x, off - i, z, rad, b, force); } } public static Vector2 getHighestBezierValue(float[] c) { Vector2 p0 = new Vector2(c[0], c[1]); Vector2 p1 = new Vector2(c[2], c[3]); Vector2 p2 = new Vector2(c[4], c[5]); Vector2 p3 = new Vector2(c[6], c[7]); float y = 0; float x = 0; for (float i = 0; i < 1; i += 0.01f) { Vector2 v = Bezier.cubic(new Vector2(), i, p0, p1, p2, p3, new Vector2()); if (v.y > y) { x = i; y = v.y; } } return new Vector2(x, y); } /** * [0 - 1] */ public static float getBezierValue(float[] c, float x) { Vector2 p0 = new Vector2(c[0], c[1]); Vector2 p1 = new Vector2(c[2], c[3]); Vector2 p2 = new Vector2(c[4], c[5]); Vector2 p3 = new Vector2(c[6], c[7]); return Bezier.cubic(new Vector2(), x, p0, p1, p2, p3, new Vector2()).y; } public static Vector2 getRandomCircleInCircle(Vector2 center, int radius, int rad2) { Vector2 v = new Vector2(); do v = new Vector2(Math.round(MathUtils.random() * radius * 2 - radius + center.x), Math.round(MathUtils.random() * radius * 2 - radius + center.y)); while (v.dst(center) > radius - rad2); return v; } public static boolean hasNaturalVoxel(Chunk c) { if (c == null) return false; for (byte b : getNaturalTypes()) if (c.getResource(b) > 0) return true; return false; } public static Vector3 pickRandomNaturalChunk(Island island, int maxY) { int i = 0; int chunks = island.getChunks().length; do i = MathUtils.random(chunks - 1); while (island.getChunk(i) == null || !hasNaturalVoxel(island.getChunk(i)) || (maxY != 0 && island.getChunk(i).pos.y + Chunk.SIZE > maxY)); return island.getChunk(i).index; } public static Vector3 pickRandomNaturalVoxel(Island island, int maxY) { Array<Byte> naturalTypes = getNaturalTypes(); Vector3 c = pickRandomNaturalChunk(island, maxY); Chunk chunk = island.getChunk(c.x, c.y, c.z); Array<Vector3> chunkVoxels = new Array<Vector3>(); for (int i = 0; i < Chunk.SIZE; i++) { for (int j = 0; j < Chunk.SIZE; j++) { for (int k = 0; k < Chunk.SIZE; k++) { if (chunkVoxels.size > 50) break; byte id = chunk.get(i, j, k); if (naturalTypes.contains(id, false)) chunkVoxels.add(new Vector3(i + c.x * Chunk.SIZE, j + c.y * Chunk.SIZE, k + c.z * Chunk.SIZE)); } } } return chunkVoxels.get((int) (MathUtils.random() * chunkVoxels.size)); } public static VoxelStats generateVein(Island island, VoxelStats maximum, float min, float max, int maxY, byte[] b) { int size = (int) MathUtils.random(min, max); Vector3 c = pickRandomNaturalVoxel(island, maxY); VoxelStats vs = new VoxelStats(); float maxDistance = (float) (size * Math.sqrt(3)) / 2; for (int i = (int) (c.x - size * .5f); i < c.x + size * .5f; i++) { for (int j = (int) (c.y - size * .5f); j < c.y + size * .5f; j++) { for (int k = (int) (c.z - size * .5f); k < c.z + size * .5f; k++) { if (MathUtils.random() * maxDistance > Vector3.dst(i, j, k, c.x, c.y, c.z)) { byte bv = b[(int) (MathUtils.random() * b.length)]; Voxel v = Voxel.getForId(bv); if (vs.uplift + v.getUplift() >= maximum.uplift && maximum.weight > 0) return vs; if (vs.weight + v.getWeight() >= maximum.weight && maximum.weight > 0) return vs; vs.uplift += v.getUplift(); vs.weight += v.getWeight(); byte b2 = island.get(i, j, k); if (b2 != Voxel.get("AIR").getId()) vs.uplift += Voxel.getForId(b2).getWeight(); // balances uplift-weight in case of v.getUplift() > 0 island.set(i, j, k, bv, true, false); } } } } return vs; } // -- structures -- // public static void generateTrees(Island island, int min, int max) { int width = MathUtils.random(min, max); int depth = MathUtils.random(min, max); int size = MathUtils.ceil(Island.SIZE / (float) Math.min(width, depth)); for (int i = 0; i < width; i++) { for (int j = 0; j < depth; j++) { int x = i * size + MathUtils.random(-size / 2, size / 2); int z = j * size + MathUtils.random(-size / 2, size / 2); VoxelPos vp = island.getHighestVoxel(x, z); if (vp.y <= 0 || vp.b != Voxel.get("DIRT").getId()) continue; generateTree(island, x, vp.y, z); } } } /** * @param y the block below the tree */ public static void generateTree(Island island, int x, int y, int z) { int height = (int) (MathUtils.random() * 5 + 5); byte wood = Voxel.get("WOOD").getId(); for (int k = 0; k < height; k++) island.set(x, y + 1 + k, z, wood, true, false); generateBezier(island, Beziers.TREE, x, z, 5, (int) (y + 1 + height * 1.5f), (int) (height * 1.4f), new byte[] { Voxel.get("LEAVES").getId() }, false); } public static void generateSpikes(Island island, int amount, int y, int radius, int topLayers, byte[] ratio) { for (int i = 0; i < amount; i++) { int MAXRAD = (int) (radius * 0.3f + 5); int rad = Math.round(MathUtils.random() * (radius * 0.3f)) + 3; Vector2 highest = getHighestBezierValue(Beziers.TOPLAYER); int radiusAt0 = (int) (highest.y * radius); Vector2 m = new Vector2(Island.SIZE / 2, Island.SIZE / 2); Vector2 pos = getRandomCircleInCircle(m, radiusAt0, rad); int h = (int) (0.3f * ((MAXRAD - rad) * (radiusAt0 - pos.cpy().sub(m).len()) + topLayers)); h = Math.min(h, Island.SIZE - topLayers - 10); island.set((int) pos.x, -1 + y, (int) pos.y, Voxel.get("STONE").getId(), true, false); generateBezier(island, Beziers.SPIKE, (int) pos.x, (int) pos.y /* Z */, rad, (int) (y - highest.x * topLayers), h, ratio, false); } } public static void generateBoulders(Island island, int y, int radius, int min, int max, int minRad, int maxRad, int minHeight, int maxHeight, byte[] b) { int boulders = MathUtils.random(min, max); int highest = (int) (getHighestBezierValue(Beziers.BOULDER).y * radius); for (int i = 0; i < boulders; i++) { int rad = MathUtils.random(minRad, maxRad); int height = MathUtils.random(minHeight, maxHeight); Vector2 pos = getRandomCircleInCircle(new Vector2(Island.SIZE / 2, Island.SIZE / 2), highest, rad); generateBezier(island, Beziers.BOULDER, (int) pos.x, (int) pos.y, rad, y + height / 2 + 1, height, b, true); } } public static void generateCrystals(Island island) { final Voxel[] CRYSTALS = { Voxel.get("BLUE_CRYSTAL"), Voxel.get("RED_CRYSTAL"), Voxel.get("YELLOW_CRYSTAL") }; island.calculateWeight(); float weightNeededToUplift = island.getWeight() / World.calculateRelativeUplift(island.pos.y); while (weightNeededToUplift > 100) { int index = (int) (MathUtils.random() * CRYSTALS.length); weightNeededToUplift -= generateVein(island, new VoxelStats(0, weightNeededToUplift), index + 1, index + 4, Island.SIZE / 4 * 3, new byte[] { CRYSTALS[index].getId() }).uplift; } int[] amounts = new int[CRYSTALS.length]; for (int i = 0; i < amounts.length; i++) { amounts[i] = (int) (weightNeededToUplift / CRYSTALS[i].getUplift()); weightNeededToUplift %= CRYSTALS[i].getUplift(); } for (int j = 0; j < amounts.length; j++) { for (int i = 0; i < amounts[j]; i++) { Vector3 v = pickRandomNaturalVoxel(island, Island.SIZE / 4 * 3); island.set((int) v.x, (int) v.y, (int) v.z, CRYSTALS[j].getId(), true, false); } } } public static void generateOreVeins(Island island) { final Voxel[] ORES = { Voxel.get("COAL_ORE"), Voxel.get("IRON_ORE"), Voxel.get("GOLD_ORE") }; final float[][] BEZIERS = { Beziers.COALORE_HEIGHT, Beziers.IRONORE_HEIGHT, Beziers.GOLDORE_HEIGHT }; int height = Island.SIZE / 4 * 3; for (int i = 0; i < ORES.length; i++) { int y = MathUtils.random(8, height); int veins = Math.round(getBezierValue(BEZIERS[i], y / (float) height) * height) * 2; for (int j = 0; j < veins; j++) { int size = MathUtils.random(3); generateVein(island, new VoxelStats(), size, size + 2, height, new byte[] { ORES[i].getId() }); } } } }