Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rotated glyphs #559

Merged
merged 13 commits into from
Jul 16, 2023
Merged
22 changes: 0 additions & 22 deletions assets/shaders/include/math.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,4 @@ f32v3 math_normal_decode(f32v2 f) {
return normalize(n);
}

/**
* Apply a tangent normal (from a normalmap for example) to a worldNormal.
* The tangent and bitangent vectors are derived from change in position and texcoords.
*/
f32v3 math_perturb_normal(
const f32v3 tangentNormal,
const f32v3 worldNormal,
const f32v3 worldPos,
const f32v2 texcoord) {
const f32v3 deltaPosX = dFdx(worldPos);
const f32v3 deltaPosY = dFdy(worldPos);
const f32v2 deltaTexX = dFdx(texcoord);
const f32v2 deltaTexY = dFdy(texcoord);

const f32v3 refNorm = normalize(worldNormal);
const f32v3 refTan = normalize(deltaPosX * deltaTexY.t - deltaPosY * deltaTexX.t);
const f32v3 refBitan = normalize(cross(refNorm, refTan));
const f32m3 rot = f32m3(refTan, refBitan, refNorm);

return normalize(rot * tangentNormal);
}

#endif // INCLUDE_MATH
28 changes: 28 additions & 0 deletions assets/shaders/include/math_frag.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef INCLUDE_MATH_FRAG
#define INCLUDE_MATH_FRAG

#include "math.glsl"

/**
* Apply a tangent normal (from a normalmap for example) to a worldNormal.
* The tangent and bitangent vectors are derived from change in position and texcoords.
*/
f32v3 math_perturb_normal(
const f32v3 tangentNormal,
const f32v3 worldNormal,
const f32v3 worldPos,
const f32v2 texcoord) {
const f32v3 deltaPosX = dFdx(worldPos);
const f32v3 deltaPosY = dFdy(worldPos);
const f32v2 deltaTexX = dFdx(texcoord);
const f32v2 deltaTexY = dFdy(texcoord);

const f32v3 refNorm = normalize(worldNormal);
const f32v3 refTan = normalize(deltaPosX * deltaTexY.t - deltaPosY * deltaTexX.t);
const f32v3 refBitan = normalize(cross(refNorm, refTan));
const f32m3 rot = f32m3(refTan, refBitan, refNorm);

return normalize(rot * tangentNormal);
}

#endif // INCLUDE_MATH_FRAG
2 changes: 1 addition & 1 deletion assets/shaders/scene/terrain.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "binding.glsl"
#include "geometry.glsl"
#include "global.glsl"
#include "math.glsl"
#include "math_frag.glsl"
#include "tags.glsl"

bind_spec(0) const f32 s_heightNormalIntensity = 1.0;
Expand Down
25 changes: 16 additions & 9 deletions assets/shaders/ui/canvas.vert
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
#include "binding.glsl"
#include "color.glsl"
#include "instance.glsl"
#include "math.glsl"
#include "ui.glsl"

