From e4f33c9c60294997a65b52bb4894eedbbff32b48 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Tue, 14 Jan 2025 23:58:28 +0100 Subject: [PATCH 01/14] Add font support with caching and style handling - Implemented `ProcessingFontManager` to manage font loading and caching. - Added font style handling (PLAIN, BOLD, ITALIC) for proper font creation. - Fonts are cached to avoid redundant loading, improving performance. - Integrated `Font` class with Processing's `PFont` for seamless font rendering. --- .../engine/application/BasicApplication.java | 2 + .../processing/ProcessingFontManager.java | 74 ++++++++++++ src/main/java/engine/resources/Font.java | 108 ++++++++++++++++++ src/main/java/workspace/GraphicsPImpl.java | 59 +++++++--- src/main/java/workspace/ui/Graphics2D.java | 14 +++ 5 files changed, 238 insertions(+), 19 deletions(-) create mode 100644 src/main/java/engine/processing/ProcessingFontManager.java create mode 100644 src/main/java/engine/resources/Font.java diff --git a/src/main/java/engine/application/BasicApplication.java b/src/main/java/engine/application/BasicApplication.java index aa70cff2..6df93aff 100644 --- a/src/main/java/engine/application/BasicApplication.java +++ b/src/main/java/engine/application/BasicApplication.java @@ -9,6 +9,7 @@ import engine.input.Input; import engine.input.Key; import engine.processing.ProcessingApplication; +import engine.resources.Font; import engine.scene.Scene; import engine.scene.SceneNode; import engine.scene.camera.PerspectiveCamera; @@ -148,6 +149,7 @@ private void renderUi(Graphics g) { private void renderDebugUi(Graphics g) { if (!displayInfo) return; + g.setFont(new Font("Lucida Sans", 12, Font.PLAIN)); debugOverlay.render(g); fpsGraph.render(g); } diff --git a/src/main/java/engine/processing/ProcessingFontManager.java b/src/main/java/engine/processing/ProcessingFontManager.java new file mode 100644 index 00000000..21b2d39e --- /dev/null +++ b/src/main/java/engine/processing/ProcessingFontManager.java @@ -0,0 +1,74 @@ +package engine.processing; + +import java.util.HashMap; +import java.util.Map; + +import engine.resources.Font; +import processing.core.PApplet; +import processing.core.PFont; + +/** + * A class to manage fonts in Processing by caching and loading fonts on demand. It supports + * different font styles such as plain, bold, and italic. + */ +public class ProcessingFontManager { + + /** Cache for fonts to avoid redundant font loading */ + private Map fontCache = new HashMap<>(); + + /** The PApplet instance used for creating fonts */ + private PApplet p; + + /** + * Constructor to initialize the font manager with a PApplet instance. + * + * @param p the PApplet instance used for creating fonts + */ + public ProcessingFontManager(PApplet p) { + this.p = p; + } + + /** + * Loads a font based on the provided Font object. If the font is already cached, it returns the + * cached version. Otherwise, it creates a new font and stores it in the cache. Font smoothing is + * enabled by default. + * + * @param font the Font object to load + * @return the corresponding PFont object + */ + public PFont loadFont(Font font) { + if (!fontCache.containsKey(font)) { + // Enable smoothing by default + boolean smooth = true; + // Create the PFont and add it to the cache + PFont pFont = p.createFont(getFontName(font), font.getSize(), smooth); + fontCache.put(font, pFont); + } + return fontCache.get(font); + } + + /** + * Constructs the font name string, appending the appropriate style based on the Font object's + * style (PLAIN, BOLD, or ITALIC). + * + * @param font the Font object + * @return the constructed font name string with the style + */ + private String getFontName(Font font) { + String style = ""; + switch (font.getStyle()) { + case Font.BOLD: + style = " Bold"; + break; + case Font.ITALIC: + style = " Italic"; + break; + case Font.PLAIN: + style = ""; + break; + default: + throw new IllegalArgumentException("Unexpected value: " + font.getStyle()); + } + return font.getName() + style; + } +} diff --git a/src/main/java/engine/resources/Font.java b/src/main/java/engine/resources/Font.java new file mode 100644 index 00000000..41c7b4a7 --- /dev/null +++ b/src/main/java/engine/resources/Font.java @@ -0,0 +1,108 @@ +package engine.resources; + +import java.util.Objects; + +/** + * Represents a font, including its name, size, and style. + * + *

The {@link Font} class encapsulates the characteristics of a font that can be used for text + * rendering. It includes properties for the font's name, size, and style (plain, bold, or italic). + * The font is immutable, meaning its properties cannot be modified after instantiation. + * + *

Font objects are typically used to define the appearance of text in a rendering context, where + * the font's name specifies the typeface, the size defines the text size, and the style determines + * whether the text is rendered in plain, bold, or italic form. + * + *

Font style constants are provided for ease of use: + * + *

+ */ +public class Font { + + // Font style constants + public static final int PLAIN = 0; + public static final int BOLD = 1; + public static final int ITALIC = 2; + + private final String name; + private final int size; + private final int style; + + /** + * Constructs a {@link Font} object with the specified name, size, and style. + * + *

This constructor allows you to create a font with a specific typeface, size, and style. If + * you need a plain font, you can use the {@link #PLAIN} constant; for bold or italic fonts, use + * the {@link #BOLD} or {@link #ITALIC} constants, respectively. + * + * @param name The name of the font (e.g., "Arial", "Times New Roman"). + * @param size The size of the font in points. + * @param style The style of the font, which can be one of the following: {@link #PLAIN}, {@link + * #BOLD}, or {@link #ITALIC}. + */ + public Font(String name, int size, int style) { + this.name = name; + this.size = size; + this.style = style; + } + + /** + * Retrieves the name of the font. + * + * @return The name of the font (e.g., "Arial"). + */ + public String getName() { + return name; + } + + /** + * Retrieves the size of the font. + * + * @return The size of the font in points. + */ + public int getSize() { + return size; + } + + /** + * Retrieves the style of the font. + * + * @return The style of the font, which can be one of the following: {@link #PLAIN}, {@link + * #BOLD}, or {@link #ITALIC}. + */ + public int getStyle() { + return style; + } + + /** + * Computes a hash code for this {@link Font} object based on its name, size, and style. + * + * @return The hash code for this font. + */ + @Override + public int hashCode() { + return Objects.hash(name, size, style); + } + + /** + * Compares this {@link Font} object to another object for equality. + * + *

This method returns {@code true} if the specified object is a {@link Font} and has the same + * name, size, and style as this font. Otherwise, it returns {@code false}. + * + * @param obj The object to compare this font to. + * @return {@code true} if the object is equal to this font, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Font other = (Font) obj; + return Objects.equals(name, other.name) && size == other.size && style == other.style; + } +} diff --git a/src/main/java/workspace/GraphicsPImpl.java b/src/main/java/workspace/GraphicsPImpl.java index 30698d59..e206a7bf 100644 --- a/src/main/java/workspace/GraphicsPImpl.java +++ b/src/main/java/workspace/GraphicsPImpl.java @@ -4,10 +4,12 @@ import engine.processing.LightGizmoRenderer; import engine.processing.LightRendererImpl; +import engine.processing.ProcessingFontManager; import engine.processing.ProcessingTexture; import engine.processing.VBOProcessing; import engine.render.Material; import engine.resources.FilterMode; +import engine.resources.Font; import engine.resources.Image; import engine.resources.Texture; import engine.resources.TextureWrapMode; @@ -21,6 +23,7 @@ import mesh.Face3D; import mesh.Mesh3D; import processing.core.PApplet; +import processing.core.PFont; import processing.core.PGraphics; import processing.core.PImage; import processing.opengl.PGraphicsOpenGL; @@ -53,20 +56,11 @@ public class GraphicsPImpl implements Graphics { private ProcessingTexture texture; - @Override - public void setAmbientColor(math.Color ambientColor) { - this.ambientColor = ambientColor; - } + private Font font; - @Override - public math.Color getAmbientColor() { - return ambientColor; - } + private PFont pFont; - @Override - public void setWireframeMode(boolean wireframeMode) { - this.wireframeMode = wireframeMode; - } + private ProcessingFontManager fontManager; public GraphicsPImpl(PApplet p) { this.g = p.g; @@ -79,10 +73,27 @@ public GraphicsPImpl(PApplet p) { lightGizmoRenderer = new LightGizmoRenderer(p); lightGizmoRenderer.setGraphics(this); + fontManager = new ProcessingFontManager(p); + color = Color.BLACK; ambientColor = math.Color.WHITE; } + @Override + public void setAmbientColor(math.Color ambientColor) { + this.ambientColor = ambientColor; + } + + @Override + public math.Color getAmbientColor() { + return ambientColor; + } + + @Override + public void setWireframeMode(boolean wireframeMode) { + this.wireframeMode = wireframeMode; + } + @Override public void fillFaces(Mesh3D mesh) { faceCount += mesh.faces.size(); @@ -91,11 +102,11 @@ public void fillFaces(Mesh3D mesh) { g.noFill(); stroke(); // renderer.drawFaces(mesh); - drawMeshFaces(mesh); + drawMeshFaces(mesh, true); } else { g.noStroke(); fill(); - drawMeshFaces(mesh); + drawMeshFaces(mesh, true); } } @@ -103,7 +114,7 @@ public void fillFaces(Mesh3D mesh) { public void drawFaces(Mesh3D mesh) { g.noFill(); stroke(); - drawMeshFaces(mesh); + drawMeshFaces(mesh, false); } @Override @@ -115,7 +126,7 @@ public void draw(VBO vbo) { vboProcessing.draw(g); } - @Override + @Override // TODO remove or fix public void renderInstances(Mesh3D mesh, List instanceTransforms) { if (mesh.getFaces().isEmpty() || mesh.getVertices().isEmpty()) { return; @@ -126,7 +137,7 @@ public void renderInstances(Mesh3D mesh, List instanceTransforms) { for (Matrix4f transform : instanceTransforms) { g.pushMatrix(); applyTransform(transform); - drawMeshFaces(mesh); + drawMeshFaces(mesh, true); g.popMatrix(); } } @@ -153,6 +164,16 @@ private void applyTransform(Matrix4f transform) { matrix[15]); } + @Override + public void setFont(Font font) { + if (font == null) { + this.font = new Font("Lucida Sans", 12, Font.PLAIN); + } else { + this.font = font; + } + g.textFont(fontManager.loadFont(this.font)); + } + /** * Applies the specified texture to the current rendering context with the appropriate sampling * mode. @@ -235,7 +256,7 @@ private int getSamplingMode(FilterMode filterMode) { } } - private void drawMeshFaces(Mesh3D mesh) { + private void drawMeshFaces(Mesh3D mesh, boolean texture) { for (Face3D f : mesh.getFaces()) { if (f.indices.length == 3) { g.beginShape(PApplet.TRIANGLES); @@ -245,7 +266,7 @@ private void drawMeshFaces(Mesh3D mesh) { g.beginShape(PApplet.POLYGON); } - applyTexture(); + if (texture) applyTexture(); int[] indices = f.indices; for (int i = 0; i < indices.length; i++) { diff --git a/src/main/java/workspace/ui/Graphics2D.java b/src/main/java/workspace/ui/Graphics2D.java index 029d2bfc..0c3d86f3 100644 --- a/src/main/java/workspace/ui/Graphics2D.java +++ b/src/main/java/workspace/ui/Graphics2D.java @@ -1,5 +1,6 @@ package workspace.ui; +import engine.resources.Font; import engine.resources.Image; /** @@ -226,6 +227,19 @@ public interface Graphics2D { */ void text(String text, float x, float y); + /** + * Sets the current font used for text rendering operations. + * + *

This method changes the font that will be applied to subsequent text rendering commands. The + * provided {@link Font} object determines the style, weight, and size of the text that will be + * drawn on the rendering surface. + * + *

Passing a {@code null} value will reset the font to the default system font. + * + * @param font The {@link Font} to set. If {@code null}, the default font will be used. + */ + void setFont(Font font); + /** * Clears the rendering context with the specified color. * From 1509e3ee2f1a511cb437ffede22dfff17979170c Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 00:36:26 +0100 Subject: [PATCH 02/14] Added Monogram Font Support - Integrated custom Monogram font loading from a specific folder. --- .../processing/ProcessingFontManager.java | 23 +- .../bitmap/monogram-bitfontmaker.json | 1 + .../monogram/bitmap/monogram-bitmap.json | 392 ++++++++++++++++++ .../fonts/monogram/bitmap/monogram-bitmap.png | Bin 0 -> 1376 bytes .../bitmap/monogram-italic-bitmap.png | Bin 0 -> 1448 bytes src/main/resources/fonts/monogram/credits.txt | 15 + .../fonts/monogram/pico-8/monogram.p8 | 184 ++++++++ .../fonts/monogram/pico-8/monogram.p8.png | Bin 0 -> 4691 bytes .../monogram/ttf/monogram-extended-italic.ttf | Bin 0 -> 60904 bytes .../fonts/monogram/ttf/monogram-extended.ttf | Bin 0 -> 59008 bytes .../resources/fonts/monogram/ttf/monogram.ttf | Bin 0 -> 10468 bytes 11 files changed, 613 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/fonts/monogram/bitmap/monogram-bitfontmaker.json create mode 100644 src/main/resources/fonts/monogram/bitmap/monogram-bitmap.json create mode 100644 src/main/resources/fonts/monogram/bitmap/monogram-bitmap.png create mode 100644 src/main/resources/fonts/monogram/bitmap/monogram-italic-bitmap.png create mode 100644 src/main/resources/fonts/monogram/credits.txt create mode 100644 src/main/resources/fonts/monogram/pico-8/monogram.p8 create mode 100644 src/main/resources/fonts/monogram/pico-8/monogram.p8.png create mode 100644 src/main/resources/fonts/monogram/ttf/monogram-extended-italic.ttf create mode 100644 src/main/resources/fonts/monogram/ttf/monogram-extended.ttf create mode 100644 src/main/resources/fonts/monogram/ttf/monogram.ttf diff --git a/src/main/java/engine/processing/ProcessingFontManager.java b/src/main/java/engine/processing/ProcessingFontManager.java index 21b2d39e..a461c9ba 100644 --- a/src/main/java/engine/processing/ProcessingFontManager.java +++ b/src/main/java/engine/processing/ProcessingFontManager.java @@ -28,6 +28,21 @@ public ProcessingFontManager(PApplet p) { this.p = p; } + private void loadFont(int size, boolean smooth) { + try { + // Load the font from a custom folder + String filePath = + "monogram/ttf/monogram-extended.ttf"; // Relative path to your custom font folder + String fontPath = + ProcessingTextureLoader.class.getClassLoader().getResource("fonts/" + filePath).getPath(); + + PFont font = p.createFont(fontPath, size); + fontCache.put(new Font("monogram-extended", size, Font.PLAIN), font); + } catch (Exception e) { + e.printStackTrace(); + } + } + /** * Loads a font based on the provided Font object. If the font is already cached, it returns the * cached version. Otherwise, it creates a new font and stores it in the cache. Font smoothing is @@ -41,8 +56,12 @@ public PFont loadFont(Font font) { // Enable smoothing by default boolean smooth = true; // Create the PFont and add it to the cache - PFont pFont = p.createFont(getFontName(font), font.getSize(), smooth); - fontCache.put(font, pFont); + if (font.getName().startsWith("monogram")) { + loadFont(font.getSize(), smooth); + } else { + PFont pFont = p.createFont(getFontName(font), font.getSize(), smooth); + fontCache.put(font, pFont); + } } return fontCache.get(font); } diff --git a/src/main/resources/fonts/monogram/bitmap/monogram-bitfontmaker.json b/src/main/resources/fonts/monogram/bitmap/monogram-bitfontmaker.json new file mode 100644 index 00000000..a1b7b65c --- /dev/null +++ b/src/main/resources/fonts/monogram/bitmap/monogram-bitfontmaker.json @@ -0,0 +1 @@ +{"33":[0,0,0,0,0,16,16,16,16,16,0,16,0,0,0,0],"34":[0,0,0,0,0,40,40,40,0,0,0,0,0,0,0,0],"35":[0,0,0,0,0,0,40,124,40,40,124,40,0,0,0,0],"36":[0,0,0,0,0,16,120,20,56,80,60,16,0,0,0,0],"37":[0,0,0,0,0,68,68,32,16,8,68,68,0,0,0,0],"38":[0,0,0,0,0,24,36,36,120,36,36,88,0,0,0,0],"39":[0,0,0,0,0,16,16,16,0,0,0,0,0,0,0,0],"40":[0,0,0,0,0,32,16,16,16,16,16,32,0,0,0,0],"41":[0,0,0,0,0,8,16,16,16,16,16,8,0,0,0,0],"42":[0,0,0,0,0,0,16,84,56,84,16,0,0,0,0,0],"43":[0,0,0,0,0,0,16,16,124,16,16,0,0,0,0,0],"44":[0,0,0,0,0,0,0,0,0,0,16,16,8,0,0,0],"45":[0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0],"46":[0,0,0,0,0,0,0,0,0,0,16,16,0,0,0,0],"47":[0,0,0,0,0,64,64,32,16,8,4,4,0,0,0,0],"48":[0,0,0,0,0,56,68,100,84,76,68,56,0,0,0,0],"49":[0,0,0,0,0,16,24,16,16,16,16,124,0,0,0,0],"50":[0,0,0,0,0,56,68,64,32,16,8,124,0,0,0,0],"51":[0,0,0,0,0,56,68,64,48,64,68,56,0,0,0,0],"52":[0,0,0,0,0,72,72,68,124,64,64,64,0,0,0,0],"53":[0,0,0,0,0,124,4,60,64,64,68,56,0,0,0,0],"54":[0,0,0,0,0,56,4,4,60,68,68,56,0,0,0,0],"55":[0,0,0,0,0,124,64,64,32,16,16,16,0,0,0,0],"56":[0,0,0,0,0,56,68,68,56,68,68,56,0,0,0,0],"57":[0,0,0,0,0,56,68,68,120,64,68,56,0,0,0,0],"58":[0,0,0,0,0,0,16,16,0,0,16,16,0,0,0,0],"59":[0,0,0,0,0,0,16,16,0,0,16,16,8,0,0,0],"60":[0,0,0,0,0,0,96,24,4,24,96,0,0,0,0,0],"61":[0,0,0,0,0,0,0,124,0,124,0,0,0,0,0,0],"62":[0,0,0,0,0,0,12,48,64,48,12,0,0,0,0,0],"63":[0,0,0,0,0,56,68,64,32,16,0,16,0,0,0,0],"64":[0,0,0,0,0,56,100,84,84,100,4,56,0,0,0,0],"65":[0,0,0,0,0,56,68,68,68,124,68,68,0,0,0,0],"66":[0,0,0,0,0,60,68,68,60,68,68,60,0,0,0,0],"67":[0,0,0,0,0,56,68,4,4,4,68,56,0,0,0,0],"68":[0,0,0,0,0,60,68,68,68,68,68,60,0,0,0,0],"69":[0,0,0,0,0,124,4,4,60,4,4,124,0,0,0,0],"70":[0,0,0,0,0,124,4,4,60,4,4,4,0,0,0,0],"71":[0,0,0,0,0,56,68,4,116,68,68,56,0,0,0,0],"72":[0,0,0,0,0,68,68,68,124,68,68,68,0,0,0,0],"73":[0,0,0,0,0,124,16,16,16,16,16,124,0,0,0,0],"74":[0,0,0,0,0,64,64,64,64,68,68,56,0,0,0,0],"75":[0,0,0,0,0,68,36,20,12,20,36,68,0,0,0,0],"76":[0,0,0,0,0,4,4,4,4,4,4,124,0,0,0,0],"77":[0,0,0,0,0,68,108,84,68,68,68,68,0,0,0,0],"78":[0,0,0,0,0,68,68,76,84,100,68,68,0,0,0,0],"79":[0,0,0,0,0,56,68,68,68,68,68,56,0,0,0,0],"80":[0,0,0,0,0,60,68,68,60,4,4,4,0,0,0,0],"81":[0,0,0,0,0,56,68,68,68,68,68,56,96,0,0,0],"82":[0,0,0,0,0,60,68,68,60,68,68,68,0,0,0,0],"83":[0,0,0,0,0,56,68,4,56,64,68,56,0,0,0,0],"84":[0,0,0,0,0,124,16,16,16,16,16,16,0,0,0,0],"85":[0,0,0,0,0,68,68,68,68,68,68,56,0,0,0,0],"86":[0,0,0,0,0,68,68,68,68,40,40,16,0,0,0,0],"87":[0,0,0,0,0,68,68,68,68,84,108,68,0,0,0,0],"88":[0,0,0,0,0,68,68,40,16,40,68,68,0,0,0,0],"89":[0,0,0,0,0,68,68,40,16,16,16,16,0,0,0,0],"90":[0,0,0,0,0,124,64,32,16,8,4,124,0,0,0,0],"91":[0,0,0,0,0,48,16,16,16,16,16,48,0,0,0,0],"92":[0,0,0,0,0,4,4,8,16,32,64,64,0,0,0,0],"93":[0,0,0,0,0,24,16,16,16,16,16,24,0,0,0,0],"94":[0,0,0,0,0,16,40,68,0,0,0,0,0,0,0,0],"95":[0,0,0,0,0,0,0,0,0,0,0,124,0,0,0,0],"96":[0,0,0,0,0,8,16,0,0,0,0,0,0,0,0,0],"97":[0,0,0,0,0,0,0,120,68,68,68,120,0,0,0,0],"98":[0,0,0,0,0,4,4,60,68,68,68,60,0,0,0,0],"99":[0,0,0,0,0,0,0,56,68,4,68,56,0,0,0,0],"100":[0,0,0,0,0,64,64,120,68,68,68,120,0,0,0,0],"101":[0,0,0,0,0,0,0,56,68,124,4,56,0,0,0,0],"102":[0,0,0,0,0,48,72,8,60,8,8,8,0,0,0,0],"103":[0,0,0,0,0,0,0,120,68,68,68,120,64,56,0,0],"104":[0,0,0,0,0,4,4,60,68,68,68,68,0,0,0,0],"105":[0,0,0,0,0,16,0,24,16,16,16,124,0,0,0,0],"106":[0,0,0,0,0,64,0,96,64,64,64,64,68,56,0,0],"107":[0,0,0,0,0,4,4,68,36,28,36,68,0,0,0,0],"108":[0,0,0,0,0,12,8,8,8,8,8,112,0,0,0,0],"109":[0,0,0,0,0,0,0,60,84,84,84,84,0,0,0,0],"110":[0,0,0,0,0,0,0,60,68,68,68,68,0,0,0,0],"111":[0,0,0,0,0,0,0,56,68,68,68,56,0,0,0,0],"112":[0,0,0,0,0,0,0,60,68,68,68,60,4,4,0,0],"113":[0,0,0,0,0,0,0,120,68,68,68,120,64,64,0,0],"114":[0,0,0,0,0,0,0,52,76,4,4,4,0,0,0,0],"115":[0,0,0,0,0,0,0,120,4,56,64,60,0,0,0,0],"116":[0,0,0,0,0,8,8,60,8,8,8,112,0,0,0,0],"117":[0,0,0,0,0,0,0,68,68,68,68,120,0,0,0,0],"118":[0,0,0,0,0,0,0,68,68,68,40,16,0,0,0,0],"119":[0,0,0,0,0,0,0,68,68,84,84,40,0,0,0,0],"120":[0,0,0,0,0,0,0,68,40,16,40,68,0,0,0,0],"121":[0,0,0,0,0,0,0,68,68,68,68,120,64,56,0,0],"122":[0,0,0,0,0,0,0,124,32,16,8,124,0,0,0,0],"123":[0,0,0,0,0,32,16,16,8,16,16,32,0,0,0,0],"124":[0,0,0,0,0,16,16,16,16,16,16,16,0,0,0,0],"125":[0,0,0,0,0,8,16,16,32,16,16,8,0,0,0,0],"126":[0,0,0,0,0,0,0,72,52,0,0,0,0,0,0,0],"161":[0,0,0,0,0,16,0,16,16,16,16,16,0,0,0,0],"162":[0,0,0,0,0,16,56,84,20,84,56,16,0,0,0,0],"163":[0,0,0,0,0,48,72,8,60,8,8,124,0,0,0,0],"164":[0,0,0,0,0,0,68,56,40,56,68,0,0,0,0,0],"165":[0,0,0,0,0,68,40,16,124,16,124,16,0,0,0,0],"166":[0,0,0,0,0,16,16,16,0,16,16,16,0,0,0,0],"167":[0,0,0,0,0,120,4,56,68,56,64,60,0,0,0,0],"168":[0,0,0,0,0,40,0,0,0,0,0,0,0,0,0,0],"169":[0,0,0,0,0,56,108,84,116,84,108,56,0,0,0,0],"170":[0,0,0,0,0,56,36,36,36,56,0,0,0,0,0,0],"171":[0,0,0,0,0,0,0,72,36,72,0,0,0,0,0,0],"172":[0,0,0,0,0,0,0,0,124,64,0,0,0,0,0,0],"174":[0,0,0,0,0,56,100,84,84,100,84,56,0,0,0,0],"175":[0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0],"176":[0,0,0,0,0,24,36,36,24,0,0,0,0,0,0,0],"177":[0,0,0,0,0,16,16,124,16,16,0,124,0,0,0,0],"178":[0,0,0,0,0,12,16,8,4,28,0,0,0,0,0,0],"179":[0,0,0,0,0,12,16,8,16,12,0,0,0,0,0,0],"180":[0,0,0,0,32,16,0,0,0,0,0,0,0,0,0,0],"181":[0,0,0,0,0,0,0,68,68,68,68,60,4,4,0,0],"182":[0,0,0,0,0,120,92,92,92,88,80,80,0,0,0,0],"183":[0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0],"184":[0,0,0,0,0,0,0,0,0,0,0,16,32,24,0,0],"185":[0,0,0,0,0,8,12,8,8,28,0,0,0,0,0,0],"186":[0,0,0,0,0,24,36,36,36,24,0,0,0,0,0,0],"187":[0,0,0,0,0,0,0,36,72,36,0,0,0,0,0,0],"188":[0,0,0,0,0,4,36,20,8,84,112,64,0,0,0,0],"189":[0,0,0,0,0,4,36,20,56,68,32,112,0,0,0,0],"190":[0,0,0,0,0,28,88,60,16,88,116,64,0,0,0,0],"191":[0,0,0,0,0,16,0,16,8,4,68,56,0,0,0,0],"192":[0,0,8,16,0,56,68,68,124,68,68,68,0,0,0,0],"193":[0,0,32,16,0,56,68,68,124,68,68,68,0,0,0,0],"194":[0,0,16,40,0,56,68,68,124,68,68,68,0,0,0,0],"195":[0,0,88,36,0,56,68,68,124,68,68,68,0,0,0,0],"196":[0,0,0,40,0,56,68,68,124,68,68,68,0,0,0,0],"197":[0,0,16,40,16,56,68,68,124,68,68,68,0,0,0,0],"198":[0,0,0,0,0,120,20,20,124,20,20,116,0,0,0,0],"199":[0,0,0,0,0,56,68,4,4,4,68,56,32,24,0,0],"200":[0,0,8,16,0,124,4,4,60,4,4,124,0,0,0,0],"201":[0,0,32,16,0,124,4,4,60,4,4,124,0,0,0,0],"202":[0,0,16,40,0,124,4,4,60,4,4,124,0,0,0,0],"203":[0,0,0,40,0,124,4,4,60,4,4,124,0,0,0,0],"204":[0,0,8,16,0,124,16,16,16,16,16,124,0,0,0,0],"205":[0,0,32,16,0,124,16,16,16,16,16,124,0,0,0,0],"206":[0,0,16,40,0,124,16,16,16,16,16,124,0,0,0,0],"207":[0,0,0,40,0,124,16,16,16,16,16,124,0,0,0,0],"208":[0,0,0,0,0,60,68,68,78,68,68,60,0,0,0,0],"209":[0,0,88,36,0,68,68,76,84,100,68,68,0,0,0,0],"210":[0,0,8,16,0,56,68,68,68,68,68,56,0,0,0,0],"211":[0,0,32,16,0,56,68,68,68,68,68,56,0,0,0,0],"212":[0,0,16,40,0,56,68,68,68,68,68,56,0,0,0,0],"213":[0,0,88,36,0,56,68,68,68,68,68,56,0,0,0,0],"214":[0,0,0,40,0,56,68,68,68,68,68,56,0,0,0,0],"215":[0,0,0,0,0,0,0,0,40,16,40,0,0,0,0,0],"216":[0,0,0,0,0,88,36,100,84,76,72,52,0,0,0,0],"217":[0,0,8,16,0,68,68,68,68,68,68,56,0,0,0,0],"218":[0,0,32,16,0,68,68,68,68,68,68,56,0,0,0,0],"219":[0,0,16,40,0,68,68,68,68,68,68,56,0,0,0,0],"220":[0,0,0,40,0,68,68,68,68,68,68,56,0,0,0,0],"221":[0,0,32,16,0,68,68,40,16,16,16,16,0,0,0,0],"222":[0,0,0,0,0,4,60,68,68,68,60,4,0,0,0,0],"223":[0,0,0,0,0,24,36,36,52,68,68,52,0,0,0,0],"224":[0,0,0,0,8,16,0,120,68,68,68,120,0,0,0,0],"225":[0,0,0,0,32,16,0,120,68,68,68,120,0,0,0,0],"226":[0,0,0,0,16,40,0,120,68,68,68,120,0,0,0,0],"227":[0,0,0,0,88,36,0,120,68,68,68,120,0,0,0,0],"228":[0,0,0,0,0,40,0,120,68,68,68,120,0,0,0,0],"229":[0,0,0,16,40,16,0,120,68,68,68,120,0,0,0,0],"230":[0,0,0,0,0,0,0,56,84,116,20,120,0,0,0,0],"231":[0,0,0,0,0,0,0,56,68,4,68,56,32,24,0,0],"232":[0,0,0,0,8,16,0,56,68,124,4,56,0,0,0,0],"233":[0,0,0,0,32,16,0,56,68,124,4,56,0,0,0,0],"234":[0,0,0,0,16,40,0,56,68,124,4,56,0,0,0,0],"235":[0,0,0,0,0,40,0,56,68,124,4,56,0,0,0,0],"236":[0,0,0,0,8,16,0,24,16,16,16,124,0,0,0,0],"237":[0,0,0,0,32,16,0,24,16,16,16,124,0,0,0,0],"238":[0,0,0,0,16,40,0,24,16,16,16,124,0,0,0,0],"239":[0,0,0,0,0,40,0,24,16,16,16,124,0,0,0,0],"240":[0,0,0,0,56,192,96,120,68,68,68,56,0,0,0,0],"241":[0,0,0,0,88,36,0,60,68,68,68,68,0,0,0,0],"242":[0,0,0,0,8,16,0,56,68,68,68,56,0,0,0,0],"243":[0,0,0,0,32,16,0,56,68,68,68,56,0,0,0,0],"244":[0,0,0,0,16,40,0,56,68,68,68,56,0,0,0,0],"245":[0,0,0,0,88,36,0,56,68,68,68,56,0,0,0,0],"246":[0,0,0,0,0,40,0,56,68,68,68,56,0,0,0,0],"247":[0,0,0,0,0,0,0,16,0,124,0,16,0,0,0,0],"248":[0,0,0,0,0,0,0,88,36,84,72,52,0,0,0,0],"249":[0,0,0,0,8,16,0,68,68,68,68,120,0,0,0,0],"250":[0,0,0,0,32,16,0,68,68,68,68,120,0,0,0,0],"251":[0,0,0,0,16,40,0,68,68,68,68,120,0,0,0,0],"252":[0,0,0,0,0,40,0,68,68,68,68,120,0,0,0,0],"253":[0,0,0,0,32,16,0,68,68,68,68,120,64,56,0,0],"254":[0,0,0,0,0,4,4,60,68,68,68,60,4,4,0,0],"255":[0,0,0,0,0,40,0,68,68,68,68,120,64,56,0,0],"256":[0,0,0,56,0,56,68,68,124,68,68,68,0,0,0,0],"257":[0,0,0,0,0,56,0,120,68,68,68,120,0,0,0,0],"258":[0,0,40,16,0,56,68,68,124,68,68,68,0,0,0,0],"259":[0,0,0,0,40,16,0,120,68,68,68,120,0,0,0,0],"260":[0,0,0,0,0,56,68,68,124,68,68,68,32,64,0,0],"261":[0,0,0,0,0,0,0,120,68,68,68,120,16,96,0,0],"262":[0,0,32,16,0,56,68,4,4,4,68,56,0,0,0,0],"263":[0,0,0,0,32,16,0,56,68,4,68,56,0,0,0,0],"264":[0,0,16,40,0,56,68,4,4,4,68,56,0,0,0,0],"265":[0,0,0,0,16,40,0,56,68,4,68,56,0,0,0,0],"266":[0,0,0,16,0,56,68,4,4,4,68,56,0,0,0,0],"267":[0,0,0,0,0,16,0,56,68,4,68,56,0,0,0,0],"268":[0,0,40,16,0,56,68,4,4,4,68,56,0,0,0,0],"269":[0,0,0,0,40,16,0,56,68,4,68,56,0,0,0,0],"270":[0,0,40,16,0,60,68,68,68,68,68,60,0,0,0,0],"271":[0,0,0,0,320,320,64,120,68,68,68,120,0,0,0,0],"272":[0,0,0,0,0,60,68,68,78,68,68,60,0,0,0,0],"273":[0,0,0,0,64,240,64,120,68,68,68,120,0,0,0,0],"274":[0,0,0,56,0,124,4,4,28,4,4,124,0,0,0,0],"275":[0,0,0,0,0,56,0,56,68,124,4,56,0,0,0,0],"276":[0,0,40,16,0,124,4,4,28,4,4,124,0,0,0,0],"277":[0,0,0,0,40,16,0,56,68,124,4,56,0,0,0,0],"278":[0,0,0,16,0,124,4,4,28,4,4,124,0,0,0,0],"279":[0,0,0,0,0,16,0,56,68,124,4,56,0,0,0,0],"280":[0,0,0,0,0,124,4,4,28,4,4,124,16,96,0,0],"281":[0,0,0,0,0,0,0,56,68,124,4,120,16,96,0,0],"282":[0,0,0,56,0,124,4,4,28,4,4,124,0,0,0,0],"283":[0,0,0,0,0,40,0,56,68,124,4,56,0,0,0,0],"284":[0,0,16,40,0,56,68,4,116,68,68,56,0,0,0,0],"285":[0,0,0,0,16,40,0,120,68,68,68,120,64,56,0,0],"286":[0,0,40,16,0,56,68,4,116,68,68,56,0,0,0,0],"287":[0,0,0,0,40,16,0,120,68,68,68,120,64,56,0,0],"288":[0,0,0,16,0,56,68,4,116,68,68,56,0,0,0,0],"289":[0,0,0,0,0,16,0,120,68,68,68,120,64,56,0,0],"290":[0,0,0,0,0,56,68,4,116,68,68,56,32,24,0,0],"291":[0,0,0,0,32,16,0,120,68,68,68,120,64,56,0,0],"292":[0,0,16,40,0,68,68,68,124,68,68,68,0,0,0,0],"293":[0,0,0,0,32,84,4,60,68,68,68,68,0,0,0,0],"294":[0,0,0,0,0,68,254,68,124,68,68,68,0,0,0,0],"295":[0,0,0,0,0,4,14,4,60,68,68,68,0,0,0,0],"296":[0,0,88,36,0,124,16,16,16,16,16,124,0,0,0,0],"297":[0,0,0,0,88,36,0,24,16,16,16,124,0,0,0,0],"298":[0,0,0,56,0,124,16,16,16,16,16,124,0,0,0,0],"299":[0,0,0,0,0,56,0,24,16,16,16,124,0,0,0,0],"300":[0,0,40,16,0,124,16,16,16,16,16,124,0,0,0,0],"301":[0,0,0,0,40,16,0,24,16,16,16,124,0,0,0,0],"302":[0,0,0,0,0,124,16,16,16,16,16,124,16,96,0,0],"303":[0,0,0,0,0,16,0,24,16,16,16,124,16,96,0,0],"304":[0,0,88,36,0,124,16,16,16,16,16,124,0,0,0,0],"305":[0,0,0,0,0,0,0,24,16,16,16,124,0,0,0,0],"306":[0,0,0,0,0,92,72,72,72,72,72,60,0,0,0,0],"307":[0,0,0,0,0,72,0,108,72,72,72,124,64,56,0,0],"308":[0,0,16,40,0,64,64,64,64,68,68,56,0,0,0,0],"309":[0,0,0,0,64,160,0,96,64,64,64,64,68,56,0,0],"310":[0,0,0,0,0,68,36,20,12,20,36,68,16,16,0,0],"311":[0,0,0,0,0,4,4,68,36,28,36,68,16,16,0,0],"312":[0,0,0,0,0,0,0,68,36,28,36,68,0,0,0,0],"313":[0,0,32,16,0,4,4,4,4,4,4,124,0,0,0,0],"314":[0,0,32,16,0,124,16,16,16,16,16,124,0,0,0,0],"315":[0,0,0,0,0,4,4,4,4,4,4,124,32,24,0,0],"316":[0,0,0,0,0,124,16,16,16,16,16,124,32,24,0,0],"317":[0,0,0,0,0,68,68,36,4,4,4,124,0,0,0,0],"318":[0,0,0,0,0,76,72,40,8,8,8,112,0,0,0,0],"319":[0,0,0,0,0,4,4,4,36,4,4,124,0,0,0,0],"320":[0,0,0,0,0,12,8,8,40,8,8,112,0,0,0,0],"321":[0,0,0,0,0,4,4,4,12,6,4,124,0,0,0,0],"322":[0,0,0,0,0,12,8,8,24,12,8,112,0,0,0,0],"323":[0,0,32,16,0,68,68,76,84,100,68,68,0,0,0,0],"324":[0,0,0,0,32,16,0,60,68,68,68,68,0,0,0,0],"325":[0,0,0,0,0,68,68,76,84,100,68,68,16,12,0,0],"326":[0,0,0,0,0,0,0,60,68,68,68,68,16,12,0,0],"327":[0,0,40,16,0,68,68,76,84,100,68,68,0,0,0,0],"328":[0,0,0,0,40,16,0,60,68,68,68,68,0,0,0,0],"329":[0,0,0,0,0,2,2,60,68,68,68,68,0,0,0,0],"330":[0,0,0,0,0,68,68,76,84,100,68,68,64,48,0,0],"331":[0,0,0,0,0,0,0,60,68,68,68,68,64,48,0,0],"332":[0,0,0,56,0,56,68,68,68,68,68,56,0,0,0,0],"333":[0,0,0,0,0,56,0,56,68,68,68,56,0,0,0,0],"334":[0,0,40,16,0,56,68,68,68,68,68,56,0,0,0,0],"335":[0,0,0,0,40,16,0,56,68,68,68,56,0,0,0,0],"336":[0,0,80,40,0,56,68,68,68,68,68,56,0,0,0,0],"337":[0,0,0,0,80,40,0,56,68,68,68,56,0,0,0,0],"338":[0,0,0,0,0,120,20,20,116,20,20,120,0,0,0,0],"339":[0,0,0,0,0,0,0,56,84,116,20,56,0,0,0,0],"340":[0,0,32,16,0,60,68,68,60,68,68,68,0,0,0,0],"341":[0,0,0,0,32,16,0,52,76,4,4,4,0,0,0,0],"342":[0,0,0,0,0,60,68,68,60,68,68,68,16,12,0,0],"343":[0,0,0,0,0,0,0,52,76,4,4,4,16,12,0,0],"344":[0,0,40,16,0,60,68,68,60,68,68,68,0,0,0,0],"345":[0,0,0,0,40,16,0,52,76,4,4,4,0,0,0,0],"346":[0,0,32,16,0,56,68,4,56,64,68,56,0,0,0,0],"347":[0,0,0,0,32,16,0,120,4,56,64,60,0,0,0,0],"348":[0,0,16,40,0,56,68,4,56,64,68,56,0,0,0,0],"349":[0,0,0,0,16,40,0,120,4,56,64,60,0,0,0,0],"350":[0,0,0,0,0,56,68,4,56,64,68,56,16,12,0,0],"351":[0,0,0,0,0,0,0,120,4,56,64,60,16,12,0,0],"352":[0,0,40,16,0,56,68,4,56,64,68,56,0,0,0,0],"353":[0,0,0,0,40,16,0,120,4,56,64,60,0,0,0,0],"354":[0,0,0,0,0,124,16,16,16,16,16,16,16,12,0,0],"355":[0,0,0,0,0,8,8,60,8,8,8,112,32,24,0,0],"356":[0,0,40,16,0,124,16,16,16,16,16,16,0,0,0,0],"357":[0,0,0,0,32,40,8,60,8,8,8,112,0,0,0,0],"358":[0,0,0,0,0,124,16,56,16,16,16,16,0,0,0,0],"359":[0,0,0,0,0,8,60,8,60,8,8,112,0,0,0,0],"360":[0,0,88,36,0,68,68,68,68,68,68,56,0,0,0,0],"361":[0,0,0,0,88,36,0,68,68,68,68,120,0,0,0,0],"362":[0,0,0,56,0,68,68,68,68,68,68,56,0,0,0,0],"363":[0,0,0,0,0,56,0,68,68,68,68,120,0,0,0,0],"364":[0,0,40,16,0,68,68,68,68,68,68,56,0,0,0,0],"365":[0,0,0,0,40,16,0,68,68,68,68,120,0,0,0,0],"366":[0,16,40,16,0,68,68,68,68,68,68,56,0,0,0,0],"367":[0,0,0,16,40,16,0,68,68,68,68,120,0,0,0,0],"368":[0,0,80,40,0,68,68,68,68,68,68,56,0,0,0,0],"369":[0,0,0,0,80,40,0,68,68,68,68,120,0,0,0,0],"370":[0,0,0,0,0,68,68,68,68,68,68,56,16,96,0,0],"371":[0,0,0,0,0,0,0,68,68,68,68,120,16,96,0,0],"372":[0,0,16,40,0,68,68,68,68,84,108,68,0,0,0,0],"373":[0,0,0,0,16,40,0,68,68,84,84,40,0,0,0,0],"374":[0,0,16,40,0,68,68,40,16,16,16,16,0,0,0,0],"375":[0,0,0,0,16,40,0,68,68,68,68,120,64,56,0,0],"376":[0,0,0,40,0,68,68,40,16,16,16,16,0,0,0,0],"377":[0,0,32,16,0,124,64,32,16,8,4,124,0,0,0,0],"378":[0,0,0,0,32,16,0,124,32,16,8,124,0,0,0,0],"379":[0,0,0,16,0,124,64,32,16,8,4,124,0,0,0,0],"380":[0,0,0,0,0,16,0,124,32,16,8,124,0,0,0,0],"381":[0,0,40,16,0,124,64,32,16,8,4,124,0,0,0,0],"382":[0,0,0,0,40,16,0,124,32,16,8,124,0,0,0,0],"1025":[0,0,0,40,0,124,4,4,60,4,4,124,0,0,0,0],"1040":[0,0,0,0,0,56,68,68,68,124,68,68,0,0,0,0],"1041":[0,0,0,0,0,124,4,4,60,68,68,60,0,0,0,0],"1042":[0,0,0,0,0,60,68,68,60,68,68,60,0,0,0,0],"1043":[0,0,0,0,0,124,4,4,4,4,4,4,0,0,0,0],"1044":[0,0,0,0,0,48,40,40,40,40,40,124,68,0,0,0],"1045":[0,0,0,0,0,124,4,4,60,4,4,124,0,0,0,0],"1046":[0,0,0,0,0,84,84,84,56,84,84,84,0,0,0,0],"1047":[0,0,0,0,0,56,68,64,56,64,68,56,0,0,0,0],"1048":[0,0,0,0,0,68,68,100,84,76,68,68,0,0,0,0],"1049":[0,0,0,40,16,68,68,100,84,76,68,68,0,0,0,0],"1050":[0,0,0,0,0,100,20,20,12,20,36,68,0,0,0,0],"1051":[0,0,0,0,0,120,72,72,72,72,72,68,0,0,0,0],"1052":[0,0,0,0,0,68,108,84,68,68,68,68,0,0,0,0],"1053":[0,0,0,0,0,68,68,68,124,68,68,68,0,0,0,0],"1054":[0,0,0,0,0,56,68,68,68,68,68,56,0,0,0,0],"1055":[0,0,0,0,0,124,68,68,68,68,68,68,0,0,0,0],"1056":[0,0,0,0,0,60,68,68,60,4,4,4,0,0,0,0],"1057":[0,0,0,0,0,56,68,4,4,4,68,56,0,0,0,0],"1058":[0,0,0,0,0,124,16,16,16,16,16,16,0,0,0,0],"1059":[0,0,0,0,0,68,68,68,68,120,64,56,0,0,0,0],"1060":[0,0,0,0,0,16,56,84,84,84,56,16,0,0,0,0],"1061":[0,0,0,0,0,68,68,40,16,40,68,68,0,0,0,0],"1062":[0,0,0,0,0,0,36,36,36,36,36,124,64,0,0,0],"1063":[0,0,0,0,0,68,68,68,120,64,64,64,0,0,0,0],"1064":[0,0,0,0,0,84,84,84,84,84,84,124,0,0,0,0],"1065":[0,0,0,0,0,84,84,84,84,84,84,124,64,0,0,0],"1066":[0,0,0,0,0,0,12,8,56,72,72,56,0,0,0,0],"1067":[0,0,0,0,0,0,68,68,76,84,84,76,0,0,0,0],"1068":[0,0,0,0,0,0,4,4,60,68,68,60,0,0,0,0],"1069":[0,0,0,0,0,56,68,64,112,64,68,56,0,0,0,0],"1070":[0,0,0,0,0,36,84,84,92,84,84,36,0,0,0,0],"1071":[0,0,0,0,0,0,120,68,68,120,68,68,0,0,0,0],"1072":[0,0,0,0,0,0,0,56,64,120,68,120,0,0,0,0],"1073":[0,0,0,0,0,120,4,52,76,68,68,56,0,0,0,0],"1074":[0,0,0,0,0,0,0,60,68,60,68,60,0,0,0,0],"1075":[0,0,0,0,0,0,0,124,4,4,4,4,0,0,0,0],"1076":[0,0,0,0,0,0,0,48,40,40,40,124,68,0,0,0],"1077":[0,0,0,0,0,0,0,56,68,124,4,56,0,0,0,0],"1078":[0,0,0,0,0,0,0,84,56,16,56,84,0,0,0,0],"1079":[0,0,0,0,0,0,0,24,36,16,36,24,0,0,0,0],"1080":[0,0,0,0,0,0,0,68,100,84,76,68,0,0,0,0],"1081":[0,0,0,0,0,40,16,68,100,84,76,68,0,0,0,0],"1082":[0,0,0,0,0,0,0,68,36,28,36,68,0,0,0,0],"1083":[0,0,0,0,0,0,0,120,72,72,72,68,0,0,0,0],"1084":[0,0,0,0,0,0,0,68,108,84,68,68,0,0,0,0],"1085":[0,0,0,0,0,0,0,68,68,124,68,68,0,0,0,0],"1086":[0,0,0,0,0,0,0,56,68,68,68,56,0,0,0,0],"1087":[0,0,0,0,0,0,0,124,68,68,68,68,0,0,0,0],"1088":[0,0,0,0,0,0,0,52,76,68,68,60,4,4,0,0],"1089":[0,0,0,0,0,0,0,56,68,4,68,56,0,0,0,0],"1090":[0,0,0,0,0,0,0,124,16,16,16,16,0,0,0,0],"1091":[0,0,0,0,0,0,0,68,68,68,68,120,64,56,0,0],"1092":[0,0,0,0,0,16,16,56,84,84,84,56,16,16,0,0],"1093":[0,0,0,0,0,0,0,68,40,16,40,68,0,0,0,0],"1094":[0,0,0,0,0,0,0,36,36,36,36,124,64,0,0,0],"1095":[0,0,0,0,0,0,0,68,68,68,120,64,0,0,0,0],"1096":[0,0,0,0,0,0,0,84,84,84,84,124,0,0,0,0],"1097":[0,0,0,0,0,0,0,84,84,84,84,124,64,0,0,0],"1098":[0,0,0,0,0,0,0,12,8,56,72,56,0,0,0,0],"1099":[0,0,0,0,0,0,0,68,68,76,84,76,0,0,0,0],"1100":[0,0,0,0,0,0,0,4,4,60,68,60,0,0,0,0],"1101":[0,0,0,0,0,0,0,56,68,112,68,56,0,0,0,0],"1102":[0,0,0,0,0,0,0,36,84,92,84,36,0,0,0,0],"1103":[0,0,0,0,0,0,0,120,68,68,120,68,0,0,0,0],"1105":[0,0,0,0,0,40,0,56,68,124,4,56,0,0,0,0],"8212":[0,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0],"8217":[0,0,0,0,0,32,16,0,0,0,0,0,0,0,0,0],"8230":[0,0,0,0,0,0,0,0,0,0,84,84,0,0,0,0],"8364":[0,0,0,0,0,48,72,28,8,28,72,48,0,0,0,0],"8592":[0,0,0,0,0,0,0,16,120,124,120,16,0,0,0,0],"8593":[0,0,0,0,0,0,0,16,56,124,56,56,0,0,0,0],"8594":[0,0,0,0,0,0,0,16,60,124,60,16,0,0,0,0],"8595":[0,0,0,0,0,0,0,56,56,124,56,16,0,0,0,0],"name":"monogramextended","copy":"ViniciusMenezio","letterspace":"64","basefont_size":"512","basefont_left":"62","basefont_top":"0","basefont":"Arial","basefont2":"","monospace":true,"monospacewidth":"6"} \ No newline at end of file diff --git a/src/main/resources/fonts/monogram/bitmap/monogram-bitmap.json b/src/main/resources/fonts/monogram/bitmap/monogram-bitmap.json new file mode 100644 index 00000000..45949df0 --- /dev/null +++ b/src/main/resources/fonts/monogram/bitmap/monogram-bitmap.json @@ -0,0 +1,392 @@ +{ +"0":[0,0,0,14,17,25,21,19,17,14,0,0], +"1":[0,0,0,4,6,4,4,4,4,31,0,0], +"2":[0,0,0,14,17,16,8,4,2,31,0,0], +"3":[0,0,0,14,17,16,12,16,17,14,0,0], +"4":[0,0,0,18,18,17,31,16,16,16,0,0], +"5":[0,0,0,31,1,15,16,16,17,14,0,0], +"6":[0,0,0,14,1,1,15,17,17,14,0,0], +"7":[0,0,0,31,16,16,8,4,4,4,0,0], +"8":[0,0,0,14,17,17,14,17,17,14,0,0], +"9":[0,0,0,14,17,17,30,16,17,14,0,0], +"!":[0,0,0,4,4,4,4,4,0,4,0,0], +"\"":[0,0,0,10,10,10,0,0,0,0,0,0], +"#":[0,0,0,0,10,31,10,10,31,10,0,0], +"$":[0,0,0,4,30,5,14,20,15,4,0,0], +"%":[0,0,0,17,17,8,4,2,17,17,0,0], +"&":[0,0,0,6,9,9,30,9,9,22,0,0], +"'":[0,0,0,4,4,4,0,0,0,0,0,0], +"(":[0,0,0,8,4,4,4,4,4,8,0,0], +")":[0,0,0,2,4,4,4,4,4,2,0,0], +"*":[0,0,0,0,4,21,14,21,4,0,0,0], +"+":[0,0,0,0,4,4,31,4,4,0,0,0], +",":[0,0,0,0,0,0,0,0,4,4,2,0], +"-":[0,0,0,0,0,0,31,0,0,0,0,0], +".":[0,0,0,0,0,0,0,0,4,4,0,0], +"/":[0,0,0,16,16,8,4,2,1,1,0,0], +":":[0,0,0,0,4,4,0,0,4,4,0,0], +";":[0,0,0,0,4,4,0,0,4,4,2,0], +"<":[0,0,0,0,24,6,1,6,24,0,0,0], +"=":[0,0,0,0,0,31,0,31,0,0,0,0], +">":[0,0,0,0,3,12,16,12,3,0,0,0], +"?":[0,0,0,14,17,16,8,4,0,4,0,0], +"@":[0,0,0,14,25,21,21,25,1,14,0,0], +"A":[0,0,0,14,17,17,17,31,17,17,0,0], +"B":[0,0,0,15,17,17,15,17,17,15,0,0], +"C":[0,0,0,14,17,1,1,1,17,14,0,0], +"D":[0,0,0,15,17,17,17,17,17,15,0,0], +"E":[0,0,0,31,1,1,15,1,1,31,0,0], +"F":[0,0,0,31,1,1,15,1,1,1,0,0], +"G":[0,0,0,14,17,1,29,17,17,14,0,0], +"H":[0,0,0,17,17,17,31,17,17,17,0,0], +"I":[0,0,0,31,4,4,4,4,4,31,0,0], +"J":[0,0,0,16,16,16,16,17,17,14,0,0], +"K":[0,0,0,17,9,5,3,5,9,17,0,0], +"L":[0,0,0,1,1,1,1,1,1,31,0,0], +"M":[0,0,0,17,27,21,17,17,17,17,0,0], +"N":[0,0,0,17,17,19,21,25,17,17,0,0], +"O":[0,0,0,14,17,17,17,17,17,14,0,0], +"P":[0,0,0,15,17,17,15,1,1,1,0,0], +"Q":[0,0,0,14,17,17,17,17,17,14,24,0], +"R":[0,0,0,15,17,17,15,17,17,17,0,0], +"S":[0,0,0,14,17,1,14,16,17,14,0,0], +"T":[0,0,0,31,4,4,4,4,4,4,0,0], +"U":[0,0,0,17,17,17,17,17,17,14,0,0], +"V":[0,0,0,17,17,17,17,10,10,4,0,0], +"W":[0,0,0,17,17,17,17,21,27,17,0,0], +"X":[0,0,0,17,17,10,4,10,17,17,0,0], +"Y":[0,0,0,17,17,10,4,4,4,4,0,0], +"Z":[0,0,0,31,16,8,4,2,1,31,0,0], +"[":[0,0,0,12,4,4,4,4,4,12,0,0], +"\\":[0,0,0,1,1,2,4,8,16,16,0,0], +"]":[0,0,0,6,4,4,4,4,4,6,0,0], +"^":[0,0,0,4,10,17,0,0,0,0,0,0], +"_":[0,0,0,0,0,0,0,0,0,31,0,0], +"`":[0,0,0,2,4,0,0,0,0,0,0,0], +"a":[0,0,0,0,0,30,17,17,17,30,0,0], +"b":[0,0,0,1,1,15,17,17,17,15,0,0], +"c":[0,0,0,0,0,14,17,1,17,14,0,0], +"d":[0,0,0,16,16,30,17,17,17,30,0,0], +"e":[0,0,0,0,0,14,17,31,1,14,0,0], +"f":[0,0,0,12,18,2,15,2,2,2,0,0], +"g":[0,0,0,0,0,30,17,17,17,30,16,14], +"h":[0,0,0,1,1,15,17,17,17,17,0,0], +"i":[0,0,0,4,0,6,4,4,4,31,0,0], +"j":[0,0,0,16,0,24,16,16,16,16,17,14], +"k":[0,0,0,1,1,17,9,7,9,17,0,0], +"l":[0,0,0,3,2,2,2,2,2,28,0,0], +"m":[0,0,0,0,0,15,21,21,21,21,0,0], +"n":[0,0,0,0,0,15,17,17,17,17,0,0], +"o":[0,0,0,0,0,14,17,17,17,14,0,0], +"p":[0,0,0,0,0,15,17,17,17,15,1,1], +"q":[0,0,0,0,0,30,17,17,17,30,16,16], +"r":[0,0,0,0,0,13,19,1,1,1,0,0], +"s":[0,0,0,0,0,30,1,14,16,15,0,0], +"t":[0,0,0,2,2,15,2,2,2,28,0,0], +"u":[0,0,0,0,0,17,17,17,17,30,0,0], +"v":[0,0,0,0,0,17,17,17,10,4,0,0], +"w":[0,0,0,0,0,17,17,21,21,10,0,0], +"x":[0,0,0,0,0,17,10,4,10,17,0,0], +"y":[0,0,0,0,0,17,17,17,17,30,16,14], +"z":[0,0,0,0,0,31,8,4,2,31,0,0], +"{":[0,0,0,8,4,4,2,4,4,8,0,0], +"|":[0,0,0,4,4,4,4,4,4,4,0,0], +"}":[0,0,0,2,4,4,8,4,4,2,0,0], +"~":[0,0,0,0,0,18,13,0,0,0,0,0], +"¡":[0,0,0,4,0,4,4,4,4,4,0,0], +"¢":[0,0,0,4,14,21,5,21,14,4,0,0], +"£":[0,0,0,12,18,2,15,2,2,31,0,0], +"¤":[0,0,0,0,17,14,10,14,17,0,0,0], +"¥":[0,0,0,17,10,4,31,4,31,4,0,0], +"¦":[0,0,0,4,4,4,0,4,4,4,0,0], +"§":[0,0,0,30,1,14,17,14,16,15,0,0], +"¨":[0,0,0,10,0,0,0,0,0,0,0,0], +"©":[0,0,0,14,27,21,29,21,27,14,0,0], +"ª":[0,0,0,14,9,9,9,14,0,0,0,0], +"«":[0,0,0,0,0,18,9,18,0,0,0,0], +"¬":[0,0,0,0,0,0,31,16,0,0,0,0], +"®":[0,0,0,14,25,21,21,25,21,14,0,0], +"¯":[0,0,0,0,0,0,31,0,0,0,0,0], +"°":[0,0,0,6,9,9,6,0,0,0,0,0], +"±":[0,0,0,4,4,31,4,4,0,31,0,0], +"²":[0,0,0,3,4,2,1,7,0,0,0,0], +"³":[0,0,0,3,4,2,4,3,0,0,0,0], +"´":[0,0,8,4,0,0,0,0,0,0,0,0], +"µ":[0,0,0,0,0,17,17,17,17,15,1,1], +"¶":[0,0,0,30,23,23,23,22,20,20,0,0], +"·":[0,0,0,4,0,0,0,0,0,0,0,0], +"¸":[0,0,0,0,0,0,0,0,0,4,8,6], +"¹":[0,0,0,2,3,2,2,7,0,0,0,0], +"º":[0,0,0,6,9,9,9,6,0,0,0,0], +"»":[0,0,0,0,0,9,18,9,0,0,0,0], +"¼":[0,0,0,1,9,5,2,21,28,16,0,0], +"½":[0,0,0,1,9,5,14,17,8,28,0,0], +"¾":[0,0,0,7,22,15,4,22,29,16,0,0], +"¿":[0,0,0,4,0,4,2,1,17,14,0,0], +"À":[2,4,0,14,17,17,31,17,17,17,0,0], +"Á":[8,4,0,14,17,17,31,17,17,17,0,0], +"Â":[4,10,0,14,17,17,31,17,17,17,0,0], +"Ã":[22,9,0,14,17,17,31,17,17,17,0,0], +"Ä":[0,10,0,14,17,17,31,17,17,17,0,0], +"Å":[4,10,4,14,17,17,31,17,17,17,0,0], +"Æ":[0,0,0,30,5,5,31,5,5,29,0,0], +"Ç":[0,0,0,14,17,1,1,1,17,14,8,6], +"È":[2,4,0,31,1,1,15,1,1,31,0,0], +"É":[8,4,0,31,1,1,15,1,1,31,0,0], +"Ê":[4,10,0,31,1,1,15,1,1,31,0,0], +"Ë":[0,10,0,31,1,1,15,1,1,31,0,0], +"Ì":[2,4,0,31,4,4,4,4,4,31,0,0], +"Í":[8,4,0,31,4,4,4,4,4,31,0,0], +"Î":[4,10,0,31,4,4,4,4,4,31,0,0], +"Ï":[0,10,0,31,4,4,4,4,4,31,0,0], +"Ð":[0,0,0,15,17,17,19,17,17,15,0,0], +"Ñ":[22,9,0,17,17,19,21,25,17,17,0,0], +"Ò":[2,4,0,14,17,17,17,17,17,14,0,0], +"Ó":[8,4,0,14,17,17,17,17,17,14,0,0], +"Ô":[4,10,0,14,17,17,17,17,17,14,0,0], +"Õ":[22,9,0,14,17,17,17,17,17,14,0,0], +"Ö":[0,10,0,14,17,17,17,17,17,14,0,0], +"×":[0,0,0,0,0,0,10,4,10,0,0,0], +"Ø":[0,0,0,22,9,25,21,19,18,13,0,0], +"Ù":[2,4,0,17,17,17,17,17,17,14,0,0], +"Ú":[8,4,0,17,17,17,17,17,17,14,0,0], +"Û":[4,10,0,17,17,17,17,17,17,14,0,0], +"Ü":[0,10,0,17,17,17,17,17,17,14,0,0], +"Ý":[8,4,0,17,17,10,4,4,4,4,0,0], +"Þ":[0,0,0,1,15,17,17,17,15,1,0,0], +"ß":[0,0,0,6,9,9,13,17,17,13,0,0], +"à":[0,0,2,4,0,30,17,17,17,30,0,0], +"á":[0,0,8,4,0,30,17,17,17,30,0,0], +"â":[0,0,4,10,0,30,17,17,17,30,0,0], +"ã":[0,0,22,9,0,30,17,17,17,30,0,0], +"ä":[0,0,0,10,0,30,17,17,17,30,0,0], +"å":[0,4,10,4,0,30,17,17,17,30,0,0], +"æ":[0,0,0,0,0,14,21,29,5,30,0,0], +"ç":[0,0,0,0,0,14,17,1,17,14,8,6], +"è":[0,0,2,4,0,14,17,31,1,14,0,0], +"é":[0,0,8,4,0,14,17,31,1,14,0,0], +"ê":[0,0,4,10,0,14,17,31,1,14,0,0], +"ë":[0,0,0,10,0,14,17,31,1,14,0,0], +"ì":[0,0,2,4,0,6,4,4,4,31,0,0], +"í":[0,0,8,4,0,6,4,4,4,31,0,0], +"î":[0,0,4,10,0,6,4,4,4,31,0,0], +"ï":[0,0,0,10,0,6,4,4,4,31,0,0], +"ð":[0,0,14,48,24,30,17,17,17,14,0,0], +"ñ":[0,0,22,9,0,15,17,17,17,17,0,0], +"ò":[0,0,2,4,0,14,17,17,17,14,0,0], +"ó":[0,0,8,4,0,14,17,17,17,14,0,0], +"ô":[0,0,4,10,0,14,17,17,17,14,0,0], +"õ":[0,0,22,9,0,14,17,17,17,14,0,0], +"ö":[0,0,0,10,0,14,17,17,17,14,0,0], +"÷":[0,0,0,0,0,4,0,31,0,4,0,0], +"ø":[0,0,0,0,0,22,9,21,18,13,0,0], +"ù":[0,0,2,4,0,17,17,17,17,30,0,0], +"ú":[0,0,8,4,0,17,17,17,17,30,0,0], +"û":[0,0,4,10,0,17,17,17,17,30,0,0], +"ü":[0,0,0,10,0,17,17,17,17,30,0,0], +"ý":[0,0,8,4,0,17,17,17,17,30,16,14], +"þ":[0,0,0,1,1,15,17,17,17,15,1,1], +"ÿ":[0,0,0,10,0,17,17,17,17,30,16,14], +"Ā":[0,14,0,14,17,17,31,17,17,17,0,0], +"ā":[0,0,0,14,0,30,17,17,17,30,0,0], +"Ă":[10,4,0,14,17,17,31,17,17,17,0,0], +"ă":[0,0,10,4,0,30,17,17,17,30,0,0], +"Ą":[0,0,0,14,17,17,31,17,17,17,8,16], +"ą":[0,0,0,0,0,30,17,17,17,30,4,24], +"Ć":[8,4,0,14,17,1,1,1,17,14,0,0], +"ć":[0,0,8,4,0,14,17,1,17,14,0,0], +"Ĉ":[4,10,0,14,17,1,1,1,17,14,0,0], +"ĉ":[0,0,4,10,0,14,17,1,17,14,0,0], +"Ċ":[0,4,0,14,17,1,1,1,17,14,0,0], +"ċ":[0,0,0,4,0,14,17,1,17,14,0,0], +"Č":[10,4,0,14,17,1,1,1,17,14,0,0], +"č":[0,0,10,4,0,14,17,1,17,14,0,0], +"Ď":[10,4,0,15,17,17,17,17,17,15,0,0], +"ď":[0,0,80,80,16,30,17,17,17,30,0,0], +"Đ":[0,0,0,15,17,17,19,17,17,15,0,0], +"đ":[0,0,16,60,16,30,17,17,17,30,0,0], +"Ē":[0,14,0,31,1,1,7,1,1,31,0,0], +"ē":[0,0,0,14,0,14,17,31,1,14,0,0], +"Ĕ":[10,4,0,31,1,1,7,1,1,31,0,0], +"ĕ":[0,0,10,4,0,14,17,31,1,14,0,0], +"Ė":[0,4,0,31,1,1,7,1,1,31,0,0], +"ė":[0,0,0,4,0,14,17,31,1,14,0,0], +"Ę":[0,0,0,31,1,1,7,1,1,31,4,24], +"ę":[0,0,0,0,0,14,17,31,1,30,4,24], +"Ě":[0,14,0,31,1,1,7,1,1,31,0,0], +"ě":[0,0,0,10,0,14,17,31,1,14,0,0], +"Ĝ":[4,10,0,14,17,1,29,17,17,14,0,0], +"ĝ":[0,0,4,10,0,30,17,17,17,30,16,14], +"Ğ":[10,4,0,14,17,1,29,17,17,14,0,0], +"ğ":[0,0,10,4,0,30,17,17,17,30,16,14], +"Ġ":[0,4,0,14,17,1,29,17,17,14,0,0], +"ġ":[0,0,0,4,0,30,17,17,17,30,16,14], +"Ģ":[0,0,0,14,17,1,29,17,17,14,8,6], +"ģ":[0,0,8,4,0,30,17,17,17,30,16,14], +"Ĥ":[4,10,0,17,17,17,31,17,17,17,0,0], +"ĥ":[0,0,8,21,1,15,17,17,17,17,0,0], +"Ħ":[0,0,0,17,63,17,31,17,17,17,0,0], +"ħ":[0,0,0,1,3,1,15,17,17,17,0,0], +"Ĩ":[22,9,0,31,4,4,4,4,4,31,0,0], +"ĩ":[0,0,22,9,0,6,4,4,4,31,0,0], +"Ī":[0,14,0,31,4,4,4,4,4,31,0,0], +"ī":[0,0,0,14,0,6,4,4,4,31,0,0], +"Ĭ":[10,4,0,31,4,4,4,4,4,31,0,0], +"ĭ":[0,0,10,4,0,6,4,4,4,31,0,0], +"Į":[0,0,0,31,4,4,4,4,4,31,4,24], +"į":[0,0,0,4,0,6,4,4,4,31,4,24], +"İ":[22,9,0,31,4,4,4,4,4,31,0,0], +"ı":[0,0,0,0,0,6,4,4,4,31,0,0], +"IJ":[0,0,0,23,18,18,18,18,18,15,0,0], +"ij":[0,0,0,18,0,27,18,18,18,31,16,14], +"Ĵ":[4,10,0,16,16,16,16,17,17,14,0,0], +"ĵ":[0,0,16,40,0,24,16,16,16,16,17,14], +"Ķ":[0,0,0,17,9,5,3,5,9,17,4,4], +"ķ":[0,0,0,1,1,17,9,7,9,17,4,4], +"ĸ":[0,0,0,0,0,17,9,7,9,17,0,0], +"Ĺ":[8,4,0,1,1,1,1,1,1,31,0,0], +"ĺ":[8,4,0,31,4,4,4,4,4,31,0,0], +"Ļ":[0,0,0,1,1,1,1,1,1,31,8,6], +"ļ":[0,0,0,31,4,4,4,4,4,31,8,6], +"Ľ":[0,0,0,17,17,9,1,1,1,31,0,0], +"ľ":[0,0,0,19,18,10,2,2,2,28,0,0], +"Ŀ":[0,0,0,1,1,1,9,1,1,31,0,0], +"ŀ":[0,0,0,3,2,2,10,2,2,28,0,0], +"Ł":[0,0,0,1,1,1,3,1,1,31,0,0], +"ł":[0,0,0,3,2,2,6,3,2,28,0,0], +"Ń":[8,4,0,17,17,19,21,25,17,17,0,0], +"ń":[0,0,8,4,0,15,17,17,17,17,0,0], +"Ņ":[0,0,0,17,17,19,21,25,17,17,4,3], +"ņ":[0,0,0,0,0,15,17,17,17,17,4,3], +"Ň":[10,4,0,17,17,19,21,25,17,17,0,0], +"ň":[0,0,10,4,0,15,17,17,17,17,0,0], +"ʼn":[0,0,0,0,0,15,17,17,17,17,0,0], +"Ŋ":[0,0,0,17,17,19,21,25,17,17,16,12], +"ŋ":[0,0,0,0,0,15,17,17,17,17,16,12], +"Ō":[0,14,0,14,17,17,17,17,17,14,0,0], +"ō":[0,0,0,14,0,14,17,17,17,14,0,0], +"Ŏ":[10,4,0,14,17,17,17,17,17,14,0,0], +"ŏ":[0,0,10,4,0,14,17,17,17,14,0,0], +"Ő":[20,10,0,14,17,17,17,17,17,14,0,0], +"ő":[0,0,20,10,0,14,17,17,17,14,0,0], +"Œ":[0,0,0,30,5,5,29,5,5,30,0,0], +"œ":[0,0,0,0,0,14,21,29,5,14,0,0], +"Ŕ":[8,4,0,15,17,17,15,17,17,17,0,0], +"ŕ":[0,0,8,4,0,13,19,1,1,1,0,0], +"Ŗ":[0,0,0,15,17,17,15,17,17,17,4,3], +"ŗ":[0,0,0,0,0,13,19,1,1,1,4,3], +"Ř":[10,4,0,15,17,17,15,17,17,17,0,0], +"ř":[0,0,10,4,0,13,19,1,1,1,0,0], +"Ś":[8,4,0,14,17,1,14,16,17,14,0,0], +"ś":[0,0,8,4,0,30,1,14,16,15,0,0], +"Ŝ":[4,10,0,14,17,1,14,16,17,14,0,0], +"ŝ":[0,0,4,10,0,30,1,14,16,15,0,0], +"Ş":[0,0,0,14,17,1,14,16,17,14,4,3], +"ş":[0,0,0,0,0,30,1,14,16,15,4,3], +"Š":[10,4,0,14,17,1,14,16,17,14,0,0], +"š":[0,0,10,4,0,30,1,14,16,15,0,0], +"Ţ":[0,0,0,31,4,4,4,4,4,4,4,3], +"ţ":[0,0,0,2,2,15,2,2,2,28,8,6], +"Ť":[10,4,0,31,4,4,4,4,4,4,0,0], +"ť":[0,0,8,10,2,15,2,2,2,28,0,0], +"Ŧ":[0,0,0,31,4,14,4,4,4,4,0,0], +"ŧ":[0,0,0,2,15,2,15,2,2,28,0,0], +"Ũ":[22,9,0,17,17,17,17,17,17,14,0,0], +"ũ":[0,0,22,9,0,17,17,17,17,30,0,0], +"Ū":[0,14,0,17,17,17,17,17,17,14,0,0], +"ū":[0,0,0,14,0,17,17,17,17,30,0,0], +"Ŭ":[10,4,0,17,17,17,17,17,17,14,0,0], +"ŭ":[0,0,10,4,0,17,17,17,17,30,0,0], +"Ů":[10,4,0,17,17,17,17,17,17,14,0,0], +"ů":[0,4,10,4,0,17,17,17,17,30,0,0], +"Ű":[20,10,0,17,17,17,17,17,17,14,0,0], +"ű":[0,0,20,10,0,17,17,17,17,30,0,0], +"Ų":[0,0,0,17,17,17,17,17,17,14,4,24], +"ų":[0,0,0,0,0,17,17,17,17,30,4,24], +"Ŵ":[4,10,0,17,17,17,17,21,27,17,0,0], +"ŵ":[0,0,4,10,0,17,17,21,21,10,0,0], +"Ŷ":[4,10,0,17,17,10,4,4,4,4,0,0], +"ŷ":[0,0,4,10,0,17,17,17,17,30,16,14], +"Ÿ":[0,10,0,17,17,10,4,4,4,4,0,0], +"Ź":[8,4,0,31,16,8,4,2,1,31,0,0], +"ź":[0,0,8,4,0,31,8,4,2,31,0,0], +"Ż":[0,4,0,31,16,8,4,2,1,31,0,0], +"ż":[0,0,0,4,0,31,8,4,2,31,0,0], +"Ž":[10,4,0,31,16,8,4,2,1,31,0,0], +"ž":[0,0,10,4,0,31,8,4,2,31,0,0], +"Ё":[0,10,0,31,1,1,15,1,1,31,0,0], +"А":[0,0,0,14,17,17,17,31,17,17,0,0], +"Б":[0,0,0,31,1,1,15,17,17,15,0,0], +"В":[0,0,0,15,17,17,15,17,17,15,0,0], +"Г":[0,0,0,31,1,1,1,1,1,1,0,0], +"Д":[0,0,0,12,10,10,10,10,10,31,17,0], +"Е":[0,0,0,31,1,1,15,1,1,31,0,0], +"Ж":[0,0,0,21,21,21,14,21,21,21,0,0], +"З":[0,0,0,14,17,16,14,16,17,14,0,0], +"И":[0,0,0,17,17,25,21,19,17,17,0,0], +"Й":[0,10,4,17,17,25,21,19,17,17,0,0], +"К":[0,0,0,25,5,5,3,5,9,17,0,0], +"Л":[0,0,0,30,18,18,18,18,18,17,0,0], +"М":[0,0,0,17,27,21,17,17,17,17,0,0], +"Н":[0,0,0,17,17,17,31,17,17,17,0,0], +"О":[0,0,0,14,17,17,17,17,17,14,0,0], +"П":[0,0,0,31,17,17,17,17,17,17,0,0], +"Р":[0,0,0,15,17,17,15,1,1,1,0,0], +"С":[0,0,0,14,17,1,1,1,17,14,0,0], +"Т":[0,0,0,31,4,4,4,4,4,4,0,0], +"У":[0,0,0,17,17,17,17,30,16,14,0,0], +"Ф":[0,0,0,4,14,21,21,21,14,4,0,0], +"Х":[0,0,0,17,17,10,4,10,17,17,0,0], +"Ц":[0,0,0,0,9,9,9,9,9,31,16,0], +"Ч":[0,0,0,17,17,17,30,16,16,16,0,0], +"Ш":[0,0,0,21,21,21,21,21,21,31,0,0], +"Щ":[0,0,0,21,21,21,21,21,21,31,16,0], +"Ъ":[0,0,0,0,3,2,14,18,18,14,0,0], +"Ы":[0,0,0,0,17,17,19,21,21,19,0,0], +"Ь":[0,0,0,0,1,1,15,17,17,15,0,0], +"Э":[0,0,0,14,17,16,28,16,17,14,0,0], +"Ю":[0,0,0,9,21,21,23,21,21,9,0,0], +"Я":[0,0,0,0,30,17,17,30,17,17,0,0], +"а":[0,0,0,0,0,14,16,30,17,30,0,0], +"б":[0,0,0,30,1,13,19,17,17,14,0,0], +"в":[0,0,0,0,0,15,17,15,17,15,0,0], +"г":[0,0,0,0,0,31,1,1,1,1,0,0], +"д":[0,0,0,0,0,12,10,10,10,31,17,0], +"е":[0,0,0,0,0,14,17,31,1,14,0,0], +"ж":[0,0,0,0,0,21,14,4,14,21,0,0], +"з":[0,0,0,0,0,6,9,4,9,6,0,0], +"и":[0,0,0,0,0,17,25,21,19,17,0,0], +"й":[0,0,0,10,4,17,25,21,19,17,0,0], +"к":[0,0,0,0,0,17,9,7,9,17,0,0], +"л":[0,0,0,0,0,30,18,18,18,17,0,0], +"м":[0,0,0,0,0,17,27,21,17,17,0,0], +"н":[0,0,0,0,0,17,17,31,17,17,0,0], +"о":[0,0,0,0,0,14,17,17,17,14,0,0], +"п":[0,0,0,0,0,31,17,17,17,17,0,0], +"р":[0,0,0,0,0,13,19,17,17,15,1,1], +"с":[0,0,0,0,0,14,17,1,17,14,0,0], +"т":[0,0,0,0,0,31,4,4,4,4,0,0], +"у":[0,0,0,0,0,17,17,17,17,30,16,14], +"ф":[0,0,0,4,4,14,21,21,21,14,4,4], +"х":[0,0,0,0,0,17,10,4,10,17,0,0], +"ц":[0,0,0,0,0,9,9,9,9,31,16,0], +"ч":[0,0,0,0,0,17,17,17,30,16,0,0], +"ш":[0,0,0,0,0,21,21,21,21,31,0,0], +"щ":[0,0,0,0,0,21,21,21,21,31,16,0], +"ъ":[0,0,0,0,0,3,2,14,18,14,0,0], +"ы":[0,0,0,0,0,17,17,19,21,19,0,0], +"ь":[0,0,0,0,0,1,1,15,17,15,0,0], +"э":[0,0,0,0,0,14,17,28,17,14,0,0], +"ю":[0,0,0,0,0,9,21,23,21,9,0,0], +"я":[0,0,0,0,0,30,17,17,30,17,0,0], +"ё":[0,0,0,10,0,14,17,31,1,14,0,0], +"—":[0,0,0,0,0,0,31,0,0,0,0,0], +"’":[0,0,0,8,4,0,0,0,0,0,0,0], +"…":[0,0,0,0,0,0,0,0,21,21,0,0], +"€":[0,0,0,12,18,7,2,7,18,12,0,0], +"←":[0,0,0,0,0,4,30,31,30,4,0,0], +"↑":[0,0,0,0,0,4,14,31,14,14,0,0], +"→":[0,0,0,0,0,4,15,31,15,4,0,0], +"↓":[0,0,0,0,0,14,14,31,14,4,0,0], +" ":[0,0,0,0,0,0,0,0,0,0,0,0] +} \ No newline at end of file diff --git a/src/main/resources/fonts/monogram/bitmap/monogram-bitmap.png b/src/main/resources/fonts/monogram/bitmap/monogram-bitmap.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a99d4712d4402aa24cdb5cfdaf70475aa372e9 GIT binary patch literal 1376 zcmV-m1)utfP)Px)8c9S!RCt{2oZ)igAPj`Ez3%R&ZB&<6K7nx=_) zIA!RV>c{*1aD01pMs?hY$DbWe%_}O88s%{u$NBFs+rg(P^L$h~cqywpz&8H)dNs^x z-XFkbH+Q&89>;N<>IG*S=d!iuj2CHrA^)~fl=@kmk^d8vDfMRUy?DYCvbO5?hG$o; zJ1S>)YNgIBKYXiGP-gVAS2P`YcgarB_k=<=KGd@L$yV9u=46!el`aP?*`OhN&w2O zSSim=9joH?P&$_#r*Vu^Q1P?}S}m|SuCf}j0nV&m zR;hv8SQ%14x}H=CHT7@ioV zh~0_UZkU9Sk&`R_nXUI32BMVjzO~W3>y<_JUVPEKo;g;QEp|Pox|3@Z+Qye|d0?Mq zrA`#e(b*|wrM)+$pmVcC+K$>u+3$uj-g2vHnx^?|dVaqFYA4KgR;C`swjvdv<6b^< zs{#6o4p7A~)t^E&IV+=VOJVC}wtiR2nSW)6L^U4oSp{}H$_12ISUg@#w1QWul@Ie* zjfZ`AALJ|QBs8MOvhf(w zY&`#Hx>EKeA(nJxX4HH#w~K$b9DN5`^J$u4rq7+?-jp9r_nRqNnZa{JYOYRtB zqGz*rN$eZd(3ug%$|F0)9>s++Ty)3C4FUXSA9b?X`1ltRHAFOZD7PwW1kWD5N3X2x z_Nc3i%v`+NPh}N^?hH$3$xYKVP4mA}$zIiR=F3~3XR>ByAC+h7@UG3vkd@DFVw7pn zsKRWbrq&zz_v)Q_uh_YRF8WbEdOj+n(^N*NS})U$W;O;?h<;bL+49pr?^f}o3nmjV zNkM&hLy0FVWn?87RPXhwWb$romHBXX%8KUIo#l+|wPmmO$7`qG^F%Vr#R-m`-2rn% iQ(pFavT2&;mHY+y5HF%`Cq;_@0000Px)Vo5|nRCt{2UG0wJFbGUfeg9X^{W;#DufzFm z1MY-B0f6018%%tpEgjB9`QCPR}Mq(<-Z&nqgUn6?13FrUeRNL4wZ&U1@RW`C=qoA3qVAZ0oT!63P zt|1}9Q|&Gk3WdUhkkwzJcwMD?vb+c>l5>ju8U0&u?Lpxo3K>~BhzHSUY+yrFltd!i z*c17DcSMwqv!#KEo+Ti*MA0wQh`b%}$|%uX*>Su~dnkM)HD@n*r9{D8jb2i>%LUE4 zFH~-kGE|0c+wG(wdB{bv?l9}4g1H|l_oR?Obs%YoXO-W+%Kx|a6+BcY=5o*aM0yxw z>9c2poSoP$|Iw()DHIBY!exlEy6C}n-=6V%Z4p14c_Udio6mG)c_#>9Kt%Z&yl%?Z3KKvs-?q3(XDk8m9asOE>hi7tZ~rE{r!zCIr(an{ zR>0cEULnwPygNn|S$GE6ySa@7ygptukVRMr+2+BXeT)Cdtmey3WY+UP`Dai ztZ&;?{w$wZQO1}(&T<^3T%snCAM|;E7os&G-uqe%zlHZ|(kiGS>dX9j?W`ORZ5~pa zP$^;hyppJ<{j&o`hhtaTAA)cXuXzc&;qjB4Zk)1T}~nRmsY+DPQj z?Gq=j9l?%y^a>fRO-W#VdZAD#6rPFl@i)LFck}^0Y*IJc&dR^lNqWTsM1`~&nMlU$ zLP=n^rFfM~+h%DDZ^m!YKc`+{kJm>{EKyZ=#GcRqt1MFJ_0Qx)`q1~t2CrX4%laZ) zJYIQz-iHQtS6ak_b{kmZqjGUpkFxUAqMw4{a<;kvJCIH8m0J(3{*33+Lf&cqJbJ{h z&^^7#9*;M&5&7%vII?v_j&u+BCU!k=c{R^cZ8a zAV8r*v|3OU>mP=OQf}MF<6~lJHQpYL&v(Y;a;S@H7bAnrbfE7e^_k4Z$To&YZ9^yM z2kOt)v-O|oN@COZo>xu#XE%C{Y(24Z`duHXJ*)nIKtEZ%V#Q}hwCoirM9ZKq> z;zx8vM>e)BLuS6GiIFm)NTHgjv3imGOz+Gw!|F1pi~<5jBw00008 +-- load_monogram + +-- COPY SNIPPET BELOW +-- INTO YOUR OWN CART: + +function load_monogram() + -- enable custom fonts + poke(0x5f58,0x81) + + -- add font to memory + poke(0x5600,unpack(split"6,6,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,31,31,31,31,31,31,0,0,0,31,31,31,0,0,0,0,0,31,27,31,0,0,0,0,0,27,4,27,0,0,0,0,0,27,0,27,0,0,0,0,0,27,27,27,0,0,0,0,8,12,14,12,8,0,0,0,2,6,14,6,2,0,0,15,1,1,1,1,0,0,0,0,0,16,16,16,16,30,0,17,10,4,31,4,31,4,0,0,0,0,14,0,0,0,0,0,0,0,0,0,6,12,0,0,0,0,0,0,12,12,0,0,0,10,10,0,0,0,0,0,4,10,4,0,0,0,0,0,0,0,0,0,0,0,0,4,4,4,4,4,0,4,0,10,10,0,0,0,0,0,0,0,10,31,10,10,31,10,0,8,62,11,62,104,62,8,0,0,51,24,12,6,51,0,0,6,9,9,30,9,9,22,0,8,4,0,0,0,0,0,0,8,4,4,4,4,4,8,0,2,4,4,4,4,4,2,0,0,4,21,14,21,4,0,0,0,4,4,31,4,4,0,0,0,0,0,0,0,4,4,2,0,0,0,31,0,0,0,0,0,0,0,0,0,4,4,0,16,16,8,4,2,1,1,0,14,17,25,21,19,17,14,0,4,6,4,4,4,4,31,0,14,17,16,8,4,2,31,0,14,17,16,12,16,17,14,0,18,18,17,31,16,16,16,0,31,1,1,15,16,16,15,0,14,1,1,15,17,17,14,0,31,16,16,8,4,4,4,0,14,17,17,14,17,17,14,0,14,17,17,30,16,16,14,0,0,4,4,0,0,4,4,0,0,4,4,0,0,4,4,2,0,24,6,1,6,24,0,0,0,0,31,0,31,0,0,0,0,3,12,16,12,3,0,0,14,17,16,8,4,0,4,0,14,25,21,21,25,1,14,0,0,0,30,17,17,17,30,0,1,1,15,17,17,17,15,0,0,0,14,17,1,17,14,0,16,16,30,17,17,17,30,0,0,0,14,17,31,1,14,0,12,18,2,15,2,2,2,0,0,0,30,17,17,30,16,14,1,1,15,17,17,17,17,0,4,0,6,4,4,4,31,0,16,0,24,16,16,16,17,14,1,1,17,9,7,9,17,0,3,2,2,2,2,2,28,0,0,0,15,21,21,21,21,0,0,0,15,17,17,17,17,0,0,0,14,17,17,17,14,0,0,0,15,17,17,15,1,1,0,0,30,17,17,30,16,16,0,0,13,19,1,1,1,0,0,0,30,1,14,16,15,0,2,2,15,2,2,2,28,0,0,0,17,17,17,17,30,0,0,0,17,17,17,10,4,0,0,0,17,17,21,21,10,0,0,0,17,10,4,10,17,0,0,0,17,17,17,30,16,14,0,0,31,8,4,2,31,0,12,4,4,4,4,4,12,0,1,1,2,4,8,16,16,0,12,8,8,8,8,8,12,0,4,10,17,0,0,0,0,0,0,0,0,0,0,0,31,0,2,4,0,0,0,0,0,0,14,17,17,17,31,17,17,0,15,17,17,15,17,17,15,0,14,17,1,1,1,17,14,0,15,17,17,17,17,17,15,0,31,1,1,15,1,1,31,0,31,1,1,15,1,1,1,0,14,17,1,29,17,17,14,0,17,17,17,31,17,17,17,0,31,4,4,4,4,4,31,0,16,16,16,16,17,17,14,0,17,9,5,3,5,9,17,0,1,1,1,1,1,1,31,0,17,27,21,17,17,17,17,0,17,17,19,21,25,17,17,0,14,17,17,17,17,17,14,0,15,17,17,15,1,1,1,0,14,17,17,17,21,9,22,0,15,17,17,15,17,17,17,0,14,17,1,14,16,17,14,0,31,4,4,4,4,4,4,0,17,17,17,17,17,17,14,0,17,17,17,17,17,10,4,0,17,17,17,17,21,27,17,0,17,17,10,4,10,17,17,0,17,17,10,4,4,4,4,0,31,16,8,4,2,1,31,0,8,4,4,2,4,4,8,0,4,4,4,0,4,4,4,0,4,8,8,16,8,8,4,0,0,0,18,13,0,0,0,0,0,0,0,0,0,0,0,0,31,31,31,31,31,31,31,0,21,10,21,10,21,10,21,0,0,17,31,21,21,14,0,0,14,31,17,27,14,17,14,0,17,4,17,4,17,4,17,0,2,6,30,14,15,12,8,0,0,14,19,19,31,23,14,0,0,27,31,31,14,4,0,0,4,17,14,27,27,14,17,4,0,14,14,0,31,14,10,0,0,4,14,31,21,29,0,0,14,27,25,27,14,17,14,0,0,14,31,21,31,17,14,0,4,12,20,20,4,7,3,0,14,17,21,17,14,17,14,0,0,4,14,27,14,4,0,0,0,0,0,21,0,0,0,0,14,27,19,27,14,17,14,0,0,0,4,31,14,27,0,0,31,17,10,4,10,17,31,0,14,27,17,31,14,17,14,0,0,5,2,0,20,8,0,0,8,21,2,0,8,21,2,0,14,21,27,21,14,17,14,0,31,0,31,0,31,0,31,0,21,21,21,21,21,21,21,0")) +end +__gfx__ +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000002882000028820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00700700088888800888888000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00077000288888888888888200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00077000888888888888888800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00700700888888888888888800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000888888888888888800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000288888888888888200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000088888888888888000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000008888888888880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000888888888800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000088888888000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000008888880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000888800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000088000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__label__ +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ccc00000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000c0000000000000000000000000000100 +00100000000000000000000000000000888800088800888800088800088880808800088880888800000000cccc00c000c0000000000000000000000000000100 +00100000000000000000000000000000808080800080800080800080800080880080800080808080000000c000c00ccc00000000000000000000000000000100 +00100000000000000000000000000000808080800080800080800080800080800000800080808080000000c000c0c000c0000000000000000000000000000100 +0010000000000000000000000000000080808080008080008080008008888080000080008080808000c000cccc00c000c0000000000000000000000000000100 +0010000000000000000000000000000080808008880080008008880000008080000008888080808000c000c000000ccc00000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000088800000000000000000000000000c00000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000002882000028820000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000088888800888888000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000288888888888888200000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000888888888888888800000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000888888888888888800000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000888888888888888800000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000288888888888888200000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000088888888888888000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000008888888888880000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000888888888800000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000088888888000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000008888880000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000888800000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000088000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000660000000000000000000000000000660000000000000006000000600000000000000000000000000100 +00100000000000000000000000000000000000000006006000000000000000000000000006006000000000000006000000600000000000000000000000000100 +00100000000000000000000000000006666000000006000060660006660006660000000006000006660066660066660000600000000000000000000000000100 +00100000000000000000000000000060006000000066660066006060006060006000000066660060006060006006000000600000000000000000000000000100 +00100000000000000000000000000060006000000006000060000066666066666000000006000060006060006006000000600000000000000000000000000100 +00100000000000000000000000000060006000000006000060000060000060000000000006000060006060006006000000000000000000000000000000000100 +00100000000000000000000000000006666000000006000060000006660006660000000006000006660060006000666000600000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 +00111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + diff --git a/src/main/resources/fonts/monogram/pico-8/monogram.p8.png b/src/main/resources/fonts/monogram/pico-8/monogram.p8.png new file mode 100644 index 0000000000000000000000000000000000000000..70f2cf5d32e03ee230a2ffb19e68eaf89370b6e6 GIT binary patch literal 4691 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 O09a!SqZ$J@;y(eZf2XPd literal 0 HcmV?d00001 diff --git a/src/main/resources/fonts/monogram/ttf/monogram-extended-italic.ttf b/src/main/resources/fonts/monogram/ttf/monogram-extended-italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8c39014e9f217e919e73b2882264d5fb25de9c71 GIT binary patch literal 60904 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 Mg!^y0KY#B3199;QlK=n! literal 0 HcmV?d00001 diff --git a/src/main/resources/fonts/monogram/ttf/monogram-extended.ttf b/src/main/resources/fonts/monogram/ttf/monogram-extended.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e1debf0fd34aa5f788d33dbf6b37c273c8cc8443 GIT binary patch literal 59008 zcmdtL3!Gh5eJ{S&K4)g~eh`N6nlQnP1 z+WVX{Gg!R0pZ`u~@3kN6_g=r>Z$0)t#u{VhnAB9v?K^i~fAw4c{390{^UcG!dhX@h zckDFF&6zmgj`PlIu3O!6^q;!-8Do#)eE*?i1IMRd)%R9oTK5`LJ$mTw@lLZOdC-^{ zAHn_Vk%1G(Pn|Nr@Xbv)pLS&U-aDU|)_1uvbFVPwvpUG{AY0fmSY3=98VrGuf=`9Go2#? z#|FRm&*yJ8<}W^AOv`JIkDeI+^xO9RtTE43jhWX^O8B??jKx2=ZSJOjGUwqkwrB5I zv4zKPq#u9!RCX%0S@Jker7r;L6YG(f)(!wbKFLv|`UKHOcQ9`}{xXnK3=%<9C{x>0AR9ox(-X zme!2y@8m*Okf)dHPqsKrt( zoW+GyPF^w9r?MDG6WE1EUfq)t{t1E8$|iiE;mvZL@_EBbKc0&<$XEYSPpKfuOD4Kz z{vT5L&o}5w%S&`wt8dr z=R0R~F6-RfxxMpv=NmhJqVqR9pXmJk#b+#DviO&moVVn{C08sN>`J=kEq!`vwruON zJC`q5-m%6Y@)*-;-fcbz9DkOa2^^nHzEByj+*f%sa6DXjFK~Rf@^ZCOomaiC`bekg zT+q2eaEt@T`+(yq!STqF(*(!ug5#;B&n&Ag8}v9%edE+0pZfTz51tx7wc>|}gg-p- z^54Duw=aMG<-dCQb1(np%bzypWyGSF--W-!U;92NGGF`D*M9A5@BG?hC>_V&^yh#0 z{LdTn{IsvWV9ZzhzIp+lG3HBO{L-o~E&bd!m7!jf{q<2Rk+kC)0DJ9w2E9{ks9=q&&u>W=YUi&`#xczOEpRm7UzhnOwO5e5rJF!Wk zb&^U_O6ZynBRq_pE4gdzh{2m{DFDe z{Gs`%dB*$^AiT~THiKp;nQmSWix@SpO*+jB045=8e$I9p)!tXYa5J?CG=?u@BgaVK$iA4auD3TyGt? za!=AC*7H;5t>%97koj5ju=zRjhyTn|tkE zy9f3vc_!qp|7wuZ1HkiwU1rzWeRjk?W}mjt+ArG|l3B^t~48u%lleB)AB-VXY2OXJ6j)YeWvxfHq&-d+kv(> zwtcYe%hS%B)-&zEv^P(Ca@vd2&zOG2^xLPudHOr2e{A|+O#k|f))}j29Gr3gj3;J% zer7Ur*~}|uj?H}6%x7kPeO5AS<*a?P?wj@4tY>F^d-j~!YiHj&`|Y!{+25KoYtEuM zTj$)4e|OJ$|C}$(ZJm4R-2HRkG4}&=pP!e^yJ+5}^X{JamU$nX_uRaf<}aGRX8ykU zchA3n{$uk$JpYTQC8w=BZTPfzo%X2(X2H4zV+%gK;DytdoxcC{hfn|G)4$igvi;Wf zhuS~d{^A+up0WFkhtGI+VY0Bc@PUQd!spLibmrbO??3aSXMX#v&a-x&b5`gBWHj8oHNhaeNKALN6&fj+%wL-^xV77edOFvocpgGYdQ{hysP6Yi%wg# zX3_AX#}+-i=%w?Popm%nlO zlgt19g3~Y9biv&hJbuA*7rb=gstXTZ_`rovUHI*brd_n=qPs47*F~ROF=xfr6~ikY zU6HN$`<3%nu3ve_%KKNoZ{;UfzHo8p#k(#}FMi_UZ>~CH)upRORz14v*;OyCUcY*5 z_0y}rzh>E*y=xv^^Win$?moNwK=*^)pY3Vw+1K+(&vU(VdJpuzqxU1d-&{Ly?WVOi zuYGXsvwcZlPv6abZ}0o$x@6rY>+V_i&A08uH3kFqob~xb32CcWryu zwoh$u-M()7$o9v!e{M(Xj!iob?|8?Kr*}NR^R%7ociz79ojX6d^M%V#zkK`UM=yWy z@+U8U{)$Cc?78CpD?WI|^H-jJ<<2V~xbnkSKELaC8{jIC#UA_D22d;kd>KFDb+Ou`f@SaEZd~DD2*Ho`rd(EC} z?z`p#*L-vDjJ>P&9@zWly^rtx{N9(YU3Kjp*FJphN3Q+OzH|5W>^r#c&HFyQ@A>Ph z*R8+qj_dBf?gQ67d);@hpLhMH>yKWaUjM}PU%BD*8`j;h|AzZ+c=ru|e8ac)&)9#- z{-gUJ+5d_CKe+Mi8+Y9}eB+}xK5^qGn*96xjo-TQr314LoO@u?ftwG!@xa3e9zXD@ z125ck+D&V3+JDo?O^@943auHKX}2x^#}JI96tE&gP(iNjMr>^&E2p0*lWIh z^PHPk-Mshav76t2^A~RZ!7XRpvi_C>w+!F%@GXzs@`+o%f9srEdu}~?>qECbed`Oi zExK*jZ8zUGe%o7ad-rWm-}ar`dv4!z`(3v`di#^NGnOXohuI$V&MJs0t3G7S>P=6z zRn0&4e5keM%bT96Bq*8>Rd~6Dm!E2Dwg0;5Da-4<^B2!w-aEf*iJkZ2XFiiW{__4M z$(@9m0y6uX6~rae+t=H^d~>z8qng&Z>0XL_w#E)8o^9i^3pOXc9Z8q$vs#9G@|@cV zz1Z2BO%c_)2)nDjtFNoCceBL~h~%%mtHbuTcLA$KDT_fgD5tyzsGtQKU}u25oUm8e zzKs^p1vG$fwCyWwJCG13_w5xNr9NRyxRDcpZwokD5WD9iR-dJuEZ%H;JFM<2EGIyS zqy9lE77%`zm2g-&FJ47{fKB+QAV*jXUM@CfF~BYF1^txO3fp13037TITCmb#m-9+{ zzc3A41e)Scw=6#K7sD+Sh|;7;QZmt{(_|Vni2D`Vh3J}gvA|hI7fquwALIc?X;Yz9 z451TlRBv+&45|Xnl&bI)Qb7egZD3urm4(QI&NHST_5jtvMjY3o$PU{NmY=GaFX6(3ox^kQxqu1>9QOVX1*aY+^%gu%Ff7 z6SO3>mjoi@KdLQ(DESYX;LRkH`bAJ4WTT@hitFXYjL}+xORIETGoX*CXtOaVbHCO zNJZ|@rGhIJ=$Vw=t^rCQOafnL(Q+4WPFBz>v~NtvL#sC1NHldsODg~hz^MjU^sx&_ zb*@b)k*41d@X5w%`-%!sD18lRjp_Cdp$LCAsD=Cke3b}o#coVsL82A7wity%O^d+Y z8Kpz=V|$#5)(o4691>OPib~(cYDZEJA#+d^Mq4C@*^mP@ShQ_NE#%xUMxq*A>>Oik z;gq+B9+v~cs6e!Nn{0C)AHB+4N`ei{bX2K~z6J;to~qd+j&S7Kpr1*BF@xV@jB(z9 z){}cCwH!QEF*W)mJWHR12u!auU!K((9tRcS+lJ$(Ccc$8<|2+kCv3a6uUq^fcD>}! z_US&@YAS$A;iU$UV)H3r)&LP6%Xxx?^-Eq*L2#c5<0{`edSd+a1Iv{%HdR;GN4S*d zvSR_}68n`l$05c81PUSHFZge5;VG(RF$|Wo9BL{&#%*x{tPhcyRH8+!uo4J+ky=3f zeIzw$M`SD|F@QLg&CAn>50^HH++sz-xR1!0s2^!jw;gmd31kGeBHZapzXI-ExFXry zy0HSHnOqQ;aDbSbO#--lJ8m!Zw{u}#U5F8odPSs#w2UEJB(^{r4|qrgOu=nY&n4H0 z2f;)_MnpDq)n9F{g5YYzL-m!AEpc1nEp?U)QP7<_zlQ@8xG8NyTA&YB*Ij~@Ob90O zHI&^#NDjUxmYt?72|a&f2@-zf)fz~VPf(J1?1p|Ar2w^Sb8E1$)+sR!@&dnzb|D9- zvK2szwxc>Z923U~@kiRSkh8$zTfi&)dPc%wDJ7$jtF>Cu?m7@3mNHr{rm+cA%>5;4 z?Ca`C7T_{r@(<+ICFc%kIOHtE{CZDE_$?C|No2F_wAsa*VWNx{_}9@&`bb)k)THE) z-3aDt_bl{fC;EYA6Lu=*bOXLT_)lwG}<7rLIDJT-rP!){=mW9ZGJ`eke5L|64ssdih3;jH21DYo-aDCCG)D@_GP{o$P7k=!~Rhj17OJi*fk2b4ss-W_9;1X0Xd1UCea&h z=w%5Dnv};u56~yvL{!XOWsHF11BfK(Qp2Zt$6c(C3VB`~^>tOE%OO$l=>AAz%#DMT z>JcbwfW|Qf!AsS5Ax-RPbr0xcZm)PjCH)7-&6uH}o+KrZY^=*zZ$GL=-XK7s4ho8iTJQKK57XP}tyg zlt|JC?U8txsW0S5>8L1;Sm9H%q_Kn1WTBT~r0tf#dL|Ik04FbnOVZkT7RhtEjx~t; zWIm#O_6`RBNum>Z&h=7(9K}|33`Rzc*qSOuI%vQ#T)<#lCZCzujEve9GFNStNap%z z(F%@xIxPW*a$!@L&R#0$N1P`%11pq_t^#E+!{LZ1-Me!WfvU6s%$;O%jQDMXj->^= zew?_D2~yTvRy9Nj+OT+KM69lk3UYE}2q=I-#pKX7JVbIQEcC(^7P3P?LDQc^d69f* zTdtLc2+ctjMAE#CDn%N78d9hBk37NIE4XWSIS&W+>5OC~s4(tH)>7`u8_1)a9V}_@ zE$mwc4>v(`Pm91ZJO&;&x8bQN+aav}C>jH`@;;E@u;Fcb^`05sdu z0T5gaQxK_#1xNzSpX)KwWd>ci5f!zmlJ|(?Y7vN>1>3A4Qqv}cBpO+Dspdr?9>n23 zheWyi*jhtdWQ^m8ndVxErJ6lN=8-l?9|F7*ERIOS&oXaALvHVP)=%e%6pm?I2Kl7& z7hseNA-D4MF$U{MKU);w@-L3XY}`UO@2LktL(fzfDd}%4jGEX?@W1DvS6J}B7)$}p zEl#q4hb@)8Iwn{22Y-t)u5c%QojFzL`%*G))S8;BBKZso^I+*i(mE*- z9kr4ylDR`Z6Oh$|z?HNnAW}ff0EaVpj0LT@Uf7!B5_dsi>y#3a7HaY&^;dNKI1?Fg z43cs%tjrb#-|zG&6+}UquL&|?=blbx7@(9L%nIrR`0_MELy&a_JC26@&U0?@<<7}VdT`cmCe9Qaq<)TUC`{(N7C z`3yyfKk=ZHjo&DOAlOn8F|9 zy26S2%(=If7y&JWJSaAr2pHQh+XGB6^i4?ykZW@wGTd}=vz^fmnIMK{T>htF-%*#v zSa>aSH)_-y^hLcfu5WJX>S%#&&_J}4JM}SU&@1vo)Lj4$Qy~=8*Fd8&HKX#7*shl7 z``W1|21lVs;UH46#i@5kHxZK){z-pi1#I*SzyTgwBxgh>ft4mPDJcD+$$fRJw~ zpfLud_3f3p>tg(cBb-wk{-llihMGB34G;k7DIvn>#2Bnsc7TJ-|8F(9W#ImJ2 zi$XI5iN9_Y;o=mz%eufK4_(p@5vNUtPX$09TTI{`%!Qe z<&-&yO%I7tRFD|`4s_!9Vr)d@ZpIl{^4t|M(%q(t&K>k5+9!r5DIX(Br>6qm4b3sL z4XIqII8kFHT+v)c_}Eg}H{2zLmxTrr;qro{COVb8U~Z-0=j; zA?!wKiFGm|?4}B70vQ_jzCd*$y)055bWx^$0!1_y4X;Lar8+(#{?g^iYOiDi{M=`AUYaqm7w@Cci$P2rZP95tAf(_h zSW4dLxtaasKHJ4s=d{zwiAaopCZ-`@M}b&OOg_HLa6`e<_>2)?G{|QPFjW^_wl%$3 zgS$yQ9#_1-&thhu#IUUL=m_EOA)1SNgDM zla&gd5wVJl(j2DBEIKb2W&B2I$KKh^OqmUaCn&TIx9*1X`wPI3!=fqz*u*qd3+~p? zACGbwwg=8QPlN+JrL5`Of!bgb&GRwo@_W5|8|=`WyUzM!Zh)z*b>(L=j%qU&=2_1=XBNQn> z^KXJJm3wfpKK2^47agG!$umpz!wLDE`TX2wU42C%y*34@7sBON2A>udgYo6&@FDRa zr-VFkmMF9dklWsb6WpN{c?>~dn!o!DSox4WZvdBX8!;}7)RA&V+s4>7i7hxz!mDh-`i8yiB zKxvXU)u6X7oinaD%S+lZ42+=&Oo6rQ0CA(P7=V)0GzX(@-TX#3{5J>lZOWMOstVEFYk(8KbL>R%vKo)#}@Zr`>L@ANtkv%YQ zOP$Cvp2$Y~_|fo`)S}=rJv2m9E7|v4_{h-Hlh!1L+?Xq1%N?^vf}=y2iYdSt+&S)$ zGYB#FbDHzWdnA9;jYdL5o^t*8{ zCm5In>>@{X%<%$Q%JH!sb7nvEazo!Hn%vjLPf^Q!1HDYL$;=k&t2Zq>- zDg927smGijW$MDo#@UUmUJB8t4nrAwbJ(Pv(Y^&qLgr6ewA6 zaw=g--8otV8K@OxR?cj&snE6IX)O_jGiOCC1?? z^#BV;GMC$GK2J5le2$x`g1(uMM$BY6WI%1aC5BBW#Z9skw-N2bx&HISSv#xyEp-JP z$xDLf0lN))tUNNSp>;@~&U5Lr zKxxU1IZeI7ip;`u17Aj*P1r5v1BzcW0$EE#uIlmEaorR@a4UM*gAS@Ag`j;dR||5M zmhO2jO1W-MOu*1%Whh~5s#A~0W&w}(VJFdM6YI$YV?mEFQJ+((4s$!jxcg-MP`=K$ z05-RhwWbp=hkLNt9KR{>D4FC`tadW|Sv}K=+50r|y+CZ%jla*=nU&I;sTId7E0?x( z(s9*|Li<0^_e%T>^g_k<7nLq5T`8e<$7 z{2@#%(5@Szhv4`=SKBX7mLJYAd zj^D`nWVK20Eqa%`XXgVLL@x__@%6EPP(kTn^=ENKBC+#P>E%H~6GV0`?WlVkSlj z-HIm4!qdQBS-l+i1ZhYKPOxdg1uw;));E1z;wEx}EXw(n&W{J1@H8}*1GvZpnnN1# zKOyVD>)USz8&JL@|I;So>aLWhd#zIx-nS>IjIc;dTM~1lqq@->mcC&-L?O)O&A5PpC)$ir zpbT|AP6{204-UNGeaT+X&k31!CPLK;CWDgpa;lONV3Un}M|oTu$LRr|!>D{on&1H$ zD3G>H?L!8tbCF9b{;k2Nsf^P{Yq%&NcCU53G;2U>%4>h7@scj%&01SB( zG#KSgCFRP39)sec##;1G5i8uc zEBRIp@_2E?94zc8e4B_^;%20QkXP%r+%nW|BfRcgNF}U|-k(naHsHev&wcP^(Qla) zf}en`a!roB^H2Lgbm55pD||oS0oAdM8sfXY^B>xBm9oNjmgsd-A;9et(>QJzK#;a1 zu!1E8FIMuFsRLIyB&Hec*q7n`D9)&%eIP_DnH2=q6rlLmbR&5&9Qec>PQ6Q<0z{80 z@Q#>6IjT?WKr*z#%MlCtp{2~%;E_g@v5;?SSD>@lr!GL$F%SK~LlrN}mB6MA;AX@S z`7F|CFgISHP#)rgLRBh-u%qmpeuYQ*wxowppePG<8o)6&tcL~#+5`40mH9$3?Kp2+ z5u{L59g5G+kexUvLIGN|d_4rqH3%zCL__tw5M&H{^=qdmSvL(?E6~ttb8rnyiIK~> zK4E@--9pH2{r4y6^BjhW;j0gy#0B5j)8dHAUljON^ER&RiGp1ir6P)`r8B<7wh# zoKkAwsS<3Qx`r<(!^Wpe)RJPTp3MBnelOu6M{)55Ba&P@+JV~mM*+2UQ5KpFFrZcP z>QgEKe1OlfTfnCT>ZUu)?1dS1Kp~nZnW7=)Zm^@8YqghS7|AzHpaT12Val zu%(SV1M;YZ1UyhH&I#u0j?gDA;I8L$GTb!NlGO>fKsnJ^7O^tdU^HhgTCYu7G@ri5EK!P$cJw1Iu8|LB)zCv;DaKy+P0MBZ!F9z40%P7~0ykFX!o6sAf`+!}&LWU9p(y&pEt=!4Dc&;Dy z?oBmBq^O?q_tW$jtk;1U}p4D7v;jmxx${Z$mmff)80Wmj~=##xnp_HtqLIF$;816b(nqX_l~k*QQ&< zH>nizz8+i>`-@x~TyGLYlyVnuSwQTsroI4t`kQ5%Coo>cZ6TTy3mHRm-X~d_5t9J| znl}p3him`EHsR*9NlQ~ZV5^`r2t0;DnE>2~eH5RAJd$7wEr_6A?wecbJ>?_St$40p z$h_z`v}A8jEs!WYm+z@N3*v+<`v{b!3^M9B`%?j+dk?FK3iRc%GJFNC@P*zA45Lm4 zgjR;@5*jiMdY!!a-4etQ?eDv>X3pi8sLcp4+UlDXZUUsjMJ|G#E;4>V>GN`IXNS3x zMnKBNJl9}61y#&t;0i$xd)@3XgIAHC+fOB%AxYlndjGHQ0wTWA=Pt%y@O;txdAuj- zqr^`o+IOy!HR7yB>HA<(#)xw@3>bj7xmv`V>yhb-);fEGBqN-F z6*!!jMKQM~ZOrkBrrWfQ@m?Otl~tZ61|&?{ylCoySL)HXFUPB3Zm3O(O8@b4H0$IslfG&!YRX*?-@y|##Z(ShT z+XKfB{cHnAuVfq&!vgy01$2qJ+UfZy9ge0~@FxveLT4JKR!x{l3~$gv)lU@r$9xx_ zzkMWc5zw8Be*BS~$#4@pl{}F?h8jl(*TFxxHH<)dPh3gJFH>ay+N&U5d9zX9mL_)| zJA+h%H>NWHO!>d4w8Y?g;aa9`z~yp@+95Lm8Rzu!^iifS*kk<0gtAG1d^4Vw zloF`hQeWUPZR)aVd_ZktUrpSlIUCwi>D#JhgfTw#Xjy#8A@c2{R^Tz3X==`48i9+; zO?e|7Fb;X@>)y@)g=;VE%d0GuO~Ae80jA~fIj%KCC%c`6!<;;R5pV-zXvCNYh@ zw0Mo&@0h?}=TqA#D+V7*mwR0u%OzDaFi5QNyX6;F7zVYE;nt z1t)g=hk`TWuh~A2$jWHna@aeOm zwmX#rE!oF^_E*tU5h~>i=$4;HE_a{e+AOE;fJ)20F@$fnSD}j{)P6km! zTI3|vgrMr^A!Yh)9Oct><6Q;{s&XEjY*N|>q{Q!e=IevvMQ^xCf6DK{3SPpWuh`ko z;us1;FPMRKW~zoDDS$55om;F+7_KVn=M_^P`S#9d#)7ots}iA ziAbj4a7eX;y~2i#V-_>BDP;0We(x}u!nbZ|6InEV3Y$PjNk$DQA4(q0MhBMIO8iEi z^ISrIC&=ZWi&4q{LQn-T&>xd#?!JE`cim94z;#g5h;L$w2D4IP#UCq@a@jv z4$g41mQY5LeKRZ63?QuPm&KkKp(0TP*Kli`swIXvbS@bf29sQsJmoi~+0H)bW z={c!+_!IPJ@d!a<1NkuzlK%xB7~P{aJ53mvfb^g%$|s*c4Zk@nEvw<|X`?2P3DHf4 zhF~tKu)`MabmH@N&gd+au`~BIFM3W!rnq-P>80Yo07si11teQyw6W-!Gm0FK`V7uE z$lS2V&-oa(J5J~62b?ka&Hk7BM1;2o>5(YLOffi4fmOHE7u+!`^9m~wzt0?Ac#T^V zM<8)7JnNtzXrKr}6C=Z!!VV6&U_QJ`vF3R-+n!t5kheQ9R@defiGvO>ZYz}lJ0Sv@ zbDhq&$?=^;`H*k)JCm4hl}qyK`j7NmnFs+62fhd%ek-!Y6ID9K{OSq=SkdAY6`BgC zljMiMnM$;gYh@kmMqB2~sW0%DaR;B`X#rG@%3_@ubEOJ~v%SwKLSwbY8$q=Mc)qu~ z+`}ckuhMUhd78Vprlo-&GK~o=T#aeX{X$rc*4Qhg3Zm3b*bo|QkXs~Q#Bz~u$)8Ow z+(@R(Y2CWc#b`S6i%8AakpgrSNE#4`OF8;j9&A_od&`h-)UvodploPBkvVHp6saC` zpYZ^7Xbc~)LJPP@D~2SnqNbsc=K;QNnfJjR}|C_ z%4;_67ke75p8*1Fne4TIa!f@k=;=S$|4+DvOKl#bLoG7RvNWvZqCWFgT;MYXB%F9W zg%dKKIzFAB0v`4yXTu`hx8`)nu#}rJfmViQY|tgHS^nJ#2;z%qAqhqQR=Ohd1x5k8KO=oe+VYD0JsOXq3<3|nYI zB5nkA>w-fl${!!bcj>Fah4Oj%jPo3iXUi&Q+0a!RSyvCQYE;pJS`r*@sXvhd8d4H;wSby(zu~0c)W9#8savQ@ ztqgmqCTFsXP9X!I+ffD@leqrJ*_T?DdKvtfBB(}X`5)&{0#$Z^hqe*fEZ3-!fAq9- zn}vD$pFW48kaN3skCO^IPg#J6QtD4omMwKh9JGi%&_=rSJ|NG<>)L;Y+N@75G8W4l zgp3Vx4(%L~gnk!qSV(${q)C!N{+MfHSvZgck+&_Ef3-9A$(%#NK^xN7*srEBVVRy} zHNu3QiJVn$v@}`cO(`>yGM5P6L9z&Xg@o|tmT-uW#e-?Gpb#|#|v zL|NLXzYB=^&9+#EEockU+hSTlKyJ>)LWGSs@|m|$%`qJ<5^0@YWv~lYp9z$vS_M-I z&&f?Cwv-Wwu0piHY}hHp2Mh=@b>vY(sh6K#%iJv<(i$)z*9v;^-`Au+l`N0ns?%J* zUs@B;I-ji`3wg76g{8Pra0dXbe4)^E3wD-4(p-=0IpYc?#~J%Z;EW>kpp>gRs9EU> zWEaOhYD4o^NRa-5yr?OYHCY8Bc8BY2xRs%yrSgXz|2NoDUIQNQlQQR%=F17K($rjS4aY?LR^*Kw>(MloZ^jqNHUVc3FgH9(;EJq|P<9k1nV>~f_(tjl$F zn>R})*C_!+DD<>0CFK(-q~zjEz@y_vBBe4O-#!{+X@J4wDE1qe_tM939S;1t*SU;C zOXF#%clKVWdu$mAM+yIBYl0c~GBJbTgZO?7WcaG~$Gwfwe=>)Yxj6QP3i%uPvYIoZ zJ*Gl-Fan-sl$GC77_ZMsGFVtK3w`zSo-*yShO-gv7>`9J)R2DCoGIZrq*}tBJQU&U zih|;NpX7o~XZ&h$V6B6m!)N>Ee2viW=YymL>gX9>&zGpxNbseIMf#zk`Zlib2Zs4j z_OFQniiR5m8uxo-DeCi&dpg97MX%l{GV)?WJnYxPMrrPBXF3DVQ3>R?912lRQUFc~ zW2$JpJtZkh((dT3n$7(Ik3ts|^}EENe_pzbYX+OM+oXolkN@|81ihRV+oX=w{{=jlY+NwzK; z_*ho{u1*$ABnF!po|Sb6StJyQdxsE^kzHU^*A{=5QX0|ZbP|KaPJ+zg@>h(I{}txR z!UPiBV^_Y!K_N`IFT?)#G6x5Kv38?o`Thgs*wVKGFG<28(SYGDkUwD>uKXHi+KAKW zB6-cn@t68}vVYV3o6m=ReBnA^7kxQ4Evb2)%N9p=VFFnox#ZNsbuAREg?zLLGn5Z~ zw!ZTv@vY#uWhx1lts$o{Vw4Ktf_L>e8#KPO27I~fpb_@GFsOn29Md51+sHqVv;*PP z=0k{!EI9ckHE5Y*cp|@MkSoqBal9y!NakaOXQ08+cVi=*B0C*tbA3rJt>9QWGh6)Q zKs+VLp%U8%goO;Y0TNtNvNB;VmzRt*B}s;4Cq?z+R%*Y!{okK= zgFQ)1NzrR!M5%Dtr|ObIv~z8{ULuW;MftGDO@FWQM(3mz-jIEi0IJ@tyITO1+c)0K z;tCV^pBDXqnCz$SNjqJTm5k`~#mtzzmW5e3yKP zYAV@B3~M&67Qa{zzLEYjsKfa(qVUI$Wv-@PehtWtY&OM+4>H3-enhxaUp)k|QOS~f zu&ukEL@};Phl{}>>P&>51Surl84Cyr_$#pi9RNk{J1d}BX0Q-L{VEfIE|N(4#g#Jp zWgb%IunC6dJK_%QIR!5wZ5Z;oTj73tE-S;SyO7oh z)7xqd(AgpwkztR>l9cTV7Xg+U0%3#6cCzku^!+R;Hg0_|Ivi_W3T*IdaJMzw48sAedQ~18$zpqNHi;%5RNpK zHET{l$Y#nd$3r>4T618zEn+SSdArORP;HwlX!^x>DwU3WPu(Zn$^7bk`c{>qmmO#4 z+M)9B`s4&jGQk8`LW}c6Y|uzp=uabw)7GJMpNZvc-V}zO1Q;(o!56B(W zwo5eO_Z|4^WEE?>CZk5$nwwwXh$TD-Xa7VVI@aN*FLd%Jpm)@y;er(0)vT8LLE6Eq zIUbtx3cmycpa+F(f|n`LA_T?!@tb?#Z=uWX|Lr-Z$jyC^S>}9PXPI3trUCi#F7N+} zBo@zvUfpGS;M|Y1TaKaW$^dOs&U?{=^`jkdLWC{WwOThE*=)`y!sLr$*UpC{JCeK$ zHjR&eG6|;xpU|uUhNQ;r>@51%kXG_z1{0)}sn8L%E%Jwak|gMRtFoWinj7%~eFt75 zK>YUXl(v|s7Wj0Dua@hI^ff~HhR0-~HIfT|H4Xj;e7R4`(NH7^O(j_@%2>W38OM9^ zK?FBRppRk|+ol^Yr|slzgRdDNvNV1JN7KPfC5S_+CG6E7#j%!N1=>WILS9HW$$*8I z;=;TrN4KYv6#`F@f6lgvjY$Cai6DR6c}j_(5FPv!Oqa6wvH#w!jM>oh|G0kRqCz3b z+BjX^6Xk9TAZcnjXsb}*>=`yzZ{bejrTXqydp-!l7Wf%tjy6TQWCj8@hXhAbMkPil z!md2Chw6?`C(qJ+?koHaSQk@37O3GI0y3Zui&q+2JOOpb;Hy%u6OwOFx;mA*XL$k7 z*YH*vtF?h+i9v3G0eum~0iH6*+`tMAlvG|cu20b)rF_cqEwZCd(~yG!&J>>!q$wl` zir79K9Fki@zDz7N;PK-e?#n=IXh;h1JOU3|ZiYsm;aRDMUCILU9ABUCxjIj1WgT6R zr*P`)v%1y?eUO>ZC-q5Vv-(oY?;rw0d6NQ>C;C~Nu{@^qQ9?lGgEf;1k9a#&*HS2D zx(67N7;kJJ3R`8Taam{TKpqkj`F0AWS*+-QOi-V7_~#bB_r_@RGW08Siwz*2P%g`5 zG#fds8z*oB5c#4#Up@5&e*`Gs-ZPZ^%6Hf__3SBY<7Z%2!#-8JCo>6aDG)q?;JE(?WTu#*rAd zixg8|sTOM!_!z4^-F)AjtNutg5DVGuu(^ov0to;ry3qQp4XVK@Is!~flOQdJJtGmo zhxQiX%=5Ykb8bW4o=`PqeeU=dZ2(u`L*Jxw&|s2`xHKvDKBZcUJ^yq|abQD1Tgz-p z|09e3@Gx@;eJ0Zb+ZF$3Xf@!%`LR@P%218SL*X#W`6s9t^n&$41x1g6bJCtT+!vE^0r?}TFZS(m z1$2Q~Hy(+ZMaG7t9i|ME;OHR;R3VA6$O2LJO+Cyh9f2cnf4a`Rzz_HWXP~~0slZYS zL@FhRkr!(99M$L)u|eMC35VsJuUZJu98eHME*~Xt?7-+aW*7VmKN%}<348J_%^#tz z96siP7%OgwQNA}MaisySEC&~t()dK$mBDWXatu}1$XyKnHZV&RYmgtbA%lz~oE;On zI;Vj~j+ieVM~CIaR@`7f=J1G=3rKSUDDU`|;XBZb)R6ORc&9EH6XXfhGAf22*Ga^{ zF*XLvfn6abc1f6E%>;B4ixvBaiqT&Ady>xEqLB-u?SO_6K=`njAc3blT!tO6%_|Jn zN9rZC(cFTFn$(x-jcGv3;N*<}f!%BFdHWE<#(qTuBc(ZybK2It~o*Zh|GLO9A5q18xEq+@8Zf((=6 z4dm8{b`Pz@gS(vK2w5cfOJOzCtJ5k*yf*Ut&FyNom81ij2t0EU*W4hM~dOB55AwS=0*0^#Jm>lYrvd%L8T zFYNkbB)cLMJxfKT*aK!O0Yk?bawz{zZg?X17PmmeT6IZ8u#-22Y@bS}0T#gImdQTW z`eb39S(q0%B4Wb~%Z!5XRQdSW?^BMXgvlO!^d3q8c1!^gFm?9v8Ry@!7#&(HeA%P8a zlws3`U`2evDef%^74mEhSDqD0I;vR&Qi_z9%X2vDCBG(*abeLnQ6LP7bs}+GAOQ|D zfpjN2!I+l7(Z^?{Yc~23;|Gr;IjBY7B$xV8YWl3(4!HpJ(OC|TGzi}g7 zOk6~u(zwKt(*5ucWP)J)FK&84Y|ThMjPW1-95=fPQlHKQi=OnkVqHChd&oc9qZdaad&QIRv%5zLl!QUbLoirzKD^|P{H-x&|KQk(q0y1f?p58Za9Rc)kvf?~OqqKC2gZqn*r19h-~==>0O=2c+~WXq z4@$!*k3&z#QNGjN6#`EjA2>AFd3@-e!QsyFdyfy^iBltr9LK{a%q8d^t_E*jfvZFk z=rSH1A3uKLlGUq^QD3WuMtKP+UU{u;knbQ!8VBUNQR)<#Mp4u4zgJ#><;K2CM-GiOmMvp;}Cpr%fj*WKS4dry+FoNr2CkF6TnaD#W^oM{b zN4^SYV=%MT5XMP!2hh;256d$HD39RIeU7}&C>#LCXmJMJ1hB!Yb1$${aa z@q0U8KRWhWR5J;p0WzI$Jy$IgA6B+mt^vuu4%MO{7(3Kj3!)SHkwUlGyLXd@b+8GRv6HBv%~B(mzyihm1dW@%It;@+GDOU zd(E|I^VgZ{VLdm_IfD6(0ExDB}^R z?bA@pv*uIg&&_AdpPA2^{{fZ$6{5^vn%{*Qzhi#hJZC;{{u*}q59S}u&q9qu=Cv?} zVc6EYp|aP(@@V2Gp~kzR#1~)=_d%g=F#o~4(R{?b$NYqO6Z%a*X})b8&b9N*Ddb}F?P+#_wf1z|ZqKj_?V0v0d$v8to{O|#kv-4+!2GlA zw2SQ$+hxzUOYJhd++JWWv=<>~T4^t~tL$p?lKC6E#&+8tBq?jLhHz^{+m5uZ?XsNYwXST7JI9`&E9SY>>c)yJ!}WeAFw}TAGAMf-(uftAF^+=51S9zpR)8<#~AJ~uB|71UE|Ij{T|HyvK{%3r{@ZM~*jQunFS^MYqFYI61&)L7SpSOQ)pR<2szhM8?e$jr(e%bz= z{fhmnecpb}e%*e<{=NNg_M7&<+i%(bVPCNSV83nu(Y|Q^r~QuoU-rB9f7|cbf3n}V z|HuBo{n1Jvk#;n4FoMm7JZNlbnkgoJGlbNoTS+S(0=m=O;_eQ|9-~)8-G%C(MV<@0&j{ zKVyE|e9ZjkWSMy;S)N>wT$o&h)vYU&i<4E!>SRsQo%AHV$=akZS(o&ihm!TlhU5}- zXEr69lS`A!l3KFG{Lf@7a**FJk0#rb9p*R9&m}v}+s!W~mnT;wS0=lXtCHQx)ybaZ znq+TsZL%-9uI=Q=(3&+{*649-k306dW1l;2XxnmZ;LzCUNZWuOTesXXHhA}7>wp~E zwu~Mb9T|LW+khTtZaXwIcIf1>JBJ7FnRzHL&)9Z&bbR2@p}~>y8F1{b*t-4D06@1M z*28w-85nQd;SdfwggX@Bpd4oG2(@PnhGN?eN5-HYTX!hLK{?F4JSSx4k-R+P@&I7Q zkx-m6^u+ks=<%b2 zl^r8TDuW|O+OBcTBZ||0TCY*&N98d4nxiL2jtq>QJT^RVa(wn^eAc>8X&6%)_C;uC zjYTJ|`;>+;JzS@?PRL>Ab-Czfp2*9yu8#oB8jns|uU9bRa;RQUm5x)TH>gTas!DHg zT%B}W-Jo2Zltar6V?!fHT2695`-YgO*(c+(wi}%4PwH{zjX8xgUmulkipuxq<<^7B z>wR*VaWL5KjQc`S+Hd!^?e4f^)^!oHvra@O@l@F3q~;*bt3KPi}}Fo^j2w!6W+6{2@GEz||tS zs#^!g2U;#4K%;JRc&Z0+sRBGL*B?cxN<=MJ4IDo{fChBzj>7}V)hClZCzAt1Fk``- z>>aA?J388O-O!O^1C{FsPPREJDtnI(Rkq=8?}?#VgySig>mKmuGX?_Vvj!s$rhxzz z2kDp6=_mERuPPT?q<-}dJl=AI%(NUH93CHNbFixS;YNiw$Au*VYk94(G^{L*oJ{T+ zY8zF4Dq}}STThUg?iM*z#=(=rTR9GX4&e`Ew2V@~vk1^)4S6d)J|tmJJ-4U&VY9AaOXSaxOUANG0pBZYkGQpsjnxL@@swJ+B#qA z?hmE)p|ru5dUD9!;aY&)vo>4{kb45;o&dQgK<){UdjsU&0J%3n?hQ2e1{}QsM{mH< z8*uan9K8X@+JIwiz_B*qSQ~Jx4LH^Y9BTuPwE@T4fMadIu`bkD7iz2vHP(e1>q3qG zKvjRBr9aTpA86?hkoyDV{s6f@K<*Ea*9XY!1HbD7j`ab@`ha77z_C8ySRZh#4>;Bb z92)|T4FSi7fMY|zu_55t5O8b=I5zaF<@N<={T>>u*jZKgn*5rF+r1{h&FgMhPiQ@B z)=1KZqqHX+r9I&&$s3N+u5grgg`>199EBPjJsffDI2T9Wv!++tUC)|+2VWgN&K;b7 z2dCe`>34AY9UKh^cwPbmjuH@X+*utS8XIUmK6nEDsk-Cj*r+n1S!d6httz^8-Mwti zV`HPQA0E7OyiH0ckI#^!jDhLR!=tYs(b65G<43i0^7!Eq0ouJmsq9wS_jEg{b#G8A zd%Bey+?X*sHhz>5cwl(e(8xGOKiH`s9ho`!x|2h94-5~E92#snI(qWN;B2J4!=p!r z4h;+=WVMZt4GkPQd0g=IZC$754u9XaHLZ6dbsZk8a@TTnaA0h_?eNe5##;_cjs9-c zQ-8O}yBGT8;D9}KO2!5ZMgj~*0nU`M1dPwi(Ykwh4kh!|=RS>h&5U{Wo)ueo{6_lm Gr~f~BJ%ow? literal 0 HcmV?d00001 diff --git a/src/main/resources/fonts/monogram/ttf/monogram.ttf b/src/main/resources/fonts/monogram/ttf/monogram.ttf new file mode 100644 index 0000000000000000000000000000000000000000..aceaebab76d4ccb69d59241d154216f6f53d7e8d GIT binary patch literal 10468 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 Date: Wed, 15 Jan 2025 12:22:42 +0100 Subject: [PATCH 03/14] Removed builder pattern for materials. --- .../demos/landmass/NoiseMapDisplay.java | 3 +- .../landmass/ProceduralLandmassDemo.java | 50 +++- src/main/java/engine/render/Material.java | 225 ++++++++---------- .../java/engine/render/MaterialFactory.java | 72 +++--- 4 files changed, 175 insertions(+), 175 deletions(-) diff --git a/src/main/java/engine/demos/landmass/NoiseMapDisplay.java b/src/main/java/engine/demos/landmass/NoiseMapDisplay.java index bf0b2a23..aaa70bbe 100644 --- a/src/main/java/engine/demos/landmass/NoiseMapDisplay.java +++ b/src/main/java/engine/demos/landmass/NoiseMapDisplay.java @@ -73,7 +73,8 @@ private void createPlaneMesh() { */ public void setPixels(int[] pixels) { texture.setPixels(pixels); - Material material = new Material.Builder().setDiffuseTexture(texture).build(); + Material material = new Material(); + material.setDiffuseTexture(texture); planeGeometry = new Geometry(planeMesh, material); } diff --git a/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java b/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java index ee8fd3ac..efe549a0 100644 --- a/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java +++ b/src/main/java/engine/demos/landmass/ProceduralLandmassDemo.java @@ -11,7 +11,9 @@ import engine.scene.SceneNode; import engine.scene.camera.PerspectiveCamera; import engine.scene.light.DirectionalLight; +import engine.ui.LoadingScreen; import math.Color; +import math.Vector3f; import mesh.Mesh3D; import mesh.modifier.CenterAtModifier; import mesh.modifier.ScaleModifier; @@ -42,10 +44,15 @@ public enum DrawMode { // Configuration fields private int levelOfDetail = 0; // Level of detail for the terrain mesh (0 - 6) - private int chunkSize = 240; // TODO Note that the size has to fit LOD - private int chunkScale = 3; + // private int chunkSize = 960; // TODO Note that the size has to fit LOD + private int chunkSize = 480; + private int chunkScale = 4; private DrawMode drawMode = DrawMode.COLOR_MAP; private Scene scene; + // private EndlessTerrain endlessTerrain; + + private LoadingScreen loadingScreen; + private RoundReticle roundReticle; public static void main(String[] args) { ProceduralLandmassDemo application = new ProceduralLandmassDemo(); @@ -57,10 +64,22 @@ public static void main(String[] args) { /** Initializes the demo scene, including terrain generation, lighting, and UI components. */ @Override public void onInitialize() { - setupScene(); setupUI(); - createTerrain(); + setupScene(); createCamera(); + runTerrainCreation(); + // endlessTerrain = new EndlessTerrain(scene, chunkSize * chunkScale); + } + + private void runTerrainCreation() { + new Thread( + new Runnable() { + @Override + public void run() { + createTerrain(); + } + }) + .start(); } /** Sets up the base scene with a background color and directional lighting. */ @@ -76,8 +95,15 @@ private void setupScene() { /** Sets up the UI elements, such as the round reticle. */ private void setupUI() { SceneNode reticleNode = new SceneNode(); - reticleNode.addComponent(new RoundReticle()); + roundReticle = new RoundReticle(); + roundReticle.setActive(false); + reticleNode.addComponent(roundReticle); rootUI.addChild(reticleNode); + + SceneNode loadingScreenNode = new SceneNode(); + loadingScreen = new LoadingScreen(); + loadingScreenNode.addComponent(loadingScreen); + rootUI.addChild(loadingScreenNode); } /** Creates the terrain based on the selected draw mode and level of detail. */ @@ -98,7 +124,8 @@ private void createTerrain() { // Create a texture from the display and apply it to the terrain material Texture2D texture = display.getTexture(); - Material mapMaterial = new Material.Builder().setDiffuseTexture(texture).build(); + Material mapMaterial = new Material(); + mapMaterial.setDiffuseTexture(texture); // Generate the terrain mesh, apply transformations, and create a geometry node Mesh3D terrainMesh = new TerrainMeshLOD(noiseMap, levelOfDetail).getMesh(); @@ -114,13 +141,18 @@ private void createTerrain() { SceneNode chunkDisplayNode = new SceneNode(); chunkDisplayNode.addComponent(new ChunkBoxDisplay(chunkSize * chunkScale)); scene.addNode(chunkDisplayNode); + + roundReticle.setActive(true); + loadingScreen.hide(); } /** Sets up the camera with smooth fly-by controls. */ private void createCamera() { PerspectiveCamera camera = new PerspectiveCamera(); + camera.getTransform().setPosition(0, -chunkSize / 2f * chunkScale, 0); + SmoothFlyByCameraControl cameraControl = new SmoothFlyByCameraControl(input, camera); - cameraControl.setMoveSpeed(70); + cameraControl.setMoveSpeed(200); SceneNode cameraNode = new SceneNode(); cameraNode.addComponent(cameraControl); @@ -131,7 +163,9 @@ private void createCamera() { /** Update logic (currently empty). */ @Override public void onUpdate(float tpf) { - // No update logic for this demo + Vector3f viewerPosition = activeScene.getActiveCamera().getTransform().getPosition(); + // endlessTerrain.setViewerPosition(viewerPosition); + // endlessTerrain.update(); } /** Render logic (currently empty). */ diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index 17a1e5d5..5ab0dc4f 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -13,8 +13,7 @@ * reflects off the object's surface. * *

This class includes predefined materials for common use cases (e.g., default white, black, or - * other colors) and supports the creation of custom materials with specific properties through the - * use of {@link Builder}. + * other colors) and supports the creation of custom materials with specific properties. * *

The material properties can control effects like reflectivity, surface roughness, and color, * enabling diverse visual representations for 3D meshes in a rendering engine. @@ -51,149 +50,51 @@ public class Material { /** Water material with a reflective blue appearance. */ public static final Material WATER_MATERIAL = MaterialFactory.createWater(); + /** Indicates whether this material should use lighting effects during rendering. */ private boolean useLighting; /** Base color for the material. */ - private final Color color; + private Color color; - /** Ambient light coefficient (R, G, B). */ - private final float[] ambient; + /** Ambient light coefficient (R, G, B), controlling the material's reaction to ambient light. */ + private float[] ambient; - /** Diffuse light coefficient (R, G, B). */ - private final float[] diffuse; + /** Diffuse light coefficient (R, G, B), controlling how the material reflects diffuse light. */ + private float[] diffuse; - /** Specular light coefficient (R, G, B). */ - private final float[] specular; + /** + * Specular light coefficient (R, G, B), controlling the shininess and reflections on the surface. + */ + private float[] specular; - /** Shininess factor for specular highlights. */ - private final float shininess; + /** + * Shininess factor for specular highlights, controlling the size and intensity of reflections. + */ + private float shininess; /** The diffuse texture map (map_Kd) of the material. */ private Texture diffuseTexture; + /** Default constructor that initializes the material with a base white color. */ + public Material() { + this(Color.WHITE); + } + /** * Constructor to set the base color of the material. * * @param color The base color of the material. */ public Material(Color color) { - this(new Builder().setColor(color)); - } - - private Material(Builder builder) { - this.useLighting = builder.useLighting; - this.color = builder.color; - this.ambient = builder.ambient; - this.diffuse = builder.diffuse; - this.specular = builder.specular; - this.shininess = builder.shininess; - this.diffuseTexture = builder.diffuseTexture; - } - - /** - * Builder class to facilitate the creation of custom materials with specific lighting, shader and - * texture properties. - */ - public static class Builder { - - private boolean useLighting = true; - - private Color color = new Color(1, 1, 1); // Default color is white - - private float[] ambient = new float[] {0.2f, 0.2f, 0.2f}; - - private float[] diffuse = new float[] {1.0f, 1.0f, 1.0f}; - - private float[] specular = new float[] {1.0f, 1.0f, 1.0f}; - - private float shininess = 10.0f; - - private Texture diffuseTexture = null; - - /** - * Sets the base color of the material. - * - * @param color The desired base color. - * @return The builder instance for chaining. - */ - public Builder setColor(Color color) { - this.color = color; - return this; - } - - /** - * Sets the ambient light coefficient of the material. - * - * @param ambient The desired ambient light coefficient (R, G, B). - * @return The builder instance for chaining. - */ - public Builder setAmbient(float[] ambient) { - this.ambient = ambient; - return this; - } - - /** - * Sets the diffuse light coefficient of the material. - * - * @param diffuse The desired diffuse light coefficient (R, G, B). - * @return The builder instance for chaining. - */ - public Builder setDiffuse(float[] diffuse) { - this.diffuse = diffuse; - return this; - } - - /** - * Sets the specular light coefficient of the material. - * - * @param specular The desired specular light coefficient (R, G, B). - * @return The builder instance for chaining. - */ - public Builder setSpecular(float[] specular) { - this.specular = specular; - return this; - } - - /** - * Sets the shininess value of the material. - * - * @param shininess The shininess factor for specular highlights. - * @return The builder instance for chaining. - */ - public Builder setShininess(float shininess) { - this.shininess = shininess; - return this; - } - - public Builder setUseLighting(boolean useLighting) { - this.useLighting = useLighting; - return this; - } - - /** - * Sets the diffuse texture of the material. - * - * @param diffuseTexture The diffuse texture, can be null. - * @return The builder instance for chaining - */ - public Builder setDiffuseTexture(Texture diffuseTexture) { - this.diffuseTexture = diffuseTexture; - return this; - } - - /** - * Builds and returns the Material instance with the set properties. - * - * @return A new instance of {@link Material}. - */ - public Material build() { - return new Material(this); - } + this.color = color; } /** * Applies this material's properties to the provided rendering context. * + *

This method sets the material's color, lighting properties, and binds any associated + * textures to the rendering context. + * * @param g The {@link Graphics} instance to apply this material to. */ public void apply(Graphics g) { @@ -208,16 +109,33 @@ public void apply(Graphics g) { * Releases this material's properties from the rendering context, useful for cleaning up shaders * or material-specific settings. * + *

This method unbinds textures and resets any material properties set during the rendering + * process to ensure the rendering context is restored to a neutral state. + * * @param g The {@link Graphics} instance from which this material will be unbound. */ public void release(Graphics g) { // Logic for releasing or resetting rendering context goes here } + /** + * Checks whether lighting effects are enabled for this material. + * + * @return {@code true} if lighting is used; {@code false} otherwise. + */ public boolean isUseLighting() { return useLighting; } + /** + * Sets whether this material should use lighting effects during rendering. + * + * @param useLighting {@code true} to enable lighting; {@code false} to disable. + */ + public void setUseLighting(boolean useLighting) { + this.useLighting = useLighting; + } + /** * Retrieves the base color of the material. * @@ -227,6 +145,15 @@ public Color getColor() { return color; } + /** + * Sets the base color of the material. + * + * @param color The new base color to set. + */ + public void setColor(Color color) { + this.color = color; + } + /** * Retrieves the ambient light coefficient of the material. * @@ -236,6 +163,15 @@ public float[] getAmbient() { return ambient; } + /** + * Sets the ambient light coefficient of the material. + * + * @param ambient An array representing the new ambient light coefficient (R, G, B). + */ + public void setAmbient(float[] ambient) { + this.ambient = ambient; + } + /** * Retrieves the diffuse light coefficient of the material. * @@ -245,6 +181,15 @@ public float[] getDiffuse() { return diffuse; } + /** + * Sets the diffuse light coefficient of the material. + * + * @param diffuse An array representing the new diffuse light coefficient (R, G, B). + */ + public void setDiffuse(float[] diffuse) { + this.diffuse = diffuse; + } + /** * Retrieves the specular light coefficient of the material. * @@ -254,6 +199,15 @@ public float[] getSpecular() { return specular; } + /** + * Sets the specular light coefficient of the material. + * + * @param specular An array representing the new specular light coefficient (R, G, B). + */ + public void setSpecular(float[] specular) { + this.specular = specular; + } + /** * Retrieves the shininess factor of the material. * @@ -264,18 +218,29 @@ public float getShininess() { } /** - * Returns the diffuse texture map (map_Kd) of the material. + * Sets the shininess factor of the material. * - *

The diffuse texture is a 2D image used to define the base color and pattern of the surface, - * simulating the appearance of the material under diffuse lighting. This texture is typically - * applied using UV mapping to wrap the image onto the geometry of a 3D model. - * - *

In the context of material definition files (e.g., MTL for OBJ models), this corresponds to - * the `map_Kd` property, which specifies the file path to the texture image. + * @param shininess The new shininess factor to set. + */ + public void setShininess(float shininess) { + this.shininess = shininess; + } + + /** + * Returns the diffuse texture map (map_Kd) of the material. * * @return The diffuse texture map as {@link Texture}. */ public Texture getDiffuseTexture() { return diffuseTexture; } + + /** + * Sets the diffuse texture map (map_Kd) of the material. + * + * @param diffuseTexture The new diffuse texture map to set. + */ + public void setDiffuseTexture(Texture diffuseTexture) { + this.diffuseTexture = diffuseTexture; + } } diff --git a/src/main/java/engine/render/MaterialFactory.java b/src/main/java/engine/render/MaterialFactory.java index 24dd29ee..cce0403d 100644 --- a/src/main/java/engine/render/MaterialFactory.java +++ b/src/main/java/engine/render/MaterialFactory.java @@ -3,7 +3,7 @@ import math.Color; /** - * Factory class for creating predefined Material instances using a builder pattern. + * Factory class for creating predefined Material instances. * *

This class provides various predefined material configurations, such as metallic silver, * golden metallic, clear glass, stone grey, and water effects. Each material can be created through @@ -28,13 +28,13 @@ public class MaterialFactory { * @return A {@link Material} instance configured as metallic silver. */ public static Material createMetallicSilver() { - return new Material.Builder() - .setColor(new Color(0.75f, 0.75f, 0.75f)) - .setAmbient(new float[] {0.2f, 0.2f, 0.2f}) - .setDiffuse(new float[] {1.0f, 1.0f, 1.0f}) - .setSpecular(new float[] {1.0f, 1.0f, 1.0f}) - .setShininess(50.0f) - .build(); + Material material = new Material(); + material.setColor(new Color(0.75f, 0.75f, 0.75f)); + material.setAmbient(new float[] {0.2f, 0.2f, 0.2f}); + material.setDiffuse(new float[] {1.0f, 1.0f, 1.0f}); + material.setSpecular(new float[] {1.0f, 1.0f, 1.0f}); + material.setShininess(50.0f); + return material; } /** @@ -45,13 +45,13 @@ public static Material createMetallicSilver() { * @return A {@link Material} instance configured as golden metallic. */ public static Material createGoldenMetallic() { - return new Material.Builder() - .setColor(new Color(1.0f, 0.84f, 0.0f)) - .setAmbient(new float[] {0.3f, 0.3f, 0.3f}) - .setDiffuse(new float[] {1.0f, 0.84f, 0.0f}) - .setSpecular(new float[] {1.0f, 1.0f, 0.5f}) - .setShininess(100.0f) - .build(); + Material material = new Material(); + material.setColor(new Color(1.0f, 0.84f, 0.0f)); + material.setAmbient(new float[] {0.3f, 0.3f, 0.3f}); + material.setDiffuse(new float[] {1.0f, 0.84f, 0.0f}); + material.setSpecular(new float[] {1.0f, 1.0f, 0.5f}); + material.setShininess(100.0f); + return material; } /** @@ -62,13 +62,13 @@ public static Material createGoldenMetallic() { * @return A {@link Material} instance configured as clear glass. */ public static Material createGlassClear() { - return new Material.Builder() - .setColor(new Color(0.7f, 0.9f, 1.0f)) - .setAmbient(new float[] {0.3f, 0.3f, 0.3f}) - .setDiffuse(new float[] {0.8f, 0.8f, 0.8f}) - .setSpecular(new float[] {1.0f, 1.0f, 1.0f}) - .setShininess(5.0f) - .build(); + Material material = new Material(); + material.setColor(new Color(0.7f, 0.9f, 1.0f)); + material.setAmbient(new float[] {0.3f, 0.3f, 0.3f}); + material.setDiffuse(new float[] {0.8f, 0.8f, 0.8f}); + material.setSpecular(new float[] {1.0f, 1.0f, 1.0f}); + material.setShininess(5.0f); + return material; } /** @@ -80,13 +80,13 @@ public static Material createGlassClear() { * @return A {@link Material} instance configured as stone grey. */ public static Material createStoneGrey() { - return new Material.Builder() - .setColor(new Color(0.5f, 0.5f, 0.5f)) - .setAmbient(new float[] {0.4f, 0.4f, 0.4f}) - .setDiffuse(new float[] {0.6f, 0.6f, 0.6f}) - .setSpecular(new float[] {0.2f, 0.2f, 0.2f}) - .setShininess(10.0f) - .build(); + Material material = new Material(); + material.setColor(new Color(0.5f, 0.5f, 0.5f)); + material.setAmbient(new float[] {0.4f, 0.4f, 0.4f}); + material.setDiffuse(new float[] {0.6f, 0.6f, 0.6f}); + material.setSpecular(new float[] {0.2f, 0.2f, 0.2f}); + material.setShininess(10.0f); + return material; } /** @@ -98,12 +98,12 @@ public static Material createStoneGrey() { * @return A {@link Material} instance configured as water. */ public static Material createWater() { - return new Material.Builder() - .setColor(new Color(0.0f, 0.5f, 1.0f)) - .setAmbient(new float[] {0.1f, 0.3f, 0.5f}) - .setDiffuse(new float[] {0.3f, 0.5f, 0.7f}) - .setSpecular(new float[] {0.2f, 0.2f, 0.6f}) - .setShininess(2.0f) - .build(); + Material material = new Material(); + material.setColor(new Color(0.0f, 0.5f, 1.0f)); + material.setAmbient(new float[] {0.1f, 0.3f, 0.5f}); + material.setDiffuse(new float[] {0.3f, 0.5f, 0.7f}); + material.setSpecular(new float[] {0.2f, 0.2f, 0.6f}); + material.setShininess(2.0f); + return material; } } From 30ad22676b5bf828083b96ac9bb2db0e7e7af74b Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 12:56:08 +0100 Subject: [PATCH 04/14] Added default values and name property. --- src/main/java/engine/render/Material.java | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index 5ab0dc4f..2127bc9c 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -53,6 +53,17 @@ public class Material { /** Indicates whether this material should use lighting effects during rendering. */ private boolean useLighting; + /** + * The name of the material, used to identify and reference the material in rendering pipelines + * and material libraries. + * + *

This name is often defined in material definition files (e.g., MTL files for OBJ models) and + * is used to associate textures and shading properties with specific parts of a 3D model. Having + * a descriptive name can help streamline asset management and debugging within a rendering + * engine. + */ + private String name; + /** Base color for the material. */ private Color color; @@ -87,6 +98,12 @@ public Material() { */ public Material(Color color) { this.color = color; + this.name = ""; + this.useLighting = true; + this.ambient = new float[] {0.2f, 0.2f, 0.2f}; + this.diffuse = new float[] {1.0f, 1.0f, 1.0f}; + this.specular = new float[] {1.0f, 1.0f, 1.0f}; + this.shininess = 10.0f; } /** @@ -136,6 +153,28 @@ public void setUseLighting(boolean useLighting) { this.useLighting = useLighting; } + /** + * Returns the name of the material. + * + * @return The name of the material as a String. + */ + public String getName() { + return name; + } + + /** + * Sets the name of the material. + * + * @param name The new name of the material. + * @throws IllegalArgumentException If the name is null. + */ + public void setName(String name) { + if (name == null) { + throw new IllegalArgumentException("Material name cannot be null."); + } + this.name = name; + } + /** * Retrieves the base color of the material. * From c9b56ef767ba74d9795fdd06c895354bdfb025a0 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 13:14:48 +0100 Subject: [PATCH 05/14] Added validation for color. --- src/main/java/engine/render/Material.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index 2127bc9c..1fc4196c 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -95,8 +95,12 @@ public Material() { * Constructor to set the base color of the material. * * @param color The base color of the material. + * @throws IllegalArgumentException If the color is null. */ public Material(Color color) { + if (color == null) { + throw new IllegalArgumentException("Color cannot be null."); + } this.color = color; this.name = ""; this.useLighting = true; @@ -188,8 +192,12 @@ public Color getColor() { * Sets the base color of the material. * * @param color The new base color to set. + * @throws IllegalArgumentException If color is null. */ public void setColor(Color color) { + if (color == null) { + throw new IllegalArgumentException("Color cannot be null."); + } this.color = color; } From e17a0d965e61d4c33eed1b180049ded717bab8c6 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 20:08:54 +0100 Subject: [PATCH 06/14] Initial commit of the bone class. --- .../java/engine/animation/skeleton/Bone.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/main/java/engine/animation/skeleton/Bone.java diff --git a/src/main/java/engine/animation/skeleton/Bone.java b/src/main/java/engine/animation/skeleton/Bone.java new file mode 100644 index 00000000..b551b634 --- /dev/null +++ b/src/main/java/engine/animation/skeleton/Bone.java @@ -0,0 +1,117 @@ +package engine.animation.skeleton; + +import java.util.ArrayList; +import java.util.List; + +import math.Matrix4; + +public class Bone { + + private final String name; + + private Bone parent; + + private final List children; + + private Matrix4 localTransform; + + private Matrix4 globalTransform; + + private boolean transformDirty = true; + + private float weight; // Added weight property + + public Bone(String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be null."); + } + this.name = name; + this.children = new ArrayList<>(); + this.localTransform = new Matrix4().identity(); + this.globalTransform = new Matrix4().identity(); + this.weight = 1.0f; // Default weight + } + + public void updateGlobalTransform() { + if (!transformDirty) return; + + if (parent != null) { + globalTransform = parent.globalTransform.multiply(localTransform); + } else { + globalTransform = localTransform; + } + + transformDirty = false; + + for (Bone child : children) { + child.markDirty(); + child.updateGlobalTransform(); + } + } + + public void markDirty() { + transformDirty = true; + } + + public void addChild(Bone child) { + if (child == null) { + throw new IllegalArgumentException("Child bone cannot be null."); + } + if (child.getParent() != null) { + child.getParent().removeChild(child); + } + children.add(child); + child.setParent(this); + } + + public void removeChild(Bone child) { + if (children.remove(child)) { + child.setParent(null); + } + } + + private void setParent(Bone parent) { + if (this.parent == parent) return; + this.parent = parent; + } + + public String getName() { + return name; + } + + public Bone getParent() { + return parent; + } + + public List getChildren() { + // Return a copy for immutability + return new ArrayList<>(children); + } + + public Matrix4 getLocalTransform() { + return new Matrix4(localTransform); + } + + public void setLocalTransform(Matrix4 localTransform) { + if (localTransform == null) { + throw new IllegalArgumentException("LocalTransform cannot be null."); + } + this.localTransform = localTransform; + markDirty(); + } + + public Matrix4 getGlobalTransform() { + return new Matrix4(globalTransform); + } + + public float getWeight() { + return weight; + } + + public void setWeight(float weight) { + if (weight < 0.0f || weight > 1.0f) { + throw new IllegalArgumentException("Weight must be between 0.0 and 1.0."); + } + this.weight = weight; + } +} From 3602c12337199850eac7d538a2f20e2f8a872088 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 20:09:11 +0100 Subject: [PATCH 07/14] Refactored bone class. --- .../java/engine/animation/skeleton/Bone.java | 66 ++++++++++++++----- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/src/main/java/engine/animation/skeleton/Bone.java b/src/main/java/engine/animation/skeleton/Bone.java index b551b634..a51adcb1 100644 --- a/src/main/java/engine/animation/skeleton/Bone.java +++ b/src/main/java/engine/animation/skeleton/Bone.java @@ -1,25 +1,21 @@ package engine.animation.skeleton; import java.util.ArrayList; +import java.util.Collections; import java.util.List; - import math.Matrix4; public class Bone { private final String name; - private Bone parent; - private final List children; - private Matrix4 localTransform; - private Matrix4 globalTransform; - + private Matrix4 inverseBindPose; private boolean transformDirty = true; - - private float weight; // Added weight property + private float weight; + private int index; public Bone(String name) { if (name == null) { @@ -29,23 +25,21 @@ public Bone(String name) { this.children = new ArrayList<>(); this.localTransform = new Matrix4().identity(); this.globalTransform = new Matrix4().identity(); - this.weight = 1.0f; // Default weight + this.weight = 1.0f; } public void updateGlobalTransform() { if (!transformDirty) return; - - if (parent != null) { - globalTransform = parent.globalTransform.multiply(localTransform); - } else { - globalTransform = localTransform; - } - + globalTransform = + (parent != null) ? parent.globalTransform.multiply(localTransform) : localTransform; transformDirty = false; + } + public void updateChildrenTransforms() { for (Bone child : children) { child.markDirty(); child.updateGlobalTransform(); + child.updateChildrenTransforms(); } } @@ -57,6 +51,9 @@ public void addChild(Bone child) { if (child == null) { throw new IllegalArgumentException("Child bone cannot be null."); } + if (isAncestorOf(child)) { + throw new IllegalArgumentException("Cannot add a child that would create a cycle."); + } if (child.getParent() != null) { child.getParent().removeChild(child); } @@ -64,6 +61,17 @@ public void addChild(Bone child) { child.setParent(this); } + private boolean isAncestorOf(Bone bone) { + Bone current = this; + while (current != null) { + if (current == bone) { + return true; + } + current = current.getParent(); + } + return false; + } + public void removeChild(Bone child) { if (children.remove(child)) { child.setParent(null); @@ -84,8 +92,7 @@ public Bone getParent() { } public List getChildren() { - // Return a copy for immutability - return new ArrayList<>(children); + return Collections.unmodifiableList(children); } public Matrix4 getLocalTransform() { @@ -104,6 +111,14 @@ public Matrix4 getGlobalTransform() { return new Matrix4(globalTransform); } + public Matrix4 getInverseBindPose() { + return new Matrix4(inverseBindPose); + } + + public void setInverseBindPose(Matrix4 bindPose) { + this.inverseBindPose = bindPose.invert(); + } + public float getWeight() { return weight; } @@ -114,4 +129,19 @@ public void setWeight(float weight) { } this.weight = weight; } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public void printHierarchy(int depth) { + System.out.println(" ".repeat(depth) + name); + for (Bone child : children) { + child.printHierarchy(depth + 1); + } + } } From 892000e3f402064f8e372b356a0288f5ed8b3394 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 20:10:03 +0100 Subject: [PATCH 08/14] Initial commit of the skeleton class. --- .../engine/animation/skeleton/Skeleton.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/engine/animation/skeleton/Skeleton.java diff --git a/src/main/java/engine/animation/skeleton/Skeleton.java b/src/main/java/engine/animation/skeleton/Skeleton.java new file mode 100644 index 00000000..44c98c81 --- /dev/null +++ b/src/main/java/engine/animation/skeleton/Skeleton.java @@ -0,0 +1,44 @@ +package engine.animation.skeleton; + +import java.util.ArrayList; +import java.util.List; + +public class Skeleton { + + private Bone rootBone; + + private final List allBones; + + public Skeleton(Bone rootBone) { + this.rootBone = rootBone; + this.allBones = new ArrayList<>(); + collectBones(rootBone); + } + + private void collectBones(Bone bone) { + allBones.add(bone); + for (Bone child : bone.getChildren()) { + collectBones(child); + } + } + + public void update() { + if (rootBone != null) { + rootBone.updateGlobalTransform(); + } + } + + public Bone getRootBone() { + return rootBone; + } + + public void setRootBone(Bone rootBone) { + this.rootBone = rootBone; + allBones.clear(); + collectBones(rootBone); + } + + public List getAllBones() { + return allBones; + } +} From 01e2d5106f5b1e49805d0b383bee72d7aec2a262 Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 20:12:05 +0100 Subject: [PATCH 09/14] Feat: Added hierarchy print. --- .../engine/animation/skeleton/Skeleton.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/engine/animation/skeleton/Skeleton.java b/src/main/java/engine/animation/skeleton/Skeleton.java index 44c98c81..1a443e48 100644 --- a/src/main/java/engine/animation/skeleton/Skeleton.java +++ b/src/main/java/engine/animation/skeleton/Skeleton.java @@ -1,27 +1,33 @@ package engine.animation.skeleton; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class Skeleton { private Bone rootBone; - private final List allBones; + private final Map allBones; public Skeleton(Bone rootBone) { this.rootBone = rootBone; - this.allBones = new ArrayList<>(); + this.allBones = new HashMap<>(); collectBones(rootBone); } private void collectBones(Bone bone) { - allBones.add(bone); + allBones.put(bone.getName(), bone); for (Bone child : bone.getChildren()) { collectBones(child); } } + public Bone getBone(String name) { + return allBones.get(name); + } + public void update() { if (rootBone != null) { rootBone.updateGlobalTransform(); @@ -39,6 +45,20 @@ public void setRootBone(Bone rootBone) { } public List getAllBones() { - return allBones; + return new ArrayList<>(allBones.values()); + } + + public void printHierarchy() { + printHierarchy(rootBone, 0); + } + + private void printHierarchy(Bone bone, int level) { + for (int i = 0; i < level; i++) { + System.out.print(" "); + } + System.out.println(bone.getName()); + for (Bone child : bone.getChildren()) { + printHierarchy(child, level + 1); + } } } From c496057618d2c043d8c4845d8fd404b9fd51df0e Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Wed, 15 Jan 2025 22:18:02 +0100 Subject: [PATCH 10/14] Feat: Added opacity texture map attribute. --- src/main/java/engine/render/Material.java | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/engine/render/Material.java b/src/main/java/engine/render/Material.java index 1fc4196c..78727c4a 100644 --- a/src/main/java/engine/render/Material.java +++ b/src/main/java/engine/render/Material.java @@ -86,6 +86,9 @@ public class Material { /** The diffuse texture map (map_Kd) of the material. */ private Texture diffuseTexture; + /** The opacity texture map (map_d) of the material */ + private Texture opacityMap; + /** Default constructor that initializes the material with a base white color. */ public Material() { this(Color.WHITE); @@ -290,4 +293,22 @@ public Texture getDiffuseTexture() { public void setDiffuseTexture(Texture diffuseTexture) { this.diffuseTexture = diffuseTexture; } + + /** + * Returns the opacity texture map (map_d) of the material. + * + * @return The opacity texture map as {@link Texture}. + */ + public Texture getOpacityMap() { + return opacityMap; + } + + /** + * Sets the opacity texture map (map_d) of the material. + * + * @param opacityMap The new opacity texture map to set. + */ + public void setOpacityMap(Texture opacityMap) { + this.opacityMap = opacityMap; + } } From 1e9a34359e4d632b2d36689ace9e8781993f99cd Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Thu, 16 Jan 2025 21:16:05 +0100 Subject: [PATCH 11/14] Format changes. --- .../mesh/creator/primitives/PlaneCreator.java | 109 +++++++++--------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/src/main/java/mesh/creator/primitives/PlaneCreator.java b/src/main/java/mesh/creator/primitives/PlaneCreator.java index 7ecdf2ca..78d38449 100644 --- a/src/main/java/mesh/creator/primitives/PlaneCreator.java +++ b/src/main/java/mesh/creator/primitives/PlaneCreator.java @@ -6,59 +6,58 @@ public class PlaneCreator implements IMeshCreator { - private float radius; - - private Mesh3D mesh; - - public PlaneCreator() { - this(1); - } - - public PlaneCreator(float radius) { - this.radius = radius; - } - - @Override - public Mesh3D create() { - initializeMesh(); - createVertices(); - createFace(); - return mesh; - } - - private void initializeMesh() { - mesh = new Mesh3D(); - } - - private void createVertices() { - addVertex(+radius, 0, -radius); - addVertex(+radius, 0, +radius); - addVertex(-radius, 0, +radius); - addVertex(-radius, 0, -radius); - } - - private void addVertex(float x, float y, float z) { - mesh.addVertex(x, y, z); - } - - private void createFace() { - mesh.add(new Face3D(0, 1, 2, 3)); - } - - public float getRadius() { - return radius; - } - - public void setRadius(float radius) { - this.radius = radius; - } - - public float getSize() { - return radius * 2; - } - - public void setSize(float size) { - setRadius(size * 0.5f); - } - + private float radius; + + private Mesh3D mesh; + + public PlaneCreator() { + this(1); + } + + public PlaneCreator(float radius) { + this.radius = radius; + } + + @Override + public Mesh3D create() { + initializeMesh(); + createVertices(); + createFace(); + return mesh; + } + + private void initializeMesh() { + mesh = new Mesh3D(); + } + + private void createVertices() { + addVertex(+radius, 0, -radius); + addVertex(+radius, 0, +radius); + addVertex(-radius, 0, +radius); + addVertex(-radius, 0, -radius); + } + + private void addVertex(float x, float y, float z) { + mesh.addVertex(x, y, z); + } + + private void createFace() { + mesh.add(new Face3D(0, 1, 2, 3)); + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + } + + public float getSize() { + return radius * 2; + } + + public void setSize(float size) { + setRadius(size * 0.5f); + } } From 2b8cf43b48de7949ca0a94afbeea8cc04cb4670c Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Thu, 16 Jan 2025 21:16:24 +0100 Subject: [PATCH 12/14] Implemented PlaneCreator with uv support. --- .../creator/primitives/PlaneCreatorUV.java | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 src/main/java/mesh/creator/primitives/PlaneCreatorUV.java diff --git a/src/main/java/mesh/creator/primitives/PlaneCreatorUV.java b/src/main/java/mesh/creator/primitives/PlaneCreatorUV.java new file mode 100644 index 00000000..5bad06dd --- /dev/null +++ b/src/main/java/mesh/creator/primitives/PlaneCreatorUV.java @@ -0,0 +1,138 @@ +package mesh.creator.primitives; + +import mesh.Face3D; +import mesh.Mesh3D; +import mesh.creator.IMeshCreator; + +/** + * A class for creating a 2D plane mesh centered at the origin. The plane lies on the XZ plane and + * is defined by its radius. + * + *

This creator is UV-ready and generates UV coordinates for the plane, mapping it to a texture + * space ranging from (0, 0) to (1, 1). + */ +public class PlaneCreatorUV implements IMeshCreator { + + /** The radius of the plane. Defines half the width and length of the plane. */ + private float radius; + + /** The mesh object that stores the geometry of the plane. */ + private Mesh3D mesh; + + /** Constructs a PlaneCreator with a default radius of 1. */ + public PlaneCreatorUV() { + this(1); + } + + /** + * Constructs a PlaneCreator with the specified radius. + * + * @param radius the radius of the plane + */ + public PlaneCreatorUV(float radius) { + this.radius = radius; + } + + /** + * Creates the mesh for the plane. + * + *

The resulting mesh includes vertices, a face, and UV coordinates for texture mapping. + * + * @return the generated {@link Mesh3D} object representing the plane + */ + @Override + public Mesh3D create() { + initializeMesh(); + createVertices(); + createUvCoordinates(); + createFace(); + return mesh; + } + + /** Initializes the mesh object to store the geometry. */ + private void initializeMesh() { + mesh = new Mesh3D(); + } + + /** + * Creates the vertices for the plane. The vertices are arranged in a clockwise order on the XZ + * plane. + */ + private void createVertices() { + addVertex(+radius, 0, -radius); // Top-right corner + addVertex(+radius, 0, +radius); // Bottom-right corner + addVertex(-radius, 0, +radius); // Bottom-left corner + addVertex(-radius, 0, -radius); // Top-left corner + } + + /** + * Creates UV coordinates for the plane's vertices. The UV coordinates map the plane to a texture + * space from (0, 0) to (1, 1). + * + *

Note: The `v` values are flipped because the Processing backend uses a texture coordinate + * system where `v = 0` is at the top and `v = 1` is at the bottom. + */ + private void createUvCoordinates() { + mesh.addUvCoordinate(1, 1); // Top-right + mesh.addUvCoordinate(1, 0); // Bottom-right + mesh.addUvCoordinate(0, 0); // Bottom-left + mesh.addUvCoordinate(0, 1); // Top-left + } + + /** + * Adds a vertex to the mesh. + * + * @param x the X coordinate of the vertex + * @param y the Y coordinate of the vertex + * @param z the Z coordinate of the vertex + */ + private void addVertex(float x, float y, float z) { + mesh.addVertex(x, y, z); + } + + /** + * Creates a single face for the plane. The face uses the vertices and UV coordinates in a + * clockwise order. + */ + private void createFace() { + int[] vertexIndices = new int[] {0, 1, 2, 3}; + int[] uvIndices = new int[] {0, 1, 2, 3}; + mesh.add(new Face3D(vertexIndices, uvIndices)); + } + + /** + * Returns the radius of the plane. + * + * @return the radius of the plane + */ + public float getRadius() { + return radius; + } + + /** + * Sets the radius of the plane. + * + * @param radius the new radius of the plane + */ + public void setRadius(float radius) { + this.radius = radius; + } + + /** + * Returns the size (width or length) of the plane. The size is twice the radius. + * + * @return the size of the plane + */ + public float getSize() { + return radius * 2; + } + + /** + * Sets the size (width or length) of the plane. The radius is set to half the size. + * + * @param size the new size of the plane + */ + public void setSize(float size) { + setRadius(size * 0.5f); + } +} From b4b28a6b550e034f787c35461fa7d15abbe5022e Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Thu, 16 Jan 2025 22:44:46 +0100 Subject: [PATCH 13/14] Implemented cross line reticle. --- .../engine/components/CrossLineReticle.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/main/java/engine/components/CrossLineReticle.java diff --git a/src/main/java/engine/components/CrossLineReticle.java b/src/main/java/engine/components/CrossLineReticle.java new file mode 100644 index 00000000..0f293793 --- /dev/null +++ b/src/main/java/engine/components/CrossLineReticle.java @@ -0,0 +1,121 @@ +/** + * The CrossLineReticle class represents a visual reticle consisting of cross lines rendered on a + * plane. It is designed to be part of a 3D scene and implements the {@link RenderableComponent} + * interface for rendering capabilities. + * + *

The reticle is created as a textured plane using a {@link Mesh3D} and is configurable with + * parameters like radius, thickness, and color. The texture is generated dynamically. + */ +package engine.components; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +import engine.resources.FilterMode; +import engine.resources.Texture; +import engine.resources.TextureManager; +import math.Color; +import math.Mathf; +import mesh.Mesh3D; +import mesh.creator.primitives.PlaneCreatorUV; +import mesh.modifier.RotateXModifier; +import workspace.ui.Graphics; + +public class CrossLineReticle extends AbstractComponent implements RenderableComponent { + + /** The radius of the reticle, defining its size. */ + private int radius; + + /** The thickness of the cross lines. */ + private int thickness; + + /** The color of the cross lines. */ + private Color color; + + /** The mesh used to represent the plane of the reticle. */ + private Mesh3D mesh; + + /** The texture used for rendering the reticle. */ + private Texture texture; + + /** Creates a default CrossLineReticle with a radius of 9, thickness of 2, and white color. */ + public CrossLineReticle() { + this(9, 2, Color.WHITE); + } + + /** + * Creates a CrossLineReticle with the specified radius, thickness, and color. + * + * @param radius The radius of the reticle. + * @param thickness The thickness of the cross lines. + * @param color The color of the cross lines. + */ + public CrossLineReticle(int radius, int thickness, Color color) { + this.radius = radius; + this.color = color; + this.thickness = thickness; + this.mesh = new PlaneCreatorUV(radius).create(); + this.mesh.apply(new RotateXModifier(-Mathf.HALF_PI)); + this.texture = createTexture(); + } + + /** + * Renders the reticle onto the provided {@link Graphics} context. + * + * @param g The graphics context used for rendering. + */ + @Override + public void render(Graphics g) { + float centerX = g.getWidth() / 2.0f; + float centerY = g.getHeight() / 2.0f; + g.pushMatrix(); + g.translate(centerX, centerY); + g.bindTexture(texture, 0); + g.fillFaces(mesh); + g.unbindTexture(0); + g.popMatrix(); + } + + /** + * Creates a {@link Texture} for the reticle using a dynamically generated {@link BufferedImage}. + * + * @return The generated texture. + */ + private Texture createTexture() { + BufferedImage image = createTextureImage(); + Texture texture = TextureManager.getInstance().createTexture(image); + texture.setFilterMode(FilterMode.POINT); + return texture; + } + + /** + * Generates a {@link BufferedImage} containing the cross lines of the reticle. + * + * @return The generated image. + */ + private BufferedImage createTextureImage() { + int size = radius + radius; + BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) image.getGraphics(); + g2d.setColor(new java.awt.Color(this.color.getRGBA())); + g2d.fillRect(radius - (thickness / 2), 0, thickness, size); + g2d.fillRect(0, radius - (thickness / 2), size, thickness); + return image; + } + + /** + * Called during each update cycle. This reticle does not require updates. + * + * @param tpf The time per frame in seconds. + */ + @Override + public void onUpdate(float tpf) {} + + /** Called when the component is attached to a {@link engine.SceneNode}. */ + @Override + public void onAttach() {} + + /** Called when the component is detached from a {@link engine.SceneNode}. */ + @Override + public void onDetach() {} +} From 647b448a990f073b07324286fe2cd6efead4a9ed Mon Sep 17 00:00:00 2001 From: Simon Dietz Date: Thu, 16 Jan 2025 22:46:12 +0100 Subject: [PATCH 14/14] Smooth fly by camera control is now default. --- src/main/java/engine/application/BasicApplication.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/engine/application/BasicApplication.java b/src/main/java/engine/application/BasicApplication.java index 6df93aff..51cf5128 100644 --- a/src/main/java/engine/application/BasicApplication.java +++ b/src/main/java/engine/application/BasicApplication.java @@ -2,6 +2,7 @@ import engine.Timer; import engine.components.FlyByCameraControl; +import engine.components.SmoothFlyByCameraControl; import engine.debug.DebugInfoUpdater; import engine.debug.DebugOverlay; import engine.debug.FpsGraph; @@ -87,7 +88,7 @@ private void setupDefaultCamera() { PerspectiveCamera defaultCamera = new PerspectiveCamera(); activeScene.setActiveCamera(defaultCamera); SceneNode cameraNode = new SceneNode("DefaultCamera"); - cameraNode.addComponent(new FlyByCameraControl(input, defaultCamera)); + cameraNode.addComponent(new SmoothFlyByCameraControl(input, defaultCamera)); activeScene.addNode(cameraNode); }