From 167be50b5f31fefc95dfd059631d795c4e63f2c5 Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Tue, 3 Jun 2025 20:29:53 +0200 Subject: [PATCH] Created minimal C library, true SDF sampling (WIP) --- CMakeLists.txt | 23 +++- c/CompiledShape.c | 79 +++++++++++++ c/CompiledShape.h | 61 ++++++++++ c/Vector2.h | 59 ++++++++++ c/equation-solver.c | 76 ++++++++++++ c/equation-solver.h | 16 +++ c/msdfgen-c.h | 85 ++++++++++++++ c/msdfgen.c | 273 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 669 insertions(+), 3 deletions(-) create mode 100644 c/CompiledShape.c create mode 100644 c/CompiledShape.h create mode 100644 c/Vector2.h create mode 100644 c/equation-solver.c create mode 100644 c/equation-solver.h create mode 100644 c/msdfgen-c.h create mode 100644 c/msdfgen.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 29d1537..a30a77f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ if(MSDFGEN_USE_VCPKG) endif() # Version is specified in vcpkg.json -project(msdfgen VERSION ${MSDFGEN_VERSION} LANGUAGES CXX) +project(msdfgen VERSION ${MSDFGEN_VERSION} LANGUAGES C CXX) if(MSDFGEN_DYNAMIC_RUNTIME) set(MSDFGEN_MSVC_RUNTIME "MultiThreaded$<$:Debug>DLL") @@ -87,11 +87,30 @@ if(MAX_WARNING_LEVEL) endif() endif() +file(GLOB_RECURSE MSDFGEN_C_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "c/*.h") +file(GLOB_RECURSE MSDFGEN_C_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "c/*.c") file(GLOB_RECURSE MSDFGEN_CORE_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "core/*.h" "core/*.hpp") file(GLOB_RECURSE MSDFGEN_CORE_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "core/*.cpp") file(GLOB_RECURSE MSDFGEN_EXT_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "ext/*.h" "ext/*.hpp") file(GLOB_RECURSE MSDFGEN_EXT_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "ext/*.cpp" "lib/*.cpp") +# C library +add_library(msdfgen-c ${MSDFGEN_C_HEADERS} ${MSDFGEN_C_SOURCES}) +add_library(msdfgen::msdfgen-c ALIAS msdfgen-c) +set_target_properties(msdfgen-c PROPERTIES PUBLIC_HEADER "${MSDFGEN_C_HEADERS}") +set_property(TARGET msdfgen-c PROPERTY MSVC_RUNTIME_LIBRARY "${MSDFGEN_MSVC_RUNTIME}") +target_compile_definitions(msdfgen-c PUBLIC + MSDFGEN_VERSION=${MSDFGEN_VERSION} + MSDFGEN_VERSION_MAJOR=${MSDFGEN_VERSION_MAJOR} + MSDFGEN_VERSION_MINOR=${MSDFGEN_VERSION_MINOR} + MSDFGEN_VERSION_REVISION=${MSDFGEN_VERSION_REVISION} + MSDFGEN_COPYRIGHT_YEAR=${MSDFGEN_COPYRIGHT_YEAR} +) +target_include_directories(msdfgen-c INTERFACE + $ + $ +) + # Core library add_library(msdfgen-core "${CMAKE_CURRENT_SOURCE_DIR}/msdfgen.h" ${MSDFGEN_CORE_HEADERS} ${MSDFGEN_CORE_SOURCES}) add_library(msdfgen::msdfgen-core ALIAS msdfgen-core) @@ -166,8 +185,6 @@ if(NOT MSDFGEN_CORE_ONLY) PUBLIC $ $ - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/include ) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT msdfgen-ext) diff --git a/c/CompiledShape.c b/c/CompiledShape.c new file mode 100644 index 0000000..4cc7045 --- /dev/null +++ b/c/CompiledShape.c @@ -0,0 +1,79 @@ + +#include "CompiledShape.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static MSDFGEN_Vector2 computeCornerVec(MSDFGEN_Vector2 normalizedDirection, MSDFGEN_Vector2 adjacentEdgeDirection1) { + return MSDFGEN_Vector2_normalizeOrZero(MSDFGEN_Vector2_sum(normalizedDirection, MSDFGEN_Vector2_normalizeOrZero(adjacentEdgeDirection1))); +} + +void MSDFGEN_compileLinearEdge(MSDFGEN_CompiledLinearEdge *dstEdgePtr, int colorMask, MSDFGEN_Vector2 point0, MSDFGEN_Vector2 point1, MSDFGEN_Vector2 prevEdgeDirection1, MSDFGEN_Vector2 nextEdgeDirection0) { + MSDFGEN_Vector2 p0p1 = MSDFGEN_Vector2_difference(point1, point0); + MSDFGEN_real p0p1SqLen = MSDFGEN_Vector2_squaredLength(p0p1); + dstEdgePtr->colorMask = colorMask; + dstEdgePtr->endpoint0 = point0; + dstEdgePtr->endpoint1 = point1; + dstEdgePtr->direction = MSDFGEN_Vector2_normalizeOrZero(p0p1); + dstEdgePtr->cornerVec0 = computeCornerVec(dstEdgePtr->direction, prevEdgeDirection1); + dstEdgePtr->cornerVec1 = computeCornerVec(dstEdgePtr->direction, nextEdgeDirection0); + dstEdgePtr->invDerivative.x = 0; + dstEdgePtr->invDerivative.y = 0; + if (p0p1SqLen != (MSDFGEN_real) 0) { + dstEdgePtr->invDerivative.x = -p0p1.x/p0p1SqLen; + dstEdgePtr->invDerivative.y = -p0p1.y/p0p1SqLen; + } +} + +void MSDFGEN_compileQuadraticEdge(MSDFGEN_CompiledQuadraticEdge *dstEdgePtr, int colorMask, MSDFGEN_Vector2 point0, MSDFGEN_Vector2 point1, MSDFGEN_Vector2 point2, MSDFGEN_Vector2 prevEdgeDirection1, MSDFGEN_Vector2 nextEdgeDirection0) { + MSDFGEN_Vector2 p1p2 = MSDFGEN_Vector2_difference(point2, point1); + dstEdgePtr->colorMask = colorMask; + dstEdgePtr->endpoint0 = point0; + dstEdgePtr->endpoint1 = point2; + dstEdgePtr->derivative0 = MSDFGEN_Vector2_difference(point1, point0); + dstEdgePtr->derivative1 = MSDFGEN_Vector2_difference(p1p2, dstEdgePtr->derivative0); + dstEdgePtr->direction0 = dstEdgePtr->derivative0; + dstEdgePtr->direction1 = p1p2; + if (dstEdgePtr->direction0.x == (MSDFGEN_real) 0 && dstEdgePtr->direction0.y == (MSDFGEN_real) 0) + dstEdgePtr->direction0 = MSDFGEN_Vector2_difference(point2, point0); + if (dstEdgePtr->direction1.x == (MSDFGEN_real) 0 && dstEdgePtr->direction1.y == (MSDFGEN_real) 0) + dstEdgePtr->direction1 = MSDFGEN_Vector2_difference(point2, point0); + dstEdgePtr->direction0 = MSDFGEN_Vector2_normalizeOrZero(dstEdgePtr->direction0); + dstEdgePtr->direction1 = MSDFGEN_Vector2_normalizeOrZero(dstEdgePtr->direction1); + dstEdgePtr->cornerVec0 = computeCornerVec(dstEdgePtr->direction0, prevEdgeDirection1); + dstEdgePtr->cornerVec1 = computeCornerVec(dstEdgePtr->direction1, nextEdgeDirection0); +} + +void MSDFGEN_compileCubicEdge(MSDFGEN_CompiledCubicEdge *dstEdgePtr, int colorMask, MSDFGEN_Vector2 point0, MSDFGEN_Vector2 point1, MSDFGEN_Vector2 point2, MSDFGEN_Vector2 point3, MSDFGEN_Vector2 prevEdgeDirection1, MSDFGEN_Vector2 nextEdgeDirection0) { + MSDFGEN_Vector2 p1p2 = MSDFGEN_Vector2_difference(point2, point1); + MSDFGEN_Vector2 p2p3 = MSDFGEN_Vector2_difference(point3, point2); + dstEdgePtr->colorMask = colorMask; + dstEdgePtr->endpoint0 = point0; + dstEdgePtr->endpoint1 = point3; + dstEdgePtr->derivative0 = MSDFGEN_Vector2_difference(point1, point0); + dstEdgePtr->derivative1 = MSDFGEN_Vector2_difference(p1p2, dstEdgePtr->derivative0); + dstEdgePtr->derivative2 = MSDFGEN_Vector2_difference(MSDFGEN_Vector2_difference(p2p3, p1p2), dstEdgePtr->derivative1); + dstEdgePtr->direction0 = dstEdgePtr->derivative0; + if (dstEdgePtr->direction0.x == (MSDFGEN_real) 0 && dstEdgePtr->direction0.y == (MSDFGEN_real) 0) { + dstEdgePtr->direction0 = MSDFGEN_Vector2_difference(point2, point0); + if (dstEdgePtr->direction0.x == (MSDFGEN_real) 0 && dstEdgePtr->direction0.y == (MSDFGEN_real) 0) + dstEdgePtr->direction0 = MSDFGEN_Vector2_difference(point3, point0); + } + dstEdgePtr->direction1 = p2p3; + if (dstEdgePtr->direction1.x == (MSDFGEN_real) 0 && dstEdgePtr->direction1.y == (MSDFGEN_real) 0) { + dstEdgePtr->direction1 = MSDFGEN_Vector2_difference(point3, point1); + if (dstEdgePtr->direction1.x == (MSDFGEN_real) 0 && dstEdgePtr->direction1.y == (MSDFGEN_real) 0) + dstEdgePtr->direction1 = MSDFGEN_Vector2_difference(point3, point0); + } + dstEdgePtr->direction0 = MSDFGEN_Vector2_normalizeOrZero(dstEdgePtr->direction0); + dstEdgePtr->direction1 = MSDFGEN_Vector2_normalizeOrZero(dstEdgePtr->direction1); + dstEdgePtr->cornerVec0 = computeCornerVec(dstEdgePtr->direction0, prevEdgeDirection1); + dstEdgePtr->cornerVec1 = computeCornerVec(dstEdgePtr->direction1, nextEdgeDirection0); +} + +#ifdef __cplusplus +} +#endif diff --git a/c/CompiledShape.h b/c/CompiledShape.h new file mode 100644 index 0000000..1560003 --- /dev/null +++ b/c/CompiledShape.h @@ -0,0 +1,61 @@ + +#pragma once + +#include +#include "Vector2.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*typedef struct { + void *userPtr; + void *(*reallocPtr)(void *userPtr, void *memoryPtr, size_t memorySize); + void *(*freePtr)(void *userPtr, void *memoryPtr); +} MSDFGEN_Allocator;*/ + +typedef struct { + int colorMask; + MSDFGEN_Vector2 endpoint0, endpoint1; + MSDFGEN_Vector2 cornerVec0, cornerVec1; + MSDFGEN_Vector2 direction; + MSDFGEN_Vector2 invDerivative; +} MSDFGEN_CompiledLinearEdge; + +typedef struct { + int colorMask; + MSDFGEN_Vector2 endpoint0, endpoint1; + MSDFGEN_Vector2 cornerVec0, cornerVec1; + MSDFGEN_Vector2 direction0, direction1; + MSDFGEN_Vector2 derivative0, derivative1; +} MSDFGEN_CompiledQuadraticEdge; + +typedef struct { + int colorMask; + MSDFGEN_Vector2 endpoint0, endpoint1; + MSDFGEN_Vector2 cornerVec0, cornerVec1; + MSDFGEN_Vector2 direction0, direction1; + MSDFGEN_Vector2 derivative0, derivative1, derivative2; +} MSDFGEN_CompiledCubicEdge; + +typedef struct { + MSDFGEN_CompiledLinearEdge *linearEdges; + MSDFGEN_CompiledQuadraticEdge *quadraticEdges; + MSDFGEN_CompiledCubicEdge *cubicEdges; + size_t nLinearEdges, nQuadraticEdges, nCubicEdges; +} MSDFGEN_CompiledShape; + +/*void MSDFGEN_Shape_initialize(MSDFGEN_Shape *shapePtr); +void MSDFGEN_Shape_clear(MSDFGEN_Shape *shapePtr, MSDFGEN_Allocator allocator);*/ + +void MSDFGEN_compileLinearEdge(MSDFGEN_CompiledLinearEdge *dstEdgePtr, int colorMask, MSDFGEN_Vector2 point0, MSDFGEN_Vector2 point1, MSDFGEN_Vector2 prevEdgeDirection1, MSDFGEN_Vector2 nextEdgeDirection0); +void MSDFGEN_compileQuadraticEdge(MSDFGEN_CompiledQuadraticEdge *dstEdgePtr, int colorMask, MSDFGEN_Vector2 point0, MSDFGEN_Vector2 point1, MSDFGEN_Vector2 point2, MSDFGEN_Vector2 prevEdgeDirection1, MSDFGEN_Vector2 nextEdgeDirection0); +void MSDFGEN_compileCubicEdge(MSDFGEN_CompiledCubicEdge *dstEdgePtr, int colorMask, MSDFGEN_Vector2 point0, MSDFGEN_Vector2 point1, MSDFGEN_Vector2 point2, MSDFGEN_Vector2 point3, MSDFGEN_Vector2 prevEdgeDirection1, MSDFGEN_Vector2 nextEdgeDirection0); + +/*void MSDFGEN_Shape_addLinearEdge(MSDFGEN_Shape *shapePtr, const MSDFGEN_Shape::LinearEdge *edgePtr, MSDFGEN_Allocator allocator); +void MSDFGEN_Shape_addQuadraticEdge(MSDFGEN_Shape *shapePtr, const MSDFGEN_Shape::QuadraticEdge *edgePtr, MSDFGEN_Allocator allocator); +void MSDFGEN_Shape_addCubicEdge(MSDFGEN_Shape *shapePtr, const MSDFGEN_Shape::CubicEdge *edgePtr, MSDFGEN_Allocator allocator);*/ + +#ifdef __cplusplus +} +#endif diff --git a/c/Vector2.h b/c/Vector2.h new file mode 100644 index 0000000..1a85b98 --- /dev/null +++ b/c/Vector2.h @@ -0,0 +1,59 @@ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef double MSDFGEN_real; + +typedef struct { + MSDFGEN_real x, y; +} MSDFGEN_Vector2; + +inline MSDFGEN_real MSDFGEN_Vector2_squaredLength(MSDFGEN_Vector2 v) { + return v.x*v.x+v.y*v.y; +} + +inline MSDFGEN_real MSDFGEN_Vector2_dot(MSDFGEN_Vector2 a, MSDFGEN_Vector2 b) { + return a.x*b.x+a.y*b.y; +} + +inline MSDFGEN_real MSDFGEN_Vector2_cross(MSDFGEN_Vector2 a, MSDFGEN_Vector2 b) { + return a.x*b.y-a.y*b.x; +} + +inline MSDFGEN_Vector2 MSDFGEN_Vector2_scale(MSDFGEN_real s, MSDFGEN_Vector2 v) { + v.x *= s; + v.y *= s; + return v; +} + +inline MSDFGEN_Vector2 MSDFGEN_Vector2_sum(MSDFGEN_Vector2 a, MSDFGEN_Vector2 b) { + MSDFGEN_Vector2 result; + result.x = a.x+b.x; + result.y = a.y+b.y; + return result; +} + +inline MSDFGEN_Vector2 MSDFGEN_Vector2_difference(MSDFGEN_Vector2 a, MSDFGEN_Vector2 b) { + MSDFGEN_Vector2 result; + result.x = a.x-b.x; + result.y = a.y-b.y; + return result; +} + +inline MSDFGEN_Vector2 MSDFGEN_Vector2_normalizeOrZero(MSDFGEN_Vector2 v) { + MSDFGEN_real length = sqrt(MSDFGEN_Vector2_squaredLength(v)); + if (length != (MSDFGEN_real) 0) { + v.x /= length; + v.y /= length; + } + return v; +} + +#ifdef __cplusplus +} +#endif diff --git a/c/equation-solver.c b/c/equation-solver.c new file mode 100644 index 0000000..3ab8256 --- /dev/null +++ b/c/equation-solver.c @@ -0,0 +1,76 @@ + +#include "equation-solver.h" + +#define _USE_MATH_DEFINES +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int MSDFGEN_solveQuadratic(double x[2], double a, double b, double c) { + // a == 0 -> linear equation + if (a == 0 || fabs(b) > 1e12*fabs(a)) { + // a == 0, b == 0 -> no solution + if (b == 0) { + if (c == 0) + return -1; // 0 == 0 + return 0; + } + x[0] = -c/b; + return 1; + } + double dscr = b*b-4*a*c; + if (dscr > 0) { + dscr = sqrt(dscr); + x[0] = (-b+dscr)/(2*a); + x[1] = (-b-dscr)/(2*a); + return 2; + } else if (dscr == 0) { + x[0] = -b/(2*a); + return 1; + } else + return 0; +} + +static int solveCubicNormed(double x[3], double a, double b, double c) { + double a2 = a*a; + double q = 1/9.*(a2-3*b); + double r = 1/54.*(a*(2*a2-9*b)+27*c); + double r2 = r*r; + double q3 = q*q*q; + a *= 1/3.; + if (r2 < q3) { + double t = r/sqrt(q3); + if (t < -1) t = -1; + if (t > 1) t = 1; + t = acos(t); + q = -2*sqrt(q); + x[0] = q*cos(1/3.*t)-a; + x[1] = q*cos(1/3.*(t+2*M_PI))-a; + x[2] = q*cos(1/3.*(t-2*M_PI))-a; + return 3; + } else { + double u = (r < 0 ? 1 : -1)*pow(fabs(r)+sqrt(r2-q3), 1/3.); + double v = u == 0 ? 0 : q/u; + x[0] = (u+v)-a; + if (u == v || fabs(u-v) < 1e-12*fabs(u+v)) { + x[1] = -.5*(u+v)-a; + return 2; + } + return 1; + } +} + +int MSDFGEN_solveCubic(double x[3], double a, double b, double c, double d) { + if (a != 0) { + double bn = b/a; + if (fabs(bn) < 1e6) // Above this ratio, the numerical error gets larger than if we treated a as zero + return solveCubicNormed(x, bn, c/a, d/a); + } + return MSDFGEN_solveQuadratic(x, b, c, d); +} + +#ifdef __cplusplus +} +#endif diff --git a/c/equation-solver.h b/c/equation-solver.h new file mode 100644 index 0000000..5aba544 --- /dev/null +++ b/c/equation-solver.h @@ -0,0 +1,16 @@ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// ax^2 + bx + c = 0 +int MSDFGEN_solveQuadratic(double x[2], double a, double b, double c); + +// ax^3 + bx^2 + cx + d = 0 +int MSDFGEN_solveCubic(double x[3], double a, double b, double c, double d); + +#ifdef __cplusplus +} +#endif diff --git a/c/msdfgen-c.h b/c/msdfgen-c.h new file mode 100644 index 0000000..c7d3698 --- /dev/null +++ b/c/msdfgen-c.h @@ -0,0 +1,85 @@ + +#pragma once + +#include +#include "Vector2.h" +#include "CompiledShape.h" + +#define MSDFGEN_CUBIC_SEARCH_STARTS 4 +#define MSDFGEN_CUBIC_SEARCH_STEPS 4 + +#define MSDFGEN_ABTEST_ALT_CACHE 0 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { +#if MSDFGEN_ABTEST_ALT_CACHE + MSDFGEN_Vector2 origin; +#endif + MSDFGEN_real edgeDistance; +} MSDFGEN_TrueDistanceEdgeCache; + +typedef struct { + MSDFGEN_Vector2 origin; + MSDFGEN_real minDistance; + MSDFGEN_TrueDistanceEdgeCache edgeData[1]; +} MSDFGEN_TrueDistanceCache; + +typedef struct { + MSDFGEN_Vector2 origin; + struct EdgeData { + MSDFGEN_real absDistance; + MSDFGEN_real domainDistance0, domainDistance1; + MSDFGEN_real perpendicularDistance0, perpendicularDistance1; + } edgeData[1]; +} MSDFGEN_PerpendicularDistanceCache; + +typedef struct { + struct { + MSDFGEN_real scale, translate; + } distanceMapping; + struct { + MSDFGEN_Vector2 scale, translate; + } projection; +} MSDFGEN_SDFTransformation; + +typedef struct { + enum Mode { + DISABLED, + INDISCRIMINATE, + EDGE_PRIORITY, + EDGE_ONLY + } mode; + enum DistanceCheckMode { + DO_NOT_CHECK_DISTANCE, + CHECK_DISTANCE_AT_EDGE, + ALWAYS_CHECK_DISTANCE + } distanceCheckMode; + MSDFGEN_real minDeviationRatio; + MSDFGEN_real minImproveRatio; + void *buffer; +} MSDFGEN_ErrorCorrectionSettings; + +size_t MSDFGEN_TrueDistanceCache_size(const MSDFGEN_CompiledShape *shapePtr); +size_t MSDFGEN_PerpendicularDistanceCache_size(const MSDFGEN_CompiledShape *shapePtr); + +void MSDFGEN_TrueDistanceCache_initialize(MSDFGEN_TrueDistanceCache *cachePtr, const MSDFGEN_CompiledShape *shapePtr); +void MSDFGEN_PerpendicularDistanceCache_initialize(MSDFGEN_PerpendicularDistanceCache *cachePtr, const MSDFGEN_CompiledShape *shapePtr); + +MSDFGEN_real MSDFGEN_signedDistance(const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_Vector2 origin, MSDFGEN_TrueDistanceCache *cachePtr); +MSDFGEN_real MSDFGEN_perpendicularSignedDistance(const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_Vector2 origin, MSDFGEN_PerpendicularDistanceCache *cachePtr); +MSDFGEN_real MSDFGEN_multiSignedDistance(MSDFGEN_real dstMultiDistance[3], const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_Vector2 origin, MSDFGEN_PerpendicularDistanceCache *cachePtr); + +void MSDFGEN_generateSDF(float *dstPixels, int width, int height, size_t rowStride, const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_SDFTransformation transformation, MSDFGEN_TrueDistanceCache *cachePtr); +void MSDFGEN_generatePSDF(float *dstPixels, int width, int height, size_t rowStride, const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_SDFTransformation transformation, MSDFGEN_PerpendicularDistanceCache *cachePtr); +void MSDFGEN_generateMSDF(float *dstPixels, int width, int height, size_t rowStride, const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_SDFTransformation transformation, MSDFGEN_PerpendicularDistanceCache *cachePtr); +void MSDFGEN_generateMTSDF(float *dstPixels, int width, int height, size_t rowStride, const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_SDFTransformation transformation, MSDFGEN_PerpendicularDistanceCache *cachePtr); + +void MSDFGEN_errorCorrectionMSDF(MSDFGEN_ErrorCorrectionSettings settings, float *pixels, int width, int height, size_t rowStride, const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_SDFTransformation transformation, MSDFGEN_PerpendicularDistanceCache *cachePtr); +void MSDFGEN_errorCorrectionMTSDF(MSDFGEN_ErrorCorrectionSettings settings, float *pixels, int width, int height, size_t rowStride, const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_SDFTransformation transformation, MSDFGEN_PerpendicularDistanceCache *cachePtr); + +#ifdef __cplusplus +} +#endif diff --git a/c/msdfgen.c b/c/msdfgen.c new file mode 100644 index 0000000..2517fff --- /dev/null +++ b/c/msdfgen.c @@ -0,0 +1,273 @@ + +#include "msdfgen-c.h" + +#include +#include +#include "equation-solver.h" + +#define DISTANCE_DELTA_FACTOR 1.001 + +//#define MSDFGEN_IF_CACHE_AND(...) // disable cache +#define MSDFGEN_IF_CACHE_AND if + +extern long long MSDFGEN_PERFSTATS_CACHE_TESTS; +extern long long MSDFGEN_PERFSTATS_CACHE_MISSES; + +long long MSDFGEN_PERFSTATS_CACHE_TESTS = 0; +long long MSDFGEN_PERFSTATS_CACHE_MISSES = 0; + +#define MSDFGEN_PERFSTATS_CACHE_TEST() (++MSDFGEN_PERFSTATS_CACHE_TESTS) +#define MSDFGEN_PERFSTATS_CACHE_MISS() (++MSDFGEN_PERFSTATS_CACHE_MISSES) + +//#define NO_CACHE_AND_SQUARE_DISTANCE + +#ifdef __cplusplus +extern "C" { +#endif + +static int nonZeroSign(MSDFGEN_real x) { + return x > (MSDFGEN_real) 0 ? 1 : -1; +} + +static int crossNonZeroSign(MSDFGEN_Vector2 a, MSDFGEN_Vector2 b) { + return a.x*b.y > b.x*a.y ? 1 : -1; +} + +size_t MSDFGEN_TrueDistanceCache_size(const MSDFGEN_CompiledShape *shapePtr) { + return sizeof(MSDFGEN_TrueDistanceCache) + (shapePtr->nLinearEdges+shapePtr->nQuadraticEdges+shapePtr->nCubicEdges)*sizeof(MSDFGEN_TrueDistanceEdgeCache); +} + +void MSDFGEN_TrueDistanceCache_initialize(MSDFGEN_TrueDistanceCache *cachePtr, const MSDFGEN_CompiledShape *shapePtr) { + MSDFGEN_TrueDistanceEdgeCache *cur, *end; + cachePtr->origin.x = 0; + cachePtr->origin.y = 0; + cachePtr->minDistance = .0625f*FLT_MAX; + for (cur = cachePtr->edgeData, end = cachePtr->edgeData+(shapePtr->nLinearEdges+shapePtr->nQuadraticEdges+shapePtr->nCubicEdges); cur < end; ++cur) { + #if MSDFGEN_ABTEST_ALT_CACHE + cur->origin.x = 0; + cur->origin.y = 0; + #else + cur->edgeDistance = 0; + #endif + } +} + +#ifdef NO_CACHE_AND_SQUARE_DISTANCE + +// INCOMPLETE !!!! + +MSDFGEN_real MSDFGEN_signedDistance(const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_Vector2 origin, MSDFGEN_TrueDistanceCache *cachePtr) { + int sign = -1; + MSDFGEN_real sqd = FLT_MAX; + size_t i; + + for (i = 0; i < shapePtr->nLinearEdges; ++i) { + MSDFGEN_Vector2 originP0 = MSDFGEN_Vector2_difference(shapePtr->linearEdges[i].endpoint0, origin); + MSDFGEN_real originP0SqD = MSDFGEN_Vector2_squaredLength(originP0); + MSDFGEN_real t = MSDFGEN_Vector2_dot(originP0, shapePtr->linearEdges[i].invDerivative); + if (originP0SqD < sqd) { + sqd = originP0SqD; + sign = crossNonZeroSign(shapePtr->linearEdges[i].cornerVec0, MSDFGEN_Vector2_sum(shapePtr->linearEdges[i].direction, originP0)); + } + if (t > (MSDFGEN_real) 0 && t < (MSDFGEN_real) 1) { + MSDFGEN_real pd = MSDFGEN_Vector2_cross(shapePtr->linearEdges[i].direction, originP0); + MSDFGEN_real sqpd = pd*pd; + if (sqpd < sqd) { + sqd = sqpd; + sign = nonZeroSign(pd); + } + } + } + + for (i = 0; i < shapePtr->nQuadraticEdges; ++i) { + MSDFGEN_Vector2 originP0 = MSDFGEN_Vector2_difference(shapePtr->quadraticEdges[i].endpoint0, origin); + MSDFGEN_real originP0SqD = MSDFGEN_Vector2_squaredLength(originP0); + MSDFGEN_real a = MSDFGEN_Vector2_squaredLength(shapePtr->quadraticEdges[i].derivative1); + MSDFGEN_real b = (MSDFGEN_real) 3*MSDFGEN_Vector2_dot(shapePtr->quadraticEdges[i].derivative0, shapePtr->quadraticEdges[i].derivative1); + MSDFGEN_real c = (MSDFGEN_real) 2*MSDFGEN_Vector2_squaredLength(shapePtr->quadraticEdges[i].derivative0) + MSDFGEN_Vector2_dot(originP0, shapePtr->quadraticEdges[i].derivative1); + MSDFGEN_real d = MSDFGEN_Vector2_dot(originP0, shapePtr->quadraticEdges[i].derivative0); + double t[3]; + int solutions = MSDFGEN_solveCubic(t, a, b, c, d); + if (originP0SqD < sqd) { + sqd = originP0SqD; + sign = crossNonZeroSign(shapePtr->quadraticEdges[i].cornerVec0, MSDFGEN_Vector2_sum(shapePtr->quadraticEdges[i].direction0, originP0)); + } + #define MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t) if (t > 0 && t < 1) { \ + MSDFGEN_Vector2 originP = MSDFGEN_Vector2_sum(MSDFGEN_Vector2_sum(originP0, MSDFGEN_Vector2_scale((MSDFGEN_real) (2*t), shapePtr->quadraticEdges[i].derivative0)), MSDFGEN_Vector2_scale((MSDFGEN_real) (t*t), shapePtr->quadraticEdges[i].derivative1)); \ + MSDFGEN_real originPSqD = MSDFGEN_Vector2_squaredLength(originP); \ + if (originPSqD < sqd) { \ + MSDFGEN_Vector2 direction = MSDFGEN_Vector2_sum(shapePtr->quadraticEdges[i].derivative0, MSDFGEN_Vector2_scale(t, shapePtr->quadraticEdges[i].derivative1)); \ + sqd = originPSqD; \ + sign = crossNonZeroSign(direction, originP); \ + } \ + } + if (solutions > 0) { + MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t[0]); + if (solutions > 1) { + MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t[1]); + if (solutions > 2) + MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t[2]); + } + } + } + + return (MSDFGEN_real) sign*sqrt(sqd); +} + +#else + +#if MSDFGEN_ABTEST_ALT_CACHE +#define MSDFGEN_IF_TRUE_DISTANCE_UNCACHED MSDFGEN_PERFSTATS_CACHE_TEST(); MSDFGEN_IF_CACHE_AND(++edgeCache, edgeCache[-1].edgeDistance-DISTANCE_DELTA_FACTOR*sqrt(MSDFGEN_Vector2_squaredLength(MSDFGEN_Vector2_difference(origin, edgeCache[-1].origin))) <= minDistance) +#else +#define MSDFGEN_IF_TRUE_DISTANCE_UNCACHED MSDFGEN_PERFSTATS_CACHE_TEST(); MSDFGEN_IF_CACHE_AND((edgeCache++->edgeDistance -= cacheDelta) <= minDistance) +#endif + +MSDFGEN_real MSDFGEN_signedDistance(const MSDFGEN_CompiledShape *shapePtr, MSDFGEN_Vector2 origin, MSDFGEN_TrueDistanceCache *cachePtr) { + + MSDFGEN_real cacheDelta = DISTANCE_DELTA_FACTOR*sqrt(MSDFGEN_Vector2_squaredLength(MSDFGEN_Vector2_difference(origin, cachePtr->origin))); + MSDFGEN_real minDistance = cachePtr->minDistance+cacheDelta; + int distanceSign = -1; + MSDFGEN_TrueDistanceEdgeCache *edgeCache = cachePtr->edgeData; + size_t i; + + for (i = 0; i < shapePtr->nLinearEdges; ++i) { + MSDFGEN_IF_TRUE_DISTANCE_UNCACHED { + MSDFGEN_Vector2 originP0 = MSDFGEN_Vector2_difference(shapePtr->linearEdges[i].endpoint0, origin); + MSDFGEN_real originP0Dist = sqrt(MSDFGEN_Vector2_squaredLength(originP0)); + MSDFGEN_real t = MSDFGEN_Vector2_dot(originP0, shapePtr->linearEdges[i].invDerivative); + if (originP0Dist < minDistance) { + minDistance = originP0Dist; + distanceSign = crossNonZeroSign(shapePtr->linearEdges[i].cornerVec0, MSDFGEN_Vector2_sum(shapePtr->linearEdges[i].direction, originP0)); + } + edgeCache[-1].edgeDistance = originP0Dist; + if (t > (MSDFGEN_real) 0 && t < (MSDFGEN_real) 1) { + MSDFGEN_real psd = MSDFGEN_Vector2_cross(shapePtr->linearEdges[i].direction, originP0); + MSDFGEN_real pd = fabs(psd); + if (pd < minDistance) { + minDistance = pd; + distanceSign = nonZeroSign(psd); + } + edgeCache[-1].edgeDistance = pd; + } else { + MSDFGEN_real originP1Dist = sqrt(MSDFGEN_Vector2_squaredLength(MSDFGEN_Vector2_difference(shapePtr->linearEdges[i].endpoint1, origin))); + if (originP1Dist < edgeCache[-1].edgeDistance) + edgeCache[-1].edgeDistance = originP1Dist; + } + MSDFGEN_PERFSTATS_CACHE_MISS(); + #if MSDFGEN_ABTEST_ALT_CACHE + edgeCache[-1].origin = origin; + #endif + } + } + + for (i = 0; i < shapePtr->nQuadraticEdges; ++i) { + MSDFGEN_IF_TRUE_DISTANCE_UNCACHED { + MSDFGEN_Vector2 originP0 = MSDFGEN_Vector2_difference(shapePtr->quadraticEdges[i].endpoint0, origin); + MSDFGEN_real originP0Dist = sqrt(MSDFGEN_Vector2_squaredLength(originP0)); + MSDFGEN_real originP1Dist = sqrt(MSDFGEN_Vector2_squaredLength(MSDFGEN_Vector2_difference(shapePtr->quadraticEdges[i].endpoint1, origin))); + MSDFGEN_real a = MSDFGEN_Vector2_squaredLength(shapePtr->quadraticEdges[i].derivative1); + MSDFGEN_real b = (MSDFGEN_real) 3*MSDFGEN_Vector2_dot(shapePtr->quadraticEdges[i].derivative0, shapePtr->quadraticEdges[i].derivative1); + MSDFGEN_real c = (MSDFGEN_real) 2*MSDFGEN_Vector2_squaredLength(shapePtr->quadraticEdges[i].derivative0) + MSDFGEN_Vector2_dot(originP0, shapePtr->quadraticEdges[i].derivative1); + MSDFGEN_real d = MSDFGEN_Vector2_dot(originP0, shapePtr->quadraticEdges[i].derivative0); + double t[3]; + int solutions = MSDFGEN_solveCubic(t, a, b, c, d); + if (originP0Dist < minDistance) { + minDistance = originP0Dist; + distanceSign = crossNonZeroSign(shapePtr->quadraticEdges[i].cornerVec0, MSDFGEN_Vector2_sum(shapePtr->quadraticEdges[i].direction0, originP0)); + } + edgeCache[-1].edgeDistance = originP0Dist; + if (originP1Dist < edgeCache[-1].edgeDistance) + edgeCache[-1].edgeDistance = originP1Dist; + #define MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t) if (t > 0 && t < 1) { \ + MSDFGEN_Vector2 originP = MSDFGEN_Vector2_sum(MSDFGEN_Vector2_sum(originP0, MSDFGEN_Vector2_scale((MSDFGEN_real) (2*t), shapePtr->quadraticEdges[i].derivative0)), MSDFGEN_Vector2_scale((MSDFGEN_real) (t*t), shapePtr->quadraticEdges[i].derivative1)); \ + MSDFGEN_real originPDist = sqrt(MSDFGEN_Vector2_squaredLength(originP)); \ + if (originPDist < minDistance) { \ + MSDFGEN_Vector2 direction = MSDFGEN_Vector2_sum(shapePtr->quadraticEdges[i].derivative0, MSDFGEN_Vector2_scale(t, shapePtr->quadraticEdges[i].derivative1)); \ + minDistance = originPDist; \ + distanceSign = crossNonZeroSign(direction, originP); \ + } \ + if (originPDist < edgeCache[-1].edgeDistance) \ + edgeCache[-1].edgeDistance = originPDist; \ + } + if (solutions > 0) { + MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t[0]); + if (solutions > 1) { + MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t[1]); + if (solutions > 2) + MSDFGEN_SD_RESOLVE_QUADRATIC_SOLUTION(t[2]); + } + } + MSDFGEN_PERFSTATS_CACHE_MISS(); + #if MSDFGEN_ABTEST_ALT_CACHE + edgeCache[-1].origin = origin; + #endif + } + } + + for (i = 0; i < shapePtr->nCubicEdges; ++i) { + MSDFGEN_IF_TRUE_DISTANCE_UNCACHED { + int start, step; + MSDFGEN_Vector2 originP0 = MSDFGEN_Vector2_difference(shapePtr->cubicEdges[i].endpoint0, origin); + MSDFGEN_real originP0Dist = sqrt(MSDFGEN_Vector2_squaredLength(originP0)); + MSDFGEN_real originP1Dist = sqrt(MSDFGEN_Vector2_squaredLength(MSDFGEN_Vector2_difference(shapePtr->cubicEdges[i].endpoint1, origin))); + if (originP0Dist < minDistance) { + minDistance = originP0Dist; + distanceSign = crossNonZeroSign(shapePtr->cubicEdges[i].cornerVec0, MSDFGEN_Vector2_sum(shapePtr->cubicEdges[i].direction0, originP0)); + } + edgeCache[-1].edgeDistance = originP0Dist; + if (originP1Dist < edgeCache[-1].edgeDistance) + edgeCache[-1].edgeDistance = originP1Dist; + for (start = 0; start <= MSDFGEN_CUBIC_SEARCH_STARTS; ++start) { + MSDFGEN_real t = (MSDFGEN_real) 1/(MSDFGEN_real) MSDFGEN_CUBIC_SEARCH_STARTS*(MSDFGEN_real) start; + MSDFGEN_Vector2 originP = MSDFGEN_Vector2_sum( + MSDFGEN_Vector2_sum( + MSDFGEN_Vector2_sum(originP0, MSDFGEN_Vector2_scale((MSDFGEN_real) 3*t, shapePtr->cubicEdges[i].derivative0)), + MSDFGEN_Vector2_scale((MSDFGEN_real) 3*(t*t), shapePtr->cubicEdges[i].derivative1) + ), MSDFGEN_Vector2_scale(t*t*t, shapePtr->cubicEdges[i].derivative2) + ); + for (step = 0; step < MSDFGEN_CUBIC_SEARCH_STEPS; ++step) { + MSDFGEN_Vector2 derivative0 = MSDFGEN_Vector2_sum( + MSDFGEN_Vector2_sum( + MSDFGEN_Vector2_scale((MSDFGEN_real) 3, shapePtr->cubicEdges[i].derivative0), + MSDFGEN_Vector2_scale((MSDFGEN_real) 6*t, shapePtr->cubicEdges[i].derivative1) + ), MSDFGEN_Vector2_scale((MSDFGEN_real) 3*(t*t), shapePtr->cubicEdges[i].derivative2) + ); + MSDFGEN_Vector2 derivative1 = MSDFGEN_Vector2_sum( + MSDFGEN_Vector2_scale((MSDFGEN_real) 6, shapePtr->cubicEdges[i].derivative1), + MSDFGEN_Vector2_scale((MSDFGEN_real) 6*t, shapePtr->cubicEdges[i].derivative2) + ); + t -= MSDFGEN_Vector2_dot(originP, derivative0)/(MSDFGEN_Vector2_squaredLength(derivative0)+MSDFGEN_Vector2_dot(originP, derivative1)); + if (t <= (MSDFGEN_real) 0 || t >= (MSDFGEN_real) 1) + break; + originP = MSDFGEN_Vector2_sum( + MSDFGEN_Vector2_sum( + MSDFGEN_Vector2_sum(originP0, MSDFGEN_Vector2_scale((MSDFGEN_real) 3*t, shapePtr->cubicEdges[i].derivative0)), + MSDFGEN_Vector2_scale((MSDFGEN_real) 3*(t*t), shapePtr->cubicEdges[i].derivative1) + ), MSDFGEN_Vector2_scale(t*t*t, shapePtr->cubicEdges[i].derivative2) + ); + MSDFGEN_real originPDist = sqrt(MSDFGEN_Vector2_squaredLength(originP)); + if (originPDist < minDistance) { + minDistance = originPDist; + distanceSign = crossNonZeroSign(derivative0, originP); + } + if (originPDist < edgeCache[-1].edgeDistance) + edgeCache[-1].edgeDistance = originPDist; + } + } + MSDFGEN_PERFSTATS_CACHE_MISS(); + #if MSDFGEN_ABTEST_ALT_CACHE + edgeCache[-1].origin = origin; + #endif + } + } + + cachePtr->origin = origin; + cachePtr->minDistance = minDistance; + return (MSDFGEN_real) distanceSign*minDistance; +} + +#endif + +#ifdef __cplusplus +} +#endif