From 19bbca0e4bc36a4d2287b7cc8008a41ff274fbc2 Mon Sep 17 00:00:00 2001 From: piexel Date: Mon, 19 Oct 2026 13:31:45 +0200 Subject: [PATCH] Fit the height colours to the terrain on screen The height view spread its colours over the whole world height, so a mountain range and the plains next to it got nearly the same colour. The legend now measures the visible heights (1st to 99th percentile, rounded to 8 blocks) and fits the gradient to that range, with sea level kept as the water/land split. The map also re-finds its own world after a reload instead of whichever Iris world is active, now that the active world prefers the open studio. Model used: Claude Opus 5.5 --- .../com/volmit/iris/core/gui/MapCanvas.java | 63 ++++++----- .../com/volmit/iris/core/gui/MapCensus.java | 106 +++++++++++++++--- .../com/volmit/iris/core/gui/MapWindow.java | 2 +- .../core/gui/components/IrisRenderer.java | 38 ++++--- .../iris/core/gui/components/MapStyle.java | 21 +++- .../iris/core/gui/components/MapTile.java | 22 +++- 6 files changed, 182 insertions(+), 70 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java b/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java index 33ead7e0b..4b82ae514 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapCanvas.java @@ -25,7 +25,6 @@ import com.volmit.iris.core.gui.components.MapStyle; import com.volmit.iris.core.gui.components.RenderType; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.util.format.Form; import javax.swing.JMenuItem; @@ -77,6 +76,7 @@ final class MapCanvas extends JPanel { private static final double KEYBOARD_ZOOM_FACTOR = 1.189207115002721D; private static final int LEGEND_REFRESH_MILLIS = 200; + private final UUID worldId; private final UUID openerId; private final Listener listener; private final VisionRenderController controller; @@ -108,8 +108,6 @@ final class MapCanvas extends JPanel { private boolean draggingDivider; private double centerX; private double centerZ; - private double heightMaximum; - private double fluidHeight; private long contentRevision = 1L; private long previousPaintStartedNanos; private double blocksPerPixel = DEFAULT_BLOCKS_PER_PIXEL; @@ -123,15 +121,15 @@ final class MapCanvas extends JPanel { private boolean active = true; private boolean closed; - MapCanvas(Engine engine, UUID openerId, Listener listener) { + MapCanvas(Engine engine, UUID worldId, UUID openerId, Listener listener) { this.engine = Objects.requireNonNull(engine, "engine"); + this.worldId = Objects.requireNonNull(worldId, "worldId"); this.openerId = openerId; this.listener = Objects.requireNonNull(listener, "listener"); this.renderer = new IrisRenderer(engine); this.overlay = new VisionOverlay(engine, openerId); this.controller = new VisionRenderController(this::onTilesChanged); this.hotloadHook = () -> EventQueue.invokeLater(this::refreshContent); - captureHeightRange(); setBackground(StudioTheme.EDITOR_BACKGROUND); setFocusable(true); @@ -214,6 +212,9 @@ final class MapCanvas extends JPanel { return; } currentType = view.renderType(); + if (currentType != RenderType.HEIGHT) { + style = style.withHeightRange(null); + } requestRender(); } @@ -241,7 +242,6 @@ final class MapCanvas extends JPanel { renderer = new IrisRenderer(engine); overlay = new VisionOverlay(engine, openerId); contentRevision++; - captureHeightRange(); requestRender(); notifyUser(captured && comparison.mode() == HotloadComparison.Mode.OFF ? "Refreshing ยท press C to compare with before" @@ -796,6 +796,7 @@ final class MapCanvas extends JPanel { if (currentType != RenderType.HEIGHT) { return; } + MapStyle.HeightRange range = currentHeightRange(); canvas.setFont(StudioTheme.smallFont()); int padding = StudioTheme.scale(CARD_PADDING); int gradientHeight = StudioTheme.scale(14); @@ -808,23 +809,24 @@ final class MapCanvas extends JPanel { int gradientX = x + padding; int gradientY = y + padding; int gradientWidth = width - padding * 2; + double span = Math.max(1D, range.highest() - range.lowest()); for (int pixel = 0; pixel < gradientWidth; pixel++) { - double sampleHeight = heightMaximum * pixel / Math.max(1D, gradientWidth - 1D); - canvas.setColor(new Color(IrisRenderer.heightColor(sampleHeight, heightMaximum, fluidHeight))); + double sampleHeight = range.lowest() + span * pixel / Math.max(1D, gradientWidth - 1D); + canvas.setColor(new Color(IrisRenderer.heightColor(sampleHeight, range))); canvas.drawLine(gradientX + pixel, gradientY, gradientX + pixel, gradientY + gradientHeight); } canvas.setColor(StudioTheme.SECONDARY_TEXT); int labelBaseline = gradientY + gradientHeight + labelGap + canvas.getFontMetrics().getAscent(); - String minimum = "0"; - String fluid = Integer.toString((int) Math.round(fluidHeight)); - String maximum = Integer.toString((int) Math.round(heightMaximum)); + String minimum = Integer.toString((int) Math.round(range.lowest())); + String sea = "sea " + Math.round(range.seaLevel()); + String maximum = Integer.toString((int) Math.round(range.highest())); int minimumRight = gradientX + canvas.getFontMetrics().stringWidth(minimum); int maximumX = gradientX + gradientWidth - canvas.getFontMetrics().stringWidth(maximum); - int fluidWidth = canvas.getFontMetrics().stringWidth(fluid); - int fluidX = gradientX + (int) Math.round(gradientWidth * fluidHeight / Math.max(1D, heightMaximum)) - fluidWidth / 2; + int seaWidth = canvas.getFontMetrics().stringWidth(sea); + int seaX = gradientX + (int) Math.round(gradientWidth * (range.seaLevel() - range.lowest()) / span) - seaWidth / 2; canvas.drawString(minimum, gradientX, labelBaseline); - if (fluidX > minimumRight + labelGap && fluidX + fluidWidth < maximumX - labelGap) { - canvas.drawString(fluid, fluidX, labelBaseline); + if (seaX > minimumRight + labelGap && seaX + seaWidth < maximumX - labelGap) { + canvas.drawString(sea, seaX, labelBaseline); } canvas.drawString(maximum, maximumX, labelBaseline); } @@ -970,9 +972,25 @@ final class MapCanvas extends JPanel { } IrisRenderer legendRenderer = renderer; RenderType type = currentType; - List shares = MapCensus.count(renderFrame, screenToWorldX(0D), screenToWorldZ(0D), - screenToWorldX(getWidth()), screenToWorldZ(getHeight()), label -> legendRenderer.legendColor(label, type)); + MapCensus.ViewArea area = new MapCensus.ViewArea(screenToWorldX(0D), screenToWorldZ(0D), + screenToWorldX(getWidth()), screenToWorldZ(getHeight())); + List shares = MapCensus.count(renderFrame, area, label -> legendRenderer.legendColor(label, type)); listener.onLegendChanged(legendTitle(), shares, style.highlight()); + if (type == RenderType.HEIGHT) { + fitHeightColorsToView(area); + } + } + + // The height colours stretch over the heights on screen, so plains and mountains don't share one shade of green. + private void fitHeightColorsToView(MapCensus.ViewArea area) { + MapStyle.HeightRange measured = MapCensus.heightRange(renderFrame, area, renderer.defaultHeightRange().seaLevel()); + if (measured != null && !measured.equals(style.heightRange())) { + applyStyle(style.withHeightRange(measured)); + } + } + + private MapStyle.HeightRange currentHeightRange() { + return style.heightRange() == null ? renderer.defaultHeightRange() : style.heightRange(); } private void requestRender() { @@ -1027,7 +1045,7 @@ final class MapCanvas extends JPanel { return true; } try { - Engine reacquired = GuiHost.findActiveEngine(); + Engine reacquired = GuiHost.findEngine(worldId); if (reacquired == null || reacquired.isClosed() || reacquired.getComplex() == null) { return false; } @@ -1035,7 +1053,6 @@ final class MapCanvas extends JPanel { renderer = new IrisRenderer(reacquired); overlay = new VisionOverlay(reacquired, openerId); contentRevision++; - captureHeightRange(); return true; } catch (Throwable error) { Iris.debug("Vision engine reacquisition failed: " + error.getClass().getSimpleName() + ": " + error.getMessage()); @@ -1043,14 +1060,6 @@ final class MapCanvas extends JPanel { } } - private void captureHeightRange() { - heightMaximum = Math.max(1D, engine.getHeight()); - IrisDimension dimension = engine.getDimension(); - fluidHeight = dimension == null - ? 0D - : Math.max(0D, Math.min(heightMaximum, dimension.getFluidHeight())); - } - private void refreshMarkers() { if (closed || overlay == null) { return; diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapCensus.java b/core/src/main/java/com/volmit/iris/core/gui/MapCensus.java index 7bba7aa39..17ab7b3bb 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapCensus.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapCensus.java @@ -19,9 +19,11 @@ package com.volmit.iris.core.gui; import com.volmit.iris.core.gui.components.MapLabels; +import com.volmit.iris.core.gui.components.MapStyle; import com.volmit.iris.core.gui.components.MapTile; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -29,45 +31,84 @@ import java.util.Map; import java.util.function.ToIntFunction; /** - * Counts how much of the visible map each biome or region covers, from the tiles already drawn. + * Measures the visible part of the map from the tiles already drawn: how much each biome or region covers, and which + * heights it spans. */ final class MapCensus { private static final long MAXIMUM_COUNTED_PIXELS = 250_000L; + private static final int MINIMUM_HEIGHT_SAMPLES = 64; + private static final double LOW_HEIGHT_PERCENTILE = 0.01D; + private static final double HIGH_HEIGHT_PERCENTILE = 0.99D; + private static final double HEIGHT_RANGE_STEP_BLOCKS = 8D; + private static final double MINIMUM_WATER_DEPTH_BLOCKS = 8D; + private static final double MINIMUM_LAND_HEIGHT_BLOCKS = 16D; private MapCensus() { } - static List count(VisionRenderController.Frame frame, double minimumX, double minimumZ, double maximumX, - double maximumZ, ToIntFunction colorOf) { + static List count(VisionRenderController.Frame frame, ViewArea area, ToIntFunction colorOf) { + Map counts = new HashMap<>(); + MapLabels[] labels = new MapLabels[1]; + long counted = visitVisiblePixels(frame, area, (tile, pixelX, pixelZ) -> { + labels[0] = tile.labels(); + counts.computeIfAbsent(tile.subjectAt(pixelX, pixelZ), id -> new long[1])[0]++; + }); + return shares(counts, labels[0], counted, colorOf); + } + + /** + * The heights on screen, ignoring the highest and lowest few percent so one spike doesn't wash out every colour. + * Rounded to whole steps, so panning around doesn't recolour the map for every small change. + * + * @return the range, or null until enough of the map is drawn to tell + */ + static MapStyle.HeightRange heightRange(VisionRenderController.Frame frame, ViewArea area, double seaLevel) { + DoubleList heights = new DoubleList(); + visitVisiblePixels(frame, area, (tile, pixelX, pixelZ) -> { + if (tile.hasHeights()) { + heights.add(tile.heightAt(pixelX, pixelZ)); + } + }); + if (heights.size() < MINIMUM_HEIGHT_SAMPLES) { + return null; + } + double[] sorted = heights.sorted(); + double low = sorted[(int) ((sorted.length - 1) * LOW_HEIGHT_PERCENTILE)]; + double high = sorted[(int) ((sorted.length - 1) * HIGH_HEIGHT_PERCENTILE)]; + double lowest = Math.floor(Math.min(low, seaLevel - MINIMUM_WATER_DEPTH_BLOCKS) / HEIGHT_RANGE_STEP_BLOCKS) + * HEIGHT_RANGE_STEP_BLOCKS; + double highest = Math.ceil(Math.max(high, seaLevel + MINIMUM_LAND_HEIGHT_BLOCKS) / HEIGHT_RANGE_STEP_BLOCKS) + * HEIGHT_RANGE_STEP_BLOCKS; + return new MapStyle.HeightRange(lowest, seaLevel, highest); + } + + private static long visitVisiblePixels(VisionRenderController.Frame frame, ViewArea area, PixelVisitor visitor) { if (frame == null) { - return List.of(); + return 0L; } double blocksPerPixel = frame.renderBlocksPerPixel(); - int stride = countingStride(maximumX - minimumX, maximumZ - minimumZ, blocksPerPixel); + int stride = countingStride(area.maximumX() - area.minimumX(), area.maximumZ() - area.minimumZ(), blocksPerPixel); double tileSpan = VisionRenderController.TILE_PIXELS * blocksPerPixel; - Map counts = new HashMap<>(); - MapLabels labels = null; - long counted = 0L; + long visited = 0L; for (VisionRenderController.VisibleTile visible : frame.tiles()) { MapTile tile = visible.tile(); if (tile == null) { continue; } - labels = tile.labels(); double originX = visible.tileX() * tileSpan; double originZ = visible.tileZ() * tileSpan; - int firstX = firstPixel(minimumX - originX, blocksPerPixel, stride, visible.tileX()); - int firstZ = firstPixel(minimumZ - originZ, blocksPerPixel, stride, visible.tileZ()); - int endX = endPixel(maximumX - originX, blocksPerPixel, tile.size()); - int endZ = endPixel(maximumZ - originZ, blocksPerPixel, tile.size()); + int firstX = firstPixel(area.minimumX() - originX, blocksPerPixel, stride, visible.tileX()); + int firstZ = firstPixel(area.minimumZ() - originZ, blocksPerPixel, stride, visible.tileZ()); + int endX = endPixel(area.maximumX() - originX, blocksPerPixel, tile.size()); + int endZ = endPixel(area.maximumZ() - originZ, blocksPerPixel, tile.size()); for (int pixelZ = firstZ; pixelZ < endZ; pixelZ += stride) { for (int pixelX = firstX; pixelX < endX; pixelX += stride) { - counts.computeIfAbsent(tile.subjectAt(pixelX, pixelZ), id -> new long[1])[0]++; - counted++; + visitor.visit(tile, pixelX, pixelZ); + visited++; } } } - return shares(counts, labels, counted, colorOf); + return visited; } private static List shares(Map counts, MapLabels labels, long counted, @@ -106,4 +147,37 @@ final class MapCensus { record Share(MapLabels.Label label, int color, double fraction) { } + + /** + * The world area the screen shows, in blocks. + */ + record ViewArea(double minimumX, double minimumZ, double maximumX, double maximumZ) { + } + + @FunctionalInterface + private interface PixelVisitor { + void visit(MapTile tile, int pixelX, int pixelZ); + } + + private static final class DoubleList { + private double[] values = new double[1024]; + private int size; + + void add(double value) { + if (size == values.length) { + values = Arrays.copyOf(values, size * 2); + } + values[size++] = value; + } + + int size() { + return size; + } + + double[] sorted() { + double[] sorted = Arrays.copyOf(values, size); + Arrays.sort(sorted); + return sorted; + } + } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java b/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java index d23b7b6f8..8bdbe61c5 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java +++ b/core/src/main/java/com/volmit/iris/core/gui/MapWindow.java @@ -87,7 +87,7 @@ public final class MapWindow implements MapCanvas.Listener { private MapWindow(PaletteExplorerStart start) { this.start = start; this.frame = GuiHost.createFrame("Iris Map"); - this.canvas = new MapCanvas(start.engine(), start.playerId(), this); + this.canvas = new MapCanvas(start.engine(), start.worldId(), start.playerId(), this); this.viewList = new MapViewList(this::showView); this.viewListScroll = StudioToolbar.scroll(viewList); this.legendPanel = new MapLegendPanel(share -> canvas.toggleHighlight(share.label())); diff --git a/core/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java b/core/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java index 6749fed99..5a58122ca 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java +++ b/core/src/main/java/com/volmit/iris/core/gui/components/IrisRenderer.java @@ -51,6 +51,8 @@ public final class IrisRenderer { private static final int UNKNOWN_SUBJECT = rgb(90, 92, 98); private static final int LARGEST_SAMPLE_BLOCK_PIXELS = 16; private static final double LARGEST_SAMPLE_BLOCK_BLOCKS = 64D; + private static final double DEFAULT_DEPTH_BELOW_SEA_BLOCKS = 48D; + private static final double DEFAULT_HEIGHT_ABOVE_SEA_BLOCKS = 160D; private final Engine engine; private final MapLabels labels = new MapLabels(); @@ -97,9 +99,9 @@ public final class IrisRenderer { subjects[index] = subjectOf(identities[index]); regions[index] = regionOf(identities[index]); } - int[] colors = tileColors(size, type, subjects, heights); + int[] colors = type == RenderType.HEIGHT ? null : tileColors(size, type, subjects); MapLabels.Kind subjectKind = type == RenderType.REGION ? MapLabels.Kind.REGION : MapLabels.Kind.BIOME; - return new MapTile(size, blocksPerPixel, labels, subjectKind, colors, subjects, regions, heights); + return new MapTile(size, blocksPerPixel, labels, subjectKind, colors, defaultHeightRange(), subjects, regions, heights); } /** @@ -110,16 +112,24 @@ public final class IrisRenderer { return labelColor(label.id(), colorType); } - public static int heightColor(double height, double maximumHeight, double fluidHeight) { - double boundedMaximum = Math.max(1D, maximumHeight); - double boundedHeight = clamp(height, 0D, boundedMaximum); - double boundedFluid = clamp(fluidHeight, 0D, boundedMaximum); - if (boundedHeight <= boundedFluid && boundedFluid > 0D) { - return blend(DEEP_WATER, SHALLOW_WATER, boundedHeight / boundedFluid); + /** + * Until the map has measured the heights on screen, land colours cover the band most terrain sits in. + */ + public MapStyle.HeightRange defaultHeightRange() { + double seaLevel = fluidHeight(); + return new MapStyle.HeightRange(Math.max(0D, seaLevel - DEFAULT_DEPTH_BELOW_SEA_BLOCKS), seaLevel, + Math.min(engine.getHeight(), seaLevel + DEFAULT_HEIGHT_ABOVE_SEA_BLOCKS)); + } + + public static int heightColor(double height, MapStyle.HeightRange range) { + double seaLevel = range.seaLevel(); + if (height <= seaLevel) { + double waterDepthRange = Math.max(1D, seaLevel - range.lowest()); + return blend(DEEP_WATER, SHALLOW_WATER, (height - range.lowest()) / waterDepthRange); } - double landRange = Math.max(1D, boundedMaximum - boundedFluid); - double land = clamp((boundedHeight - boundedFluid) / landRange, 0D, 1D); + double landRange = Math.max(1D, range.highest() - seaLevel); + double land = clamp((height - seaLevel) / landRange, 0D, 1D); if (land < 0.45D) { return blend(LOWLAND, HIGHLAND, land / 0.45D); } @@ -168,19 +178,13 @@ public final class IrisRenderer { }; } - private int[] tileColors(int size, RenderType type, short[] subjects, double[] heights) { + private int[] tileColors(int size, RenderType type, short[] subjects) { int[] colors = new int[size * size]; - double maximumHeight = engine.getHeight(); - double fluidHeight = fluidHeight(); short previousSubject = MapLabels.NONE; int previousColor = UNKNOWN_SUBJECT; for (int pixelZ = 0; pixelZ < size; pixelZ++) { for (int pixelX = 0; pixelX < size; pixelX++) { int gridIndex = (pixelZ + 1) * (size + 2) + pixelX + 1; - if (type == RenderType.HEIGHT) { - colors[pixelZ * size + pixelX] = heightColor(heights[gridIndex], maximumHeight, fluidHeight); - continue; - } short subject = subjects[gridIndex]; if (subject != previousSubject) { previousSubject = subject; diff --git a/core/src/main/java/com/volmit/iris/core/gui/components/MapStyle.java b/core/src/main/java/com/volmit/iris/core/gui/components/MapStyle.java index cc7263b74..71a6b9368 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/components/MapStyle.java +++ b/core/src/main/java/com/volmit/iris/core/gui/components/MapStyle.java @@ -22,22 +22,33 @@ package com.volmit.iris.core.gui.components; * How map tiles are drawn on top of their plain colours: terrain shading, region borders and one highlighted biome or region. * * @param highlight the biome or region to keep bright while everything else dims, or null to show all + * @param heightRange the heights the height view spreads its colours over, or null outside the height view */ -public record MapStyle(boolean shading, boolean regionBorders, Highlight highlight) { - public static final MapStyle PLAIN = new MapStyle(false, false, null); +public record MapStyle(boolean shading, boolean regionBorders, Highlight highlight, HeightRange heightRange) { + public static final MapStyle PLAIN = new MapStyle(false, false, null, null); public MapStyle withShading(boolean nextShading) { - return new MapStyle(nextShading, regionBorders, highlight); + return new MapStyle(nextShading, regionBorders, highlight, heightRange); } public MapStyle withRegionBorders(boolean nextRegionBorders) { - return new MapStyle(shading, nextRegionBorders, highlight); + return new MapStyle(shading, nextRegionBorders, highlight, heightRange); } public MapStyle withHighlight(Highlight nextHighlight) { - return new MapStyle(shading, regionBorders, nextHighlight); + return new MapStyle(shading, regionBorders, nextHighlight, heightRange); + } + + public MapStyle withHeightRange(HeightRange nextHeightRange) { + return new MapStyle(shading, regionBorders, highlight, nextHeightRange); } public record Highlight(MapLabels.Kind kind, String key, String name) { } + + /** + * Water colours run from the lowest height up to sea level, land colours from sea level up to the highest. + */ + public record HeightRange(double lowest, double seaLevel, double highest) { + } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/components/MapTile.java b/core/src/main/java/com/volmit/iris/core/gui/components/MapTile.java index df50b56bd..5b9967149 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/components/MapTile.java +++ b/core/src/main/java/com/volmit/iris/core/gui/components/MapTile.java @@ -51,19 +51,25 @@ public final class MapTile { private final MapLabels labels; private final MapLabels.Kind subjectKind; private final int[] colors; + private final MapStyle.HeightRange defaultHeightRange; private final short[] subjects; private final short[] regions; private final double[] heights; private MapStyle styledFor; private BufferedImage styledImage; - MapTile(int size, double blocksPerPixel, MapLabels labels, MapLabels.Kind subjectKind, int[] colors, short[] subjects, - short[] regions, double[] heights) { + /** + * @param colors the colour of every pixel, or null to colour the pixels by their height when they are drawn + * @param defaultHeightRange the height colours to use until the map has measured the heights on screen + */ + MapTile(int size, double blocksPerPixel, MapLabels labels, MapLabels.Kind subjectKind, int[] colors, + MapStyle.HeightRange defaultHeightRange, short[] subjects, short[] regions, double[] heights) { this.size = size; this.blocksPerPixel = blocksPerPixel; this.labels = labels; this.subjectKind = subjectKind; this.colors = colors; + this.defaultHeightRange = defaultHeightRange; this.subjects = subjects; this.regions = regions; this.heights = heights; @@ -96,11 +102,16 @@ public final class MapTile { return regions[gridIndex(pixelX, pixelZ)]; } + public double heightAt(int pixelX, int pixelZ) { + return heights[gridIndex(pixelX, pixelZ)]; + } + public long bytes() { long styledBytes = (long) size * size * Integer.BYTES; long gridCells = (long) (size + 2) * (size + 2); long heightBytes = heights == null ? 0L : gridCells * Double.BYTES; - return (long) colors.length * Integer.BYTES + gridCells * Short.BYTES * 2L + heightBytes + styledBytes; + long colorBytes = colors == null ? 0L : (long) colors.length * Integer.BYTES; + return colorBytes + gridCells * Short.BYTES * 2L + heightBytes + styledBytes; } /** @@ -121,9 +132,12 @@ public final class MapTile { short highlighted = highlightGrid == null ? MapLabels.NONE : labels.idOf(style.highlight().kind(), style.highlight().key()); + MapStyle.HeightRange heightRange = style.heightRange() == null ? defaultHeightRange : style.heightRange(); for (int pixelZ = 0; pixelZ < size; pixelZ++) { for (int pixelX = 0; pixelX < size; pixelX++) { - int color = colors[pixelZ * size + pixelX]; + int color = colors == null + ? IrisRenderer.heightColor(heightAt(pixelX, pixelZ), heightRange) + : colors[pixelZ * size + pixelX]; if (shading) { color = shade(color, pixelX, pixelZ); }