diff --git a/src/generated/resources/data/rocketnautics/universe_planets/gas_giant.json b/src/generated/resources/data/rocketnautics/universe_planets/gas_giant.json index 31e54fd..64cceb8 100644 --- a/src/generated/resources/data/rocketnautics/universe_planets/gas_giant.json +++ b/src/generated/resources/data/rocketnautics/universe_planets/gas_giant.json @@ -5,6 +5,10 @@ "planet_extras": { "light_source_name": "sol" }, + "planet_texture": { + "type": "resloc", + "texture": "rocketnautics:textures/planet/gas_giant.png" + }, "position": { "type": "circular_orbit_period", "orbit_axis": { @@ -24,7 +28,5 @@ "y": -1.0, "z": 0.0 } - }, - "texture_override": "rocketnautics:textures/planet/gas_giant.png", - "use_texture_override": true + } } \ No newline at end of file diff --git a/src/generated/resources/data/rocketnautics/universe_planets/ice_world.json b/src/generated/resources/data/rocketnautics/universe_planets/ice_world.json index ca3af90..9e775c1 100644 --- a/src/generated/resources/data/rocketnautics/universe_planets/ice_world.json +++ b/src/generated/resources/data/rocketnautics/universe_planets/ice_world.json @@ -5,6 +5,10 @@ "planet_extras": { "light_source_name": "sol" }, + "planet_texture": { + "type": "resloc", + "texture": "rocketnautics:textures/planet/ice_planet.png" + }, "position": { "type": "circular_orbit_period", "orbit_axis": { @@ -24,7 +28,5 @@ "y": -1.0, "z": 0.0 } - }, - "texture_override": "rocketnautics:textures/planet/ice_planet.png", - "use_texture_override": true + } } \ No newline at end of file diff --git a/src/generated/resources/data/rocketnautics/universe_planets/mars.json b/src/generated/resources/data/rocketnautics/universe_planets/mars.json index 758d2e3..17b96e1 100644 --- a/src/generated/resources/data/rocketnautics/universe_planets/mars.json +++ b/src/generated/resources/data/rocketnautics/universe_planets/mars.json @@ -5,6 +5,10 @@ "planet_extras": { "light_source_name": "sol" }, + "planet_texture": { + "type": "resloc", + "texture": "rocketnautics:textures/planet/mars.png" + }, "position": { "type": "circular_orbit_period", "orbit_axis": { @@ -24,7 +28,5 @@ "y": -1.0, "z": 0.0 } - }, - "texture_override": "rocketnautics:textures/planet/mars.png", - "use_texture_override": true + } } \ No newline at end of file diff --git a/src/generated/resources/data/rocketnautics/universe_planets/moon.json b/src/generated/resources/data/rocketnautics/universe_planets/moon.json index cb52b13..66afffc 100644 --- a/src/generated/resources/data/rocketnautics/universe_planets/moon.json +++ b/src/generated/resources/data/rocketnautics/universe_planets/moon.json @@ -31,6 +31,42 @@ "planet_extras": { "light_source_name": "sol" }, + "planet_texture": { + "type": "biome_sampler", + "colors": [ + { + "blue": 70, + "green": 150, + "matching_tags": [ + "rocketnautics:lunar_chasm" + ], + "priority": 10, + "red": 220 + }, + { + "blue": 190, + "green": 190, + "matching_tags": [ + "rocketnautics:lunar_highlands" + ], + "red": 190 + }, + { + "blue": 90, + "flags": [ + "ocean" + ], + "green": 90, + "matching_tags": [ + "rocketnautics:lunar_maria" + ], + "red": 90 + } + ], + "fallback_blue": 160, + "fallback_green": 160, + "fallback_red": 160 + }, "position": { "type": "circular_orbit_period", "orbit_axis": { diff --git a/src/generated/resources/data/rocketnautics/universe_planets/overworld.json b/src/generated/resources/data/rocketnautics/universe_planets/overworld.json index 8709a8c..ff47f7e 100644 --- a/src/generated/resources/data/rocketnautics/universe_planets/overworld.json +++ b/src/generated/resources/data/rocketnautics/universe_planets/overworld.json @@ -30,6 +30,142 @@ "has_clouds": true, "light_source_name": "sol" }, + "planet_texture": { + "type": "biome_sampler", + "colors": [ + { + "blue": 40, + "green": 90, + "matching_tags": [ + "minecraft:is_badlands" + ], + "priority": 10, + "red": 195 + }, + { + "blue": 25, + "green": 85, + "matching_tags": [ + "minecraft:is_jungle" + ], + "priority": 1, + "red": 15 + }, + { + "blue": 135, + "flags": [ + "ocean" + ], + "green": 45, + "matching_tags": [ + "minecraft:is_ocean", + "minecraft:is_deep_ocean" + ], + "red": 15 + }, + { + "blue": 215, + "green": 95, + "matching_tags": [ + "minecraft:is_river" + ], + "red": 25 + }, + { + "blue": 155, + "green": 205, + "matching_tags": [ + "minecraft:is_beach" + ], + "red": 225 + }, + { + "blue": 115, + "green": 195, + "matching_tags": [ + "c:is_desert" + ], + "red": 215 + }, + { + "blue": 55, + "green": 145, + "matching_tags": [ + "c:is_plains" + ], + "red": 45 + }, + { + "blue": 35, + "green": 105, + "matching_tags": [ + "minecraft:is_forest" + ], + "red": 25 + }, + { + "blue": 55, + "green": 75, + "matching_tags": [ + "minecraft:is_taiga" + ], + "red": 30 + }, + { + "blue": 70, + "green": 140, + "matching_tags": [ + "minecraft:is_savanna" + ], + "red": 160 + }, + { + "blue": 40, + "green": 70, + "matching_tags": [ + "c:is_swamp" + ], + "red": 50 + }, + { + "blue": 80, + "green": 100, + "matching_tags": [ + "c:is_windswept" + ], + "red": 80 + }, + { + "blue": 100, + "green": 90, + "matching_tags": [ + "c:is_mushroom" + ], + "red": 100 + }, + { + "blue": 135, + "green": 135, + "matching_tags": [ + "minecraft:is_mountain", + "c:is_stony_shores" + ], + "red": 135 + }, + { + "blue": 245, + "green": 240, + "matching_tags": [ + "c:is_snowy" + ], + "priority": 10, + "red": 240 + } + ], + "fallback_blue": 40, + "fallback_green": 120, + "fallback_red": 30 + }, "position": { "type": "circular_orbit_period", "orbit_axis": { diff --git a/src/generated/resources/data/rocketnautics/universe_planets/sol.json b/src/generated/resources/data/rocketnautics/universe_planets/sol.json index 8bf689a..b1456fd 100644 --- a/src/generated/resources/data/rocketnautics/universe_planets/sol.json +++ b/src/generated/resources/data/rocketnautics/universe_planets/sol.json @@ -5,6 +5,10 @@ "planet_extras": { "is_star": true }, + "planet_texture": { + "type": "resloc", + "texture": "rocketnautics:textures/planet/sol.png" + }, "position": { "type": "fixed", "position": { @@ -23,7 +27,5 @@ "y": 1.0, "z": 0.0 } - }, - "texture_override": "rocketnautics:textures/planet/sol.png", - "use_texture_override": true + } } \ No newline at end of file diff --git a/src/main/java/dev/devce/rocketnautics/SkyDataHandler.java b/src/main/java/dev/devce/rocketnautics/SkyDataHandler.java index c58acd5..93022f7 100644 --- a/src/main/java/dev/devce/rocketnautics/SkyDataHandler.java +++ b/src/main/java/dev/devce/rocketnautics/SkyDataHandler.java @@ -2,18 +2,24 @@ package dev.devce.rocketnautics; -import dev.devce.rocketnautics.client.PlanetColors; -import dev.devce.rocketnautics.content.physics.SpaceTransitionHandler; +import dev.devce.rocketnautics.api.orbit.ColorPalette; +import dev.devce.rocketnautics.content.orbit.DeepSpaceData; +import dev.devce.rocketnautics.content.orbit.universe.CubePlanet; +import dev.devce.rocketnautics.content.orbit.universe.DeepSpaceTextureDefinition; import dev.devce.rocketnautics.network.PlanetMapPayload; import it.unimi.dsi.fastutil.doubles.DoubleObjectPair; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import net.minecraft.core.Holder; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; -import net.minecraft.tags.BiomeTags; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.Climate; +import net.minecraft.world.level.border.BorderChangeListener; +import net.minecraft.world.level.border.WorldBorder; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; @@ -34,11 +40,15 @@ public class SkyDataHandler { public final ServerLevel level; protected final RecursiveDataSquare root; + protected final DeepSpaceDataSquare deepSpace; + protected final DeepSpaceTextureDefinition.BiomeSampleDriven mapper; - public SkyDataHandler(ServerLevel level) { + public SkyDataHandler(ServerLevel level, DeepSpaceTextureDefinition.BiomeSampleDriven mapper) { this.level = level; - this.root = new RecursiveDataSquare(null, MAX_POWER_SIZE + 1, -MAX_TRUE_SIZE, -MAX_TRUE_SIZE); + this.mapper = mapper; + this.deepSpace = new DeepSpaceDataSquare(level.getWorldBorder()); + level.getWorldBorder().addListener(deepSpace); } @@ -53,20 +63,17 @@ public static SkyDataHandler getHandlerForLevel(ServerLevel level) { if (OVERRIDES.containsKey(level.dimension())) { level = level.getServer().getLevel(OVERRIDES.get(level.dimension()).right()); } - return HANDLERS.computeIfAbsent(level, SkyDataHandler::new); + return HANDLERS.computeIfAbsent(level, l -> { + DeepSpaceData d = DeepSpaceData.getInstance(l.getServer()); + CubePlanet associated = d.getUniverse().getPlanetByDimension(l.dimension()); + DeepSpaceTextureDefinition.BiomeSampleDriven mapper = associated == null ? null : (associated.textureDefinition() instanceof DeepSpaceTextureDefinition.BiomeSampleDriven sample ? sample : null); + return new SkyDataHandler(l, mapper); + }); } - public byte[] getRenderDataForDeepSpace(int powerSizeClamp) { - // we can render the root at this point - DataSquare square = root; - while (powerSizeClamp < square.powerSize) { - if (square instanceof RecursiveDataSquare r) { - square = r.getChildAtTruePosition(0, 0); - } else { - break; - } - } - return square.getRenderData(); + public ColorPalette getRenderDataForDeepSpace(int powerSizeClamp) { + // TODO respect power size clamp + return deepSpace.getRenderData(); } public PlanetMapPayload getRenderDataAtScaleAndPosition(int powerSize, int trueX, int trueZ) { @@ -209,7 +216,7 @@ protected class DataSquare { public final int trueNegXCorner; public final int trueNegZCorner; - protected byte[] renderData; + protected ColorPalette renderData; public DataSquare(@Nullable RecursiveDataSquare parent, int powerSize, int trueNegXCorner, int trueNegZCorner) { this.parent = parent; @@ -218,7 +225,7 @@ public DataSquare(@Nullable RecursiveDataSquare parent, int powerSize, int trueN this.trueNegZCorner = trueNegZCorner; } - public byte[] getRenderData() { + public ColorPalette getRenderData() { if (renderData == null) { buildRenderData(); } @@ -234,10 +241,13 @@ public boolean isPosZ(int trueZ) { } protected void buildRenderData() { - renderData = new byte[256 * 256]; + renderData = ColorPalette.EMPTY; + if (mapper == null) return; + var builder = new ColorPalette.PaletteBuilder(mapper.packedFallback()); try { BiomeSource source = level.getChunkSource().getGenerator().getBiomeSource(); Climate.Sampler sampler = level.getChunkSource().randomState().sampler(); + Object2IntMap> cache = new Object2IntOpenHashMap<>(); int step = toTrueSize(powerSize - 8); @@ -247,12 +257,94 @@ protected void buildRenderData() { int worldZ = trueNegZCorner + z * step; // use some arbitrarily large value as our y picker so we don't get underground biomes Holder biome = source.getNoiseBiome(worldX >> 2, 1000, worldZ >> 2, sampler); - renderData[x + z * 256] = PlanetColors.getBiomeColor(biome); + builder.write(x, z, cache.computeIfAbsent(biome, mapper::match)); } } + + for (DeepSpaceTextureDefinition.BiomeSampleDriven.ColorEntry entry : mapper.colors()) { + builder.attachFlags(entry.packedColor(), entry.flags()); + } } catch (Exception e) { e.printStackTrace(); } + renderData = builder.build(); } } + + protected class DeepSpaceDataSquare implements BorderChangeListener { + protected ColorPalette renderData = null; + protected double diameter; + protected double centerX; + protected double centerZ; + + public DeepSpaceDataSquare(WorldBorder border) { + if (mapper == null) { + renderData = ColorPalette.EMPTY; + } + this.diameter = border.getSize(); + this.centerX = border.getCenterX(); + this.centerZ = border.getCenterZ(); + } + + public ColorPalette getRenderData() { + synchronized (this) { + if (renderData == null) { + var builder = new ColorPalette.PaletteBuilder(mapper.packedFallback()); + try { + BiomeSource source = level.getChunkSource().getGenerator().getBiomeSource(); + Climate.Sampler sampler = level.getChunkSource().randomState().sampler(); + Object2IntMap> cache = new Object2IntOpenHashMap<>(); + + for (int x = 0; x < 256; x++) { + for (int z = 0; z < 256; z++) { + int worldX = (int) (diameter * (x - 128) / 128 + centerX); + int worldZ = (int) (diameter * (z - 128) / 128 + centerZ); + // use some arbitrarily large value as our y picker so we don't get underground biomes + Holder biome = source.getNoiseBiome(worldX >> 2, 1000, worldZ >> 2, sampler); + builder.write(x, z, cache.computeIfAbsent(biome, mapper::match)); + } + } + + for (DeepSpaceTextureDefinition.BiomeSampleDriven.ColorEntry entry : mapper.colors()) { + builder.attachFlags(entry.packedColor(), entry.flags()); + } + } catch (Exception e) { + e.printStackTrace(); + } + renderData = builder.build(); + } + return renderData; + } + } + + @Override + public void onBorderSizeSet(@NotNull WorldBorder p_61847_, double p_61848_) { + renderData = null; + diameter = p_61848_; + } + + @Override + public void onBorderSizeLerping(@NotNull WorldBorder p_61852_, double p_61853_, double p_61854_, long p_61855_) { + this.onBorderSizeSet(p_61852_, p_61854_); + } + + @Override + public void onBorderCenterSet(@NotNull WorldBorder p_61849_, double p_61850_, double p_61851_) { + renderData = null; + centerX = p_61850_; + centerZ = p_61851_; + } + + @Override + public void onBorderSetWarningTime(@NotNull WorldBorder p_61856_, int p_61857_) {} + + @Override + public void onBorderSetWarningBlocks(@NotNull WorldBorder p_61860_, int p_61861_) {} + + @Override + public void onBorderSetDamagePerBlock(@NotNull WorldBorder p_61858_, double p_61859_) {} + + @Override + public void onBorderSetDamageSafeZOne(@NotNull WorldBorder p_61862_, double p_61863_) {} + } } diff --git a/src/main/java/dev/devce/rocketnautics/api/orbit/AtmosphereFlags.java b/src/main/java/dev/devce/rocketnautics/api/orbit/AtmosphereFlags.java index 2e5691a..d7a4c64 100644 --- a/src/main/java/dev/devce/rocketnautics/api/orbit/AtmosphereFlags.java +++ b/src/main/java/dev/devce/rocketnautics/api/orbit/AtmosphereFlags.java @@ -4,6 +4,7 @@ import net.minecraft.util.StringRepresentable; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.EnumSet; import java.util.Locale; @@ -15,6 +16,11 @@ public static EnumSet empty() { return EnumSet.noneOf(AtmosphereFlags.class); } + public static EnumSet properCopy(Collection collection) { + if (collection.isEmpty()) return empty(); + return EnumSet.copyOf(collection); + } + @Override public @NotNull String getSerializedName() { return name().toLowerCase(Locale.ROOT); diff --git a/src/main/java/dev/devce/rocketnautics/api/orbit/ColorFlags.java b/src/main/java/dev/devce/rocketnautics/api/orbit/ColorFlags.java new file mode 100644 index 0000000..a824578 --- /dev/null +++ b/src/main/java/dev/devce/rocketnautics/api/orbit/ColorFlags.java @@ -0,0 +1,28 @@ +package dev.devce.rocketnautics.api.orbit; + +import com.mojang.serialization.Codec; +import net.minecraft.util.StringRepresentable; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Locale; + +public enum ColorFlags implements StringRepresentable { + OCEAN; + public static final Codec CODEC = StringRepresentable.fromEnum(ColorFlags::values); + + public static EnumSet empty() { + return EnumSet.noneOf(ColorFlags.class); + } + + public static EnumSet properCopy(Collection collection) { + if (collection.isEmpty()) return empty(); + return EnumSet.copyOf(collection); + } + + @Override + public @NotNull String getSerializedName() { + return name().toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/dev/devce/rocketnautics/api/orbit/ColorPalette.java b/src/main/java/dev/devce/rocketnautics/api/orbit/ColorPalette.java new file mode 100644 index 0000000..64bb58b --- /dev/null +++ b/src/main/java/dev/devce/rocketnautics/api/orbit/ColorPalette.java @@ -0,0 +1,129 @@ +package dev.devce.rocketnautics.api.orbit; + +import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectMaps; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.*; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.EnumSet; +import java.util.Map; + +public record ColorPalette(int width, byte @NotNull [] dataArray, int @NotNull [] palette, @UnmodifiableView Byte2ObjectMap> flags) implements PaletteAccess { + public static final ColorPalette EMPTY = new ColorPalette(256, new byte[256 * 256], new int[1], Byte2ObjectMaps.emptyMap()); + public static final StreamCodec CODEC_S = StreamCodec.of( + (buf, v) -> { + buf.writeVarInt(v.width()); + buf.writeByteArray(v.dataArray()); + buf.writeVarInt(v.palette().length); + for (int i : v.palette()) { + buf.writeInt(i); + } + buf.writeMap(v.flags(), ByteBufCodecs.BYTE, (b, c) -> b.writeEnumSet(c, ColorFlags.class)); + }, buf -> { + int width = buf.readVarInt(); + byte[] data = buf.readByteArray(); + int s = buf.readVarInt(); + int[] palette = new int[s]; + for (int i = 0; i < s; i++) { + palette[i] = buf.readInt(); + } + Map> map = buf.readMap(ByteBufCodecs.BYTE, b -> b.readEnumSet(ColorFlags.class)); + return new ColorPalette(width, data, palette, new Byte2ObjectOpenHashMap<>(map)); + } + ); + + @Override + public int height() { + return dataArray.length / width; + } + + @Override + public int size() { + return dataArray.length; + } + + @Override + public int getColor(int x, int y) { + if (x > width() || y > height()) throw new IndexOutOfBoundsException(); + return palette[dataArray[x + width() * y]]; + } + + public static int[] unpackColorARGB(int packed) { + int a = (packed >> 24) & 0xFF; + int b = (packed >> 16) & 0xFF; + int g = (packed >> 8) & 0xFF; + int r = packed & 0xFF; + return new int[] {a, r, g, b}; + } + + // note -- do not use this packer if any of your inputs are byte type, because it will preserve negatives + public static int packColor(int a, int r, int g, int b) { + return (a << 24) | (b << 16) | (g << 8) | r; + } + + public static int packColor(byte a, byte r, byte g, byte b) { + return ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((g & 0xFF) << 8) | r & 0xFF; + } + + @Override + public @NotNull EnumSet getFlags(int x, int y) { + if (x > width() || y > height()) throw new IndexOutOfBoundsException(); + return flags.getOrDefault(dataArray[x + width() * y], ColorFlags.empty()); + } + + public static class PaletteBuilder { + public final int width; + public final byte[] data; + public final Int2ByteMap palette = new Int2ByteOpenHashMap(); + public final Int2ObjectMap> flags = new Int2ObjectOpenHashMap<>(); + + public PaletteBuilder(int fallback) { + this(256, fallback); + } + + public PaletteBuilder(int sideLength, int fallback) { + this(sideLength, sideLength, fallback); + } + + public PaletteBuilder(int width, int height, int fallback) { + this.width = width; + data = new byte[width * height]; + palette.put(fallback, (byte) 0); + } + + public void attachFlags(int packedColor, EnumSet flags) { + this.flags.put(packedColor, flags); + } + + public void write(int x, int y, int packedColor) { + if (x > width || y > data.length / width) throw new IndexOutOfBoundsException(); + if (!palette.containsKey(packedColor)) { + if (palette.size() >= 256) { + throw new IllegalStateException("Palette already has 256 values defined!"); + } + byte key = (byte) palette.size(); + palette.put(packedColor, key); + data[x + width * y] = key; + } else { + data[x + width * y] = palette.get(packedColor); + } + } + + public ColorPalette build() { + int[] builtPalette = new int[palette.size()]; + Byte2ObjectMap> builtFlags = new Byte2ObjectOpenHashMap<>(); + for (var entry : palette.int2ByteEntrySet()) { + builtPalette[entry.getByteValue()] = entry.getIntKey(); + if (flags.containsKey(entry.getIntKey())) { + builtFlags.put(entry.getByteValue(), flags.get(entry.getIntKey())); + } + } + return new ColorPalette(width, data, builtPalette, builtFlags.isEmpty() ? EMPTY.flags() : builtFlags); + } + } +} diff --git a/src/main/java/dev/devce/rocketnautics/api/orbit/CompoundPaletteAccess.java b/src/main/java/dev/devce/rocketnautics/api/orbit/CompoundPaletteAccess.java new file mode 100644 index 0000000..5c3d261 --- /dev/null +++ b/src/main/java/dev/devce/rocketnautics/api/orbit/CompoundPaletteAccess.java @@ -0,0 +1,66 @@ +package dev.devce.rocketnautics.api.orbit; + +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; + +public record CompoundPaletteAccess(PaletteAccess posPos, PaletteAccess posNeg, PaletteAccess negPos, PaletteAccess negNeg) implements PaletteAccess { + + public CompoundPaletteAccess(PaletteAccess posPos, PaletteAccess posNeg, PaletteAccess negPos, PaletteAccess negNeg) { + if (posPos.width() != posNeg.width() || negPos.width() != negNeg.width()) throw new IllegalArgumentException("Widths do not match!"); + if (posPos.height() != negPos.height() || posNeg.height() != negNeg.height()) throw new IllegalArgumentException("Heights do not match!"); + this.posPos = posPos; + this.posNeg = posNeg; + this.negPos = negPos; + this.negNeg = negNeg; + } + + @Override + public int width() { + return posPos.width() + negNeg.width(); + } + + @Override + public int height() { + return posPos.height() + negNeg.height(); + } + + @Override + public int size() { + return height() * width(); + } + + @Override + public int getColor(int x, int y) { + if (x >= negNeg().width()) { + if (y >= negNeg().height()) { + return posPos().getColor(x - negNeg().width(), y - negNeg().height()); + } else { + return posNeg().getColor(x - negNeg().width(), y); + } + } else { + if (y >= negNeg().height()) { + return negPos().getColor(x, y - negNeg().height()); + } else { + return negNeg().getColor(x, y); + } + } + } + + @Override + public @NotNull EnumSet getFlags(int x, int y) { + if (x >= negNeg().width()) { + if (y >= negNeg().height()) { + return posPos().getFlags(x - negNeg().width(), y - negNeg().height()); + } else { + return posNeg().getFlags(x - negNeg().width(), y); + } + } else { + if (y >= negNeg().height()) { + return negPos().getFlags(x, y - negNeg().height()); + } else { + return negNeg().getFlags(x, y); + } + } + } +} diff --git a/src/main/java/dev/devce/rocketnautics/api/orbit/DeepSpaceHelper.java b/src/main/java/dev/devce/rocketnautics/api/orbit/DeepSpaceHelper.java index 7cc1dd7..973386f 100644 --- a/src/main/java/dev/devce/rocketnautics/api/orbit/DeepSpaceHelper.java +++ b/src/main/java/dev/devce/rocketnautics/api/orbit/DeepSpaceHelper.java @@ -10,14 +10,17 @@ import dev.devce.rocketnautics.content.orbit.universe.PlanetDimensionData; import dev.devce.rocketnautics.content.orbit.universe.PlanetExtras; import dev.devce.rocketnautics.content.orbit.universe.UniverseDefinition; +import dev.ryanhcode.sable.companion.math.JOMLConversion; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.Pair; import net.minecraft.client.Minecraft; +import net.minecraft.core.Direction; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.OnlyIn; import org.hipparchus.geometry.euclidean.threed.Rotation; @@ -250,15 +253,17 @@ public static Rotation adapt(Quaterniondc rot) { return new Rotation(rot.w(), rot.x(), rot.y(), rot.z(), true); } - public static Pair localPositionToGlobalPositionAndRotation(Vector3dc localPosition, @Nullable Vector3dc localVelocity, CubePlanet planet, AbsoluteDate date) { + public static Pair localPositionToGlobalPositionAndRotation(Vector3dc localPosition, @Nullable Vector3dc localVelocity, Level level, CubePlanet planet, AbsoluteDate date) { Rotation rotation = planet.getRotationAtTime(date); rotation = rotation.compose(new Rotation(Vector3D.PLUS_J, Vector3D.PLUS_K), RotationConvention.VECTOR_OPERATOR); - Vector3d scaledPosition = localPosition.mul(planet.radius() / 30_000_000, new Vector3d()); - Vector3D unrotatedPosition = new Vector3D(scaledPosition.x(), localPosition.y() + planet.radius(), scaledPosition.z()); + double scaleFactor = planet.radius() * 2 / level.getWorldBorder().getSize(); + Vector3d scaledPosition = localPosition + .sub(level.getWorldBorder().getCenterX(), -planet.radius(), level.getWorldBorder().getCenterZ(), new Vector3d()) + .mul(scaleFactor, 1, scaleFactor); if (localVelocity != null && localVelocity.lengthSquared() < 1e-5) { localVelocity = new Vector3d(0, 1, 0); } - Vector3D actualPosition = rotation.applyTo(unrotatedPosition); + Vector3D actualPosition = rotation.applyTo(adapt(scaledPosition)); Vector3D actualVelocity = Vector3D.ZERO; if (localVelocity != null) { actualVelocity = rotation.applyTo(adapt(localVelocity)) @@ -267,8 +272,76 @@ public static Pair localPositionToGlobalPosi return Pair.of(new TimeStampedPVCoordinates(date, actualPosition, actualVelocity), rotation); } + public static Pair globalPositionToLocalPositionAndRotation(TimeStampedPVCoordinates coordsInPlanetFrame, CubePlanet planet, Level planetLevel) { + Rotation planetRot = planet.getRotationAtTime(coordsInPlanetFrame.getDate()); + if (planet.linkedDimension() == null) return Pair.of(new Vector3d(), new Quaterniond()); + Vector3D correctedPos = planetRot.applyInverseTo(coordsInPlanetFrame.getPosition()); + double ax = Math.abs(correctedPos.getX()); + double ay = Math.abs(correctedPos.getY()); + double az = Math.abs(correctedPos.getZ()); + // at most one of these will be true + boolean xMajor = ax > ay && ax > az; + boolean zMajor = az > ax && az > ay; + boolean yMajor = ay > ax && ay > az; + Direction.Axis axis; + if (xMajor) { + axis = Direction.Axis.X; + } else if (zMajor) { + axis = Direction.Axis.Z; + } else if (yMajor) { + axis = Direction.Axis.Y; + } else { + return Pair.of(new Vector3d(0, planet.linkedDimension().transitionHeight(), 0), new Quaterniond()); + } + Vector3D axi; + Vector3d destPos = (switch (axis) { + case X -> { + if (correctedPos.getX() > 0) { + // pos y => neg z + // pos z => neg x + axi = Vector3D.PLUS_I; + yield new Vector3d(-correctedPos.getZ(), ax, -correctedPos.getY()); + } else { + // pos y => neg z + // pos z => pos x + axi = Vector3D.MINUS_I; + yield new Vector3d(correctedPos.getZ(), ax, -correctedPos.getY()); + } + } + case Z -> { + if (correctedPos.getZ() > 0) { + // pos y => neg z + // pos x => pos x + axi = Vector3D.PLUS_K; + yield new Vector3d(correctedPos.getX(), az, -correctedPos.getY()); + } else { + // pos y => neg z + // pos x => neg x + axi = Vector3D.MINUS_K; + yield new Vector3d(-correctedPos.getX(), az, -correctedPos.getY()); + } + } + case Y -> { + if (correctedPos.getY() > 0) { + axi = Vector3D.PLUS_J; + } else { + axi = Vector3D.MINUS_J; + } + // pos x => pos x + // pos z => pos z + yield new Vector3d(correctedPos.getX(), ay, correctedPos.getZ()); + } + }); + double scaleFactor = 0.5 * planetLevel.getWorldBorder().getSize() / planet.radius(); + destPos.mul(scaleFactor, 1, scaleFactor) + .add(planetLevel.getWorldBorder().getCenterX(), -planet.radius(), planetLevel.getWorldBorder().getCenterZ()); + Quaterniond rot = DeepSpaceHelper.adapt(planetRot.compose(new Rotation(Vector3D.PLUS_J, axi), RotationConvention.VECTOR_OPERATOR)).conjugate(); + return Pair.of(destPos, rot); + } + @OnlyIn(Dist.CLIENT) public static boolean isDeepSpace() { + if (Minecraft.getInstance().level == null) return false; return Minecraft.getInstance().level.dimension() == RocketDimensions.DEEP_SPACE; } diff --git a/src/main/java/dev/devce/rocketnautics/api/orbit/PaletteAccess.java b/src/main/java/dev/devce/rocketnautics/api/orbit/PaletteAccess.java new file mode 100644 index 0000000..110156a --- /dev/null +++ b/src/main/java/dev/devce/rocketnautics/api/orbit/PaletteAccess.java @@ -0,0 +1,18 @@ +package dev.devce.rocketnautics.api.orbit; + +import org.jetbrains.annotations.NotNull; + +import java.util.EnumSet; + +public interface PaletteAccess { + + int width(); + + int height(); + + int size(); + + int getColor(int x, int y); + + @NotNull EnumSet getFlags(int x, int y); +} diff --git a/src/main/java/dev/devce/rocketnautics/client/DeepSpaceHandler.java b/src/main/java/dev/devce/rocketnautics/client/DeepSpaceHandler.java index 2e85ab1..c301cb5 100644 --- a/src/main/java/dev/devce/rocketnautics/client/DeepSpaceHandler.java +++ b/src/main/java/dev/devce/rocketnautics/client/DeepSpaceHandler.java @@ -6,6 +6,7 @@ import com.mojang.datafixers.util.Either; import dev.devce.rocketnautics.RocketConfig; import dev.devce.rocketnautics.RocketNautics; +import dev.devce.rocketnautics.api.orbit.ColorPalette; import dev.devce.rocketnautics.api.orbit.DeepSpaceHelper; import dev.devce.rocketnautics.content.orbit.DeepSpaceData; import dev.devce.rocketnautics.content.orbit.universe.CubePlanet; @@ -13,10 +14,7 @@ import dev.devce.rocketnautics.content.orbit.universe.UniverseDefinition; import dev.devce.rocketnautics.network.PlanetRenderRequestPayload; import it.unimi.dsi.fastutil.Pair; -import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.ints.*; import net.minecraft.Util; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; @@ -59,6 +57,7 @@ public final class DeepSpaceHandler { private static @Nullable UniverseDefinition UNIVERSE; private static final Int2ObjectAVLTreeMap> KNOWN_RENDER_DATA = new Int2ObjectAVLTreeMap<>(); + private static final Int2BooleanAVLTreeMap AWAITING_SERVER = new Int2BooleanAVLTreeMap(); private static final ResourceLocation FORCEFIELD_LOCATION = ResourceLocation.withDefaultNamespace("textures/misc/forcefield.png"); private static final float FORCEFIELD_DIST = 8; @@ -201,11 +200,12 @@ public Orbit next() { }; } - public static void receiveRenderData(int id, Either data, int powerScale) { + public static void receiveRenderData(int id, Either data, int powerScale) { KNOWN_RENDER_DATA.put(id, IntObjectPair.of(powerScale, data.map(arr -> DeepSpaceTexture.construct(id, arr), res -> { Minecraft.getInstance().getTextureManager().register(res, new SimpleTexture(res)); return () -> res; }))); + AWAITING_SERVER.remove(id); } @SubscribeEvent @@ -359,7 +359,9 @@ private static void renderUniverse(@Nullable CubePlanet exclude, PoseStack poseS if (planet.right() == exclude) continue; poseStack.pushPose(); if (renderPlanet(planet.right(), planet.left(), poseStack, renderDate, celestialAngle, partialTick)) { - needRenderData.add(planet.right().id()); + if (!AWAITING_SERVER.put(planet.right().id(), true)) { + needRenderData.add(planet.right().id()); + } } poseStack.popPose(); } @@ -664,7 +666,9 @@ public static Pair> renderHologram(Vector3D posInFrame, } poseStack.pushPose(); if (renderHoloPlanet(planet.right(), planet.left(), poseStack, date, scale, source, 0.9f, 0.9f, 1.0f, 0.9f)) { - needRenderData.add(planet.right().id()); + if (!AWAITING_SERVER.put(planet.right().id(), true)) { + needRenderData.add(planet.right().id()); + } } else { renderedPlanets.add(planet.right()); } @@ -824,13 +828,13 @@ private static long computeColor(Vector3D P, Vector3D L, int gx, int gy) { return ((long)r << 24) | ((long)g << 16) | ((long)b << 8) | a; } - public static void renderUniverseForLevel(ResourceKey dimension, Vec3 position, PoseStack poseStack, float partialDelta, float partialTick, Camera camera) { + public static void renderUniverseForLevel(Level level, Vec3 position, PoseStack poseStack, float partialDelta, float partialTick, Camera camera) { if (UNIVERSE == null || receivedUniverseDateTick == -1) return; - CubePlanet planet = UNIVERSE.getPlanetByDimension(dimension); + CubePlanet planet = UNIVERSE.getPlanetByDimension(level.dimension()); if (planet == null || planet.linkedDimension() == null || !planet.linkedDimension().renderUniverseInDimension()) return; poseStack.pushPose(); AbsoluteDate date = getPredictedUniverseDate(partialTick); - var globalCoords = DeepSpaceHelper.localPositionToGlobalPositionAndRotation(position.toVector3f().get(new Vector3d()), null, planet, date); + var globalCoords = DeepSpaceHelper.localPositionToGlobalPositionAndRotation(position.toVector3f().get(new Vector3d()), null, level, planet, date); poseStack.mulPose(DeepSpaceHelper.adapt(globalCoords.second()).get(new Quaternionf()).conjugate()); renderUniverse(planet, poseStack, null, partialDelta, partialTick, date, globalCoords.first().getPosition(), planet.orekitFrame(), camera); poseStack.popPose(); diff --git a/src/main/java/dev/devce/rocketnautics/client/DeepSpaceTexture.java b/src/main/java/dev/devce/rocketnautics/client/DeepSpaceTexture.java index 5316d5f..ca889bf 100644 --- a/src/main/java/dev/devce/rocketnautics/client/DeepSpaceTexture.java +++ b/src/main/java/dev/devce/rocketnautics/client/DeepSpaceTexture.java @@ -1,6 +1,7 @@ package dev.devce.rocketnautics.client; import com.mojang.blaze3d.platform.NativeImage; +import dev.devce.rocketnautics.api.orbit.ColorPalette; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.resources.ResourceLocation; @@ -17,10 +18,10 @@ public DeepSpaceTexture(DynamicTexture tex, ResourceLocation id) { this.id = id; } - public static DeepSpaceTexture construct(int renderID, byte[] renderData) { + public static DeepSpaceTexture construct(int renderID, ColorPalette renderData) { Minecraft mc = Minecraft.getInstance(); - NativeImage image = SkyHandler.composePlanetTexture(256, (x, y) -> renderData[x + y * 256]); + NativeImage image = SkyHandler.composePlanetTexture(renderData); DynamicTexture constructed = new DynamicTexture(image); ResourceLocation claimed = mc.getTextureManager().register("rocketnautics_deep_space_planet", constructed); diff --git a/src/main/java/dev/devce/rocketnautics/client/RenderDataSelector.java b/src/main/java/dev/devce/rocketnautics/client/RenderDataSelector.java index de148ed..fe39fcd 100644 --- a/src/main/java/dev/devce/rocketnautics/client/RenderDataSelector.java +++ b/src/main/java/dev/devce/rocketnautics/client/RenderDataSelector.java @@ -1,5 +1,5 @@ package dev.devce.rocketnautics.client; public interface RenderDataSelector { - byte select(int x, int y); + int select(int x, int y); } diff --git a/src/main/java/dev/devce/rocketnautics/client/SkyHandler.java b/src/main/java/dev/devce/rocketnautics/client/SkyHandler.java index 619933e..cdfecbb 100644 --- a/src/main/java/dev/devce/rocketnautics/client/SkyHandler.java +++ b/src/main/java/dev/devce/rocketnautics/client/SkyHandler.java @@ -10,11 +10,13 @@ import dev.devce.rocketnautics.RocketConfig; import dev.devce.rocketnautics.RocketNautics; import dev.devce.rocketnautics.SkyDataHandler; -import dev.devce.rocketnautics.api.orbit.DeepSpaceHelper; +import dev.devce.rocketnautics.api.orbit.*; import dev.devce.rocketnautics.content.orbit.universe.PlanetExtras; import dev.devce.rocketnautics.network.PlanetMapRequestPayload; import dev.ryanhcode.sable.api.sublevel.SubLevelContainer; import dev.ryanhcode.sable.sublevel.SubLevel; +import it.unimi.dsi.fastutil.ints.Int2FloatMap; +import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GameRenderer; @@ -32,9 +34,7 @@ import org.joml.Vector3d; import org.joml.Vector3f; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -86,7 +86,7 @@ public static void onRenderLevelStage(RenderLevelStageEvent event) { poseStack.mulPose(event.getModelViewMatrix()); Camera camera = event.getCamera(); - DeepSpaceHandler.renderUniverseForLevel(mc.level.dimension(), camera.getPosition(), event.getPoseStack(), event.getPartialTick().getGameTimeDeltaTicks(), event.getPartialTick().getGameTimeDeltaPartialTick(true), camera); + DeepSpaceHandler.renderUniverseForLevel(mc.level, camera.getPosition(), event.getPoseStack(), event.getPartialTick().getGameTimeDeltaTicks(), event.getPartialTick().getGameTimeDeltaPartialTick(true), camera); double camY = camera.getPosition().y + SkyDataHandler.getHeightOffsetForLevel(mc.level.dimension()); if (camY < 1000.0) return; @@ -359,7 +359,7 @@ private static void ensurePlanetTexObj() { } } - public static void updatePlanetTexture(int powerSize, int centerX, int centerZ, byte[] mapDataPosXPosZ, byte[] mapDataPosXNegZ, byte[] mapDataNegXPosZ, byte[] mapDataNegXNegZ) { + public static void updatePlanetTexture(int powerSize, int centerX, int centerZ, ColorPalette mapDataPosXPosZ, ColorPalette mapDataPosXNegZ, ColorPalette mapDataNegXPosZ, ColorPalette mapDataNegXNegZ) { PlanetRenderInfo updating = PLANET_TEXTURE_OBJ_LAST; PLANET_TEXTURE_OBJ_LAST = PLANET_TEXTURE_OBJ; PLANET_TEXTURE_OBJ = updating; @@ -372,7 +372,7 @@ public static void updatePlanetTexture(int powerSize, int centerX, int centerZ, mc.execute(() -> { if (PLANET_TEXTURE_OBJ == null) return; - NativeImage image = composePlanetTexture(512, (x, y) -> getBiomeAt(x, y, mapDataPosXPosZ, mapDataPosXNegZ, mapDataNegXPosZ, mapDataNegXNegZ)); + NativeImage image = composePlanetTexture(new CompoundPaletteAccess(mapDataPosXPosZ, mapDataPosXNegZ, mapDataNegXPosZ, mapDataNegXNegZ)); PLANET_TEXTURE_OBJ.getTexture().setPixels(image); PLANET_TEXTURE_OBJ.getTexture().upload(); @@ -382,19 +382,19 @@ public static void updatePlanetTexture(int powerSize, int centerX, int centerZ, }); } - public static NativeImage composePlanetTexture(int renderDataSize, RenderDataSelector selector) { + public static NativeImage composePlanetTexture(PaletteAccess palette) { int texSize = 1024; int virtualSize = 256; // virtual size for a gorgeous clean retro pixel art style int blockSize = texSize / virtualSize; // 4x4 blocks in the 1024x1024 texture NativeImage image = new NativeImage(texSize, texSize, false); - byte[] virtualBiomes = new byte[virtualSize * virtualSize]; + ColorPalette.PaletteBuilder virtualPaletteBuilder = new ColorPalette.PaletteBuilder(palette.width(), palette.height(), 0); // Pass 1: Compute smoothed metaball-like biomes on the virtual grid for (int vy = 0; vy < virtualSize; vy++) { for (int vx = 0; vx < virtualSize; vx++) { - double u = (vx / (double)virtualSize) * renderDataSize; - double v = (vy / (double)virtualSize) * renderDataSize; + double u = (vx / (double)virtualSize) * palette.width(); + double v = (vy / (double)virtualSize) * palette.height(); // Smooth wave distortion for organic borders double nx = u * 0.1; @@ -407,7 +407,7 @@ public static NativeImage composePlanetTexture(int renderDataSize, RenderDataSel int cx = (int) Math.round(wu); int cy = (int) Math.round(wv); - float[] influence = new float[PlanetColors.getReservedCount()]; + Int2FloatOpenHashMap influence = new Int2FloatOpenHashMap(); double radius = 3.2; double radiusSq = radius * radius; @@ -415,8 +415,11 @@ public static NativeImage composePlanetTexture(int renderDataSize, RenderDataSel for (int dy = -3; dy <= 3; dy++) { int gx = cx + dx; int gy = cy + dy; - byte biome = selector.select(Mth.clamp(gx, 0, renderDataSize - 1), Mth.clamp(gy, 0, renderDataSize - 1)); - + int color = palette.getColor(Mth.clamp(gx, 0, palette.width() - 1), Mth.clamp(gy, 0, palette.height() - 1)); + EnumSet flags = palette.getFlags(Mth.clamp(gx, 0, palette.width() - 1), Mth.clamp(gy, 0, palette.height() - 1)); + if (dx == 0 && dy == 0) { + virtualPaletteBuilder.attachFlags(color, flags); + } double distX = wu - (gx + 0.5); double distY = wv - (gy + 0.5); double dSq = distX * distX + distY * distY; @@ -428,45 +431,36 @@ public static NativeImage composePlanetTexture(int renderDataSize, RenderDataSel double weight = term * term * term; // Boost landmasses slightly for tighter shores - if (!PlanetColors.isWater(biome)) { - influence[biome] += (float) (weight * 1.1); + if (!flags.contains(ColorFlags.OCEAN)) { + influence.addTo(color, (float) (weight * 1.1)); } else { - influence[biome] += (float) weight; + influence.addTo(color, (float) weight); } } } } - - int bestBiome = PlanetColors.FALLBACK; - float maxInfluence = -1; - for (int b = 0; b < PlanetColors.getReservedCount(); b++) { - if (influence[b] > maxInfluence) { - maxInfluence = influence[b]; - bestBiome = b; - } - } - virtualBiomes[vx + vy * virtualSize] = (byte) bestBiome; + virtualPaletteBuilder.write(vx, vy, influence.int2FloatEntrySet().stream().max(Comparator.comparingDouble(Int2FloatMap.Entry::getFloatValue)).get().getIntKey()); } } + ColorPalette virtualPalette = virtualPaletteBuilder.build(); + // Pass 2: Color, Shade, and Draw the 3D-embossed pixel art planet for (int vy = 0; vy < virtualSize; vy++) { for (int vx = 0; vx < virtualSize; vx++) { - byte colorIdx = virtualBiomes[vx + vy * virtualSize]; - int[] unpacked = PlanetColors.getUnpackedColorARGB(colorIdx); + int[] unpacked = ColorPalette.unpackColorARGB(virtualPalette.getColor(vx, vy)); + EnumSet flags = virtualPalette.getFlags(vx, vy); int r = unpacked[1]; int g = unpacked[2]; int b = unpacked[3]; // Dynamic 3D pixel-art coastline shading - if (PlanetColors.isWater(colorIdx)) { + if (flags.contains(ColorFlags.OCEAN)) { // Shadow cast ONTO water from top-left land - int tlx = vx - 1; - int tly = vy - 1; - if (tlx >= 0 && tly >= 0) { - byte neighborBiome = virtualBiomes[tlx + tly * virtualSize]; - if (!PlanetColors.isWater(neighborBiome)) { + if (vx >= 1 && vy >= 1) { + EnumSet neighborFlags = virtualPalette.getFlags(vx - 1, vy - 1); + if (!neighborFlags.contains(ColorFlags.OCEAN)) { r = (int) (r * 0.75); g = (int) (g * 0.75); b = (int) (b * 0.75); @@ -474,11 +468,9 @@ public static NativeImage composePlanetTexture(int renderDataSize, RenderDataSel } } else { // Border depth cast ONTO neighboring bottom-right water - int brx = vx + 1; - int bry = vy + 1; - if (brx < virtualSize && bry < virtualSize) { - byte neighborBiome = virtualBiomes[brx + bry * virtualSize]; - if (PlanetColors.isWater(neighborBiome)) { + if (vx < virtualSize - 1 && vy < virtualSize - 1) { + EnumSet neighborFlags = virtualPalette.getFlags(vx + 1, vy + 1); + if (neighborFlags.contains(ColorFlags.OCEAN)) { r = (int) (r * 0.85); g = (int) (g * 0.85); b = (int) (b * 0.85); @@ -486,7 +478,7 @@ public static NativeImage composePlanetTexture(int renderDataSize, RenderDataSel } } - int color = (unpacked[0] << 24) | (b << 16) | (g << 8) | r; + int color = ColorPalette.packColor(unpacked[0], r, g, b); for (int bx = 0; bx < blockSize; bx++) { for (int by = 0; by < blockSize; by++) { image.setPixelRGBA(vx * blockSize + bx, vy * blockSize + by, color); @@ -722,24 +714,6 @@ private static void ensureSonicBoomTexture() { image.close(); } - private static byte getBiomeAt(int gx, int gy, byte[] posPos, byte[] posNeg, byte[] negPos, byte[] negNeg) { - int dataSize = 256; - if (gx >= dataSize) { - int sx = gx - dataSize; - if (gy >= dataSize) { - return posPos[sx + (gy - dataSize) * dataSize]; - } else { - return posNeg[sx + gy * dataSize]; - } - } else { - if (gy >= dataSize) { - return negPos[gx + (gy - dataSize) * dataSize]; - } else { - return negNeg[gx + gy * dataSize]; - } - } - } - private static class Noise2D { private final float[] grid; private final int size; diff --git a/src/main/java/dev/devce/rocketnautics/client/render/HologramTableRenderer.java b/src/main/java/dev/devce/rocketnautics/client/render/HologramTableRenderer.java index 92b406a..0e34540 100644 --- a/src/main/java/dev/devce/rocketnautics/client/render/HologramTableRenderer.java +++ b/src/main/java/dev/devce/rocketnautics/client/render/HologramTableRenderer.java @@ -76,7 +76,7 @@ protected void renderSafe(HologramTableBlockEntity be, float partialTicks, PoseS CubePlanet inhabiting = universe.getPlanetByDimension(be.getLevel().dimension()); if (inhabiting == null) return; centerFrame = inhabiting.orekitFrame(); - posInFrame = DeepSpaceHelper.localPositionToGlobalPositionAndRotation(JOMLConversion.atCenterOf(be.getBlockPos(), new Vector3d()), null, inhabiting, renderDate).first().getPosition(); + posInFrame = DeepSpaceHelper.localPositionToGlobalPositionAndRotation(Sable.HELPER.projectOutOfSubLevel(be.getLevel(), JOMLConversion.atCenterOf(be.getBlockPos(), new Vector3d())), null, be.getLevel(), inhabiting, renderDate).first().getPosition(); } ms.pushPose(); ms.translate(0.5, holoSize / 2d + 1, 0.5); diff --git a/src/main/java/dev/devce/rocketnautics/content/orbit/DeepSpaceInstance.java b/src/main/java/dev/devce/rocketnautics/content/orbit/DeepSpaceInstance.java index 26cf951..0250cad 100644 --- a/src/main/java/dev/devce/rocketnautics/content/orbit/DeepSpaceInstance.java +++ b/src/main/java/dev/devce/rocketnautics/content/orbit/DeepSpaceInstance.java @@ -180,26 +180,11 @@ public void tick(MinecraftServer server) { double dz = Math.max(0, az - lastOrbiting.radius()); double d2 = dx * dx + dy * dy + dz * dz; if (d2 < lastOrbiting.linkedDimension().transitionHeight() * lastOrbiting.linkedDimension().transitionHeight()) { - Rotation r = lastOrbiting.getRotationAtTime(lastTime); - // at most one of these will be true - boolean xMajor = ax > ay && ax > az; - boolean zMajor = az > ax && az > ay; - boolean yMajor = ay > ax && ay > az; - Direction.Axis a; - if (xMajor) { - a = Direction.Axis.X; - } else if (zMajor) { - a = Direction.Axis.Z; - } else if (yMajor) { - a = Direction.Axis.Y; - } else { - return; - } - // ensure we properly retrieve and kick everything inside this instance to the destination dimension // TODO what about players that are logged out? - // Track their instance in entity data, on load see if/where that instance exited? - SpaceTransitionHandler.exitDeepSpace(server, lastOrbiting, r, lastPosition, a, this, - () -> manager.retireInstance(this.getId())); + // Track their instance in entity data, on login see if/where that instance exited? + // Track logged out players in the instance, and add it to a map that we check on login that has the destination position? + SpaceTransitionHandler.exitDeepSpace(server, lastOrbiting, new TimeStampedPVCoordinates(lastTime, lastPosition, Vector3D.ZERO), + this, () -> manager.retireInstance(this.getId())); isProcessingRetirement = true; } } diff --git a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/CubePlanet.java b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/CubePlanet.java index 0ff510a..4b824a1 100644 --- a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/CubePlanet.java +++ b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/CubePlanet.java @@ -1,12 +1,11 @@ package dev.devce.rocketnautics.content.orbit.universe; import dev.devce.rocketnautics.SkyDataHandler; +import dev.devce.rocketnautics.api.orbit.ColorPalette; import dev.devce.rocketnautics.api.orbit.DeepSpaceHelper; import dev.devce.rocketnautics.api.orbit.FrameTree; import dev.devce.rocketnautics.api.orbit.FrameTreeOwner; -import dev.devce.rocketnautics.client.PlanetColors; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import org.hipparchus.geometry.euclidean.threed.Rotation; @@ -15,15 +14,15 @@ import org.orekit.time.AbsoluteDate; import org.orekit.utils.TimeStampedAngularCoordinates; -// note -- render data supplier is never synced to client +// note -- texture definition is never synced to client public record CubePlanet(@NotNull FrameTree frame, double radius, TimeStampedAngularCoordinates rotationDescription, - @Nullable PlanetDimensionData linkedDimension, @Nullable ResourceLocation textureOverride, + @Nullable PlanetDimensionData linkedDimension, @Nullable DeepSpaceTextureDefinition textureDefinition, @NotNull PlanetExtras extras) implements FrameTreeOwner { - public byte[] getRenderData(MinecraftServer server, int powerScaleClamp) { - if (linkedDimension == null) return PlanetColors.BLANK; + public ColorPalette getRenderData(MinecraftServer server, int powerScaleClamp) { + if (linkedDimension == null) return ColorPalette.EMPTY; ServerLevel level = server.getLevel(linkedDimension.key()); - if (level == null) return PlanetColors.BLANK; + if (level == null) return ColorPalette.EMPTY; return SkyDataHandler.getHandlerForLevel(level).getRenderDataForDeepSpace(powerScaleClamp); } diff --git a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/DeepSpaceTextureDefinition.java b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/DeepSpaceTextureDefinition.java new file mode 100644 index 0000000..7addf5f --- /dev/null +++ b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/DeepSpaceTextureDefinition.java @@ -0,0 +1,107 @@ +package dev.devce.rocketnautics.content.orbit.universe; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import dev.devce.rocketnautics.api.orbit.ColorFlags; +import dev.devce.rocketnautics.api.orbit.ColorPalette; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.level.biome.Biome; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public interface DeepSpaceTextureDefinition { + Codec CODEC = Type.CODEC.dispatch(DeepSpaceTextureDefinition::type, Type::typeCodec); + + enum Type implements StringRepresentable { + RESLOC(ResourceLocationDriven.CODEC), + BIOME_SAMPLER(BiomeSampleDriven.CODEC); + public static final Codec CODEC = StringRepresentable.fromEnum(Type::values); + + private final MapCodec typeCodec; + + Type(MapCodec codec) { + this.typeCodec = codec; + } + + public MapCodec typeCodec() { + return typeCodec; + } + + @Override + public @NotNull String getSerializedName() { + return name().toLowerCase(Locale.ROOT); + } + } + + @NotNull Type type(); + + + record ResourceLocationDriven(ResourceLocation texture) implements DeepSpaceTextureDefinition { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ResourceLocation.CODEC.fieldOf("texture").forGetter(ResourceLocationDriven::texture) + ).apply(instance, ResourceLocationDriven::new)); + + @Override + public @NotNull Type type() { + return Type.RESLOC; + } + } + + record BiomeSampleDriven(List colors, byte fallbackR, byte fallbackG, byte fallbackB) implements DeepSpaceTextureDefinition { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ColorEntry.CODEC.listOf().fieldOf("colors").forGetter(BiomeSampleDriven::colors), + Codec.INT.fieldOf("fallback_red").forGetter(d -> d.fallbackR & 0xFF), + Codec.INT.fieldOf("fallback_green").forGetter(d -> d.fallbackG & 0xFF), + Codec.INT.fieldOf("fallback_blue").forGetter(d -> d.fallbackB & 0xFF) + ).apply(instance, (l, r, g, b) -> new BiomeSampleDriven(l.stream().sorted(Comparator.comparingInt(ColorEntry::prio).reversed()).toList(), r.byteValue(), g.byteValue(), b.byteValue()))); + + public int match(Holder biome) { + for (ColorEntry entry : colors) { + if (entry.match(biome)) return entry.packedColor(); + } + return packedFallback(); + } + + public int packedFallback() { + return ColorPalette.packColor((byte) 255, fallbackR, fallbackG, fallbackB); + } + + @Override + public @NotNull Type type() { + return Type.BIOME_SAMPLER; + } + + public record ColorEntry(List> matchingBiomes, List> matchingTags, int prio, byte r, byte g, byte b, EnumSet flags) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + ResourceKey.codec(Registries.BIOME).listOf().optionalFieldOf("matching_biomes", List.of()).forGetter(ColorEntry::matchingBiomes), + TagKey.codec(Registries.BIOME).listOf().optionalFieldOf("matching_tags", List.of()).forGetter(ColorEntry::matchingTags), + Codec.INT.optionalFieldOf("priority", 0).forGetter(ColorEntry::prio), + Codec.INT.fieldOf("red").forGetter(d -> d.r & 0xFF), + Codec.INT.fieldOf("green").forGetter(d -> d.g & 0xFF), + Codec.INT.fieldOf("blue").forGetter(d -> d.b & 0xFF), + ColorFlags.CODEC.listOf().xmap(ColorFlags::properCopy, ArrayList::new).optionalFieldOf("flags", ColorFlags.empty()).forGetter(ColorEntry::flags) + ).apply(instance, (mb, mt, p, r, g, b, f) -> new ColorEntry(mb, mt, p, r.byteValue(), g.byteValue(), b.byteValue(), f))); + + public int packedColor() { + return ColorPalette.packColor((byte) 255, r, g, b); + } + + public boolean match(Holder biome) { + for (ResourceKey match : matchingBiomes) { + if (biome.is(match)) return true; + } + for (TagKey tag : matchingTags) { + if (biome.is(tag)) return true; + } + return false; + } + } + } +} diff --git a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/StandardUniverseProvider.java b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/StandardUniverseProvider.java index c35a2d1..b7c6ba9 100644 --- a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/StandardUniverseProvider.java +++ b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/StandardUniverseProvider.java @@ -2,12 +2,15 @@ import dev.devce.rocketnautics.RocketNautics; import dev.devce.rocketnautics.api.orbit.AtmosphereFlags; +import dev.devce.rocketnautics.api.orbit.ColorFlags; import dev.devce.rocketnautics.content.RocketDimensions; +import dev.devce.rocketnautics.content.orbit.universe.builder.BiomeSamplingTextureBuilder; import dev.devce.rocketnautics.content.orbit.universe.builder.PlanetDefinitionBuilder; import dev.devce.rocketnautics.content.orbit.universe.builder.UniverseDefinitionBuilder; -import dev.ryanhcode.sable.physics.config.dimension_physics.BezierResourceFunction; +import dev.devce.rocketnautics.registry.RocketTags; +import net.minecraft.tags.BiomeTags; import net.minecraft.world.level.Level; -import org.hipparchus.geometry.euclidean.threed.Rotation; +import net.neoforged.neoforge.common.Tags; import org.hipparchus.geometry.euclidean.threed.Vector3D; import java.util.EnumSet; @@ -45,7 +48,7 @@ public static PlanetDefinitionBuilder sol() { return new PlanetDefinitionBuilder("root", "sol") .setMu(solMu) .setStar(true) - .setTextureOverride(RocketNautics.path("textures/planet/sol.png")) + .setTextureFile(RocketNautics.path("textures/planet/sol.png")) .setRadius(solRadius) .setRotationPeriod(Vector3D.PLUS_J, overworldDaynightCycleLengthSeconds * 32d) .setFixedPosition(Vector3D.ZERO) @@ -57,6 +60,42 @@ public static PlanetDefinitionBuilder overworld() { .setAccelerationAtSurface(11) .setClouds(true) .setParentIsShadowLightSource() + .setTextureDefinition(new BiomeSamplingTextureBuilder() + .addMatchingTag(BiomeTags.IS_OCEAN, BiomeTags.IS_DEEP_OCEAN) + .addFlag(ColorFlags.OCEAN) + .buildEntryToColor(15, 45 ,135) // Curated deep royal blue + .addMatchingTag(BiomeTags.IS_RIVER) + .buildEntryToColor(25, 95, 215) // Vibrant blue + .addMatchingTag(BiomeTags.IS_BEACH) + .buildEntryToColor(225, 205, 155) // Warm sand + .addMatchingTag(Tags.Biomes.IS_DESERT) + .buildEntryToColor(215, 195, 115) // Golden sand + .addMatchingTag(Tags.Biomes.IS_PLAINS) + .buildEntryToColor(45, 145, 55) // Emerald green + .addMatchingTag(BiomeTags.IS_FOREST) + .buildEntryToColor(25, 105, 35) // Lush dark forest green + .addMatchingTag(BiomeTags.IS_JUNGLE) + .setPriority(1) + .buildEntryToColor(15, 85, 25) // Deep jungle teal-green + .addMatchingTag(BiomeTags.IS_TAIGA) + .buildEntryToColor(30, 75, 55) // Cool pine green + .addMatchingTag(BiomeTags.IS_SAVANNA) + .buildEntryToColor(160, 140, 70) + .addMatchingTag(Tags.Biomes.IS_SNOWY) + .setPriority(10) + .buildEntryToColor(240, 240, 245) // Pristine snow white + .addMatchingTag(BiomeTags.IS_BADLANDS) + .setPriority(10) + .buildEntryToColor(195, 90, 40) // Terracotta orange + .addMatchingTag(Tags.Biomes.IS_SWAMP) + .buildEntryToColor(50, 70, 40) + .addMatchingTag(Tags.Biomes.IS_WINDSWEPT) + .buildEntryToColor(80, 100, 80) + .addMatchingTag(Tags.Biomes.IS_MUSHROOM) + .buildEntryToColor(100, 90, 100) + .addMatchingTag(BiomeTags.IS_MOUNTAIN, Tags.Biomes.IS_STONY_SHORES) + .buildEntryToColor(135, 135, 135) + .build(30, 120, 40)) .setLinkedDimension(Level.OVERWORLD) .setDimensionTransferHeight(20_000) .addEntityDragPoint(4_000, 1, 0) @@ -73,6 +112,16 @@ public static PlanetDefinitionBuilder moon() { return new PlanetDefinitionBuilder("overworld", "moon") .setShadowLightSource("sol") .setAccelerationAtSurface(2) + .setTextureDefinition(new BiomeSamplingTextureBuilder() + .addMatchingTag(RocketTags.BiomeTags.LUNAR_CHASM.tag) + .setPriority(10) + .buildEntryToColor(220, 150, 70) + .addMatchingTag(RocketTags.BiomeTags.LUNAR_HIGHLANDS.tag) + .buildEntryToColor(190, 190, 190) + .addMatchingTag(RocketTags.BiomeTags.LUNAR_MARIA.tag) + .addFlag(ColorFlags.OCEAN) + .buildEntryToColor(90, 90, 90) + .build(160, 160, 160)) .setLinkedDimension(RocketDimensions.MOON) .setRenderUniverseInDimension(true) .setDimensionDayTimeController("sol") @@ -94,7 +143,7 @@ public static PlanetDefinitionBuilder mars() { .setRadius(overworldRadius * 0.53) // Mars is smaller than Earth .setCircularOrbit((int) (overworldOrbitalYearInOverworldDays * 1.88) * overworldDaynightCycleLengthSeconds, Vector3D.PLUS_J) .setRotationPeriod(Vector3D.MINUS_J, 1230) // ~24.6 hours - .setTextureOverride(RocketNautics.path("textures/planet/mars.png")) + .setTextureFile(RocketNautics.path("textures/planet/mars.png")) .setPriority(0); } @@ -105,7 +154,7 @@ public static PlanetDefinitionBuilder gasGiant() { .setRadius(overworldRadius * 4.2) // Jupiter is massive! .setCircularOrbit(overworldOrbitalYearInOverworldDays * 4 * overworldDaynightCycleLengthSeconds, Vector3D.PLUS_J) .setRotationPeriod(Vector3D.MINUS_J, 500) // Spins very quickly - .setTextureOverride(RocketNautics.path("textures/planet/gas_giant.png")) + .setTextureFile(RocketNautics.path("textures/planet/gas_giant.png")) .setPriority(0); } @@ -116,7 +165,7 @@ public static PlanetDefinitionBuilder iceWorld() { .setRadius(overworldRadius * 1.8) // Neptune-like .setCircularOrbit(overworldOrbitalYearInOverworldDays * 8 * overworldDaynightCycleLengthSeconds, Vector3D.PLUS_J) .setRotationPeriod(Vector3D.MINUS_J, 800) - .setTextureOverride(RocketNautics.path("textures/planet/ice_planet.png")) + .setTextureFile(RocketNautics.path("textures/planet/ice_planet.png")) .setPriority(0); } } diff --git a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/BiomeSamplingTextureBuilder.java b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/BiomeSamplingTextureBuilder.java new file mode 100644 index 0000000..1fe81d7 --- /dev/null +++ b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/BiomeSamplingTextureBuilder.java @@ -0,0 +1,72 @@ +package dev.devce.rocketnautics.content.orbit.universe.builder; + +import dev.devce.rocketnautics.api.orbit.ColorFlags; +import dev.devce.rocketnautics.content.orbit.universe.DeepSpaceTextureDefinition; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.resources.ResourceKey; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; + +public class BiomeSamplingTextureBuilder { + public final List finishedEntries = new ObjectArrayList<>(); + public List> buildingBiomes = new ObjectArrayList<>(); + public List> buildingTags = new ObjectArrayList<>(); + public int buildingPrio = 0; + public EnumSet buildingFlags = ColorFlags.empty(); + + public BiomeSamplingTextureBuilder addMatchingBiome(ResourceKey key) { + buildingBiomes.add(key); + return this; + } + + @SafeVarargs + public final BiomeSamplingTextureBuilder addMatchingBiome(ResourceKey... keys) { + buildingBiomes.addAll(Arrays.asList(keys)); + return this; + } + + public BiomeSamplingTextureBuilder addMatchingTag(TagKey tag) { + buildingTags.add(tag); + return this; + } + + @SafeVarargs + public final BiomeSamplingTextureBuilder addMatchingTag(TagKey... tags) { + buildingTags.addAll(Arrays.asList(tags)); + return this; + } + + public BiomeSamplingTextureBuilder setPriority(int prio) { + buildingPrio = prio; + return this; + } + + public BiomeSamplingTextureBuilder addFlag(ColorFlags flag) { + buildingFlags.add(flag); + return this; + } + + public BiomeSamplingTextureBuilder addFlag(ColorFlags... flag) { + buildingFlags.addAll(Arrays.asList(flag)); + return this; + } + + public BiomeSamplingTextureBuilder buildEntryToColor(int r, int g, int b) { + finishedEntries.add(new DeepSpaceTextureDefinition.BiomeSampleDriven.ColorEntry(buildingBiomes, buildingTags, buildingPrio, (byte) r, (byte) g, (byte) b, buildingFlags)); + buildingBiomes = new ObjectArrayList<>(); + buildingTags = new ObjectArrayList<>(); + buildingPrio = 0; + buildingFlags = ColorFlags.empty(); + return this; + } + + public DeepSpaceTextureDefinition.BiomeSampleDriven build(int fallbackR, int fallbackG, int fallbackB) { + return new DeepSpaceTextureDefinition.BiomeSampleDriven(finishedEntries.stream().sorted(Comparator.comparingInt(DeepSpaceTextureDefinition.BiomeSampleDriven.ColorEntry::prio).reversed()).toList(), + (byte) fallbackR, (byte) fallbackG, (byte) fallbackB); + } +} diff --git a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/PlanetDefinitionBuilder.java b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/PlanetDefinitionBuilder.java index ac4440d..8f6a061 100644 --- a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/PlanetDefinitionBuilder.java +++ b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/PlanetDefinitionBuilder.java @@ -6,10 +6,7 @@ import dev.devce.rocketnautics.api.orbit.AtmosphereFlags; import dev.devce.rocketnautics.api.orbit.DeepSpaceHelper; import dev.devce.rocketnautics.api.orbit.FrameTree; -import dev.devce.rocketnautics.content.orbit.universe.CubePlanet; -import dev.devce.rocketnautics.content.orbit.universe.PlanetDimensionData; -import dev.devce.rocketnautics.content.orbit.universe.PlanetExtras; -import dev.devce.rocketnautics.content.orbit.universe.PointGravitySource; +import dev.devce.rocketnautics.content.orbit.universe.*; import dev.ryanhcode.sable.physics.config.dimension_physics.BezierResourceFunction; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; @@ -25,10 +22,10 @@ import org.orekit.utils.TimeStampedPVCoordinates; import java.util.EnumSet; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.IntFunction; +import java.util.function.UnaryOperator; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class PlanetDefinitionBuilder { @@ -42,8 +39,7 @@ public class PlanetDefinitionBuilder { SerializableRotation.CODEC.optionalFieldOf("rotation").forGetter(p -> p.rotation), SerializableDimensionData.CODEC.optionalFieldOf("dimension_data").forGetter(PlanetDefinitionBuilder::serializeDimensionData), SerializablePlanetExtras.CODEC.optionalFieldOf("planet_extras").forGetter(PlanetDefinitionBuilder::serializePlanetExtras), - ResourceLocation.CODEC.optionalFieldOf("texture_override").forGetter(p -> p.textureOverride), - Codec.BOOL.optionalFieldOf("use_texture_override").forGetter(p -> p.useTextureOverride), + DeepSpaceTextureDefinition.CODEC.optionalFieldOf("planet_texture").forGetter(p -> p.textureDefinition), Codec.INT.optionalFieldOf("priority", 1000).forGetter(p -> p.priority), Codec.BOOL.optionalFieldOf("disabled").forGetter(p -> p.disabled) ).apply(instance, PlanetDefinitionBuilder::new)); @@ -65,8 +61,7 @@ public class PlanetDefinitionBuilder { public final Optional parent; public final @NotNull String name; public Optional radius = Optional.empty(); - public Optional useTextureOverride = Optional.empty(); - public Optional textureOverride = Optional.empty(); + public Optional textureDefinition = Optional.empty(); public Optional mu = Optional.empty(); public Optional accelerationAtSurface = Optional.empty(); @@ -89,7 +84,7 @@ protected PlanetDefinitionBuilder(Optional parent, @NotNull String name, Optional mu, Optional accelerationAtSurface, Optional position, Optional rotation, Optional dimData, Optional extras, - Optional textureOverride, Optional useTextureOverride, + Optional textureDefinition, int priority, Optional disabled) { this.parent = parent; this.name = name; @@ -117,8 +112,7 @@ protected PlanetDefinitionBuilder(Optional parent, @NotNull String name, this.lightSource = extras.get().lightSourceName(); lightSource.ifPresent(dependencies::add); } - this.textureOverride = textureOverride; - this.useTextureOverride = useTextureOverride; + this.textureDefinition = textureDefinition; this.priority = priority; this.disabled = disabled; } @@ -130,8 +124,7 @@ public PlanetDefinitionBuilder subsume(PlanetDefinitionBuilder other) { resolve(this.position, other.position), resolve(this.rotation, other.rotation), resolve(this.serializeDimensionData(), other.serializeDimensionData()), resolve(this.serializePlanetExtras(), other.serializePlanetExtras()), - resolve(this.textureOverride, other.textureOverride), - resolve(this.useTextureOverride, other.useTextureOverride), + resolve(this.textureDefinition, other.textureDefinition), this.priority, this.disabled ); } @@ -190,8 +183,11 @@ public CubePlanet build(UniverseDefinitionBuilder destination) { throw new IllegalStateException("Builder has a nonpositive mu [" + mu + "]!"); } } - if (textureOverride.isEmpty() && linkedDimension.isEmpty()) { - throw new IllegalStateException("Builder does not have a render option available!"); + if (textureDefinition.isEmpty()) { + throw new IllegalStateException("Builder does not have a texture option configured!"); + } + if (textureDefinition.get().type() == DeepSpaceTextureDefinition.Type.BIOME_SAMPLER && linkedDimension.isEmpty()) { + throw new IllegalStateException("Builder cannot sample biomes for texture with no linked dimension!"); } double roi; PointGravitySource parentSource = destination.getGravitySource(parent, false); @@ -201,7 +197,7 @@ public CubePlanet build(UniverseDefinitionBuilder destination) { roi = Double.POSITIVE_INFINITY; } destination.gravitySource(new PointGravitySource(ourFrame, mu, roi)); - CubePlanet p = new CubePlanet(ourFrame, radius, angularCoordinates, constructDimensionData(destination), useTextureOverride.orElse(true) ? textureOverride.orElse(null) : null, constructExtras(destination)); + CubePlanet p = new CubePlanet(ourFrame, radius, angularCoordinates, constructDimensionData(destination), textureDefinition.get(), constructExtras(destination)); destination.cubePlanet(p); return p; } @@ -263,9 +259,13 @@ public PlanetDefinitionBuilder setApplyGravityCorrectionToEntities(boolean apply return this; } - public PlanetDefinitionBuilder setTextureOverride(ResourceLocation textureAlternative) { - this.useTextureOverride = Optional.of(true); - this.textureOverride = Optional.ofNullable(textureAlternative); + public PlanetDefinitionBuilder setTextureFile(ResourceLocation location) { + this.textureDefinition = Optional.of(new DeepSpaceTextureDefinition.ResourceLocationDriven(location)); + return this; + } + + public PlanetDefinitionBuilder setTextureDefinition(DeepSpaceTextureDefinition definition) { + this.textureDefinition = Optional.of(definition); return this; } diff --git a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/SerializableDimensionData.java b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/SerializableDimensionData.java index e58a964..7c93851 100644 --- a/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/SerializableDimensionData.java +++ b/src/main/java/dev/devce/rocketnautics/content/orbit/universe/builder/SerializableDimensionData.java @@ -21,7 +21,7 @@ public record SerializableDimensionData(Optional> key, Option Level.RESOURCE_KEY_CODEC.optionalFieldOf("linked_dimension").forGetter(p -> p.key), AllowedTransfer.CODEC.optionalFieldOf("allowed_transfer").forGetter(p -> p.allowedTransfer), ExtraCodecs.POSITIVE_INT.optionalFieldOf("dimension_transfer_height").forGetter(p -> p.transitionHeight), - Codec.unboundedMap(Codec.STRING.xmap(Integer::parseInt, String::valueOf), AtmosphereFlags.CODEC.listOf().xmap(SerializableDimensionData::properCopy, ArrayList::new)).xmap(Int2ObjectRBTreeMap::new, i -> i).optionalFieldOf("atmosphere_composition").forGetter(p -> p.atmosphere), + Codec.unboundedMap(Codec.STRING.xmap(Integer::parseInt, String::valueOf), AtmosphereFlags.CODEC.listOf().xmap(AtmosphereFlags::properCopy, ArrayList::new)).xmap(Int2ObjectRBTreeMap::new, i -> i).optionalFieldOf("atmosphere_composition").forGetter(p -> p.atmosphere), Codec.BOOL.optionalFieldOf("render_universe_in_dimension").forGetter(p -> p.renderUniverseInDimension), Codec.STRING.optionalFieldOf("dimension_day_time_controller_name").forGetter(p -> p.dimensionDayTimeControllerName), Codec.BOOL.optionalFieldOf("apply_gravity_correction_to_entities_in_dimension").forGetter(p -> p.applyGravityCorrectionToEntities), @@ -40,9 +40,4 @@ public static Optional of(Optional } return Optional.of(new SerializableDimensionData(key, allowedTransfer, transitionHeight, atmosphere, renderUniverseInDimension, dimensionDayTimeControllerName, applyGravityCorrectionToEntities, entityDragMultiplier)); } - - private static EnumSet properCopy(Collection collection) { - if (collection.isEmpty()) return AtmosphereFlags.empty(); - return EnumSet.copyOf(collection); - } } diff --git a/src/main/java/dev/devce/rocketnautics/content/physics/SpaceTransitionHandler.java b/src/main/java/dev/devce/rocketnautics/content/physics/SpaceTransitionHandler.java index f3eb1d1..e709dfa 100644 --- a/src/main/java/dev/devce/rocketnautics/content/physics/SpaceTransitionHandler.java +++ b/src/main/java/dev/devce/rocketnautics/content/physics/SpaceTransitionHandler.java @@ -21,12 +21,12 @@ import dev.ryanhcode.sable.sublevel.storage.holding.SubLevelHoldingChunkMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import net.minecraft.core.Direction; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.TicketType; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.SubscribeEvent; @@ -35,12 +35,9 @@ import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.event.tick.ServerTickEvent; import net.neoforged.neoforge.network.PacketDistributor; -import org.hipparchus.geometry.euclidean.threed.Rotation; -import org.hipparchus.geometry.euclidean.threed.RotationConvention; -import org.hipparchus.geometry.euclidean.threed.Vector3D; -import org.jetbrains.annotations.NotNull; import org.joml.*; import org.orekit.time.AbsoluteDate; +import org.orekit.utils.TimeStampedPVCoordinates; import java.util.*; @@ -84,7 +81,7 @@ public static void init() { RigidBodyHandle handle = physicsSystem.getPhysicsHandle(ship); double captureSize = ship.boundingBox().size().length(); DeepSpaceInstance claimed = instance.claimNewInstance((int) (captureSize / 8 + 2)); - Quaterniond rotation = initInstance(claimed, ship.logicalPose().position(), handle.getLinearVelocity(new Vector3d()), linked, ship); + Quaterniond rotation = initInstance(claimed, ship.logicalPose().position(), handle.getLinearVelocity(new Vector3d()), linked, level); ServerLevel deepSpace = level.getServer().getLevel(RocketDimensions.DEEP_SPACE); // Handle all players nearby Map riding = new Object2ObjectOpenHashMap<>(); @@ -93,7 +90,6 @@ public static void init() { if (pl.isPassenger()) { riding.put(pl, pl.getVehicle().getUUID()); } - //ship.logicalPose().orientation().transformInverse(rotation.transform(ship.logicalPose().orientation().transform(offset))) Vector3f offset = pl.position().toVector3f().sub(pos.get(new Vector3f())); Vector3f o = rotation.transform(offset); pl.teleportTo(deepSpace, claimed.getCenter().x() + o.x(), claimed.getCenter().y() + o.y(), claimed.getCenter().z() + o.z(), pl.getYRot(), pl.getXRot()); @@ -111,56 +107,14 @@ public static void init() { }); } - public static void exitDeepSpace(MinecraftServer server, CubePlanet destination, Rotation correctionRotation, Vector3D positionInPlanetFrame, @NotNull Direction.Axis majorAxis, DeepSpaceInstance instance, Runnable afterFinished) { + public static void exitDeepSpace(MinecraftServer server, CubePlanet destination, TimeStampedPVCoordinates coords, DeepSpaceInstance instance, Runnable afterFinished) { assert destination.linkedDimension() != null; final ServerLevel deepSpace = server.getLevel(RocketDimensions.DEEP_SPACE); - double scaleFactor = 30_000_000 / destination.radius(); double targetHeight = destination.linkedDimension().transitionHeight() - SpaceTransitionHandler.TRANSITION_SAFE_OFFSET; - Vector3D p = correctionRotation.applyInverseTo(positionInPlanetFrame); - Vector3D axi; - Vector3d destPos = switch (majorAxis) { - case X -> { - if (p.getX() > 0) { - // pos y => neg z - // pos z => neg x - axi = Vector3D.PLUS_I; - yield new Vector3d(-p.getZ() * scaleFactor, targetHeight, -p.getY() * scaleFactor); - } else { - // pos y => neg z - // pos z => pos x - axi = Vector3D.MINUS_I; - yield new Vector3d(p.getZ() * scaleFactor, targetHeight, -p.getY() * scaleFactor); - } - } - case Z -> { - if (p.getZ() > 0) { - // pos y => neg z - // pos x => pos x - axi = Vector3D.PLUS_K; - yield new Vector3d(p.getX() * scaleFactor, targetHeight, -p.getY() * scaleFactor); - } else { - // pos y => neg z - // pos x => neg x - axi = Vector3D.MINUS_K; - yield new Vector3d(-p.getX() * scaleFactor, targetHeight, -p.getY() * scaleFactor); - } - } - case Y -> { - - if (p.getY() > 0) { - axi = Vector3D.PLUS_J; - } else { - axi = Vector3D.MINUS_J; - } - // pos x => pos x - // pos z => pos z - yield new Vector3d(p.getX() * scaleFactor, targetHeight, p.getZ() * scaleFactor); - } - }; ServerLevel destLevel = server.getLevel(destination.linkedDimension().key()); if (destLevel == null) return; - Rotation rotation = correctionRotation.compose(new Rotation(Vector3D.PLUS_J, axi), RotationConvention.VECTOR_OPERATOR); - Quaterniond rot = DeepSpaceHelper.adapt(rotation).conjugate(); + var dest = DeepSpaceHelper.globalPositionToLocalPositionAndRotation(coords, destination, destLevel); + dest.left().setComponent(1, targetHeight); // Handle all players in the instance Map riding = new Object2ObjectOpenHashMap<>(); for (ServerPlayer pl : deepSpace.getPlayers(pl -> instance.boundingBox().contains(pl.position()))) { @@ -169,14 +123,14 @@ public static void exitDeepSpace(MinecraftServer server, CubePlanet destination, riding.put(pl, pl.getVehicle().getUUID()); } Vec3 offset = pl.position().subtract(instance.boundingBox().getCenter()); - Vector3f o = rot.transform(offset.toVector3f()); - pl.teleportTo(destLevel, destPos.x() + o.x(), destPos.y() + o.y(), destPos.z() + o.z(), pl.getYRot(), pl.getXRot()); + Vector3f o = dest.right().transform(offset.toVector3f()); + pl.teleportTo(destLevel, dest.left().x() + o.x(), dest.left().y() + o.y(), dest.left().z() + o.z(), pl.getYRot(), pl.getXRot()); } // Handle ships currently in the instance List unloaded = instance.interiorPositions().filter(cPos -> !deepSpace.hasChunk(cPos.x, cPos.z)).toList(); Set seen = new ObjectOpenHashSet<>(); if (unloaded.size() != instance.getChunkSideLength() * instance.getChunkSideLength()) { - exitLoadedFromDeepSpace(instance, deepSpace, rot, destLevel, destPos, seen); + exitLoadedFromDeepSpace(instance, deepSpace, dest.right(), destLevel, dest.left(), seen); } // whatever the player was riding was absolutely loaded riding.forEach((pl, e) -> pl.startRiding(destLevel.getEntity(e), true)); @@ -195,7 +149,7 @@ public static void exitDeepSpace(MinecraftServer server, CubePlanet destination, }); ((DistanceManagerAccessor) deepSpace.getChunkSource().chunkMap.getDistanceManager()).rocketnautics$tickingTicketsTracker().runAllUpdates(); map.processChanges(); - exitLoadedFromDeepSpace(instance, deepSpace, rot, destLevel, destPos, seen); + exitLoadedFromDeepSpace(instance, deepSpace, dest.right(), destLevel, dest.left(), seen); part.forEach(cPos -> { map.updateChunkStatus(cPos, false); }); @@ -225,10 +179,10 @@ private static void exitLoadedFromDeepSpace(DeepSpaceInstance instance, ServerLe } } - private static Quaterniond initInstance(DeepSpaceInstance instance, Vector3dc dimPosition, Vector3dc velocity, CubePlanet planet, ServerSubLevel ship) { + private static Quaterniond initInstance(DeepSpaceInstance instance, Vector3dc dimPosition, Vector3dc velocity, CubePlanet planet, Level level) { AbsoluteDate currentDate = instance.getManager().getUniverseTime(); Vector3d safe = new Vector3d(0, TRANSITION_SAFE_OFFSET, 0).add(dimPosition); - var globalCoords = DeepSpaceHelper.localPositionToGlobalPositionAndRotation(safe, velocity, planet, currentDate); + var globalCoords = DeepSpaceHelper.localPositionToGlobalPositionAndRotation(safe, velocity, level, planet, currentDate); instance.getPosition().init(instance.getManager().getUniverse(), planet.orekitFrame(), globalCoords.first()); return DeepSpaceHelper.adapt(globalCoords.second()); } diff --git a/src/main/java/dev/devce/rocketnautics/network/NetworkHandler.java b/src/main/java/dev/devce/rocketnautics/network/NetworkHandler.java index c243679..f2ec438 100644 --- a/src/main/java/dev/devce/rocketnautics/network/NetworkHandler.java +++ b/src/main/java/dev/devce/rocketnautics/network/NetworkHandler.java @@ -3,12 +3,14 @@ import com.mojang.datafixers.util.Either; import dev.devce.rocketnautics.RocketNautics; import dev.devce.rocketnautics.SkyDataHandler; +import dev.devce.rocketnautics.api.orbit.ColorPalette; import dev.devce.rocketnautics.client.DeepSpaceHandler; import dev.devce.rocketnautics.client.SkyHandler; import dev.devce.rocketnautics.content.items.JetpackItem; import dev.devce.rocketnautics.content.items.LegThrustersItem; import dev.devce.rocketnautics.content.orbit.DeepSpaceData; import dev.devce.rocketnautics.content.orbit.universe.CubePlanet; +import dev.devce.rocketnautics.content.orbit.universe.DeepSpaceTextureDefinition; import dev.devce.rocketnautics.content.orbit.universe.UniverseDefinition; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; @@ -163,7 +165,7 @@ private static void handleMapRequest(net.minecraft.world.entity.player.Player ra } @net.neoforged.api.distmarker.OnlyIn(net.neoforged.api.distmarker.Dist.CLIENT) - private static void handleMapData(int powerSize, int centerX, int centerZ, byte[] mapDataPosXPosZ, byte[] mapDataPosXNegZ, byte[] mapDataNegXPosZ, byte[] mapDataNegXNegZ) { + private static void handleMapData(int powerSize, int centerX, int centerZ, ColorPalette mapDataPosXPosZ, ColorPalette mapDataPosXNegZ, ColorPalette mapDataNegXPosZ, ColorPalette mapDataNegXNegZ) { SkyHandler.updatePlanetTexture(powerSize, centerX, centerZ, mapDataPosXPosZ, mapDataPosXNegZ, mapDataNegXPosZ, mapDataNegXNegZ); } @@ -183,11 +185,11 @@ private static void handlePlanetRenderRequest(net.minecraft.world.entity.player. CubePlanet planet = def.getPlanetById(id); // computing the render data may take time, so we dispatch in separate packets. // would it be better to send a single large packet after loading everything? - Either send; + Either send; if (planet == null) { send = Either.right(ResourceLocation.withDefaultNamespace("missingno")); - } else if (planet.textureOverride() != null) { - send = Either.right(planet.textureOverride()); + } else if (planet.textureDefinition() instanceof DeepSpaceTextureDefinition.ResourceLocationDriven resloc) { + send = Either.right(resloc.texture()); } else { send = Either.left(planet.getRenderData(level.getServer(), powerSize)); } @@ -198,7 +200,7 @@ private static void handlePlanetRenderRequest(net.minecraft.world.entity.player. } @net.neoforged.api.distmarker.OnlyIn(net.neoforged.api.distmarker.Dist.CLIENT) - private static void handlePlanetRenderData(int id, Either renderData, int powerSize) { + private static void handlePlanetRenderData(int id, Either renderData, int powerSize) { DeepSpaceHandler.receiveRenderData(id, renderData, powerSize); } diff --git a/src/main/java/dev/devce/rocketnautics/network/PlanetMapPayload.java b/src/main/java/dev/devce/rocketnautics/network/PlanetMapPayload.java index bfde18b..3bcc69d 100644 --- a/src/main/java/dev/devce/rocketnautics/network/PlanetMapPayload.java +++ b/src/main/java/dev/devce/rocketnautics/network/PlanetMapPayload.java @@ -1,36 +1,34 @@ package dev.devce.rocketnautics.network; import dev.devce.rocketnautics.RocketNautics; +import dev.devce.rocketnautics.api.orbit.ColorPalette; import io.netty.buffer.ByteBuf; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; -public record PlanetMapPayload(int powerSize, int centerX, int centerZ, byte[] mapDataPosXPosZ, byte[] mapDataPosXNegZ, byte[] mapDataNegXPosZ, byte[] mapDataNegXNegZ) implements CustomPacketPayload { +public record PlanetMapPayload(int powerSize, int centerX, int centerZ, ColorPalette mapDataPosXPosZ, ColorPalette mapDataPosXNegZ, ColorPalette mapDataNegXPosZ, ColorPalette mapDataNegXNegZ) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(RocketNautics.MODID, "planet_map_data")); - public static final StreamCodec CODEC = StreamCodec.of( + public static final StreamCodec CODEC = StreamCodec.of( (buf, payload) -> { buf.writeInt(payload.powerSize()); buf.writeInt(payload.centerX()); buf.writeInt(payload.centerZ()); - buf.writeBytes(payload.mapDataPosXPosZ()); - buf.writeBytes(payload.mapDataPosXNegZ()); - buf.writeBytes(payload.mapDataNegXPosZ()); - buf.writeBytes(payload.mapDataNegXNegZ()); + ColorPalette.CODEC_S.encode(buf, payload.mapDataPosXPosZ()); + ColorPalette.CODEC_S.encode(buf, payload.mapDataPosXNegZ()); + ColorPalette.CODEC_S.encode(buf, payload.mapDataNegXPosZ()); + ColorPalette.CODEC_S.encode(buf, payload.mapDataNegXNegZ()); }, buf -> { int powerSize = buf.readInt(); int negXCorner = buf.readInt(); int negZCorner = buf.readInt(); - byte[] posXPosZ = new byte[256 * 256]; - buf.readBytes(posXPosZ); - byte[] posXNegZ = new byte[256 * 256]; - buf.readBytes(posXNegZ); - byte[] negXPosZ = new byte[256 * 256]; - buf.readBytes(negXPosZ); - byte[] negXNegZ = new byte[256 * 256]; - buf.readBytes(negXNegZ); + ColorPalette posXPosZ = ColorPalette.CODEC_S.decode(buf); + ColorPalette posXNegZ = ColorPalette.CODEC_S.decode(buf); + ColorPalette negXPosZ = ColorPalette.CODEC_S.decode(buf); + ColorPalette negXNegZ = ColorPalette.CODEC_S.decode(buf); return new PlanetMapPayload(powerSize, negXCorner, negZCorner, posXPosZ, posXNegZ, negXPosZ, negXNegZ); } ); diff --git a/src/main/java/dev/devce/rocketnautics/network/PlanetRenderPayload.java b/src/main/java/dev/devce/rocketnautics/network/PlanetRenderPayload.java index f1b69f5..05fd168 100644 --- a/src/main/java/dev/devce/rocketnautics/network/PlanetRenderPayload.java +++ b/src/main/java/dev/devce/rocketnautics/network/PlanetRenderPayload.java @@ -2,30 +2,28 @@ import com.mojang.datafixers.util.Either; import dev.devce.rocketnautics.RocketNautics; -import dev.devce.rocketnautics.client.PlanetColors; +import dev.devce.rocketnautics.api.orbit.ColorPalette; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; -public record PlanetRenderPayload(int id, Either renderData, int powerSize) implements CustomPacketPayload { +public record PlanetRenderPayload(int id, Either renderData, int powerSize) implements CustomPacketPayload { public static final CustomPacketPayload.Type TYPE = new CustomPacketPayload.Type<>(ResourceLocation.fromNamespaceAndPath(RocketNautics.MODID, "planet_render")); public static final StreamCodec CODEC = StreamCodec.of( (buf, payload) -> { buf.writeInt(payload.id()); buf.writeBoolean(payload.renderData().left().isPresent()); - payload.renderData().ifLeft(buf::writeBytes); + payload.renderData().ifLeft(p -> ColorPalette.CODEC_S.encode(buf, p)); payload.renderData().ifRight(buf::writeResourceLocation); buf.writeInt(payload.powerSize()); }, (buf) -> { int id = buf.readInt(); - boolean isArray = buf.readBoolean(); - Either renderData; - if (isArray) { - byte[] data = new byte[PlanetColors.ARRAY_SIZE]; - buf.readBytes(data); - renderData = Either.left(data); + boolean isPalette = buf.readBoolean(); + Either renderData; + if (isPalette) { + renderData = Either.left(ColorPalette.CODEC_S.decode(buf)); } else { renderData = Either.right(buf.readResourceLocation()); } diff --git a/wiki/Cube Planets.md b/wiki/Cube Planets.md index bde9a79..6e6faa2 100644 --- a/wiki/Cube Planets.md +++ b/wiki/Cube Planets.md @@ -18,17 +18,15 @@ While Cube Planets can be linked to custom dimensions, we do not provide any too **`acceleration_at_surface`** (critical, paired with `mu`): the acceleration that physics objects are subject to at a distance of `radius` from the center of the cube planet. -**`position`** (critical): A [Celestial Position](https://github.com/CosmonauticsTeam/Create-Cosmonautics/wiki/Celestial-Positions) +**`position`** (critical): A [Celestial Position](https://github.com/CosmonauticsTeam/Create-Cosmonautics/wiki/Celestial-Positions). Cannot be partially overridden. -**`rotation`** (critical): A [Celestial Rotation](https://github.com/CosmonauticsTeam/Create-Cosmonautics/wiki/Celestial-Rotations) +**`rotation`** (critical): A [Celestial Rotation](https://github.com/CosmonauticsTeam/Create-Cosmonautics/wiki/Celestial-Rotations). Cannot be partially overridden. **`dimension_data`** (optional): A compound object consisting of [these fields](https://github.com/CosmonauticsTeam/Create-Cosmonautics/wiki/Cube-Planets#Dimension-Data-Fields). Each field can be overridden separately. **`planet_extras`** (optional): A compound object consisting of [these fields](https://github.com/CosmonauticsTeam/Create-Cosmonautics/wiki/Cube-Planets#Planet-Extras-Fields). Each field can be overridden separately. -**`texture_override`** (optional): A resource location to a texture to use instead of generating the texture from biome data. Changing how the texture is generated from biome data is not yet supported. - -**`use_texture_override`** (optional, default `true`): Whether the `texture_override` should actually be used, if it is present. +**`planet_texture`** (critical): A [Texture Definition](https://github.com/CosmonauticsTeam/Create-Cosmonautics/wiki/Texture-Definition). Cannot be partially overridden. **`priority`** (optional, default `1000`): The priority of this file in the "priority override" phase. Must be an integer. Cube planets added by the base mod have a priority of 0. diff --git a/wiki/Texture Definition.md b/wiki/Texture Definition.md new file mode 100644 index 0000000..e10b832 --- /dev/null +++ b/wiki/Texture Definition.md @@ -0,0 +1,35 @@ +Texture definitions are the primary way that textures are defined in the universe. + +### Texture File +A pointer to a standard image file. + +**`type`** (required): `resloc` + +**`texture`** (required): The resource location of the image. Note that this must be **fully qualified**, meaning that the `texture` folder needs to be included in the path as well. + +### Biome Sampling +An image generated dynamically based on biome data. + +**`type`** (required): `biome_sampler` + +**`fallback_red`** (required): The red hex component of the fallback color. Ranges from 0 to 255. + +**`fallback_green`** (required): The green hex component of the fallback color. Ranges from 0 to 255. + +**`fallback_blue`** (required): The blue hex component of the fallback color. Ranges from 0 to 255. + +**`colors`** (required): A list of compound objects, each consisting of the following fields: + +>**`matching_biomes`** (optional): A list of biomes that match to this color. +> +>**`matching_tags`** (optional): A list of biome tags that match to this color. +> +>**`red`** (required): The red hex component of the color. Ranges from 0 to 255. +> +>**`green`** (required): The green hex component of the color. Ranges from 0 to 255. +> +>**`blue`** (required): The blue hex component of the color. Ranges from 0 to 255. +> +>**`priority`** (optional, default `0`): The priority of this color during matching. Only the highest priority matched color will have an effect. +> +>**`flags`** (optional): A list of flags for this color. Options are `ocean`. This mostly affects how the 3d-esque post-processing for the render interprets the color. \ No newline at end of file