const u32 c_verticesPerGlyph = 6;
const f32v2 c_unitPositions[c_verticesPerGlyph] = {
f32v2(0, 1),
f32v2(1, 1),
f32v2(0, 0),
f32v2(1, 1),
f32v2(1, 0),
f32v2(0, 0),
f32v2(-0.5, +0.5),
f32v2(+0.5, +0.5),
f32v2(-0.5, -0.5),
f32v2(+0.5, +0.5),
f32v2(+0.5, -0.5),
f32v2(-0.5, -0.5),
};
const f32v2 c_unitTexCoords[c_verticesPerGlyph] = {
f32v2(0, 1),
Expand All @@ -36,7 +37,7 @@ struct MetaData {
struct GlyphData {
f32v4 rect; // x + y = position, z + w = size
u32v4 data; // x = color,
// y = atlasIndex,
// y = 16b atlasIndex, 16b angleFrac,
// z = 16b borderFrac 16b cornerFrac,
// w = 8b clipId, 8b outlineWidth, 8b weight
};
Expand Down Expand Up @@ -76,17 +77,23 @@ void main() {
const f32v2 glyphPos = glyphData.rect.xy;
const f32v2 glyphSize = glyphData.rect.zw;
const f32v4 glyphColor = color_from_u32(glyphData.data.x);
const u32 atlasIndex = glyphData.data.y;
const u32 atlasIndex = glyphData.data.y & 0xFFFF;
const f32 angleRad = (glyphData.data.y >> 16) / f32(0xFFFF) * c_pi * 2;
const f32 borderFrac = (glyphData.data.z & 0xFFFF) / f32(0xFFFF);
const f32 cornerFrac = (glyphData.data.z >> 16) / f32(0xFFFF);
const u32 clipId = glyphData.data.w & 0xFF;
const u32 outlineWidth = (glyphData.data.w >> 8) & 0xFF;
const u32 weight = (glyphData.data.w >> 16) & 0xFF;

const f32m2 rotMat = math_rotate_mat_f32m2(angleRad);

/**
* Compute the ui positions of the vertices.
* NOTE: Expected origin of the glyph is in the lower left hand corner but rotation should happen
* around the center of the glyph.
*/
const f32v2 uiPos = glyphPos + c_unitPositions[in_vertexIndex] * glyphSize;
const f32v2 uiPosRel = rotMat * (c_unitPositions[in_vertexIndex] * glyphSize) + glyphSize * 0.5;
const f32v2 uiPos = glyphPos + uiPosRel;

/**
* Compute the x and y position in the texture atlas based on the glyphIndex.
Expand Down
2 changes: 1 addition & 1 deletion assets/shaders/vfx/decal.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "binding.glsl"
#include "geometry.glsl"
#include "global.glsl"
#include "math.glsl"
#include "math_frag.glsl"
#include "quat.glsl"
#include "tags.glsl"

Expand Down
2 changes: 1 addition & 1 deletion libs/asset/include/asset_ftx.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
typedef struct {
Unicode cp;
u8 variation;
u32 glyphIndex; // sentinel_u32 when character has no glyph (for example a space).
u16 glyphIndex; // sentinel_u16 when character has no glyph (for example a space).
f32 size;
f32 offsetX, offsetY;
f32 advance;
Expand Down
8 changes: 4 additions & 4 deletions libs/asset/src/loader_ftx.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ static void ftx_generate_font(
const FtxDefResolvedFont font,
const FtxGenFlags flags,
u32 maxGlyphs,
u32* nextGlyphIndex,
u16* nextGlyphIndex,
DynArray* outChars, // AssetFtxChar[]
AssetTexturePixelB1* outPixels,
FtxError* err) {
Expand All @@ -246,15 +246,15 @@ static void ftx_generate_font(
*dynarray_push_t(outChars, AssetFtxChar) = (AssetFtxChar){
.cp = inputChars[i].cp,
.variation = font.variation,
.glyphIndex = inputChars[i].glyph->segmentCount ? *nextGlyphIndex : sentinel_u32,
.glyphIndex = inputChars[i].glyph->segmentCount ? *nextGlyphIndex : sentinel_u16,
.size = inputChars[i].glyph->size,
.offsetX = inputChars[i].glyph->offsetX,
.offsetY = inputChars[i].glyph->offsetY + font.yOffset,
.advance = inputChars[i].glyph->advance + font.spacing,
.border = def->border / (f32)def->glyphSize,
};
if (inputChars[i].glyph->segmentCount) {
if (UNLIKELY(*nextGlyphIndex >= maxGlyphs)) {
if (UNLIKELY(*nextGlyphIndex >= maxGlyphs || *nextGlyphIndex == u16_max)) {
*err = FtxError_TooManyGlyphs;
return;
}
Expand All @@ -279,7 +279,7 @@ static void ftx_generate(

const u32 glyphsPerDim = def->size / def->glyphSize;
const u32 maxGlyphs = glyphsPerDim * glyphsPerDim;
u32 nextGlyphIndex = 0;
u16 nextGlyphIndex = 0;
if (UNLIKELY(!maxGlyphs)) {
*err = FtxError_TooManyGlyphs;
goto Error;
Expand Down
6 changes: 3 additions & 3 deletions libs/rend/src/painter.c
Original file line number Diff line number Diff line change
Expand Up @@ -754,11 +754,11 @@ static bool rend_canvas_paint(

// Fog pass.
RvkPass* fogPass = rvk_canvas_pass(painter->canvas, RendPass_Fog);
const RvkSize fogSize = set->flags & RendFlags_Fog
const RvkSize fogSize = set->flags & (RendFlags_Fog | RendFlags_DebugFog)
? (RvkSize){set->fogResolution, set->fogResolution}
: (RvkSize){1, 1};
RvkImage* fogBuffer = rvk_canvas_attach_acquire_color(painter->canvas, fogPass, 0, fogSize);
if (set->flags & RendFlags_Fog) {
if (set->flags & (RendFlags_Fog | RendFlags_DebugFog)) {
const GeoMatrix* fogTrans = rend_fog_trans(fog);
const GeoMatrix* fogProj = rend_fog_proj(fog);
const SceneTagFilter fogFilter = {0};
Expand All @@ -774,7 +774,7 @@ static bool rend_canvas_paint(

// Fog-blur pass.
RvkPass* fogBlurPass = rvk_canvas_pass(painter->canvas, RendPass_FogBlur);
if (set->flags & RendFlags_Fog && set->fogBlurSteps) {
if (set->flags & (RendFlags_Fog | RendFlags_DebugFog) && set->fogBlurSteps) {
RendPaintContext ctx = painter_context(painter, set, setGlobal, time, fogBlurPass, mainView);

struct {
Expand Down
1 change: 1 addition & 0 deletions libs/ui/include/ui_canvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,4 @@ String ui_canvas_text_editor_result(UiCanvasComp*);
* control the size of the 9-slice corner.
*/
UiId ui_canvas_draw_glyph(UiCanvasComp*, Unicode, u16 maxCorner, UiFlags);
UiId ui_canvas_draw_glyph_rotated(UiCanvasComp*, Unicode, u16 maxCorner, f32 angleRad, UiFlags);
35 changes: 33 additions & 2 deletions libs/ui/include/ui_widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ typedef struct {
String tooltip;
} UiNumboxOpts;

typedef struct {
UiBase base;
f32 radius;
u16 maxCorner;
} UiCircleOpts;

typedef struct {
UiBase base;
f32 width;
} UiLineOpts;

// clang-format off

/**
Expand Down Expand Up @@ -186,7 +197,7 @@ typedef struct {

/**
* Draw editable text box.
* NOTE: Its important that the widget has a stable identifier in the canvas.
* NOTE: Its important that the widget has a stable identifier in the canvas.
*/
#define ui_textbox(_CANVAS_, _DYN_TEXT_, ...) ui_textbox_with_opts((_CANVAS_), (_DYN_TEXT_), \
&((UiTextboxOpts){ \
Expand All @@ -198,7 +209,7 @@ typedef struct {

/**
* Draw editable number box.
* NOTE: Its important that the widget has a stable identifier in the canvas.
* NOTE: Its important that the widget has a stable identifier in the canvas.
*/
#define ui_numbox(_CANVAS_, _VALUE_, ...) ui_numbox_with_opts((_CANVAS_), (_VALUE_), \
&((UiNumboxOpts){ \
Expand All @@ -207,6 +218,24 @@ typedef struct {
.frameColor = ui_color(32, 32, 32, 192), \
__VA_ARGS__}))

/**
* Draw a circle at the given point.
*/
#define ui_circle(_CANVAS_, _POS_, ...) ui_circle_with_opts((_CANVAS_), (_POS_), \
&((UiCircleOpts){ \
.base = UiBase_Current, \
.radius = 10, \
__VA_ARGS__}))

/**
* Draw a line between two given points.
*/
#define ui_line(_CANVAS_, _FROM_, _TO_, ...) ui_line_with_opts((_CANVAS_), (_FROM_), (_TO_), \
&((UiLineOpts){ \
.base = UiBase_Current, \
.width = 10, \
__VA_ARGS__}))

// clang-format on

void ui_label_with_opts(UiCanvasComp*, String text, const UiLabelOpts*);
Expand All @@ -221,3 +250,5 @@ bool ui_tooltip_with_opts(UiCanvasComp*, UiId, String text, const UiTooltipOpts*
bool ui_section_with_opts(UiCanvasComp*, const UiSectionOpts*);
bool ui_textbox_with_opts(UiCanvasComp*, DynString*, const UiTextboxOpts*);
bool ui_numbox_with_opts(UiCanvasComp*, f64*, const UiNumboxOpts*);
void ui_circle_with_opts(UiCanvasComp*, UiVector pos, const UiCircleOpts*);
void ui_line_with_opts(UiCanvasComp*, UiVector from, UiVector to, const UiLineOpts*);
41 changes: 31 additions & 10 deletions libs/ui/src/builder.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,18 @@ static void ui_build_set_size_to(UiBuildState* state, const UiVector val, const
}
}

static f32 ui_build_angle_rad_to_frac(const f32 angle) {
static const f32 g_radToFrac = 1.0f / (math_pi_f32 * 2.0f);
return math_mod_f32(angle * g_radToFrac, 1.0f);
}

static void ui_build_glyph(
UiBuildState* state,
const Unicode cp,
const UiRect rect,
const UiBuildStyle style,
const u16 maxCorner,
const f32 angleRad,
const u8 clipId) {
const AssetFtxChar* ch = asset_ftx_lookup(state->font, cp, style.variation);
if (sentinel_check(ch->glyphIndex)) {
Expand All @@ -173,12 +179,14 @@ static void ui_build_glyph(
if (UNLIKELY(outputRect.size.width < f32_epsilon || outputRect.size.height < f32_epsilon)) {
return; // Glyph too small.
}
const bool rotated = math_abs(angleRad) > f32_epsilon;
state->ctx->outputGlyph(
state->ctx->userCtx,
(UiGlyphData){
.rect = outputRect,
.color = style.color,
.atlasIndex = ch->glyphIndex,
.angleFrac = rotated ? (u16)(ui_build_angle_rad_to_frac(angleRad) * u16_max) : 0,
.borderFrac = (u16)(border / outputRect.size.width * u16_max),
.cornerFrac = (u16)((corner + border) / outputRect.size.width * u16_max),
.clipId = clipId,
Expand Down Expand Up @@ -222,8 +230,9 @@ static void ui_build_text_background(void* userCtx, const UiTextBackgroundInfo*
.weight = UiWeight_Normal,
.layer = info->layer,
};
const u8 maxCorner = 4; // Roundedness of the backgrounds.
ui_build_glyph(state, UiShape_Circle, info->rect, style, maxCorner, clipId);
const u8 maxCorner = 4; // Roundedness of the backgrounds.
const f32 angleRad = 0.0f;
ui_build_glyph(state, UiShape_Circle, info->rect, style, maxCorner, angleRad, clipId);
}

static bool ui_rect_contains(const UiRect rect, const UiVector point) {
Expand Down Expand Up @@ -324,28 +333,33 @@ static void ui_build_draw_glyph(UiBuildState* state, const UiDrawGlyph* cmd) {
const UiBuildStyle style = *ui_build_style_current(state);
const UiBuildContainer container = *ui_build_container_active(state);

if (ui_build_cull(container, rect, style)) {
const bool rotated = math_abs(cmd->angleRad) > f32_epsilon;
// TODO: Support culling for rotated glyphs.
if (!rotated && ui_build_cull(container, rect, style)) {
return;
}
const bool debugInspector = state->ctx->settings->flags & UiSettingFlags_DebugInspector;
const bool hoverable = cmd->flags & UiFlags_Interactable || debugInspector;

if (hoverable && ui_build_is_hovered(state, container, rect, style.layer)) {
diag_assert(!rotated); // Hovering is not supported for rotated glyphs.
state->hover = (UiBuildHover){
.id = cmd->id,
.layer = style.layer,
.flags = cmd->flags,
};
}

ui_build_glyph(state, cmd->cp, rect, style, cmd->maxCorner, container.clipId);
ui_build_glyph(state, cmd->cp, rect, style, cmd->maxCorner, cmd->angleRad, container.clipId);

if (cmd->flags & UiFlags_TrackRect) {
diag_assert(!rotated); // Tracking is not supported for rotated glyphs.
state->ctx->outputRect(state->ctx->userCtx, cmd->id, rect);
}
}

static void ui_build_debug_inspector(UiBuildState* state, const UiId id, const UiFlags flags) {
static void ui_build_debug_inspector(
UiBuildState* state, const UiId id, const UiFlags flags, const f32 angleRad) {
const UiRect rect = *ui_build_rect_current(state);
const UiBuildStyle style = *ui_build_style_current(state);
const UiBuildContainer container = *ui_build_container_active(state);
Expand All @@ -360,9 +374,9 @@ static void ui_build_debug_inspector(UiBuildState* state, const UiId id, const U
.weight = UiWeight_Bold,
.layer = UiLayer_Overlay};

ui_build_glyph(state, UiShape_Square, container.logicRect, styleContainerLogic, 5, 0);
ui_build_glyph(state, UiShape_Square, container.clipRect, styleContainerClip, 5, 0);
ui_build_glyph(state, UiShape_Square, rect, styleShape, 5, 0);
ui_build_glyph(state, UiShape_Square, container.logicRect, styleContainerLogic, 5, 0.0f, 0);
ui_build_glyph(state, UiShape_Square, container.clipRect, styleContainerClip, 5, 0.0f, 0);
ui_build_glyph(state, UiShape_Square, rect, styleShape, 5, 0.0f, 0);

DynString str = dynstring_create(g_alloc_scratch, usize_kibibyte);
fmt_write(&str, "Id\t\t{}\n", fmt_int(id));
Expand All @@ -382,6 +396,11 @@ static void ui_build_debug_inspector(UiBuildState* state, const UiId id, const U
fmt_write(&str, "Variation\t{}\n", fmt_int(style.variation));
fmt_write(&str, "ClipId\t\t{}\n", fmt_int(container.clipId));
fmt_write(&str, "Interact\t{}\n", fmt_int((flags & UiFlags_Interactable) != 0));
fmt_write(
&str,
"Angle\t\t{} rad ({} deg)\n",
fmt_float(angleRad, .minDecDigits = 2, .maxDecDigits = 2),
fmt_float(angleRad * math_rad_to_deg, .maxDecDigits = 0));

const f32 textSize = 500;
const u16 fontSize = 20;
Expand Down Expand Up @@ -504,13 +523,15 @@ INLINE_HINT static void ui_build_cmd(UiBuildState* state, const UiCmd* cmd) {
case UiCmd_DrawText:
ui_build_draw_text(state, &cmd->drawText);
if (UNLIKELY(cmd->drawText.id == state->ctx->debugElem)) {
ui_build_debug_inspector(state, cmd->drawText.id, cmd->drawText.flags);
const f32 angleRad = 0.0f;
ui_build_debug_inspector(state, cmd->drawText.id, cmd->drawText.flags, angleRad);
}
break;
case UiCmd_DrawGlyph:
ui_build_draw_glyph(state, &cmd->drawGlyph);
if (UNLIKELY(cmd->drawGlyph.id == state->ctx->debugElem)) {
ui_build_debug_inspector(state, cmd->drawGlyph.id, cmd->drawGlyph.flags);
const f32 angleRad = cmd->drawGlyph.angleRad;
ui_build_debug_inspector(state, cmd->drawGlyph.id, cmd->drawGlyph.flags, angleRad);
}
break;
}
Expand Down
Loading