8c3e49f51218b82d7a8b7a66d4b25083648873df
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / renderer / raster / shapes / composite / textcanvas / TextCanvas.java
1 /*
2  * Sixth 3D engine. Author: Svjatoslav Agejenko.
3  * This project is released under Creative Commons Zero (CC0) license.
4  */
5 package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas;
6
7 import eu.svjatoslav.sixth.e3d.geometry.Point3D;
8 import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
9 import eu.svjatoslav.sixth.e3d.gui.TextPointer;
10 import eu.svjatoslav.sixth.e3d.math.Transform;
11 import eu.svjatoslav.sixth.e3d.math.TransformsStack;
12 import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
13 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.TexturedRectangle;
14
15 import java.awt.*;
16 import java.io.BufferedReader;
17 import java.io.IOException;
18 import java.io.StringReader;
19
20 import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.BLACK;
21 import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.WHITE;
22 import static java.lang.Math.PI;
23 import static java.lang.Math.abs;
24
25 public class TextCanvas extends TexturedRectangle {
26
27     /**
28      * Font character width in world coordinates.
29      */
30     public static final int FONT_CHAR_WIDTH = 8;
31
32     /**
33      * Font character height in world coordinates.
34      */
35     public static final int FONT_CHAR_HEIGHT = 16;
36
37     /**
38       * Font character width in texture pixels.
39       */
40     public static final int FONT_CHAR_WIDTH_TEXTURE_PIXELS = 16;
41
42     /**
43      * Font character height in texture pixels.
44      */
45     public static final int FONT_CHAR_HEIGHT_TEXTURE_PIXELS = 32;
46
47
48     public static final Font FONT = CanvasCharacter.getFont((int) (FONT_CHAR_HEIGHT_TEXTURE_PIXELS / 1.066));
49     private static final String GROUP_TEXTURE = "texture";
50     private static final String GROUP_CHARACTERS = "characters";
51     private final TextPointer size;
52     private final TextPointer cursorLocation = new TextPointer();
53     CanvasCharacter[][] lines;
54     private RenderMode renderMode = null;
55     private Color backgroundColor = BLACK;
56     private Color foregroundColor = WHITE;
57
58     public TextCanvas(final Transform location, final String text,
59                       final Color foregroundColor, final Color backgroundColor) {
60         this(location, getTextDimensions(text), foregroundColor,
61                 backgroundColor);
62         setText(text);
63     }
64
65     public TextCanvas(final Transform location, final TextPointer dimensions,
66                       final Color foregroundColor, final Color backgroundColor) {
67         super(location);
68         getRelativityTracker().enableOrientationTracking();
69
70         size = dimensions;
71         final int columns = dimensions.column;
72         final int rows = dimensions.row;
73
74         this.backgroundColor = backgroundColor;
75         this.foregroundColor = foregroundColor;
76
77         // initialize underlying textured rectangle
78         initialize(
79                 columns * FONT_CHAR_WIDTH,
80                 rows * FONT_CHAR_HEIGHT,
81                 columns * FONT_CHAR_WIDTH_TEXTURE_PIXELS,
82                 rows * FONT_CHAR_HEIGHT_TEXTURE_PIXELS,
83                 0);
84
85         getTexture().primaryBitmap.fillColor(backgroundColor);
86         getTexture().resetResampledBitmapCache();
87
88         setGroupForUngrouped(GROUP_TEXTURE);
89
90         lines = new CanvasCharacter[rows][];
91         for (int row = 0; row < rows; row++) {
92             lines[row] = new CanvasCharacter[columns];
93
94             for (int column = 0; column < columns; column++) {
95                 final Point3D characterCoordinate = getCharLocation(row, column);
96
97                 final CanvasCharacter character = new CanvasCharacter(
98                         characterCoordinate, ' ', foregroundColor,
99                         backgroundColor);
100                 addShape(character);
101                 lines[row][column] = character;
102             }
103
104         }
105
106         setGroupForUngrouped(GROUP_CHARACTERS);
107
108         setRenderMode(RenderMode.TEXTURE);
109     }
110
111     public static TextPointer getTextDimensions(final String text) {
112
113         final BufferedReader reader = new BufferedReader(new StringReader(text));
114
115         int rows = 0;
116         int columns = 0;
117
118         while (true) {
119             final String line;
120             try {
121                 line = reader.readLine();
122             } catch (IOException e) {
123                 throw new RuntimeException(e);
124             }
125
126             if (line == null)
127                 return new TextPointer(rows, columns);
128
129             rows++;
130             columns = Math.max(columns, line.length());
131         }
132     }
133
134     @Override
135     public void beforeTransformHook(final TransformsStack transformPipe,
136                                     final RenderingContext context) {
137         ensureOptimalRenderMode(context);
138     }
139
140     private void ensureOptimalRenderMode(RenderingContext context) {
141
142         // if the text is too far away, use texture
143         final double textRelativeSize = context.width / getRelativityTracker().getDistanceToUser();
144         if (textRelativeSize < 2d) {
145             setRenderMode(RenderMode.TEXTURE);
146             return;
147         }
148
149         // if user is looking at the text from the side, use texture
150         final double piHalf = PI / 2;
151         final double deviation = abs(getRelativityTracker().getAngleXZ()
152                 + piHalf)
153                 + abs(getRelativityTracker().getAngleYZ() + piHalf);
154
155         final double maxDeviation = 0.5;
156         setRenderMode(deviation > maxDeviation ? RenderMode.TEXTURE : RenderMode.CHARACTERS);
157     }
158
159     public void clear() {
160         for (final CanvasCharacter[] line : lines)
161             for (final CanvasCharacter character : line) {
162                 character.setValue(' ');
163                 character.setBackgroundColor(backgroundColor);
164                 character.setForegroundColor(foregroundColor);
165             }
166
167         // set background color
168         getTexture().primaryBitmap.fillColor(backgroundColor);
169         getTexture().resetResampledBitmapCache();
170     }
171
172     private void drawCharToTexture(final int row, final int column,
173                                    final char character, final Color foreground) {
174         final Graphics2D graphics = getTexture().graphics;
175
176         getTexture().primaryBitmap.drawRectangle(
177                 column * FONT_CHAR_WIDTH_TEXTURE_PIXELS,
178                 row * FONT_CHAR_HEIGHT_TEXTURE_PIXELS,
179                 (column * FONT_CHAR_WIDTH_TEXTURE_PIXELS) + FONT_CHAR_WIDTH_TEXTURE_PIXELS,
180                 (row * FONT_CHAR_HEIGHT_TEXTURE_PIXELS) + FONT_CHAR_HEIGHT_TEXTURE_PIXELS,
181                 backgroundColor);
182
183         graphics.setFont(FONT);
184         graphics.setColor(foreground.toAwtColor());
185         graphics.drawChars(
186                 new char[]{character,}, 0, 1,
187                 (column * FONT_CHAR_WIDTH_TEXTURE_PIXELS),
188                 (row * FONT_CHAR_HEIGHT_TEXTURE_PIXELS) + (int) (FONT_CHAR_HEIGHT_TEXTURE_PIXELS / 1.23f));
189
190         getTexture().resetResampledBitmapCache();
191     }
192
193     public Point3D getCharLocation(final int row, final int column) {
194         final Point3D coordinate = topLeft.clone();
195
196         coordinate.translateY((row * FONT_CHAR_HEIGHT)
197                 + (FONT_CHAR_HEIGHT / 3.2));
198
199         coordinate.translateX((column * FONT_CHAR_WIDTH)
200                 + (FONT_CHAR_WIDTH / 2));
201
202         return coordinate;
203     }
204
205     public TextPointer getSize() {
206         return size;
207     }
208
209     public void locate(final int row, final int column) {
210         cursorLocation.row = row;
211         cursorLocation.column = column;
212     }
213
214     public void print(final String text) {
215         for (final char c : text.toCharArray())
216             putChar(c);
217     }
218
219     public void putChar(final char character) {
220         putChar(cursorLocation, character);
221
222         cursorLocation.column++;
223         if (cursorLocation.column >= size.column) {
224             cursorLocation.column = 0;
225             cursorLocation.row++;
226         }
227     }
228
229     public void putChar(final int row, final int column, final char character) {
230         if ((row >= lines.length) || (row < 0))
231             return;
232
233         final CanvasCharacter[] line = lines[row];
234
235         if ((column >= line.length) || (column < 0))
236             return;
237
238         final CanvasCharacter canvasCharacter = line[column];
239         canvasCharacter.setValue(character);
240         canvasCharacter.setBackgroundColor(backgroundColor);
241         canvasCharacter.setForegroundColor(foregroundColor);
242         drawCharToTexture(row, column, character,
243                 foregroundColor);
244     }
245
246     public void putChar(final TextPointer location, final char character) {
247         putChar(location.row, location.column, character);
248     }
249
250     public void setBackgroundColor(
251             final eu.svjatoslav.sixth.e3d.renderer.raster.Color backgroundColor) {
252         this.backgroundColor = backgroundColor;
253     }
254
255     public void setForegroundColor(
256             final eu.svjatoslav.sixth.e3d.renderer.raster.Color foregroundColor) {
257         this.foregroundColor = foregroundColor;
258     }
259
260     private void setRenderMode(final RenderMode mode) {
261         if (mode == renderMode)
262             return;
263
264         switch (mode) {
265             case CHARACTERS:
266                 hideGroup(GROUP_TEXTURE);
267                 showGroup(GROUP_CHARACTERS);
268                 break;
269             case TEXTURE:
270                 hideGroup(GROUP_CHARACTERS);
271                 showGroup(GROUP_TEXTURE);
272                 break;
273         }
274
275         renderMode = mode;
276     }
277
278     public void setText(final String text) {
279         final BufferedReader reader = new BufferedReader(new StringReader(text));
280
281         int row = 0;
282
283         while (true) {
284             final String line;
285             try {
286                 line = reader.readLine();
287             } catch (IOException e) {
288                 throw new RuntimeException(e);
289             }
290
291             if (line == null)
292                 return;
293
294             int column = 0;
295             for (final char c : line.toCharArray()) {
296                 putChar(row, column, c);
297                 column++;
298             }
299             row++;
300         }
301     }
302
303     public void setTextColor(final Color color) {
304         for (final CanvasCharacter[] line : lines)
305             for (final CanvasCharacter character : line)
306                 character.setForegroundColor(color);
307     }
308
309 }