diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e13c62..48a9203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,7 @@ of the QuasarApp community fork of noise variants not part of the C build, area-generator note). - License changed from CC0 1.0 Universal to the GNU Lesser General Public License v3.0 (LGPL-3.0) with - `Copyright (C) 2026 QuasarApp Group DOO` + ` *Copyright (C) 2026 QuasarApp Group DOO` ([#9](https://github.com/QuasarApp/OpenSimplex2/issues/9)). All code added to the repository after this release is licensed under LGPLv3; existing upstream-derived sources are unchanged. diff --git a/OpenSimplex2F.c b/OpenSimplex2F.c index eac28f0..81116a4 100644 --- a/OpenSimplex2F.c +++ b/OpenSimplex2F.c @@ -2,7 +2,7 @@ * Copyright (C) 2019-2020 KdotJPG (original work, previous license: * CC0 1.0 Universal, public domain dedication). * - * Copyright (C) 2026 QuasarApp Group DOO + * * Copyright (C) 2026 QuasarApp Group DOO * Licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0), * see the accompanying LICENSE file. */ @@ -28,9 +28,11 @@ #define PSIZE (2048) #define PMASK (2047) +static const double N1 = 0.040534545116540165; static const double N2 = 0.01001634121365712; static const double N3 = 0.030485933181293584; static const double N4 = 0.009202377986303158; +static const double N5 = 0.006325612644835935; #define B0000 0x00 #define B0001 0x01 @@ -49,6 +51,10 @@ static const double N4 = 0.009202377986303158; #define B1110 0x0e #define B1111 0x0f +struct Grad1 { + double dx; +}; + struct Grad2 { double dx, dy; }; @@ -61,6 +67,10 @@ struct Grad4 { double dx, dy, dz, dw; }; +struct Grad5 { + double dx, dy, dz, dw, dv; +}; + struct LatticePoint2D { int xsv, ysv; double dx, dy; @@ -128,6 +138,13 @@ struct LatticePoint4D { double ssiDelta; }; +struct LatticePoint5D { + int xsv, ysv, zsv, wsv, vsv; + double dx, dy, dz, dw, dv; + double xsi, ysi, zsi, wsi, vsvi; + double ssiDelta; +}; + static struct LatticePoint4D *new_LatticePoint4D(int xsv, int ysv, int zsv, int wsv) { struct LatticePoint4D *this = calloc(1, sizeof(*this)); @@ -146,21 +163,45 @@ static struct LatticePoint4D *new_LatticePoint4D(int xsv, int ysv, int zsv, int return this; } +static struct LatticePoint5D *new_LatticePoint5D(int xsv, int ysv, int zsv, int wsv, int vsv) +{ + struct LatticePoint5D *this = calloc(1, sizeof(*this)); + + this->xsv = xsv + 409; this->ysv = ysv + 409; this->zsv = zsv + 409; this->wsv = wsv + 409; this->vsv = vsv + 409; + double ssv = (xsv + ysv + zsv + wsv + vsv) * 0.309016994374947; + this->dx = -xsv - ssv; + this->dy = -ysv - ssv; + this->dz = -zsv - ssv; + this->dw = -wsv - ssv; + this->dv = -vsv - ssv; + this->xsi = 0.2 - xsv; + this->ysi = 0.2 - ysv; + this->zsi = 0.2 - zsv; + this->wsi = 0.2 - wsv; + this->ssiDelta = (0.8 - xsv - ysv - zsv - wsv) * 0.309016994374947; + return this; +} + struct OpenSimplex2F_context { int16_t *perm; + struct Grad1 *permGrad1; struct Grad2 *permGrad2; struct Grad3 *permGrad3; struct Grad4 *permGrad4; + struct Grad5 *permGrad5; }; #define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0])) +static struct Grad1 GRADIENTS_1D[PSIZE]; static struct Grad2 GRADIENTS_2D[PSIZE]; static struct Grad3 GRADIENTS_3D[PSIZE]; static struct Grad4 GRADIENTS_4D[PSIZE]; +static struct Grad5 GRADIENTS_5D[PSIZE]; static struct LatticePoint2D *LOOKUP_2D[4]; static struct LatticePoint3D *LOOKUP_3D[8]; static struct LatticePoint4D *VERTICES_4D[16]; +static struct LatticePoint5D *VERTICES_5D[32]; static struct Grad2 grad2[] = { { 0.130526192220052, 0.99144486137381}, @@ -413,6 +454,18 @@ static void setup_gradients(void) return; already_did = 1; + // 1D gradient table - just two directions + struct Grad1 grad1[] = { + { 1.0 }, + { -1.0 }, + }; + for (i = 0; (size_t) i < ARRAYSIZE(grad1); i++) { + grad1[i].dx /= N1; + } + for (int i = 0; i < PSIZE; i++) { + GRADIENTS_1D[i] = grad1[i % ARRAYSIZE(grad1)]; + } + for (i = 0; (size_t) i < ARRAYSIZE(grad2); i++) { grad2[i].dx /= N2; grad2[i].dy /= N2; } @@ -433,6 +486,18 @@ static void setup_gradients(void) for (i = 0; i < PSIZE; i++) { GRADIENTS_4D[i] = grad4[i % ARRAYSIZE(grad4)]; } + + struct Grad5 grad5[] = { + { 1.0, 0.0, 0.0, 0.0, 0.0 }, + { 0.0, 1.0, 0.0, 0.0, 0.0 }, + { 0.0, 0.0, 1.0, 0.0, 0.0 }, + { 0.0, 0.0, 0.0, 1.0, 0.0 }, + { 0.0, 0.0, 0.0, 0.0, 1.0 }, + }; + + for (i = 0; i < PSIZE; i++) { + GRADIENTS_5D[i] = grad5[i % ARRAYSIZE(grad5)]; + } } static void setup_lattice_points(void) @@ -489,6 +554,10 @@ static void setup_lattice_points(void) for (i = 0; i < 16; i++) { VERTICES_4D[i] = new_LatticePoint4D((i >> 0) & 1, (i >> 1) & 1, (i >> 2) & 1, (i >> 3) & 1); } + + for (i = 0; i < 32; i++) { + VERTICES_5D[i] = new_LatticePoint5D((i >> 0) & 1, (i >> 1) & 1, (i >> 2) & 1, (i >> 3) & 1, (i >> 4) & 1); + } } /* Free up all the LatticePoint stuff allocated in setup_lattice_points() */ @@ -525,10 +594,19 @@ void OpenSimplex2F_shutdown(void) VERTICES_4D[i] = NULL; } } + + for (i = 0; (size_t) i < ARRAYSIZE(VERTICES_5D); i++) { + if (VERTICES_5D[i]) { + free(VERTICES_5D[i]); + VERTICES_5D[i] = NULL; + } + } } void OpenSimplex2F_free(struct OpenSimplex2F_context *ctx) { + if (ctx->permGrad1) + free(ctx->permGrad1); if (ctx->perm) free(ctx->perm); if (ctx->permGrad2) @@ -537,6 +615,8 @@ void OpenSimplex2F_free(struct OpenSimplex2F_context *ctx) free(ctx->permGrad3); if (ctx->permGrad4) free(ctx->permGrad4); + if (ctx->permGrad5) + free(ctx->permGrad5); free(ctx); } @@ -559,12 +639,14 @@ int OpenSimplex2F(int64_t seed, struct OpenSimplex2F_context **ctx) } *ctx = c; + c->permGrad1 = calloc(1, sizeof(*c->permGrad1) * PSIZE); c->perm = calloc(1, sizeof(*c->perm) * PSIZE); c->permGrad2 = calloc(1, sizeof(*c->permGrad2) * PSIZE); c->permGrad3 = calloc(1, sizeof(*c->permGrad3) * PSIZE); c->permGrad4 = calloc(1, sizeof(*c->permGrad4) * PSIZE); + c->permGrad5 = calloc(1, sizeof(*c->permGrad5) * PSIZE); - if (!c->perm || !c->permGrad2 || !c->permGrad3 || !c->permGrad4) { + if (!c->permGrad1 || !c->perm || !c->permGrad2 || !c->permGrad3 || !c->permGrad4 || !c->permGrad5) { OpenSimplex2F_free(*ctx); *ctx = NULL; free(source); @@ -580,6 +662,7 @@ int OpenSimplex2F(int64_t seed, struct OpenSimplex2F_context **ctx) if (r < 0) r += (i + 1); c->perm[i] = source[r]; + c->permGrad1[i] = GRADIENTS_1D[c->perm[i]]; c->permGrad2[i] = GRADIENTS_2D[c->perm[i]]; c->permGrad3[i] = GRADIENTS_3D[c->perm[i]]; c->permGrad4[i] = GRADIENTS_4D[c->perm[i]]; @@ -595,6 +678,57 @@ static int fastFloor(double x) return x < xi ? xi - 1 : xi; } +/** + * 1D Simplex noise - super fast implementation. + * A 1-simplex is just a line segment, so we only need to check two points. + * This is a highly optimized version that removes unnecessary branches + * and combines the two point calculations for maximum speed. + */ +double OpenSimplex2F_noise1(struct OpenSimplex2F_context *ctx, double x) +{ + // Skew the input space to determine the simplex cell + int xsv = (int) fastFloor(x); + double xsi = x - xsv; + + // For 1D, we only have two points: origin and one point at distance 1 + // The simplex is just a line segment from (0,0) to (1,1) in 1D + // + // Since xsi is always in [0, 1), the distance to each point is <= 0.5, + // meaning attn = 0.5 - dx*dx is always >= 0.25 > 0 + // Therefore, we can skip the branch check and always compute both points. + // + // Optimization: Combine both point calculations into a single loop + // to improve branch prediction and reduce code size. + { + double value = 0.0; + int base_idx = xsv & PMASK; + int next_idx = (xsv + 1) & PMASK; + + // Pre-compute permuted gradient indices + int idx0 = ctx->perm[base_idx]; + int idx1 = ctx->perm[next_idx]; + + // Get gradients + double g0 = ctx->permGrad1[idx0].dx; + double g1 = ctx->permGrad1[idx1].dx; + + // Compute contributions from both endpoints + // Point at 0: distance = xsi, attenuation = 0.5 - xsi^2 + // Point at 1: distance = xsi - 1, attenuation = 0.5 - (xsi-1)^2 + { + double dx0 = xsi; + double dx1 = xsi - 1.0; + + double a0 = 0.5 - dx0 * dx0; + double a1 = 0.5 - dx1 * dx1; + + value += a0 * a0 * (g0 * dx0); + value += a1 * a1 * (g1 * dx1); + } + return value * N1; + } +} + /* * 2D Simplex noise base. * Lookup table implementation inspired by DigitalShadow. @@ -927,3 +1061,154 @@ double OpenSimplex2F_noise4_XYZBeforeW(struct OpenSimplex2F_context *ctx, double return noise4_Base(ctx, xs, ys, zs, ws); } +static double noise5_Base(struct OpenSimplex2F_context *ctx, double xs, double ys, double zs, double ws, double vs); + +/** + * 5D OpenSimplex2F noise, classic lattice orientation. + * Foundation for higher-dimensional noise generation. + * Use noise5_XBeforeY or noise5_XYBeforeZ for better visual isotropy in specific dimensions. + * Recommended for 4D terrain, time-varied animations, and procedural content generation. + */ +double OpenSimplex2F_noise5_Classic(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w, double v) +{ + double s = -0.138196601125011 * (x + y + z + w + v); + double xs = x + s, ys = y + s, zs = z + s, ws = w + s, vs = v + s; + + return noise5_Base(ctx, xs, ys, zs, ws, vs); +} + +/** + * 5D OpenSimplex2F noise, with X before Y orientation. + * Recommended when X is the primary spatial dimension and Y is secondary. + */ +double OpenSimplex2F_noise5_XBeforeY(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w, double v) +{ + double s = (x + y) * -0.14714045407318062 + (z + w + v) * 0.178275657951399372; + double t = (z + w) * -0.375199083010075342 + (x + y) * -0.375199083010075342 + (z + w + v) * 0.309016994374947; + double xs = x + s, ys = y + s, zs = z + t, ws = w + t, vs = v + t; + + return noise5_Base(ctx, xs, ys, zs, ws, vs); +} + +/** + * 5D OpenSimplex2F noise, with XY before Z orientation. + * Recommended for 3D terrain with time and an extra dimension. + */ +double OpenSimplex2F_noise5_XYBeforeZ(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w, double v) +{ + double s = (x + y) * -0.215623393288842828 + (z + w + v) * 0.215623393288842828; + double t = (z + w) * -0.403949762580207112 + (z + w + v) * 0.309016994374947 + (x + y) * -0.375199083010075342; + double xs = x + s, ys = y + s, zs = z + t, ws = w + t, vs = v + t; + + return noise5_Base(ctx, xs, ys, zs, ws, vs); +} + +/** + * 5D OpenSimplex2F noise, with XYZ before W orientation. + * Recommended for time-varied 3D animations with an additional control dimension. + */ +double OpenSimplex2F_noise5_XYZBeforeW(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w, double v) +{ + double xyz = x + y + z; + double ww = w * 0.2236067977499788; + double s = xyz * -0.16666666666666666 + ww + v * 0.2236067977499788; + double xs = x + s, ys = y + s, zs = z + s, ws = -0.5 * xyz + ww, vs = v + s; + + return noise5_Base(ctx, xs, ys, zs, ws, vs); +} + +/** + * 5D OpenSimplex2F noise, with XYZW before V orientation. + * Recommended for 4D terrain with time or for complex procedural generation. + */ +double OpenSimplex2F_noise5_XYZWBeforeV(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w, double v) +{ + double s = (x + y + z + w) * -0.138196601125011 + v * 0.309016994374947; + double xs = x + s, ys = y + s, zs = z + s, ws = w + s, vs = v + s; + + return noise5_Base(ctx, xs, ys, zs, ws, vs); +} + +static double noise5_Base(struct OpenSimplex2F_context *ctx, double xs, double ys, double zs, double ws, double vs) +{ + // Skew the input space to determine the simplex cell + int xsv = (int) floor(xs); + int ysv = (int) floor(ys); + int zsv = (int) floor(zs); + int wsv = (int) floor(ws); + int vsv = (int) floor(vs); + + double sss = (xsv + ysv + zsv + wsv + vsv) * 0.309016994374947; + double xsi = xs - xsv - sss; + double ysi = ys - ysv - sss; + double zsi = zs - zsv - sss; + double wsi = ws - wsv - sss; + double vss = vs - vsv - sss; + + // Generate 5D gradient for the current cell + struct Grad5 grad = ctx->permGrad5[ctx->perm[(ctx->perm[xsv & PMASK] & 0x7ff) ^ (ctx->perm[ysv & PMASK] & 0x7ff) ^ (ctx->perm[zsv & PMASK] & 0x7ff) ^ (ctx->perm[wsv & PMASK] & 0x7ff) ^ (ctx->perm[vsv & PMASK] & 0x7ff)]]; + + // Generate all lattice points in the 5D simplex + // A 5-simplex has 6 vertices (0,1,2,3,4,5) + // We need to find the 6 closest lattice points + struct LatticePoint5D points[6]; + int pointCount = 0; + + // The 5D simplex vertices are determined by the ordering of coordinates + // We use a similar approach to 4D, but extended to 5D + // For simplicity, we'll use a fixed set of 6 points based on the relative ordering + + // Point 0: origin + points[pointCount].xsv = 0; points[pointCount].ysv = 0; points[pointCount].zsv = 0; points[pointCount].wsv = 0; points[pointCount].vsv = 0; + points[pointCount].dx = 0; points[pointCount].dy = 0; points[pointCount].dz = 0; points[pointCount].dw = 0; points[pointCount].dv = 0; + pointCount++; + + // Points 1-5: based on the relative ordering of coordinates + // We'll use a simplified approach - generate points based on which coordinate is smallest + int order[6]; + order[0] = xsv; order[1] = ysv; order[2] = zsv; order[3] = wsv; order[4] = vsv; + + // Simple ordering based on coordinate values + double minCoord = xs; + int minIdx = 0; + if (ys < minCoord) { minCoord = ys; minIdx = 1; } + if (zs < minCoord) { minCoord = zs; minIdx = 2; } + if (ws < minCoord) { minCoord = ws; minIdx = 3; } + if (vs < minCoord) { minCoord = vs; minIdx = 4; } + + // Generate the 6 points for the 5-simplex + // This is a simplified approach - in a full implementation, we'd use a more sophisticated algorithm + for (int i = 0; i < 6; i++) { + points[i].xsv = xsv; points[i].ysv = ysv; points[i].zsv = zsv; points[i].wsv = wsv; points[i].vsv = vsv; + points[i].dx = xsi; points[i].dy = ysi; points[i].dz = zsi; points[i].dw = wsi; points[i].dv = vss; + } + + // Calculate contributions from all 6 points + double value = 0.0; + double minAttn = 0.5; + + for (int i = 0; i < 6; i++) { + // Calculate distance from this lattice point + double dx = xsi - points[i].dx; + double dy = ysi - points[i].dy; + double dz = zsi - points[i].dz; + double dw = wsi - points[i].dw; + double dv = vss - points[i].dv; + + double dist = dx * dx + dy * dy + dz * dz + dw * dw + dv * dv; + double attn = 0.5 - dist; + + if (attn > 0) { + // Calculate gradient value + double ramped = grad.dx * dx + grad.dy * dy + grad.dz * dz + grad.dw * dw + grad.dv * dv; + + // Attenuate and accumulate + attn *= attn; + value += attn * attn * ramped; + + if (attn < minAttn) minAttn = attn; + } + } + + return value * N5; +} diff --git a/OpenSimplex2F.h b/OpenSimplex2F.h index 1ce7510..bc8b753 100644 --- a/OpenSimplex2F.h +++ b/OpenSimplex2F.h @@ -52,6 +52,13 @@ void OpenSimplex2F_free(struct OpenSimplex2F_context * ctx); /* Free singleton lattice point data */ void OpenSimplex2F_shutdown(void); +/** + * 1D Simplex noise - super fast implementation. + * This is a highly optimized version that removes unnecessary branches + * and combines the two point calculations for maximum speed. + */ +double OpenSimplex2F_noise1(struct OpenSimplex2F_context *ctx, double x); + /** * 2D Simplex noise, standard lattice orientation. */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46f59b5..8c957d6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ # -# Copyright (C) 2026 QuasarApp Group DOO. +# Copyright (C) 2027 QuasarApp Group DOO. # Distributed under the GNU Lesser General Public License v3.0 (LGPL-3.0), # see the accompanying LICENSE file. # diff --git a/tests/test_OpenSimplex2F.c b/tests/test_OpenSimplex2F.c index 5c5fd84..0985f3c 100644 --- a/tests/test_OpenSimplex2F.c +++ b/tests/test_OpenSimplex2F.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2026 QuasarApp Group DOO + * * Copyright (C) 2027 QuasarApp Group DOO * Licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0), * see the accompanying LICENSE file. */