OpenSimplex 2D noise

This commit is contained in:
Tobias Berger 2021-11-15 22:43:44 +01:00
parent 20b405c28d
commit 9163942d94
3 changed files with 245 additions and 130 deletions

27
include/OpenSimplex2F.h Normal file
View file

@ -0,0 +1,27 @@
typedef struct
{
int xsv, ysv;
double dx, dy;
} LatticePoint2D;
typedef struct
{
double dx, dy;
} Grad2;
typedef struct
{
short *perm;
Grad2 *permGrad2;
} OpenSimplexGradients;
typedef struct
{
Grad2 *GRADIENTS_2D;
LatticePoint2D **LOOKUP_2D;
} OpenSimplexEnv;
OpenSimplexEnv *initOpenSimplex();
OpenSimplexGradients *newOpenSimplexGradients(OpenSimplexEnv *ose, long seed);
double noise2(OpenSimplexEnv *ose, OpenSimplexGradients *osg, double x, double y);
double noise2_XBeforeY(OpenSimplexEnv *ose, OpenSimplexGradients *osg, double x, double y);

203
src/OpenSimplex2F.c Normal file
View file

@ -0,0 +1,203 @@
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>
#include "OpenSimplex2F.h"
#define PSIZE 2048
#define PMASK 2047
#define N2 0.01001634121365712
/*
* Utility
*/
int inline _fastFloor(double x)
{
int xi = (int)x;
return x < xi ? xi - 1 : xi;
}
Grad2 *_newGrad2Arr(unsigned int size)
{
return (Grad2 *)malloc(sizeof(Grad2) * size);
}
short *_newShortArr(unsigned int size)
{
return (short *)malloc(sizeof(short) * size);
}
Grad2 _newGrad2(double dx, double dy)
{
Grad2 grad2;
grad2.dx = dx;
grad2.dy = dy;
return grad2;
}
Grad2 *_newGrad2ConstArray()
{
Grad2 *arr = (Grad2 *)malloc(sizeof(Grad2) * 24);
int i = 0;
arr[i++] = _newGrad2(0.130526192220052, 0.99144486137381);
arr[i++] = _newGrad2(0.38268343236509, 0.923879532511287);
arr[i++] = _newGrad2(0.608761429008721, 0.793353340291235);
arr[i++] = _newGrad2(0.793353340291235, 0.608761429008721);
arr[i++] = _newGrad2(0.923879532511287, 0.38268343236509);
arr[i++] = _newGrad2(0.99144486137381, 0.130526192220051);
arr[i++] = _newGrad2(0.99144486137381, -0.130526192220051);
arr[i++] = _newGrad2(0.923879532511287, -0.38268343236509);
arr[i++] = _newGrad2(0.793353340291235, -0.60876142900872);
arr[i++] = _newGrad2(0.608761429008721, -0.793353340291235);
arr[i++] = _newGrad2(0.38268343236509, -0.923879532511287);
arr[i++] = _newGrad2(0.130526192220052, -0.99144486137381);
arr[i++] = _newGrad2(-0.130526192220052, -0.99144486137381);
arr[i++] = _newGrad2(-0.38268343236509, -0.923879532511287);
arr[i++] = _newGrad2(-0.608761429008721, -0.793353340291235);
arr[i++] = _newGrad2(-0.793353340291235, -0.608761429008721);
arr[i++] = _newGrad2(-0.923879532511287, -0.38268343236509);
arr[i++] = _newGrad2(-0.99144486137381, -0.130526192220052);
arr[i++] = _newGrad2(-0.99144486137381, 0.130526192220051);
arr[i++] = _newGrad2(-0.923879532511287, 0.38268343236509);
arr[i++] = _newGrad2(-0.793353340291235, 0.608761429008721);
arr[i++] = _newGrad2(-0.608761429008721, 0.793353340291235);
arr[i++] = _newGrad2(-0.38268343236509, 0.923879532511287);
arr[i++] = _newGrad2(-0.130526192220052, 0.99144486137381);
Grad2 *gradients2D = _newGrad2Arr(PSIZE);
for (int i = 0; i < 24; i++)
{
arr[i].dx /= N2;
arr[i].dy /= N2;
}
for (int i = 0; i < PSIZE; i++)
{
gradients2D[i] = arr[i % 24];
}
return gradients2D;
}
LatticePoint2D *_newLatticePoint2D(int xsv, int ysv)
{
LatticePoint2D *plp2D = (LatticePoint2D *)malloc(sizeof(LatticePoint2D));
plp2D->xsv = xsv;
plp2D->ysv = ysv;
double ssv = (xsv + ysv) * -0.211324865405187;
plp2D->dx = -xsv - ssv;
plp2D->dy = -ysv - ssv;
return plp2D;
}
LatticePoint2D **_newLatticePoint2DConstArray()
{
LatticePoint2D **plp2DArr = (LatticePoint2D **)malloc(sizeof(LatticePoint2D *) * 4);
plp2DArr[0] = _newLatticePoint2D(1, 0);
plp2DArr[1] = _newLatticePoint2D(0, 0);
plp2DArr[2] = _newLatticePoint2D(1, 1);
plp2DArr[3] = _newLatticePoint2D(0, 1);
return plp2DArr;
}
/*
* Noise Evaluators
*/
/**
* 2D Simplex noise base.
* Lookup table implementation inspired by DigitalShadow.
*/
double _noise2_Base(OpenSimplexEnv *ose, OpenSimplexGradients *osg, double xs, double ys)
{
double value = 0;
// Get base points and offsets
int xsb = _fastFloor(xs), ysb = _fastFloor(ys);
double xsi = xs - xsb, ysi = ys - ysb;
// Index to point list
int index = (int)((ysi - xsi) / 2 + 1);
double ssi = (xsi + ysi) * -0.211324865405187;
double xi = xsi + ssi, yi = ysi + ssi;
// Point contributions
for (int i = 0; i < 3; i++)
{
LatticePoint2D *c = ose->LOOKUP_2D[index + i];
double dx = xi + c->dx, dy = yi + c->dy;
double attn = 0.5 - dx * dx - dy * dy;
if (attn <= 0)
continue;
int pxm = (xsb + c->xsv) & PMASK, pym = (ysb + c->ysv) & PMASK;
Grad2 grad = osg->permGrad2[osg->perm[pxm] ^ pym];
double extrapolation = grad.dx * dx + grad.dy * dy;
attn *= attn;
value += attn * attn * extrapolation;
}
return value;
}
/**
* 2D Simplex noise, standard lattice orientation.
*/
double noise2(OpenSimplexEnv *ose, OpenSimplexGradients *osg, double x, double y)
{
// Get points for A2* lattice
double s = 0.366025403784439 * (x + y);
double xs = x + s, ys = y + s;
return _noise2_Base(ose, osg, xs, ys);
}
/**
* 2D Simplex noise, with Y pointing down the main diagonal.
* Might be better for a 2D sandbox style game, where Y is vertical.
* Probably slightly less optimal for heightmaps or continent maps.
*/
double noise2_XBeforeY(OpenSimplexEnv *ose, OpenSimplexGradients *osg, double x, double y)
{
// Skew transform and rotation baked into one.
double xx = x * 0.7071067811865476;
double yy = y * 1.224744871380249;
return _noise2_Base(ose, osg, yy + xx, yy - xx);
}
OpenSimplexEnv *initOpenSimplex()
{
OpenSimplexEnv *ose = (OpenSimplexEnv *)malloc(sizeof(OpenSimplexEnv));
ose->GRADIENTS_2D = _newGrad2ConstArray();
ose->LOOKUP_2D = _newLatticePoint2DConstArray();
return ose;
}
OpenSimplexGradients *newOpenSimplexGradients(OpenSimplexEnv *ose, long seed)
{
OpenSimplexGradients *osg = (OpenSimplexGradients *)malloc(sizeof(OpenSimplexGradients));
osg->perm = _newShortArr(PSIZE);
osg->permGrad2 = _newGrad2Arr(PSIZE);
short *source = _newShortArr(PSIZE);
for (short i = 0; i < PSIZE; i++)
{
source[i] = i;
}
for (int i = PSIZE - 1; i >= 0; i--)
{
seed = seed * 6364136223846793005L + 1442695040888963407L;
int r = (int)((seed + 31) % (i + 1));
if (r < 0)
{
r += (i + 1);
}
osg->perm[i] = source[r];
osg->permGrad2[i] = ose->GRADIENTS_2D[osg->perm[i]];
source[r] = source[i];
}
return osg;
}

