Skip to content

Commit de86bfc

Browse files
authored
Merge pull request #6000 from kwvanderlinde/bugfix/5581-grid-renderer
Fix GridRenderer hygiene
2 parents b24d1ca + 658e615 commit de86bfc

7 files changed

Lines changed: 275 additions & 406 deletions

File tree

src/main/java/net/rptools/maptool/client/ui/zone/renderer/GridRenderer.java

Lines changed: 271 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -15,60 +15,51 @@
1515
package net.rptools.maptool.client.ui.zone.renderer;
1616

1717
import com.github.weisj.jsvg.util.ColorUtil;
18-
import com.google.common.eventbus.Subscribe;
1918
import java.awt.*;
19+
import java.awt.geom.AffineTransform;
20+
import java.awt.geom.Line2D;
21+
import java.awt.geom.Path2D;
22+
import javax.swing.SwingUtilities;
2023
import net.rptools.lib.image.ImageUtil;
2124
import net.rptools.maptool.client.AppState;
25+
import net.rptools.maptool.client.ScreenPoint;
26+
import net.rptools.maptool.client.swing.SwingUtil;
27+
import net.rptools.maptool.client.ui.Scale;
2228
import net.rptools.maptool.client.ui.zone.PlayerView;
23-
import net.rptools.maptool.events.MapToolEventBus;
2429
import net.rptools.maptool.model.*;
25-
import net.rptools.maptool.model.zones.GridChanged;
30+
import org.apache.logging.log4j.LogManager;
31+
import org.apache.logging.log4j.Logger;
2632

