Bitmap font renderer, bug fixes and cleanup

This commit is contained in:
Natty 2022-04-19 22:39:16 +02:00
parent 6cbdd20731
commit 9df4da27ee
No known key found for this signature in database
GPG Key ID: 40AB22FA416C7019
34 changed files with 1321 additions and 287 deletions

View File

@ -1,7 +1,16 @@
## 22.2.0.0-alpha.1
* `[PlutoGUI]` **Added support for bitmap fonts**
* `[PlutoGUI]` Generalized the font renderer API
* `plutoengine-demos/` Removed third-party fonts
* `[PlutoRuntime]` The `ModLoader` component is now opaque,
pre-added mods are now specified when creating the token
* `[PlutoRuntime]` Added a linting annotation to `Logger#logf`
* `[PlutoRuntime]` Fixed a bug where creating relative resource paths
from URIs was not possible
* `[PlutoLib]` Fixed `toRGBA` in `HSB` and `HSBA`
* `[PlutoLib]` Added `BasicInterpolation`
* `[PlutoSpritesheet]` Added `recolor(IRGBA)` to `RectangleRenderer2D` and `Renderer2D`
* `[PlutoSpritesheet]` Added `TemporalSprite` and `OrientedSprite`
## 22.2.0.0-alpha.0
* `[PlutoComponent]` **Added support for dependencies and strengtened type checks**

View File