View file

@ -1,117 +1,30 @@
/*******************************************************************************************
*
* raylib [text] example - Input Box
*
* This example has been created using raylib 3.5 (www.raylib.com)
* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
*
* Copyright (c) 2017 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
#include "raylib.h" #include "raylib.h"
#include "OpenSimplex2F.h"
#define MAX_INPUT_CHARS 21 #define initialScreenWidth 900
#define DEBUG 1 #define initialScreenHeight 900
int main(void) int main(void)
{ {
// Initialization // Initialization
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
const float holdBackspace = 0.5f; SetConfigFlags(FLAG_WINDOW_RESIZABLE);
const float repeatBackspace = 0.05f; InitWindow(initialScreenWidth, initialScreenHeight, "raylib [core] example - basic window");
SetConfigFlags(FLAG_VSYNC_HINT); SetTargetFPS(60); // Set our game to run at 60 frames-per-second
InitWindow(screenWidth, screenHeight, "raylib [text] example - input box");
char name[MAX_INPUT_CHARS + 1] = "\0"; // NOTE: One extra space required for null terminator char '\0' OpenSimplexEnv *simplexEnv = initOpenSimplex();
int letterCount = 0; OpenSimplexGradients *simplexGradiants = newOpenSimplexGradients(simplexEnv, 0);
Rectangle textBox = {screenWidth / 2.0f - ((MAX_INPUT_CHARS + 1) * 10.0f), 180, 25.0f * MAX_INPUT_CHARS, 50};
bool mouseOnText = false;
int framesCounter = 0;
float backspaceTimer = holdBackspace;
#if DEBUG
SetTraceLogLevel(LOG_ALL);
#endif
// SetTargetFPS(60); // Set our desired framerate.
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
TraceLog(LOG_INFO, TextFormat("%i", GetScreenWidth()));
// Main game loop // Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key while (!WindowShouldClose()) // Detect window close button or ESC key
{ {
// Update // Update
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
if (CheckCollisionPointRec(GetMousePosition(), textBox))
mouseOnText = true;
else
mouseOnText = false;
if (mouseOnText)
{
// Set the window's cursor to the I-Beam
SetMouseCursor(MOUSE_CURSOR_IBEAM);
// Get char pressed (unicode character) on the queue
int key = GetCharPressed();
// Check if more characters have been pressed on the same frame
while (key > 0)
{
// NOTE: Only allow keys in range [32..125]
if ((key >= 32) && (key <= 125) && (letterCount < MAX_INPUT_CHARS))
{
name[letterCount] = (char)key;
name[letterCount + 1] = '\0'; // Add null terminator at the end of the string.
letterCount++;
}
key = GetCharPressed(); // Check next character in the queue
}
bool backspace = false;
if (IsKeyPressed(KEY_BACKSPACE))
{
backspace = true;
backspaceTimer = holdBackspace;
}
else if (IsKeyDown(KEY_BACKSPACE))
{
backspaceTimer -= GetFrameTime();
if (backspaceTimer <= 0.0f)
{
backspace = true;
backspaceTimer = repeatBackspace;
}
TraceLog(LOG_INFO, TextFormat("backspaceTimer: %f", backspaceTimer));
}
else if (IsKeyReleased(KEY_BACKSPACE))
{
backspaceTimer = holdBackspace;
}
if (backspace)
{
letterCount--;
if (letterCount < 0)
letterCount = 0;
name[letterCount] = '\0';
}
}
else
SetMouseCursor(MOUSE_CURSOR_DEFAULT);
if (mouseOnText)
framesCounter++;
else
framesCounter = 0;
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Draw // Draw
@ -120,29 +33,14 @@ int main(void)
ClearBackground(RAYWHITE); ClearBackground(RAYWHITE);
DrawText("PLACE MOUSE OVER INPUT BOX!", 240, 140, 20, GRAY); for (int x = 0; x < GetScreenWidth(); x++)
DrawRectangleRec(textBox, LIGHTGRAY);
if (mouseOnText)
DrawRectangleLines((int)textBox.x, (int)textBox.y, (int)textBox.width, (int)textBox.height, RED);
else
DrawRectangleLines((int)textBox.x, (int)textBox.y, (int)textBox.width, (int)textBox.height, DARKGRAY);
DrawText(name, (int)textBox.x + 5, (int)textBox.y + 8, 40, MAROON);
const char charCounter[sizeof("INPUT CHARS: /") + 6] = TextFormat("INPUT CHARS: %i/%i", letterCount, MAX_INPUT_CHARS);
DrawText(charCounter, (screenWidth - MeasureText(charCounter, 20)) / 2, 250, 20, DARKGRAY);
if (mouseOnText)
{ {
if (letterCount < MAX_INPUT_CHARS) for (int y = 0; y < GetScreenHeight(); y++)
{ {
// Draw blinking underscore char int noise = (noise2(simplexEnv, simplexGradiants, x / 64.0, y / 64.0) + 1.0) / 2 * 255;
if (((framesCounter / 20) % 2) == 0)
DrawText("_", (int)textBox.x + 8 + MeasureText(name, 40), (int)textBox.y + 12, 40, MAROON); DrawPixel(x, y, (Color){noise, noise, noise, 255});
} }
else
DrawText("Press BACKSPACE to delete chars...", 230, 300, 20, GRAY);
} }
EndDrawing(); EndDrawing();
@ -155,17 +53,4 @@ int main(void)
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
return 0; return 0;
}
// Check if any key is pressed
// NOTE: We limit keys check to keys between 32 (KEY_SPACE) and 126
bool IsAnyKeyPressed()
{
bool keyPressed = false;
int key = GetKeyPressed();
if ((key >= 32) && (key <= 126))
keyPressed = true;
return keyPressed;
} }