1515package net .rptools .maptool .client .ui .zone .renderer ;
1616
1717import com .github .weisj .jsvg .util .ColorUtil ;
18- import com .google .common .eventbus .Subscribe ;
1918import 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 ;
2023import net .rptools .lib .image .ImageUtil ;
2124import 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 ;
2228import net .rptools .maptool .client .ui .zone .PlayerView ;
23- import net .rptools .maptool .events .MapToolEventBus ;
2429import 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
2733public 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