309 lines
12 KiB
Java
309 lines
12 KiB
Java
/*
|
|
*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2020 TerraForged
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
package com.terraforged.mod.client.gui.preview;
|
|
|
|
import com.mojang.blaze3d.platform.GlStateManager;
|
|
import com.mojang.blaze3d.systems.RenderSystem;
|
|
import com.terraforged.core.cell.Cell;
|
|
import com.terraforged.core.concurrent.cache.CacheEntry;
|
|
import com.terraforged.core.concurrent.thread.ThreadPool;
|
|
import com.terraforged.core.concurrent.thread.ThreadPools;
|
|
import com.terraforged.core.settings.Settings;
|
|
import com.terraforged.core.tile.Size;
|
|
import com.terraforged.core.tile.Tile;
|
|
import com.terraforged.core.tile.gen.TileGenerator;
|
|
import com.terraforged.mod.client.gui.GuiKeys;
|
|
import com.terraforged.mod.util.nbt.NBTHelper;
|
|
import com.terraforged.n2d.util.NoiseUtil;
|
|
import com.terraforged.world.GeneratorContext;
|
|
import com.terraforged.world.continent.MutableVeci;
|
|
import com.terraforged.world.continent.SpawnType;
|
|
import com.terraforged.world.heightmap.Levels;
|
|
import com.terraforged.world.terrain.Terrains;
|
|
import net.minecraft.client.Minecraft;
|
|
import net.minecraft.client.gui.AbstractGui;
|
|
import net.minecraft.client.gui.FontRenderer;
|
|
import net.minecraft.client.gui.widget.button.Button;
|
|
import net.minecraft.client.renderer.texture.DynamicTexture;
|
|
import net.minecraft.client.renderer.texture.NativeImage;
|
|
import net.minecraft.nbt.CompoundNBT;
|
|
|
|
import java.awt.*;
|
|
import java.util.Random;
|
|
|
|
public class Preview extends Button {
|
|
|
|
private static final int FACTOR = 4;
|
|
public static final int WIDTH = Size.chunkToBlock(1 << FACTOR);
|
|
private static final int SLICE_HEIGHT = 64;
|
|
public static final int HEIGHT = WIDTH + SLICE_HEIGHT;
|
|
private static final float[] LEGEND_SCALES = {1, 0.9F, 0.75F, 0.6F};
|
|
|
|
private final int offsetX;
|
|
private final int offsetZ;
|
|
private final ThreadPool threadPool = ThreadPools.createDefault();
|
|
private final Random random = new Random(System.currentTimeMillis());
|
|
private final PreviewSettings previewSettings = new PreviewSettings();
|
|
private final DynamicTexture texture = new DynamicTexture(new NativeImage(WIDTH, HEIGHT, true));
|
|
|
|
private int seed;
|
|
private long lastUpdate = 0L;
|
|
private Settings settings = new Settings();
|
|
private CacheEntry<Tile> task = null;
|
|
private Tile tile = null;
|
|
|
|
private String[] labels = {GuiKeys.PREVIEW_AREA.get(), GuiKeys.PREVIEW_TERRAIN.get(), GuiKeys.PREVIEW_BIOME.get()};
|
|
private String[] values = {"", "", ""};
|
|
|
|
public Preview(int seed) {
|
|
super(0, 0, 0, 0, "", b -> {});
|
|
this.seed = seed == -1 ? random.nextInt() : seed;
|
|
this.offsetX = 0;
|
|
this.offsetZ = 0;
|
|
}
|
|
|
|
public int getSeed() {
|
|
return seed;
|
|
}
|
|
|
|
public void regenerate() {
|
|
this.seed = random.nextInt();
|
|
}
|
|
|
|
public void close() {
|
|
texture.close();
|
|
threadPool.shutdown();
|
|
}
|
|
|
|
@Override
|
|
public void render(int mx, int my, float partialTicks) {
|
|
float scale = width / (float) WIDTH;
|
|
height = width + NoiseUtil.round(SLICE_HEIGHT * scale);
|
|
|
|
preRender();
|
|
|
|
texture.bindTexture();
|
|
RenderSystem.enableBlend();
|
|
RenderSystem.enableRescaleNormal();
|
|
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
|
|
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
|
|
|
|
AbstractGui.blit(x, y, 0, 0, width, height, width, height);
|
|
RenderSystem.disableRescaleNormal();
|
|
|
|
updateLegend(mx, my);
|
|
|
|
renderLegend(labels, values, x, y + width, 10, 0xFFFFFF);
|
|
}
|
|
|
|
public void update(Settings settings, CompoundNBT prevSettings) {
|
|
long time = System.currentTimeMillis();
|
|
if (time - lastUpdate < 50) {
|
|
return;
|
|
}
|
|
lastUpdate = time;
|
|
|
|
NBTHelper.deserialize(prevSettings, previewSettings);
|
|
settings.world.seed = seed;
|
|
|
|
task = generate(settings, prevSettings);
|
|
}
|
|
|
|
private void preRender() {
|
|
if (task != null && task.isDone()) {
|
|
try {
|
|
tile = task.get();
|
|
render(tile);
|
|
} catch (Throwable t) {
|
|
t.printStackTrace();
|
|
} finally {
|
|
task = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void render(Tile tile) {
|
|
NativeImage image = texture.getTextureData();
|
|
if (image == null) {
|
|
return;
|
|
}
|
|
|
|
RenderMode renderer = previewSettings.display;
|
|
Levels levels = new Levels(settings.world);
|
|
|
|
int stroke = 2;
|
|
int width = tile.getBlockSize().size;
|
|
int zoom = (101 - previewSettings.zoom);
|
|
int half = width / 2;
|
|
|
|
int sliceStartY = image.getHeight() - 1 - SLICE_HEIGHT;
|
|
|
|
float zoomUnit = 1F - (zoom / 100F);
|
|
float zoomStrength = 0.5F;
|
|
float unit = (1 - zoomStrength) + (zoomStrength * zoomUnit);
|
|
float heightModifier = settings.world.properties.worldHeight / 256F;
|
|
float waterLevelModifier = settings.world.properties.seaLevel / (float) settings.world.properties.worldHeight;
|
|
float imageWaterLevelY = image.getHeight() - 1 - (waterLevelModifier * SLICE_HEIGHT * unit);
|
|
|
|
tile.iterate((cell, x, z) -> {
|
|
if (x < stroke || z < stroke || x >= width - stroke || z >= width - stroke) {
|
|
image.setPixelRGBA(x, z, Color.BLACK.getRGB());
|
|
} else {
|
|
image.setPixelRGBA(x, z, renderer.getColor(cell, levels));
|
|
}
|
|
|
|
if (z == half) {
|
|
int height = (int) (cell.value * SLICE_HEIGHT * unit * heightModifier);
|
|
float imageSurfaceLevelY = image.getHeight() - 1 - height;
|
|
for (int dy = sliceStartY; dy < image.getHeight(); dy++) {
|
|
if (x < stroke || x >= width - stroke || dy > image.getHeight() - 1 - stroke) {
|
|
image.setPixelRGBA(x, dy, Color.BLACK.getRGB());
|
|
continue;
|
|
}
|
|
if (dy > imageSurfaceLevelY) {
|
|
image.setPixelRGBA(x, dy, Color.BLACK.getRGB());
|
|
} else if (dy > imageWaterLevelY) {
|
|
image.setPixelRGBA(x, dy, Color.GRAY.getRGB());
|
|
} else {
|
|
image.setPixelRGBA(x, dy, Color.WHITE.getRGB());
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
texture.updateDynamicTexture();
|
|
}
|
|
|
|
private CacheEntry<Tile> generate(Settings settings, CompoundNBT prevSettings) {
|
|
NBTHelper.deserialize(prevSettings, previewSettings);
|
|
settings.world.seed = seed;
|
|
this.settings = settings;
|
|
|
|
GeneratorContext context = GeneratorContext.createNoCache(Terrains.create(settings), settings);
|
|
|
|
MutableVeci center = new MutableVeci();
|
|
if (settings.world.properties.spawnType == SpawnType.CONTINENT_CENTER) {
|
|
context.factory.getHeightmap().getContinent().getNearestCenter(offsetX, offsetZ, center);
|
|
}
|
|
|
|
TileGenerator renderer = TileGenerator.builder()
|
|
.pool(threadPool)
|
|
.size(FACTOR, 0)
|
|
.factory(context.factory)
|
|
.batch(6)
|
|
.build();
|
|
|
|
float zoom = 101 - previewSettings.zoom;
|
|
|
|
return renderer.getAsync(center.x, center.z, zoom, false);
|
|
}
|
|
|
|
private void updateLegend(int mx ,int my) {
|
|
if (tile != null) {
|
|
int left = this.x;
|
|
int top = this.y;
|
|
float size = this.width;
|
|
int zoom = (101 - previewSettings.zoom);
|
|
int width = Math.max(1, tile.getBlockSize().size * zoom);
|
|
int height = Math.max(1, tile.getBlockSize().size * zoom);
|
|
values[0] = width + "x" + height;
|
|
if (mx >= left && mx <= left + size && my >= top && my <= top + size) {
|
|
float fx = (mx - left) / size;
|
|
float fz = (my - top) / size;
|
|
int ix = NoiseUtil.round(fx * tile.getBlockSize().size);
|
|
int iz = NoiseUtil.round(fz * tile.getBlockSize().size);
|
|
Cell cell = tile.getCell(ix, iz);
|
|
values[1] = getTerrainName(cell);
|
|
values[2] = getBiomeName(cell);
|
|
}
|
|
}
|
|
}
|
|
|
|
private float getLegendScale() {
|
|
int index = Minecraft.getInstance().gameSettings.guiScale - 1;
|
|
if (index < 0 || index >= LEGEND_SCALES.length) {
|
|
// index=-1 == GuiScale(AUTO) which is the same as GuiScale(4)
|
|
// values above 4 don't exist but who knows what mods might try set it to
|
|
// in both cases use the smallest acceptable scale
|
|
index = LEGEND_SCALES.length - 1;
|
|
}
|
|
return LEGEND_SCALES[index];
|
|
}
|
|
|
|
private void renderLegend(String[] labels, String[] values, int left, int top, int lineHeight, int color) {
|
|
float scale = getLegendScale();
|
|
|
|
RenderSystem.pushMatrix();
|
|
RenderSystem.translatef(left + 3.75F * scale, top - lineHeight * (3.2F * scale), 0);
|
|
RenderSystem.scalef(scale, scale, 1);
|
|
|
|
FontRenderer renderer = Minecraft.getInstance().fontRenderer;
|
|
int spacing = 0;
|
|
for (String s : labels) {
|
|
spacing = Math.max(spacing, renderer.getStringWidth(s));
|
|
}
|
|
|
|
float maxWidth = (width - 4) / scale;
|
|
for (int i = 0; i < labels.length && i < values.length; i++) {
|
|
String label = labels[i];
|
|
String value = values[i];
|
|
|
|
while (value.length() > 0 && spacing + Minecraft.getInstance().fontRenderer.getStringWidth(value) > maxWidth) {
|
|
value = value.substring(0, value.length() - 1);
|
|
}
|
|
|
|
drawString(renderer, label, 0, i * lineHeight, color);
|
|
drawString(renderer, value, spacing, i * lineHeight, color);
|
|
}
|
|
|
|
RenderSystem.popMatrix();
|
|
}
|
|
|
|
private static String getTerrainName(Cell cell) {
|
|
if (cell.terrain.isRiver()) {
|
|
return "river";
|
|
}
|
|
return cell.terrain.getName().toLowerCase();
|
|
}
|
|
|
|
private static String getBiomeName(Cell cell) {
|
|
String terrain = cell.terrain.getName().toLowerCase();
|
|
if (terrain.contains("ocean")) {
|
|
if (cell.temperature < 0.3) {
|
|
return "cold_" + terrain;
|
|
}
|
|
if (cell.temperature > 0.6) {
|
|
return "warm_" + terrain;
|
|
}
|
|
return terrain;
|
|
}
|
|
if (terrain.contains("river")) {
|
|
return "river";
|
|
}
|
|
return cell.biomeType.name().toLowerCase();
|
|
}
|
|
}
|