@ -14,6 +14,8 @@ public class Framerate
private static double frameTime = Double.NaN;
private static double animationTimer = 0;
private static double FPS = Double.NaN;
private static int interpolatedFPS;
@ -37,6 +39,11 @@ public class Framerate
return interpolatedFPS;
}
public static float getAnimationTimer()
{
return (float) animationTimer;
}
public static void tick()
{
var now = System.nanoTime();
@ -45,6 +52,9 @@ public class Framerate
{
var frameTimeNs = now - lastDraw;
frameTime = frameTimeNs / (double) TimeUnit.MILLISECONDS.toNanos(1);
animationTimer += frameTimeNs / (double) TimeUnit.SECONDS.toNanos(1);
// Maintain precision in case the engine runs for many hours
animationTimer %= TimeUnit.DAYS.toMinutes(1);
FPS = TimeUnit.SECONDS.toMillis(1) / frameTime;
}

View File

@ -11,6 +11,8 @@ dependencies {
api("io.reactivex.rxjava3", "rxjava", "3.1.4")
implementation("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml", "2.12.3")
implementation("org.lwjgl", "lwjgl-yoga")
runtimeOnly("org.lwjgl", "lwjgl-yoga", classifier = org.plutoengine.Versions.lwjglNatives)

View File

@ -3,8 +3,7 @@ package org.plutoengine.graphics;
import org.joml.Matrix3f;
import org.joml.primitives.Rectanglef;
import org.plutoengine.graphics.gui.PlutoGUICommandParser;
import org.plutoengine.graphics.gui.stbttf.STBTTBasicTextShaper;
import org.plutoengine.graphics.gui.stbttf.STBTTFont;
import org.plutoengine.graphics.gui.font.PlutoFont;
import org.plutoengine.libra.command.LiCommandBuffer;
import org.plutoengine.libra.command.impl.LiCommandSetTransform;
import org.plutoengine.libra.text.LiTextInfo;
@ -16,17 +15,19 @@ import java.util.EnumSet;
public class ImmediateFontRenderer
{
public static void drawString(float x, float y, String text, LiFontFamily<STBTTFont> font, TextStyleOptions style)
public static <T extends PlutoFont<T>> void drawString(float x, float y, String text, LiFontFamily<T> fontFamily, TextStyleOptions style)
{
var shaper = new STBTTBasicTextShaper();
var font = style.pickFont(fontFamily);
var shaper = font.getDefaultShaper();
var info = shaper.shape(EnumSet.of(TextShaper.EnumFeature.KERNING), font, text, style);
draw(x, y, info, style);
}
public static void drawStringNoKern(float x, float y, String text, LiFontFamily<STBTTFont> font, TextStyleOptions style)
public static <T extends PlutoFont<T>> void drawStringNoKern(float x, float y, String text, LiFontFamily<T> fontFamily, TextStyleOptions style)
{
var shaper = new STBTTBasicTextShaper();
var font = style.pickFont(fontFamily);
var shaper = font.getDefaultShaper();
var info = shaper.shape(EnumSet.noneOf(TextShaper.EnumFeature.class), font, text, style);
draw(x, y, info, style);
@ -38,7 +39,7 @@ public class ImmediateFontRenderer
var fitBox = style.getFitBox();
var initialScale = style.getSize() / STBTTFont.PIXEL_HEIGHT;
var initialScale = style.getSize() / PlutoFont.NORMALIZED_PIXEL_HEIGHT;
var scaleX = initialScale;
var scaleY = initialScale;

View File

@ -28,12 +28,16 @@ public class PlutoGUIMod implements IModEntryPoint
public static FontShader fontShader;
public static FontShader bitmapFontShader;
public void onLoad(Mod mod)
{
instance = mod;
fontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexFontShader#glsl"), mod.getResource("shaders.FragmentFontShader#glsl")).build(FontShader.class, false);
bitmapFontShader = new RenderShaderBuilder(mod.getResource("shaders.VertexBitmapFontShader#glsl"), mod.getResource("shaders.FragmentBitmapFontShader#glsl")).build(FontShader.class, false);
uiElementsAtlas = new RectangleTexture();
uiElementsAtlas.load(mod.getResource("gui.elements#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
}

View File

@ -0,0 +1,93 @@
package org.plutoengine.graphics.gui;
import org.joml.Matrix3fc;
import org.plutoengine.graphics.gl.vao.attrib.ReservedAttributes;
import org.plutoengine.libra.paint.LiColorPaint;
import org.plutoengine.libra.paint.LiGradientPaint;
import org.plutoengine.libra.paint.LiPaint;
import org.plutoengine.shader.ShaderBase;
import org.plutoengine.shader.ShaderProgram;
import org.plutoengine.shader.VertexArrayAttribute;
import org.plutoengine.shader.uniform.*;
import org.plutoengine.shader.uniform.auto.AutoViewportProjection;
import org.plutoengine.util.color.IRGBA;
@ShaderProgram
public final class BitmapTextShader extends ShaderBase implements IGUIShader
{
@AutoViewportProjection
@Uniform(name = "projection")
public UniformMat4 projectionMatrix;
@Uniform(name = "transformation")
public UniformMat3 transformationMatrix;
@Uniform
public UniformInt paintType;
@Uniform
public UniformRGBA paintColor;
@Uniform
public UniformInt paintGradientStopCount;
@Uniform
public UniformArrayRGBA paintGradientColors;
@Uniform
public UniformArrayFloat paintGradientPositions;
@Uniform
public UniformArrayVec2 paintGradientEnds;
@VertexArrayAttribute(ReservedAttributes.POSITION)
public int position;
@VertexArrayAttribute(ReservedAttributes.UV)
public int uvCoords;
@VertexArrayAttribute(2)
public int paintUVCoords;
@Override
public void setTransform(Matrix3fc transform)
{
this.transformationMatrix.load(transform);
}
@Override
public void setPaint(LiPaint paint)
{
switch (paint.getType())
{
case SOLID_COLOR -> {
var col = ((LiColorPaint) paint).getColor();
this.paintType.load(0);
this.paintColor.load(col.getFloatComponentsRGBA());
}
case GRADIENT -> {
var gradPaint = (LiGradientPaint) paint;
this.paintType.load(1);
this.paintGradientEnds.load(gradPaint.getStart(), gradPaint.getEnd());
var stops = gradPaint.getStops();
this.paintGradientStopCount.load(stops.length);
var colors = new IRGBA[stops.length];
var positions = new float[stops.length];
int i = 0;
for (var stop : stops)
{
var col = stop.color();
colors[i] = col.getFloatComponentsRGBA();
positions[i] = stop.position();
i++;
}
this.paintGradientColors.load(colors);
this.paintGradientPositions.load(positions);
}
}
}
}

View File

@ -0,0 +1,159 @@
package org.plutoengine.graphics.gui.font;
import org.plutoengine.graphics.texture.Texture;
import org.plutoengine.libra.text.font.GlyphMetrics;
import org.plutoengine.libra.text.font.LiFont;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import java.util.Map;
public abstract class PlutoFont<T extends PlutoFont<? super T>> extends LiFont<PlutoFont<T>.PlutoGlyphAtlas, PlutoFont<T>.PlutoGlyphMetrics> implements AutoCloseable
{
public static final int NORMALIZED_PIXEL_HEIGHT = 64;
protected int descent;
protected int ascent;
protected int lineGap;
protected int lineAdvance;
protected float scale;
protected Map<Integer, Integer> glyphIndexLookup;
protected Map<PlutoFont.KerningPair, Integer> kerningTable;
public record KerningPair(int left, int right)
{
}
protected PlutoFont(String name)
{
super(name);
}
public abstract IShapingStrategy<? extends PlutoFont<? super T>.PlutoGlyphMetrics, ? extends PlutoFont<? super T>.PlutoGlyphAtlas, T> getDefaultShaper();
public float getScale()
{
return this.scale;
}
public int getAscent()
{
return this.ascent;
}
public int getDescent()
{
return this.descent;
}
public int getLineGap()
{
return this.lineGap;
}
public int getLineAdvance()
{
return this.lineAdvance;
}
public int getKerningOffset(int left, int right)
{
var sk = new PlutoFont.KerningPair(glyphIndexLookup.getOrDefault(left, -1), glyphIndexLookup.getOrDefault(right, -1));
return this.kerningTable.getOrDefault(sk, 0);
}
@Override
public void close()
{
var tex = this.getGlyphAtlas().getGlyphAtlasTexture();
tex.close();
}
public class PlutoGlyphAtlas extends LiFont<PlutoFont<T>.PlutoGlyphAtlas, PlutoFont<T>.PlutoGlyphMetrics>.GlyphAtlas
{
private Texture glyphAtlasTexture;
public void setGlyphAtlasTexture(Texture glyphAtlasTexture)
{
this.glyphAtlasTexture = glyphAtlasTexture;
}
public Texture getGlyphAtlasTexture()
{
return this.glyphAtlasTexture;
}
}
public class PlutoGlyphMetrics extends GlyphMetrics
{
private final int codepoint;
protected int advanceX;
protected int leftSideBearing;
protected int cx0;
protected int cy0;
protected int cx1;
protected int cy1;
protected int xOrigin;
protected int yOrigin;
public PlutoGlyphMetrics(int codepoint)
{
this.codepoint = codepoint;
}
public int getAdvanceX()
{
return this.advanceX;
}
public int getLeftSideBearing()
{
return this.leftSideBearing;
}
public int getCodepoint()
{
return this.codepoint;
}
public int getKerning(int cp)
{
return PlutoFont.this.getKerningOffset(this.codepoint, cp);
}
public int getCX0()
{
return this.cx0;
}
public int getCY0()
{
return this.cy0;
}
public int getCX1()
{
return this.cx1;
}
public int getCY1()
{
return this.cy1;
}
public int getXOrigin()
{
return this.xOrigin;
}
public int getYOrigin()
{
return this.yOrigin;
}
}
}

View File

@ -0,0 +1,127 @@
package org.plutoengine.graphics.gui.font.bitmap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.joml.primitives.Rectanglef;
import org.plutoengine.graphics.gui.font.PlutoFont;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import org.plutoengine.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
public class BitmapFont extends PlutoFont<BitmapFont>
{
private int padding;
private BitmapFont(String name)
{
super(name);
}
public int getPadding()
{
return this.padding;
}
@Override
public IShapingStrategy<BitmapFont.PlutoGlyphMetrics, BitmapFont.PlutoGlyphAtlas, BitmapFont> getDefaultShaper()
{
return new BitmapTextShaper();
}
public static BitmapFont load(Path path)
{
try
{
var objectMapper = new ObjectMapper(YAMLFactory.builder().build());
BitmapFontInfo info;
try (var br = Files.newBufferedReader(path))
{
info = objectMapper.readValue(br, BitmapFontInfo.class);
}
var font = new BitmapFont(info.name());
Logger.logf(SmartSeverity.ADDED, "Loading font: %s%n", font.getName());
font.atlas = font.new PlutoGlyphAtlas();
var tex = new RectangleTexture();
var dir = path.getParent();
var texPath = dir.resolve(info.atlas());
var magFilter = switch (info.filtering()) {
case NEAREST -> MagFilter.NEAREST;
case LINEAR -> MagFilter.LINEAR;
};
tex.load(texPath, magFilter, MinFilter.LINEAR, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
font.atlas.setGlyphAtlasTexture(tex);
var meta = info.meta();
font.ascent = meta.ascent();
font.descent = meta.descent();
font.lineGap = meta.lineGap();
font.lineAdvance = font.ascent - font.descent + font.lineGap;
font.scale = 1.0f / meta.scale() * PlutoFont.NORMALIZED_PIXEL_HEIGHT;
var kern = info.kern();
font.kerningTable = new HashMap<>();
kern.forEach(e -> font.kerningTable.put(new PlutoFont.KerningPair(e.left(), e.right()), e.offset()));
var glyphs = info.glyphs();
font.glyphIndexLookup = new HashMap<>();
font.padding = meta.padding();
glyphs.forEach(glyph -> {
var sprite = glyph.sprite();
var cp = glyph.cp();
var glyphMetrics = font.new PlutoGlyphMetrics(cp) {{
this.advanceX = glyph.advance();
this.leftSideBearing = glyph.leftBearing();
this.xOrigin = (this.leftSideBearing - font.padding) * PlutoFont.NORMALIZED_PIXEL_HEIGHT / meta.scale();
this.yOrigin = (glyph.topOffset() - font.ascent - font.padding) * PlutoFont.NORMALIZED_PIXEL_HEIGHT / meta.scale();
this.cx0 = this.leftSideBearing + font.padding;
this.cy0 = glyph.topOffset() - font.ascent + font.padding;
this.cx1 = this.leftSideBearing + sprite.w() - font.padding;
this.cy1 = sprite.h() - font.ascent - font.padding;
}};
var rect = new Rectanglef(
sprite.x(),
sprite.y(),
sprite.x() + sprite.w(),
sprite.y() + sprite.h()
);
var glyphInfo = new GlyphInfo<>(font.atlas, 0, rect);
font.addGlyph(cp, glyphMetrics, glyphInfo);
});
return font;
}
catch (Exception e)
{
Logger.logf(SmartSeverity.ERROR, "Failed to load font: %s%n", path);
Logger.log(e);
return null;
}
}
}

View File

@ -0,0 +1,65 @@
package org.plutoengine.graphics.gui.font.bitmap;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.ext.NioPathDeserializer;
import java.nio.file.Path;
import java.util.List;
record BitmapFontInfo(
String name,
FontInfoMeta meta,
@JsonDeserialize(using = NioPathDeserializer.class) Path atlas,
Filtering filtering,
@JsonDeserialize(contentAs = KerningInfo.class) List<KerningInfo> kern,
@JsonDeserialize(contentAs = GlyphInfo.class) List<GlyphInfo> glyphs
)
{
enum Filtering
{
NEAREST,
LINEAR
}
record FontInfoMeta(
int ascent,
int descent,
int lineGap,
int padding,
int scale
)
{
}
record KerningInfo(
int left,
int right,
int offset
)
{
}
record GlyphInfo(
int cp,
int glyphClass,
GlyphSprite sprite,
int leftBearing,
int advance,
int topOffset
)
{
}
record GlyphSprite(
int x,
int y,
int w,
int h
)
{
}
}

View File

@ -0,0 +1,168 @@
package org.plutoengine.graphics.gui.font.bitmap;
import org.joml.primitives.Rectanglef;
import org.lwjgl.system.MemoryUtil;
import org.plutoengine.graphics.PlutoGUIMod;
import org.plutoengine.graphics.gui.command.PlutoCommandDrawMeshDirectBuffer;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchShader;
import org.plutoengine.graphics.gui.command.PlutoCommandSwitchTexture;
import org.plutoengine.libra.command.LiCommandBuffer;
import org.plutoengine.libra.command.impl.LiCommandSetPaint;
import org.plutoengine.libra.text.LiTextInfo;
import org.plutoengine.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.libra.text.shaping.TextShaper;
import org.plutoengine.libra.text.shaping.TextStyleOptions;
import java.util.EnumSet;
import java.util.Objects;
public class BitmapTextShaper implements IShapingStrategy<BitmapFont.PlutoGlyphMetrics, BitmapFont.PlutoGlyphAtlas, BitmapFont>
{
private LiCommandBuffer commandBuffer;
public BitmapTextShaper setCommandBuffer(LiCommandBuffer commandBuffer)
{
this.commandBuffer = commandBuffer;
return this;
}
@Override
public LiTextInfo shape(EnumSet<TextShaper.EnumFeature> features, BitmapFont font, String text, TextStyleOptions style)
{
var commandBuf = Objects.requireNonNullElseGet(this.commandBuffer, LiCommandBuffer::uncleared);
var atlas = font.getGlyphAtlas();
var atlasTexture = atlas.getGlyphAtlasTexture();
var texSwitch = new PlutoCommandSwitchTexture(atlasTexture);
commandBuf.push(texSwitch);
var shader = PlutoGUIMod.bitmapFontShader;
var shaderSwitch = new PlutoCommandSwitchShader(shader);
commandBuf.push(shaderSwitch);
commandBuf.push(new LiCommandSetPaint(style.getPaint()));
var cpCount = (int) text.codePoints().count();
var mesh = new PlutoCommandDrawMeshDirectBuffer();
final var quadVerts = 4;
final var twoTriVerts = 6;
var vertDim = 2;
var vertexBuf = MemoryUtil.memAllocFloat(vertDim * quadVerts * cpCount);
var uvDim = 2;
var uvBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
var paintUVBuf = MemoryUtil.memAllocFloat(uvDim * quadVerts * cpCount);
var indexBuf = MemoryUtil.memAllocInt(twoTriVerts * cpCount);
var cpIt = text.codePoints().iterator();
var indices = new int[] {
0, 1, 2,
0, 2, 3
};
float[] vertices = new float[vertDim * quadVerts];
float[] uvs = new float[uvDim * quadVerts];
float scale = font.getScale();
float x = 0;
float y = 0;
int padding = font.getPadding();
GlyphInfo<?, ?> info;
BitmapFont.PlutoGlyphMetrics metrics = null;
int cp;
float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY, minY = Float.POSITIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY;
while (cpIt.hasNext())
{
cp = cpIt.next();
switch (cp)
{
case '\n' -> {
x = 0;
y += font.getLineAdvance() * scale;
continue;
}
}
if (metrics != null && features.contains(TextShaper.EnumFeature.KERNING))
x += metrics.getKerning(cp) * scale;
metrics = font.getGlyphMetrics(cp);
info = atlas.getGlyph(cp);
if (metrics == null)
continue;
if (info != null)
{
float gx = x + metrics.getXOrigin();
float gy = y + metrics.getYOrigin() + font.getAscent() * scale;
float xLow = gx - padding;
float xHigh = gx + metrics.getCX1() * scale - metrics.getCX0() * scale + padding;
float yLow = gy - padding;
float yHigh = gy + metrics.getCY1() * scale - metrics.getCY0() * scale + padding;
minX = Math.min(minX, xLow);
minY = Math.min(minY, yLow);
maxX = Math.max(maxX, xHigh);
maxY = Math.max(maxY, yHigh);
vertices[6] = vertices[0] = xLow;
vertices[3] = vertices[1] = yHigh;
vertices[4] = vertices[2] = xHigh;
vertices[7] = vertices[5] = yLow;
var uvRect = info.getRect();
uvs[6] = uvs[0] = uvRect.minX;
uvs[3] = uvs[1] = atlasTexture.getHeight() - uvRect.maxY;
uvs[4] = uvs[2] = uvRect.maxX;
uvs[7] = uvs[5] = atlasTexture.getHeight() - uvRect.minY;
vertexBuf.put(vertices);
uvBuf.put(uvs);
indexBuf.put(indices);
indices[0] += quadVerts;
indices[1] += quadVerts;
indices[2] += quadVerts;
indices[3] += quadVerts;
indices[4] += quadVerts;
indices[5] += quadVerts;
}
x += metrics.getAdvanceX() * scale;
}
vertexBuf.flip();
while (vertexBuf.hasRemaining())
{
paintUVBuf.put((vertexBuf.get() - minX) / (maxX - minX));
paintUVBuf.put((vertexBuf.get() - minY) / (maxY - minY));
}
mesh.addAttribute(shader.position, vertexBuf.rewind(), vertDim);
mesh.addAttribute(shader.uvCoords, uvBuf.flip(), uvDim);
mesh.addAttribute(shader.paintUVCoords, paintUVBuf.flip(), uvDim);
mesh.addIndices(indexBuf.flip());
commandBuf.push(mesh);
return new LiTextInfo(commandBuf, new Rectanglef(minX, minY, maxX, maxY));
}
}

View File

@ -1,16 +1,16 @@
package org.plutoengine.graphics.gui.stbttf;
package org.plutoengine.graphics.gui.font.stbttf;
import org.joml.primitives.Rectanglef;
import org.lwjgl.stb.*;
import org.lwjgl.system.MemoryStack;
import org.plutoengine.buffer.BufferHelper;
import org.plutoengine.graphics.gui.SDFTextureArray;
import org.plutoengine.graphics.gui.font.PlutoFont;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
import org.plutoengine.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.font.GlyphMetrics;
import org.plutoengine.libra.text.font.LiFont;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.logger.Logger;
import org.plutoengine.logger.SmartSeverity;
@ -24,157 +24,22 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTTGlyphMetrics> implements AutoCloseable
public class STBTTFont extends PlutoFont<STBTTFont>
{
public static final int PIXEL_HEIGHT = 64;
public static final int SDF_PADDING = 8;
public static final int SHEET_SIZE = 1024;
private int descent;
private int ascent;
private int lineGap;
private int lineAdvance;
private float scale;
private Map<Integer, Integer> glyphIndexLookup;
private Map<KerningPair, Integer> kerningTable;
private record KerningPair(int left, int right)
{
}
private STBTTFont(String name)
{
super(name);
}
public float getScale()
{
return this.scale;
}
public int getAscent()
{
return this.ascent;
}
public int getDescent()
{
return this.descent;
}
public int getLineGap()
{
return this.lineGap;
}
public int getLineAdvance()
{
return this.lineAdvance;
}
public int getKerningOffset(int left, int right)
{
var sk = new KerningPair(glyphIndexLookup.getOrDefault(left, -1), glyphIndexLookup.getOrDefault(right, -1));
return this.kerningTable.getOrDefault(sk, 0);
}
@Override
public void close()
public IShapingStrategy<STBTTFont.PlutoGlyphMetrics, STBTTFont.PlutoGlyphAtlas, STBTTFont> getDefaultShaper()
{
var tex = this.getGlyphAtlas().getGlyphAtlasTexture();
tex.close();
}
public class STBTTGlyphAtlas extends LiFont<STBTTGlyphAtlas, STBTTGlyphMetrics>.GlyphAtlas
{
private SDFTextureArray glyphAtlasTexture;
public void setGlyphAtlasTexture(SDFTextureArray glyphAtlasTexture)
{
this.glyphAtlasTexture = glyphAtlasTexture;
}
public SDFTextureArray getGlyphAtlasTexture()
{
return this.glyphAtlasTexture;
}
}
public class STBTTGlyphMetrics extends GlyphMetrics
{
private final int codepoint;
private int advanceX;
private int leftSideBearing;
private int cx0;
private int cy0;
private int cx1;
private int cy1;
private int xOrigin;
private int yOrigin;
private STBTTGlyphMetrics(int codepoint)
{
this.codepoint = codepoint;
}
public int getAdvanceX()
{
return this.advanceX;
}
public int getLeftSideBearing()
{
return this.leftSideBearing;
}
public int getCodepoint()
{
return this.codepoint;
}
public int getKerning(int cp)
{
return STBTTFont.this.getKerningOffset(this.codepoint, cp);
}
public int getCX0()
{
return this.cx0;
}
public int getCY0()
{
return this.cy0;
}
public int getCX1()
{
return this.cx1;
}
public int getCY1()
{
return this.cy1;
}
public int getXOrigin()
{
return this.xOrigin;
}
public int getYOrigin()
{
return this.yOrigin;
}
return new STBTTTextShaper();
}
public static STBTTFont load(Path path)
@ -210,7 +75,7 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
Logger.logf(SmartSeverity.ADDED, "Loading font: %s%n", name);
var font = new STBTTFont(name);
font.scale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, PIXEL_HEIGHT);
font.scale = STBTruetype.stbtt_ScaleForPixelHeight(fontInfo, NORMALIZED_PIXEL_HEIGHT);
var ascentBuf = stack.callocInt(1);
var descentBuf = stack.callocInt(1);
@ -238,16 +103,16 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
var colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY);
var colorModel = new ComponentColorModel(colorSpace, new int[] { 8 }, false,false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
font.atlas = font.new STBTTGlyphAtlas();
font.atlas = font.new PlutoGlyphAtlas();
font.glyphIndexLookup = new HashMap<>();
var advanceWidth = stack.mallocInt(1);
var leftSideBearing = stack.mallocInt(1);
var leftSideBearingBuf = stack.mallocInt(1);
var cx0 = stack.mallocInt(1);
var cy0 = stack.mallocInt(1);
var cx1 = stack.mallocInt(1);
var cy1 = stack.mallocInt(1);
var cx0Buf = stack.mallocInt(1);
var cy0Buf = stack.mallocInt(1);
var cx1Buf = stack.mallocInt(1);
var cy1Buf = stack.mallocInt(1);
var onedgeValue = 128;
var pixelDistScale = onedgeValue / (float) SDF_PADDING;
@ -268,7 +133,7 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
var width = sdfWidthBuf.get(0);
var height = sdfHeightBuf.get(0);
var glyphInfo = (GlyphInfo<STBTTGlyphAtlas, STBTTGlyphMetrics>) null;
var glyphInfo = (GlyphInfo<STBTTFont.PlutoGlyphAtlas, STBTTFont.PlutoGlyphMetrics>) null;
if (buf != null)
{
@ -319,20 +184,21 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
STBTruetype.stbtt_FreeSDF(buf);
}
STBTruetype.stbtt_GetCodepointHMetrics(fontInfo, cp, advanceWidth, leftSideBearing);
STBTruetype.stbtt_GetCodepointHMetrics(fontInfo, cp, advanceWidth, leftSideBearingBuf);
var glyphMetrics = font.new STBTTGlyphMetrics(cp);
glyphMetrics.advanceX = advanceWidth.get(0);
glyphMetrics.leftSideBearing = leftSideBearing.get(0);
var glyphMetrics = font.new PlutoGlyphMetrics(cp) {{
this.advanceX = advanceWidth.get(0);
this.leftSideBearing = leftSideBearingBuf.get(0);
glyphMetrics.xOrigin = xOffsBuf.get(0);
glyphMetrics.yOrigin = yOffsBuf.get(0);
this.xOrigin = xOffsBuf.get(0);
this.yOrigin = yOffsBuf.get(0);
STBTruetype.stbtt_GetCodepointBox(fontInfo, cp, cx0, cy0, cx1, cy1);
glyphMetrics.cx0 = cx0.get(0);
glyphMetrics.cy0 = cy0.get(0);
glyphMetrics.cx1 = cx1.get(0);
glyphMetrics.cy1 = cy1.get(0);
STBTruetype.stbtt_GetCodepointBox(fontInfo, cp, cx0Buf, cy0Buf, cx1Buf, cy1Buf);
this.cx0 = cx0Buf.get(0);
this.cy0 = cy0Buf.get(0);
this.cx1 = cx1Buf.get(0);
this.cy1 = cy1Buf.get(0);
}};
font.addGlyph(cp, glyphMetrics, glyphInfo);
@ -349,12 +215,12 @@ public class STBTTFont extends LiFont<STBTTFont.STBTTGlyphAtlas, STBTTFont.STBTT
{
STBTruetype.stbtt_GetKerningTable(fontInfo, kerningTable);
font.kerningTable = new HashMap<>();
kerningTable.forEach(e -> font.kerningTable.put(new KerningPair(e.glyph1(), e.glyph2()), e.advance()));
kerningTable.forEach(e -> font.kerningTable.put(new PlutoFont.KerningPair(e.glyph1(), e.glyph2()), e.advance()));
}
font.ascent = ascentBuf.get();
font.descent = descentBuf.get();
font.lineGap = lineGapBuf.get();
font.ascent = ascentBuf.get(0);
font.descent = descentBuf.get(0);
font.lineGap = lineGapBuf.get(0);
font.lineAdvance = font.ascent - font.descent + font.lineGap;
var tex = new SDFTextureArray();
tex.loadImg(sheets, SHEET_SIZE, SHEET_SIZE, sheets.size(), MagFilter.LINEAR, MinFilter.LINEAR, WrapMode.MIRROR_CLAMP_TO_EDGE, WrapMode.MIRROR_CLAMP_TO_EDGE);

View File

@ -1,4 +1,4 @@
package org.plutoengine.graphics.gui.stbttf;
package org.plutoengine.graphics.gui.font.stbttf;
import org.joml.primitives.Rectanglef;
import org.lwjgl.system.MemoryUtil;
@ -11,7 +11,6 @@ import org.plutoengine.libra.command.impl.LiCommandSetPaint;
import org.plutoengine.libra.command.impl.LiCommandSpecial;
import org.plutoengine.libra.text.LiTextInfo;
import org.plutoengine.libra.text.font.GlyphInfo;
import org.plutoengine.libra.text.font.LiFontFamily;
import org.plutoengine.libra.text.shaping.IShapingStrategy;
import org.plutoengine.libra.text.shaping.TextShaper;
import org.plutoengine.libra.text.shaping.TextStyleOptions;
@ -20,11 +19,11 @@ import java.util.Arrays;
import java.util.EnumSet;
import java.util.Objects;
public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGlyphMetrics, STBTTFont.STBTTGlyphAtlas, STBTTFont>
public class STBTTTextShaper implements IShapingStrategy<STBTTFont.PlutoGlyphMetrics, STBTTFont.PlutoGlyphAtlas, STBTTFont>
{
private LiCommandBuffer commandBuffer;
public STBTTBasicTextShaper setCommandBuffer(LiCommandBuffer commandBuffer)
public STBTTTextShaper setCommandBuffer(LiCommandBuffer commandBuffer)
{
this.commandBuffer = commandBuffer;
@ -32,12 +31,10 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
}
@Override
public LiTextInfo shape(EnumSet<TextShaper.EnumFeature> features, LiFontFamily<STBTTFont> fontFamily, String text, TextStyleOptions style)
public LiTextInfo shape(EnumSet<TextShaper.EnumFeature> features, STBTTFont font, String text, TextStyleOptions style)
{
var commandBuf = Objects.requireNonNullElseGet(this.commandBuffer, LiCommandBuffer::uncleared);
var font = style.pickFont(fontFamily);
var atlas = font.getGlyphAtlas();
var atlasTexture = atlas.getGlyphAtlasTexture();
var texSwitch = new PlutoCommandSwitchTexture(atlasTexture);
@ -88,7 +85,7 @@ public class STBTTBasicTextShaper implements IShapingStrategy<STBTTFont.STBTTGly
float y = 0;
GlyphInfo<?, ?> info;
STBTTFont.STBTTGlyphMetrics metrics = null;
STBTTFont.PlutoGlyphMetrics metrics = null;
int cp;
float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY, minY = Float.POSITIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY;

View File

@ -0,0 +1,50 @@
package org.plutoengine.math;
import org.joml.Math;
public class BasicInterpolation
{
public static int roundedLerp(float value, int min, int max)
{
return min + Math.round(value * (max - min));
}
public static int roundedLerpWrap(float value, int min, int max)
{
var range = max - min;
return min + (roundedLerp(value, 0, range) % range + range) % range;
}
public static int roundedLerp(double value, int min, int max)
{
return (int) (min + Math.round(value * (max - min)));
}
public static int roundedLerpWrap(double value, int min, int max)
{
var range = max - min;
return min + (roundedLerp(value, 0, range) % range + range) % range;
}
public static int floorLerp(float value, int min, int max)
{
return (int) (min + Math.floor(value * (max - min)));
}
public static int floorLerpWrap(float value, int min, int max)
{
var range = max - min;
return min + (floorLerp(value, 0, range) % range + range) % range;
}
public static int floorLerp(double value, int min, int max)
{
return (int) (min + Math.floor(value * (max - min)));
}
public static int floorLerpWrap(double value, int min, int max)
{
var range = max - min;
return min + (floorLerp(value, 0, range) % range + range) % range;
}
}

View File

@ -57,7 +57,7 @@ public class HSB
* @since 20.2.0.0-alpha.3
* @author 493msi
*/
public RGB toRGBA()
public RGBA toRGBA()
{
return this.toRGBA(1.0f);
}

View File

@ -44,7 +44,7 @@ public class HSBA extends HSB
* @author 493msi
*/
@Override
public RGB toRGBA()
public RGBA toRGBA()
{
return this.toRGBA(this.a);
}

View File

@ -1,5 +1,6 @@
package org.plutoengine.logger;
import org.intellij.lang.annotations.PrintFormat;
import org.plutoengine.component.ComponentToken;
import org.plutoengine.component.PlutoGlobalComponent;
import org.plutoengine.resource.filesystem.ResourceManager;
@ -215,7 +216,7 @@ public class Logger extends PlutoGlobalComponent
*
* @since pre-alpha
* */
public static synchronized void logf(ISeverity s, String formatSpec, Object... o)
public static synchronized void logf(ISeverity s, @PrintFormat String formatSpec, Object... o)
{
(s.isStdErr() ? System.err : System.out).printf(s.getDisplayName() + formatSpec, o);
}

View File

@ -110,6 +110,9 @@ public class ResourceFileSystemProvider extends FileSystemProvider
var mod = modLoader.getModByID(urp.modID());
if (urp.relative())
return new ResourcePath(null, urp.pathAddress(), urp.extension(), null);
if (mod.isEmpty())
throw new IllegalArgumentException("No such mod exists!");

View File

@ -9,6 +9,7 @@ import org.plutoengine.graphics.gl.vao.QuadPresets;
import org.plutoengine.graphics.gl.vao.VertexArray;
import org.plutoengine.graphics.sprite.Sprite;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import org.plutoengine.util.color.IRGBA;
import java.util.Stack;
@ -234,6 +235,13 @@ public class RectangleRenderer2D
return this;
}
public RectangleRenderer2D recolor(IRGBA rgba)
{
this.customShader.loadRecolor(rgba.red(), rgba.green(), rgba.blue(), rgba.alpha());
return this;
}
public RectangleRenderer2D recolor(Vector4f recolor)
{
this.customShader.loadRecolor(recolor);

View File

@ -8,6 +8,7 @@ import org.plutoengine.graphics.gl.DrawMode;
import org.plutoengine.graphics.gl.vao.QuadPresets;
import org.plutoengine.graphics.gl.vao.VertexArray;
import org.plutoengine.graphics.texture.texture2d.Texture2D;
import org.plutoengine.util.color.IRGBA;
import java.util.Stack;
@ -216,6 +217,13 @@ public class Renderer2D
return this;
}
public Renderer2D recolor(IRGBA rgba)
{
this.customShader.loadRecolor(rgba.red(), rgba.green(), rgba.blue(), rgba.alpha());
return this;
}
public Renderer2D recolor(Vector4f recolor)
{
this.customShader.loadRecolor(recolor);

View File

@ -0,0 +1,44 @@
package org.plutoengine.graphics.sprite;
import org.plutoengine.math.BasicInterpolation;
public class OrientedSprite
{
protected PartialTextureSprite[] sides;
public OrientedSprite(int sideCount)
{
this.sides = new PartialTextureSprite[sideCount];
}
public OrientedSprite(PartialTextureSprite[] sides)
{
this.sides = sides;
}
public void setSide(int side, PartialTextureSprite sprite)
{
this.sides[side] = sprite;
}
public int getSideCount()
{
return this.sides.length;
}
public PartialTextureSprite getSideFromAngle(float angle)
{
var side = BasicInterpolation.roundedLerpWrap(angle / (Math.PI * 2), 0, this.sides.length);
return this.sides[side];
}
public PartialTextureSprite getSide(int i)
{
return this.sides[i];
}
public PartialTextureSprite[] getSides()
{
return this.sides;
}
}

View File

@ -0,0 +1,127 @@
package org.plutoengine.graphics.sprite;
import org.plutoengine.display.Framerate;
import org.plutoengine.graphics.texture.texture2d.RectangleTexture;
import java.util.Arrays;
public class TemporalSprite implements Sprite<RectangleTexture>
{
protected PartialTextureSprite[] frames;
protected float[] durations;
protected float cached = 0;
protected int cacheIndex = 0;
protected transient float[] stops;
public TemporalSprite(int frameCount)
{
this.frames = new PartialTextureSprite[frameCount];
this.durations = new float[frameCount];
this.stops = new float[frameCount];
}
public TemporalSprite(PartialTextureSprite[] frames)
{
this.frames = frames;
this.durations = new float[frames.length];
this.stops = new float[frames.length];
}
public TemporalSprite(PartialTextureSprite[] frames, float duration)
{
this.frames = frames;
this.durations = new float[frames.length];
this.stops = new float[frames.length];
Arrays.fill(this.durations, duration);
this.recomputeFromIndex(0);
}
public TemporalSprite(PartialTextureSprite[] frames, float[] durations)
{
assert frames.length == durations.length;
this.frames = frames;
this.durations = durations;
this.stops = new float[durations.length];
this.recomputeFromIndex(0);
}
protected void recomputeFromIndex(int index)
{
for (int i = index + 1; i < this.durations.length; i++)
this.stops[i] = this.stops[i - 1] + this.durations[i];
}
public void setFrame(int index, PartialTextureSprite sprite)
{
this.frames[index] = sprite;
this.recomputeFromIndex(index);
}
public int getSideCount()
{
return this.frames.length;
}
public PartialTextureSprite getFrameAt(float time)
{
if (time == this.cached)
return this.frames[this.cacheIndex];
var totalDuration = this.stops[this.stops.length - 1] + durations[this.durations.length - 1];
time = (time % totalDuration + totalDuration) % totalDuration;
var i = Math.abs(Arrays.binarySearch(this.stops, time));
this.cacheIndex = i % this.durations.length;
this.cached = time;
return this.frames[this.cacheIndex];
}
public PartialTextureSprite getFrame()
{
return this.getFrameAt(Framerate.getAnimationTimer());
}
public PartialTextureSprite getFrameNr(int i)
{
return this.frames[i];
}
public PartialTextureSprite[] getFrames()
{
return this.frames;
}
@Override
public int getX()
{
return this.getFrame().getX();
}
@Override
public int getY()
{
return this.getFrame().getY();
}
@Override
public int getWidth()
{
return this.getFrame().getWidth();
}
@Override
public int getHeight()
{
return this.getFrame().getHeight();
}
@Override
public RectangleTexture getSheet()
{
return this.getFrame().getSheet();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -10,6 +10,10 @@
"plutofonts": {
"path": "plutofonts",
"type": "open"
},
"default": {
"path": "default",
"type": "open"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,240 @@
name: SRClone
meta:
scale: 8
ascent: 8
descent: 0
lineGap: 1
atlas: "pluto+asl://!font#png"
filtering: NEAREST
kern:
-
left: 0
right: 0
offset: 0
glyphs:
-
cp: 48 # 0
sprite:
x: 0
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 49 # 1
sprite:
x: 8
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 50 # 2
sprite:
x: 16
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 51 # 3
sprite:
x: 24
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 52 # 4
sprite:
x: 32
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 53 # 5
sprite:
x: 40
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 54 # 6
sprite:
x: 48
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 55 # 7
sprite:
x: 56
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 56 # 8
sprite:
x: 64
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 57 # 9
sprite:
x: 72
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 57 # 9
sprite:
x: 72
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 43 # +
sprite:
x: 80
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 45 # +
sprite:
x: 88
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 46 # .
sprite:
x: 96
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 120 # x
sprite:
x: 104
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 128150 # 💖 - sparkling heart because the default red one is two codepoints :(
sprite:
x: 112
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 105 # i
sprite:
x: 120
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 83 # S
sprite:
x: 0
y: 8
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 127776 # 🌠 - shooting star
sprite:
x: 8
y: 8
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 128994 # 🟢 - large green circle
sprite:
x: 16
y: 8
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 9728 # ☀ - sun
sprite:
x: 24
y: 8
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 128314 # 🔺 - red up triangle
sprite:
x: 32
y: 8
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 121171 # 𝥓 - spiral arrow thingy
sprite:
x: 40
y: 8
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 128160 # 💠 - diamond
sprite:
x: 48
y: 8
w: 8
h: 8
leftBearing: 0
advance: 8
-
cp: 8734 # ∞ - infinity
sprite:
x: 112
y: 0
w: 8
h: 8
leftBearing: 0
advance: 8

View File

@ -1,100 +0,0 @@
//FONT DEFINITION, DO NOT CHANGE UNLESS YOU KNOW WHAT ARE YOU DOING.
//Use double slash to comment one row.
//First uncommented row must contain name of the font texture and its size.
//Width of one character is 16px, height 24px. Different resolutions are WIP.
default,256x144
A 0;0
B 0;0
C 0;0
D 0;0
E 0;0
F 0;0
G 0;0
H 0;0
I 4;5
J 1;0
K 1;0
L 0;0
M 0;0
N 0;0
O 0;0
P 0;2
Q 0;0
R 0;1
S 0;0
T 0;0
U 0;0
V 0;1
W 0;0
X 0;1
Y 1;0
Z 0;0
a 3;3
b 2;2
c 2;3
d 2;2
e 2;3
f 3;4
g 2;3
h 2;2
i 4;7
j 4;4
k 2;3
l 4;8
m 1;0
n 2;3
o 3;2
p 3;2
q 3;4
r 3;2
s 3;4
t 1;5
u 3;3
v 2;4
w 0;0
x 3;4
y 3;2
z 2;2
1 5;1
2 1;1
3 2;3
4 2;2
5 1;3
6 1;1
7 1;1
8 1;1
9 1;1
0 1;1
- 3;3
+ 2;3
/ 1;2
* 6;2
) 7;4
( 6;5
[ 6;5
] 6;5
\ 1;3
< 3;5
> 3;6
ˇ 3;2
´ 5;6
= 2;2
^ 3;2
. 5;7
! 5;8
? 1;2
: 5;7
@ 1;0
# 0;2
~ 1;3
& 2;0
, 6;5
; 6;4
% 0;0
| 5;8
° 4;7
" 4;5
} 0;9
{ 0;9
' 5;6
_ 2;2

View File

@ -1,2 +0,0 @@
Feel free to create and send me a better font, I'll be glad.
Everything you need to know is in the definitions.txt file.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,75 @@
#version 330 core
in vec2 uvCoordinates;
in vec2 paintUVCoordinates;
uniform sampler2DRect textureSampler;
uniform int paintType;
uniform vec4 paintColor;
uniform int paintGradientStopCount;
uniform vec4[16] paintGradientColors;
uniform float[16] paintGradientPositions;
uniform vec2[2] paintGradientEnds;
out vec4 out_Color;
vec4 solidColor(void)
{
return paintColor;
}
vec4 gammaMix(vec4 lCol, vec4 rCol, float ratio)
{
float gamma = 2.2;
float one_over_gamma = 1 / gamma;
vec4 ilCol = vec4(pow(lCol.r, gamma), pow(lCol.g, gamma), pow(lCol.b, gamma), lCol.a);
vec4 irCol = vec4(pow(rCol.r, gamma), pow(rCol.g, gamma), pow(rCol.b, gamma), rCol.a);
vec4 fCol = mix(ilCol, irCol, ratio);
return vec4(pow(fCol.r, one_over_gamma), pow(fCol.g, one_over_gamma), pow(fCol.b, one_over_gamma), fCol.a);
}
vec4 gradientColor(void)
{
float angle = atan(-paintGradientEnds[1].y + paintGradientEnds[0].y, paintGradientEnds[1].x - paintGradientEnds[0].x);
float rotatedStartX = paintGradientEnds[0].x * cos(angle) - paintGradientEnds[0].y * sin(angle);
float rotatedEndX = paintGradientEnds[1].x * cos(angle) - paintGradientEnds[1].y * sin(angle);
float d = rotatedEndX - rotatedStartX;
float pX = paintUVCoordinates.x * cos(angle) - paintUVCoordinates.y * sin(angle);
float mr = smoothstep(rotatedStartX + paintGradientPositions[0] * d, rotatedStartX + paintGradientPositions[1] * d, pX);
vec4 col = gammaMix(paintGradientColors[0], paintGradientColors[1], mr);
for (int i = 1; i < paintGradientStopCount - 1; i++)
{
mr = smoothstep(rotatedStartX + paintGradientPositions[i] * d, rotatedStartX + paintGradientPositions[i + 1] * d, pX);
col = gammaMix(col, paintGradientColors[i + 1], mr);
}
return col;
}
void main(void)
{
vec4 col;
switch (paintType)
{
case 0:
col = solidColor();
break;
case 1:
col = gradientColor();
break;
}
col.rgba *= texture(textureSampler, uvCoordinates);
out_Color = col;
}

View File

@ -0,0 +1,19 @@
#version 330 core
in vec2 position;
in vec2 uvCoords;
in vec2 paintUVCoords;
out vec2 uvCoordinates;
out vec2 paintUVCoordinates;
uniform mat4 projection;
uniform mat3 transformation;
void main(void)
{
uvCoordinates = uvCoords;
paintUVCoordinates = paintUVCoords;
vec3 transformed = vec3((transformation * vec3(position, 1.0)).xy, 0.0);
gl_Position = projection * vec4(transformed, 1.0);
}

View File

@ -1,7 +1,10 @@
package org.plutoengine.demo;
import org.plutoengine.graphics.PlutoGUIMod;
import org.plutoengine.graphics.gui.stbttf.STBTTFont;
import org.plutoengine.graphics.gui.font.bitmap.BitmapFont;
import org.plutoengine.graphics.gui.font.stbttf.STBTTFont;
import org.plutoengine.graphics.sprite.PartialTextureSprite;
import org.plutoengine.graphics.sprite.TemporalSprite;
import org.plutoengine.graphics.texture.MagFilter;
import org.plutoengine.graphics.texture.MinFilter;
import org.plutoengine.graphics.texture.WrapMode;
@ -21,23 +24,44 @@ public class BasicApplicationDemoMod implements IModEntryPoint
{
public static LiFontFamily<STBTTFont> font;
public static LiFontFamily<BitmapFont> srCloneFont;
public static RectangleTexture plutoLogo;
public static RectangleTexture srCloneBoxTex;
public static TemporalSprite srCloneBox;
@Override
public void onLoad(Mod mod)
{
font = new LiFontFamily<>();
font.add(TextStyleOptions.STYLE_REGULAR, STBTTFont.load(mod.getResource("plutofonts$plutostardust#ttf")));
srCloneFont = new LiFontFamily<>();
srCloneFont.add(TextStyleOptions.STYLE_REGULAR, BitmapFont.load(mod.getResource("plutofonts$srclone.info#yaml")));
plutoLogo = new RectangleTexture();
plutoLogo.load(mod.getResource("icons$icon128#png"), MagFilter.NEAREST, MinFilter.NEAREST, WrapMode.CLAMP_TO_EDGE, WrapMode.CLAMP_TO_EDGE);
srCloneBoxTex = new RectangleTexture();
srCloneBoxTex.load(mod.getResource("box#png"));
var frames = new PartialTextureSprite[16];
for (int i = 0; i < frames.length; i++)
frames[i] = new PartialTextureSprite(srCloneBoxTex, 64 * i, 0, 64, 64);
srCloneBox = new TemporalSprite(frames, 0.04f);
}
@Override
public void onUnload()
{
srCloneBoxTex.close();
plutoLogo.close();
srCloneFont.close();
font.close();
}
}

View File

@ -84,7 +84,7 @@ public class Main extends PlutoApplication
.setPaint(LiPaint.solidColor(Color.VERY_DARK_GRAY));
ImmediateFontRenderer.drawString(0, 102, "Welcome to PlutoEngine v. %s!".formatted(Pluto.VERSION), BasicApplicationDemoMod.font, welcomeStyle);
float gPos = (System.currentTimeMillis() % 7200) / 20.0f;
float gPos = Framerate.getAnimationTimer() * 50.0f;
var stops = new LiGradientPaint.Stop[16];
for (int i = 0; i < stops.length; i++)
stops[i] = new LiGradientPaint.Stop(i / (float) stops.length, Color.from(new HSB(gPos + i * 10, 1.0f, 1.0f).toRGBA()));
@ -92,6 +92,26 @@ public class Main extends PlutoApplication
welcomeStyle.setPaint(LiPaint.horizontaLinearGradient(stops));
ImmediateFontRenderer.drawString(0, 100, "Welcome to PlutoEngine v. %s!".formatted(Pluto.VERSION), BasicApplicationDemoMod.font, welcomeStyle);
var srCloneStyle = new TextStyleOptions(32);
srCloneStyle.setHorizontalAlign(TextStyleOptions.TextAlign.CENTER);
srCloneStyle.setVerticalAlign(TextStyleOptions.TextAlign.START);
srCloneStyle.setPaint(LiPaint.solidColor(Color.WHITE));
ImmediateFontRenderer.drawString(this.display.getWidth() / 2.0f, this.display.getHeight() - 5,
"\uD83C\uDF20\uD83D\uDFE2☀\uD83D\uDD3A\uD836\uDD53\uD83D\uDCA0", BasicApplicationDemoMod.srCloneFont, srCloneStyle);
var boxRender = RectangleRenderer2D.draw();
var cnt = 20;
for (int i = 0; i < cnt; i++)
{
var hsb = new HSB(Framerate.getAnimationTimer() * 20.0f + i * 10.0f, 1.0f, 1.0f);
boxRender.at(this.display.getWidth() / 2.0f - cnt / 2.0f * 64.0f + i * 64.0f, this.display.getHeight() - 128, 64, 64)
.sprite(BasicApplicationDemoMod.srCloneBox.getFrameAt(Framerate.getAnimationTimer() + i * 0.05f))
.recolor(hsb.toRGBA())
.flush();
}
if (PlutoLocal.components().getComponent(Keyboard.class).pressed(GLFW.GLFW_KEY_R))
{
var shader = PlutoGUIMod.fontShader;

2
libra

@ -1 +1 @@
Subproject commit dc5bbae9d11aaf483c8e95c7da305f926065ce20
Subproject commit a8d8f1184f4c1663f1a600ad65cd16b398ae671a

View File

@ -13,6 +13,10 @@ file("engine-core").listFiles().forEach {
if (!it.isDirectory)
return@forEach
// Skip hidden files
if (it.name.startsWith("."))
return@forEach
include("plutoengine:${it.name}")
}
@ -20,6 +24,10 @@ file("engine-ext").listFiles().forEach {
if (!it.isDirectory)
return@forEach
// Skip hidden files
if (it.name.startsWith("."))
return@forEach
include("plutoengine-ext:${it.name}")
}
@ -27,5 +35,9 @@ file("engine-demo").listFiles().forEach {
if (!it.isDirectory)
return@forEach
// Skip hidden files
if (it.name.startsWith("."))
return@forEach
include("plutoengine-demos:${it.name}")
}