+/*
+ * Sixth 3D engine. Author: Svjatoslav Agejenko.
+ * This project is released under Creative Commons Zero (CC0) license.
+ */
+package eu.svjatoslav.sixth.e3d.renderer.raster.texture;
+
+import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.lang.Math.pow;
+import static java.lang.Math.sqrt;
+
+/**
+ * Factory class for generating reusable textures with configurable borders and glow effects.
+ *
+ * <p>Provides static factory methods to create common texture patterns:</p>
+ * <ul>
+ * <li>{@link #solidWithBorder} - solid fill color with opaque border (for bordered polygons)</li>
+ * <li>{@link #glowingBorder} - transparent center with glowing edges (for wireframe-effect shapes)</li>
+ * <li>{@link #radialGlow} - circular radial gradient (for point/billboard glows)</li>
+ * </ul>
+ *
+ * <p><b>Texture caching:</b> Textures are cached by their generation parameters using a
+ * {@link WeakReference}-based cache. When textures are no longer referenced elsewhere,
+ * they are automatically garbage collected. This reduces memory usage when many shapes
+ * share identical textures.</p>
+ *
+ * <p><b>Example usage:</b></p>
+ * <pre>{@code
+ * // RGB cube plate with black border
+ * Texture tex1 = TextureGenerator.solidWithBorder(64, new Color(255, 0, 0), new Color(0, 0, 0), 3);
+ *
+ * // Wireframe-effect texture (glowing cyan edges, transparent center)
+ * Texture tex2 = TextureGenerator.glowingBorder(64, new Color(0, 255, 255), 6, 120, true);
+ *
+ * // Circular glow point
+ * Texture tex3 = TextureGenerator.radialGlow(100, new Color(255, 200, 100));
+ * }</pre>
+ *
+ * @see Texture
+ * @see Color
+ */
+public final class TextureGenerator {
+
+ /**
+ * Cache of generated textures, keyed by configuration parameters.
+ * Uses WeakReference so textures are GC'd when no longer referenced elsewhere.
+ */
+ private static final Map<TextureKey, WeakReference<Texture>> textureCache = new HashMap<>();
+
+ /**
+ * Private constructor to prevent instantiation.
+ * This class only provides static factory methods.
+ */
+ private TextureGenerator() {
+ }
+
+ /**
+ * Creates a texture with a solid fill color and an opaque border.
+ *
+ * <p>The fill color fills the entire texture except for the border region.
+ * The border is drawn as an opaque rectangle inset from the edges.</p>
+ *
+ * <p><b>Caching:</b> Identical parameters produce the same cached texture instance.</p>
+ *
+ * @param size the texture width and height in pixels
+ * @param fillColor the color filling the interior
+ * @param borderColor the color of the border
+ * @param borderWidth the width of the border in pixels
+ * @param maxUpscale the maximum number of upscaled mipmap levels (0 = no upscale, 1 = 2x upscale, 2 = 4x upscale)
+ * @return a texture with solid fill and opaque border
+ */
+ public static Texture solidWithBorder(final int size, final Color fillColor,
+ final Color borderColor, final int borderWidth,
+ final int maxUpscale) {
+ final TextureKey key = new TextureKey(size, fillColor, borderColor, borderWidth,
+ TextureType.SOLID_WITH_BORDER, 0, false, maxUpscale);
+
+ return getOrCreate(key, () -> generateSolidWithBorder(size, fillColor, borderColor, borderWidth, maxUpscale));
+ }
+
+ /**
+ * Creates a texture with a transparent center and glowing border edges.
+ *
+ * <p>This texture is useful for creating wireframe-effect shapes: the polygon
+ * appears to have only edges, with the interior being transparent. The border
+ * has a glow effect where intensity decreases from the edge toward the center.</p>
+ *
+ * <p><b>Glow effect:</b> Multiple concentric border lines are drawn with decreasing
+ * alpha values, creating a luminous edge appearance. The glow intensity controls
+ * how bright the innermost edge appears.</p>
+ *
+ * <p><b>Caching:</b> Identical parameters produce the same cached texture instance.</p>
+ *
+ * @param size the texture width and height in pixels
+ * @param borderColor the color of the glowing border
+ * @param borderWidth the width of the border region in pixels (where glow appears)
+ * @param glowIntensity the base intensity added to the border color (0-255 range)
+ * @param transparent if true, center is fully transparent; if false, has slight tint
+ * @param maxUpscale the maximum number of upscaled mipmap levels (0 = no upscale, 1 = 2x upscale, 2 = 4x upscale)
+ * @return a texture with glowing edges and transparent center
+ */
+ public static Texture glowingBorder(final int size, final Color borderColor,
+ final int borderWidth, final int glowIntensity,
+ final boolean transparent, final int maxUpscale) {
+ final TextureKey key = new TextureKey(size, borderColor, Color.TRANSPARENT, borderWidth,
+ TextureType.GLOWING_BORDER, glowIntensity, transparent, maxUpscale);
+
+ return getOrCreate(key, () -> generateGlowingBorder(size, borderColor, borderWidth,
+ glowIntensity, transparent, maxUpscale));
+ }
+
+ /**
+ * Creates a texture with a circular radial gradient glow.
+ *
+ * <p>The texture has a circular alpha gradient: fully opaque at the center,
+ * transitioning to fully transparent at the edges. The color intensity
+ * remains constant while alpha decreases radially.</p>
+ *
+ * <p>This is suitable for rendering glowing points or circular billboards.
+ * The center of the texture is the brightest point, fading outward.</p>
+ *
+ * <p><b>Caching:</b> Identical parameters produce the same cached texture instance.</p>
+ *
+ * @param size the texture width and height in pixels (should be even)
+ * @param color the color of the glow (alpha is overridden by radial gradient)
+ * @return a texture with circular radial alpha gradient
+ */
+ public static Texture radialGlow(final int size, final Color color) {
+ final TextureKey key = new TextureKey(size, color, Color.TRANSPARENT, 0,
+ TextureType.RADIAL_GLOW, 0, false, 1);
+
+ return getOrCreate(key, () -> generateRadialGlow(size, color));
+ }
+
+ /**
+ * Retrieves a cached texture or creates a new one if not cached.
+ *
+ * @param key the cache key identifying the texture configuration
+ * @param generator the function to create the texture if not cached
+ * @return the cached or newly created texture
+ */
+ private static Texture getOrCreate(final TextureKey key, final TextureSupplier generator) {
+ synchronized (textureCache) {
+ final WeakReference<Texture> ref = textureCache.get(key);
+ if (ref != null) {
+ final Texture cached = ref.get();
+ if (cached != null) {
+ return cached;
+ }
+ // Reference was cleared, remove stale entry
+ textureCache.remove(key);
+ }
+
+ final Texture texture = generator.create();
+ textureCache.put(key, new WeakReference<>(texture));
+ return texture;
+ }
+ }
+
+ /**
+ * Generates a texture with solid fill and opaque border.
+ */
+ private static Texture generateSolidWithBorder(final int size, final Color fillColor,
+ final Color borderColor, final int borderWidth,
+ final int maxUpscale) {
+ final Texture texture = new Texture(size, size, maxUpscale);
+
+ // Fill interior with fill color
+ texture.primaryBitmap.drawRectangle(borderWidth, borderWidth,
+ size - borderWidth, size - borderWidth, fillColor);
+
+ // Draw border regions (top, bottom, left, right edges)
+ // Top border
+ texture.primaryBitmap.drawRectangle(0, 0, size, borderWidth, borderColor);
+ // Bottom border
+ texture.primaryBitmap.drawRectangle(0, size - borderWidth, size, size, borderColor);
+ // Left border (excluding top/bottom corners already filled)
+ texture.primaryBitmap.drawRectangle(0, borderWidth, borderWidth, size - borderWidth, borderColor);
+ // Right border
+ texture.primaryBitmap.drawRectangle(size - borderWidth, borderWidth, size, size - borderWidth, borderColor);
+
+ texture.resetResampledBitmapCache();
+ return texture;
+ }
+
+ /**
+ * Generates a texture with glowing border and transparent center.
+ */
+ private static Texture generateGlowingBorder(final int size, final Color borderColor,
+ final int borderWidth, final int glowIntensity,
+ final boolean transparent, final int maxUpscale) {
+ final Texture texture = new Texture(size, size, maxUpscale);
+
+ // Clear to transparent or slight tint
+ final int centerAlpha = transparent ? 0 : 30;
+ final java.awt.Color bgColor = new java.awt.Color(borderColor.r, borderColor.g, borderColor.b, centerAlpha);
+ texture.graphics.setBackground(bgColor);
+ texture.graphics.clearRect(0, 0, size, size);
+
+ // Draw concentric glow lines from outer to inner
+ for (int i = 0; i < borderWidth; i++) {
+ final int intensity = (int) (glowIntensity * (borderWidth - i) / borderWidth);
+ final int alpha = Math.max(0, 200 - i * 30);
+
+ final java.awt.Color glowColor = new java.awt.Color(
+ Math.min(255, borderColor.r + intensity),
+ Math.min(255, borderColor.g + intensity),
+ Math.min(255, borderColor.b + intensity),
+ alpha
+ );
+
+ texture.graphics.setColor(glowColor);
+ texture.graphics.drawRect(i, i, size - 1 - 2 * i, size - 1 - 2 * i);
+ }
+
+ texture.graphics.dispose();
+ texture.resetResampledBitmapCache();
+ return texture;
+ }
+
+ /**
+ * Generates a texture with circular radial alpha gradient.
+ */
+ private static Texture generateRadialGlow(final int size, final Color color) {
+ final Texture texture = new Texture(size, size, 1);
+ final int halfSize = size / 2;
+
+ for (int x = 0; x < size; x++) {
+ for (int y = 0; y < size; y++) {
+ final int distanceFromCenter = (int) sqrt(pow(halfSize - x, 2) + pow(halfSize - y, 2));
+
+ int alpha = 255 - ((270 * distanceFromCenter) / halfSize);
+ if (alpha < 0) {
+ alpha = 0;
+ }
+
+ texture.primaryBitmap.pixels[texture.primaryBitmap.getAddress(x, y)] =
+ (alpha << 24) | (color.r << 16) | (color.g << 8) | color.b;
+ }
+ }
+
+ texture.resetResampledBitmapCache();
+ return texture;
+ }
+
+ /**
+ * Functional interface for texture creation.
+ */
+ @FunctionalInterface
+ private interface TextureSupplier {
+ Texture create();
+ }
+
+ /**
+ * Enumeration of texture generation types for cache key differentiation.
+ */
+ private enum TextureType {
+ SOLID_WITH_BORDER,
+ GLOWING_BORDER,
+ RADIAL_GLOW
+ }
+
+ /**
+ * Cache key for texture lookup based on generation parameters.
+ *
+ * <p>Two textures with identical parameters should produce the same key,
+ * enabling cache reuse. The key includes all parameters that affect
+ * the visual appearance of the generated texture.</p>
+ */
+ private static final class TextureKey {
+ private final int size;
+ private final Color primaryColor;
+ private final Color secondaryColor;
+ private final int borderWidth;
+ private final TextureType type;
+ private final int glowIntensity;
+ private final boolean transparent;
+ private final int maxUpscale;
+
+ TextureKey(final int size, final Color primaryColor, final Color secondaryColor,
+ final int borderWidth, final TextureType type, final int glowIntensity,
+ final boolean transparent, final int maxUpscale) {
+ this.size = size;
+ this.primaryColor = primaryColor;
+ this.secondaryColor = secondaryColor;
+ this.borderWidth = borderWidth;
+ this.type = type;
+ this.glowIntensity = glowIntensity;
+ this.transparent = transparent;
+ this.maxUpscale = maxUpscale;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final TextureKey that = (TextureKey) o;
+
+ if (size != that.size) return false;
+ if (borderWidth != that.borderWidth) return false;
+ if (glowIntensity != that.glowIntensity) return false;
+ if (transparent != that.transparent) return false;
+ if (maxUpscale != that.maxUpscale) return false;
+ if (type != that.type) return false;
+ if (!primaryColor.equals(that.primaryColor)) return false;
+ return secondaryColor.equals(that.secondaryColor);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = size;
+ result = 31 * result + primaryColor.hashCode();
+ result = 31 * result + secondaryColor.hashCode();
+ result = 31 * result + borderWidth;
+ result = 31 * result + type.hashCode();
+ result = 31 * result + glowIntensity;
+ result = 31 * result + (transparent ? 1 : 0);
+ result = 31 * result + maxUpscale;
+ return result;
+ }
+ }
+}
\ No newline at end of file