2733
public class GridRenderer {
34+
private static final Logger log = LogManager.getLogger(GridRenderer.class);
35+
2836
private final ZoneRenderer renderer;
29-
private Zone zone;
30-
private static float gridLineWeight;
31-
private static float scale;
32-
private static float baseWidth = 2f;
33-
private static Color[] gridColours;
34-
private int baseColourInt = -1;
37+
private final Zone zone;
3538

3639
GridRenderer(ZoneRenderer renderer) {
37-
new MapToolEventBus().getMainEventBus().register(this);
3840
this.renderer = renderer;
3941
this.zone = renderer.getZone();
40-
setGridColours();
4142
}
4243

43-
@SuppressWarnings("unused")
44-
@Subscribe
45-
private void onGridChanged(GridChanged event) {
46-
if (event.zone() != null) {
47-
this.zone = event.zone();
48-
setGridColours();
49-
}
44+
private static Color[] getGridColours(int gridColourInt) {
45+
Color gc = new Color(gridColourInt);
46+
Color contrast = new Color(ImageUtil.negativeColourInt(gridColourInt));
47+
return new Color[] {
48+
gc,
49+
ColorUtil.withAlpha(gc, 0.14f),
50+
ColorUtil.withAlpha(contrast, 0.04f),
51+
ColorUtil.withAlpha(contrast, 0.05f)
52+
};
5053
}
5154

52-
private void setGridColours() {
53-
int gridColourInt = zone.getGridColor();
54-
if (this.baseColourInt == gridColourInt) {
55-
return;
56-
}
57-
this.baseColourInt = gridColourInt;
58-
Color gc = new Color(baseColourInt);
59-
Color contrast = new Color(ImageUtil.negativeColourInt(baseColourInt));
60-
gridColours =
61-
new Color[] {
62-
gc,
63-
ColorUtil.withAlpha(gc, 0.14f),
64-
ColorUtil.withAlpha(contrast, 0.04f),
65-
ColorUtil.withAlpha(contrast, 0.05f)
66-
};
67-
}
55+
private void drawGridShape(
56+
Scale zoneScale, int gridSize, Color[] gridColours, Graphics2D g, Shape shape) {
57+
final var gridLineWeight = AppState.getGridLineWeight();
58+
final var scale = (float) zoneScale.getScale();
59+
final var baseWidth = gridSize / 50f;
6860

69-
public static void drawGridShape(Graphics2D g, Shape shape) {
70-
if (scale > 0.49f) {
71-
for (int i = 3; i > -1; i--) {
61+
if (scale > 0.49f && gridColours.length > 1) {
62+
for (int i = gridColours.length - 1; i > -1; i--) {
7263
g.setColor(gridColours[i]);
7364
g.setStroke(
7465
new BasicStroke(
@@ -92,23 +83,256 @@ public static void drawGridShape(Graphics2D g, Shape shape) {
9283
}
9384

9485
public void renderGrid(Graphics2D g, PlayerView view) {
95-
if (!AppState.isShowGrid()
96-
|| zone.getGrid().getSize() * renderer.getViewModel().getZoneScale().getScale()
97-
< ZoneRendererConstants.MIN_GRID_SIZE) {
86+
if (!AppState.isShowGrid()) {
9887
return;
9988
}
100-
gridLineWeight = AppState.getGridLineWeight();
101-
scale = (float) renderer.getViewModel().getZoneScale().getScale();
102-
baseWidth = zone.getGrid().getSize() / 50f;
103-
if (gridColours == null) {
104-
setGridColours();
89+
90+
var grid = zone.getGrid();
91+
var zoneScale = renderer.getViewModel().getZoneScale();
92+
93+
if (grid.getSize() * zoneScale.getScale() < ZoneRendererConstants.MIN_GRID_SIZE) {
94+
return;
95+
}
96+
97+
var gridColours = getGridColours(zone.getGridColor());
98+
99+
var bounds = new Rectangle(0, 0, renderer.getWidth(), renderer.getHeight());
100+
switch (grid) {
101+
case SquareGrid squareGrid -> draw(g, squareGrid, gridColours, bounds);
102+
case HexGrid hexGrid -> draw(g, hexGrid, gridColours, bounds);
103+
case IsometricGrid isometricGrid -> draw(g, isometricGrid, gridColours, bounds);
104+
case GridlessGrid gridlessGrid -> {
105+
/* Nothing to do */
106+
}
107+
default -> {
108+
log.error("Unknown grid type: {}", grid.getClass());
109+
}
105110
}
106-
zone.getGrid().draw(renderer, g, g.getClipBounds());
107111
}
108112

109113
public void renderCoordinates(Graphics2D g, PlayerView view) {
110114
if (AppState.isShowCoordinates()) {
111-
zone.getGrid().drawCoordinatesOverlay(g, this.renderer);
115+
var grid = zone.getGrid();
116+
var bounds = new Rectangle(0, 0, renderer.getWidth(), renderer.getHeight());
117+
switch (grid) {
118+
case SquareGrid squareGrid -> drawCoordinates(g, squareGrid, bounds);
119+
// We only support coordinates for square grids at this time.
120+
case HexGrid hexGrid -> {
121+
/* Nothing to do */
122+
}
123+
case IsometricGrid isometricGrid -> {
124+
/* Nothing to do */
125+
}
126+
case GridlessGrid gridlessGrid -> {
127+
/* Nothing to do */
128+
}
129+
default -> {
130+
log.error("Unknown grid type: {}", grid.getClass());
131+
}
132+
}
133+
}
134+
}
135+
136+
private void draw(Graphics2D g, SquareGrid grid, Color[] gridColours, Rectangle bounds) {
137+
var size = grid.getSize();
138+
var zoneScale = renderer.getViewModel().getZoneScale();
139+
double scale = zoneScale.getScale();
140+
double scaledSize = size * scale;
141+
142+
g.setColor(new Color(zone.getGridColor()));
143+
144+
int offX = (int) (zoneScale.getOffsetX() % scaledSize + grid.getOffsetX() * scale);
145+
int offY = (int) (zoneScale.getOffsetY() % scaledSize + grid.getOffsetY() * scale);
146+
147+
int startCol = (int) ((int) (bounds.x / scaledSize) * scaledSize);
148+
int startRow = (int) ((int) (bounds.y / scaledSize) * scaledSize);
149+
Path2D path = new Path2D.Double();
150+
for (double row = startRow; row < bounds.y + bounds.height + scaledSize; row += scaledSize) {
151+
path.append(
152+
new Line2D.Double(
153+
bounds.x, (int) (row + offY), bounds.x + bounds.width, (int) (row + offY)),
154+
false);
155+
}
156+
for (double col = startCol; col < bounds.x + bounds.width + scaledSize; col += scaledSize) {
157+
path.append(
158+
new Line2D.Double(
159+
(int) (col + offX), bounds.y, (int) (col + offX), bounds.y + bounds.height),
160+
false);
161+
}
162+
drawGridShape(zoneScale, size, gridColours, g, path);
163+
}
164+
165+
private void draw(Graphics2D g, HexGrid grid, Color[] gridColours, Rectangle bounds) {
166+
var isHorizontal = grid.getType() == Grid.GridType.HexHorizontal;
167+
var zoneScale = renderer.getViewModel().getZoneScale();
168+
var scale = zoneScale.getScale();
169+
var scaledMinorRadius = grid.getMinorRadius() * scale;
170+
var scaledEdgeLength = grid.getEdgeLength() * scale;
171+
var scaledEdgeProjection = grid.getEdgeProjection() * scale;
172+
var scaledHex =
173+
createHexHalfShape(isHorizontal, scaledMinorRadius, scaledEdgeProjection, scaledEdgeLength);
174+
175+
int offU = grid.getOffU(zoneScale);
176+
int offV = grid.getOffV(zoneScale);
177+
int count = 0;
178+
179+
Object oldAntiAlias = SwingUtil.useAntiAliasing(g);
180+
g.setColor(new Color(zone.getGridColor()));
181+
g.setStroke(new BasicStroke(AppState.getGridLineWeight()));
182+
183+
for (double v = offV % (scaledMinorRadius * 2) - (scaledMinorRadius * 2);
184+
v < grid.getSizeV(bounds.getSize());
185+
v += scaledMinorRadius) {
186+
double offsetU = (int) ((count & 1) == 0 ? 0 : -(scaledEdgeProjection + scaledEdgeLength));
187+
count++;
188+
189+
double start =
190+
offU % (2 * scaledEdgeLength + 2 * scaledEdgeProjection)
191+
- (2 * scaledEdgeLength + 2 * scaledEdgeProjection);
192+
double end =
193+
grid.getSizeU(bounds.getSize()) + 2 * scaledEdgeLength + 2 * scaledEdgeProjection;
194+
double incr = 2 * scaledEdgeLength + 2 * scaledEdgeProjection;
195+
for (double u = start; u < end; u += incr) {
196+
var translateX = isHorizontal ? v : u + offsetU;
197+
var translateY = isHorizontal ? u + offsetU : v;
198+
199+
g.translate(translateX, translateY);
200+
201+
drawGridShape(zoneScale, grid.getSize(), gridColours, g, scaledHex);
202+
203+
// Undo the translation.
204+
g.translate(-translateX, -translateY);
205+
}
206+
}
207+
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntiAlias);
208+
}
209+
210+
private Path2D createHexHalfShape(
211+
boolean isHorizontal, double minorRadius, double edgeProjection, double edgeLength) {
212+
var hex = new Path2D.Double();
213+
hex.moveTo(0, minorRadius);
214+
hex.lineTo(edgeProjection, 0);
215+
hex.lineTo(edgeProjection + edgeLength, 0);
216+
hex.lineTo(edgeProjection + edgeLength + edgeProjection, minorRadius);
217+
218+
if (isHorizontal) {
219+
// flip the half-hex over y = x
220+
AffineTransform at = new AffineTransform();
221+
at.rotate(Math.toRadians(90.0));
222+
at.scale(1, -1);
223+
hex.transform(at);
224+
}
225+
226+
return hex;
227+
}
228+
229+
private void draw(Graphics2D g, IsometricGrid grid, Color[] gridColours, Rectangle bounds) {
230+
var size = grid.getSize();
231+
var zoneScale = renderer.getViewModel().getZoneScale();
232+
double scale = zoneScale.getScale();
233+
double gridSize = size * scale;
234+
double isoHeight = size * scale;
235+
double isoWidth = size * 2 * scale;
236+
Path2D path = new Path2D.Double();
237+
238+
int offX = (int) (zoneScale.getOffsetX() % isoWidth + grid.getOffsetX() * scale);
239+
int offY = (int) (zoneScale.getOffsetY() % gridSize + grid.getOffsetY() * scale);
240+
241+
int startCol = (int) ((int) (bounds.x / isoWidth) * isoWidth);
242+
int startRow = (int) ((int) (bounds.y / gridSize) * gridSize);
243+
244+
for (double row = startRow; row < bounds.y + bounds.height + gridSize; row += gridSize) {
245+
for (double col = startCol; col < bounds.x + bounds.width + isoWidth; col += isoWidth) {
246+
path.append(drawIsoHatch(zoneScale, size, (int) (col + offX), (int) (row + offY)), false);
247+
}
248+
}
249+
250+
for (double row = startRow - (isoHeight / 2);
251+
row < bounds.y + bounds.height + gridSize;
252+
row += gridSize) {
253+
for (double col = startCol - (isoWidth / 2);
254+
col < bounds.x + bounds.width + isoWidth;
255+
col += isoWidth) {
256+
path.append(drawIsoHatch(zoneScale, size, (int) (col + offX), (int) (row + offY)), false);
257+
}
258+
}
259+
drawGridShape(zoneScale, size, gridColours, g, path);
260+
}
261+
262+
private Shape drawIsoHatch(Scale zoneScale, int gridSize, int x, int y) {
263+
double isoWidth = gridSize * zoneScale.getScale();
264+
int hatchSize = isoWidth > 10 ? (int) isoWidth / 8 : 2;
265+
Path2D path = new Path2D.Double();
266+
path.append(
267+
new Line2D.Double(x - (hatchSize * 2), y - hatchSize, x + (hatchSize * 2), y + hatchSize),
268+
false);
269+
path.append(
270+
new Line2D.Double(x - (hatchSize * 2), y + hatchSize, x + (hatchSize * 2), y - hatchSize),
271+
false);
272+
return path;
273+
}
274+
275+
private void drawCoordinates(Graphics2D g, SquareGrid grid, Rectangle bounds) {
276+
Object oldAA = SwingUtil.useAntiAliasing(g);
277+
278+
Font oldFont = g.getFont();
279+
g.setFont(g.getFont().deriveFont(20f).deriveFont(Font.BOLD));
280+
FontMetrics fm = g.getFontMetrics();
281+
282+
var zoneScale = renderer.getViewModel().getZoneScale();
283+
double cellSize = grid.getSize() * zoneScale.getScale();
284+
CellPoint topLeft = grid.convert(new ScreenPoint(0, 0).convertToZone(zoneScale));
285+
var topLeftZone = grid.convert(topLeft);
286+
ScreenPoint sp = zoneScale.toScreenSpace(topLeftZone.x, topLeftZone.y);
287+
288+
Dimension size = bounds.getSize();
289+
290+
int startX = SwingUtilities.computeStringWidth(fm, "MMM") + 10;
291+
292+
double x = sp.x + cellSize / 2; // Start at middle of the cell that's on screen
293+
int nextAvailableSpace = -1;
294+
while (x < size.width) {
295+
String coord = Integer.toString(topLeft.x);
296+
297+
int strWidth = SwingUtilities.computeStringWidth(fm, coord);
298+
int strX = (int) x - strWidth / 2;
299+
300+
if (x > startX && strX > nextAvailableSpace) {
301+
g.setColor(Color.black);
302+
g.drawString(coord, strX - 1, fm.getHeight() - 1);
303+
g.drawString(coord, strX + 1, fm.getHeight() - 1);
304+
g.drawString(coord, strX - 1, fm.getHeight() + 1);
305+
g.drawString(coord, strX + 1, fm.getHeight() + 1);
306+
g.setColor(Color.orange);
307+
g.drawString(coord, strX, fm.getHeight());
308+
309+
nextAvailableSpace = strX + strWidth + 10;
310+
}
311+
x += cellSize;
312+
topLeft.x++;
313+
}
314+
double y = sp.y + cellSize / 2; // Start at middle of the cell that's on screen
315+
nextAvailableSpace = -1;
316+
while (y < size.height) {
317+
String coord = SquareGrid.decimalToAlphaCoord(topLeft.y);
318+
319+
int strY = (int) y + fm.getAscent() / 2;
320+
321+
if (y > fm.getHeight() && strY > nextAvailableSpace) {
322+
g.setColor(Color.black);
323+
g.drawString(coord, 10 - 1, strY - 1);
324+
g.drawString(coord, 10 + 1, strY - 1);
325+
g.drawString(coord, 10 - 1, strY + 1);
326+
g.drawString(coord, 10 + 1, strY + 1);
327+
g.setColor(Color.yellow);
328+
g.drawString(coord, 10, strY);
329+
330+
nextAvailableSpace = strY + fm.getAscent() / 2 + 10;
331+
}
332+
y += cellSize;
333+
topLeft.y++;
112334
}
335+
g.setFont(oldFont);
336+
SwingUtil.restoreAntiAliasing(g, oldAA);
113337
}
114338
}

0 commit comments

Comments
 (0)