{"TotalCount":14,"Files":[{"Ident":"sturnus.terraingenerationtool","Path":"Editor/OpenSimplex2S.cs","FileName":"OpenSimplex2S.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using System.Runtime.CompilerServices;\n\npublic static class OpenSimplex2S\n{\n\tprivate const long PRIME_X = 0x5205402B9270C86FL;\n\tprivate const long PRIME_Y = 0x598CD327003817B5L;\n\tprivate const long PRIME_Z = 0x5BCC226E9FA0BACBL;\n\tprivate const long PRIME_W = 0x56CC5227E58F554BL;\n\tprivate const long HASH_MULTIPLIER = 0x53A3F72DEEC546F5L;\n\tprivate const long SEED_FLIP_3D = -0x52D547B2E96ED629L;\n\n\tprivate const double ROOT2OVER2 = 0.7071067811865476;\n\tprivate const double SKEW_2D = 0.366025403784439;\n\tprivate const double UNSKEW_2D = -0.21132486540518713;\n\n\tprivate const double ROOT3OVER3 = 0.577350269189626;\n\tprivate const double FALLBACK_ROTATE3 = 2.0 / 3.0;\n\tprivate const double ROTATE3_ORTHOGONALIZER = UNSKEW_2D;\n\n\tprivate const float SKEW_4D = 0.309016994374947f;\n\tprivate const float UNSKEW_4D = -0.138196601125011f;\n\n\tprivate const int N_GRADS_2D_EXPONENT = 7;\n\tprivate const int N_GRADS_3D_EXPONENT = 8;\n\tprivate const int N_GRADS_4D_EXPONENT = 9;\n\tprivate const int N_GRADS_2D = 1 \u003C\u003C N_GRADS_2D_EXPONENT;\n\tprivate const int N_GRADS_3D = 1 \u003C\u003C N_GRADS_3D_EXPONENT;\n\tprivate const int N_GRADS_4D = 1 \u003C\u003C N_GRADS_4D_EXPONENT;\n\n\tprivate const double NORMALIZER_2D = 0.05481866495625118;\n\tprivate const double NORMALIZER_3D = 0.2781926117527186;\n\tprivate const double NORMALIZER_4D = 0.11127401889945551;\n\n\tprivate const float RSQUARED_2D = 2.0f / 3.0f;\n\tprivate const float RSQUARED_3D = 3.0f / 4.0f;\n\tprivate const float RSQUARED_4D = 4.0f / 5.0f;\n\n\t/*\n     * Noise Evaluators\n     */\n\n\t/**\n     * 2D OpenSimplex2S/SuperSimplex noise, standard lattice orientation.\n     */\n\tpublic static float Noise2( long seed, double x, double y )\n\t{\n\t\t// Get points for A2* lattice\n\t\tdouble s = SKEW_2D * (x \u002B y);\n\t\tdouble xs = x \u002B s, ys = y \u002B s;\n\n\t\treturn Noise2_UnskewedBase( seed, xs, ys );\n\t}\n\n\t/**\n     * 2D OpenSimplex2S/SuperSimplex noise, with Y pointing down the main diagonal.\n     * Might be better for a 2D sandbox style game, where Y is vertical.\n     * Probably slightly less optimal for heightmaps or continent maps,\n     * unless your map is centered around an equator. It\u0027s a slight\n     * difference, but the option is here to make it easy.\n     */\n\tpublic static float Noise2_ImproveX( long seed, double x, double y )\n\t{\n\t\t// Skew transform and rotation baked into one.\n\t\tdouble xx = x * ROOT2OVER2;\n\t\tdouble yy = y * (ROOT2OVER2 * (1 \u002B 2 * SKEW_2D));\n\n\t\treturn Noise2_UnskewedBase( seed, yy \u002B xx, yy - xx );\n\t}\n\n\t/**\n     * 2D  OpenSimplex2S/SuperSimplex noise base.\n     */\n\tprivate static float Noise2_UnskewedBase( long seed, double xs, double ys )\n\t{\n\t\t// Get base points and offsets.\n\t\tint xsb = FastFloor( xs ), ysb = FastFloor( ys );\n\t\tfloat xi = (float)(xs - xsb), yi = (float)(ys - ysb);\n\n\t\t// Prime pre-multiplication for hash.\n\t\tlong xsbp = xsb * PRIME_X, ysbp = ysb * PRIME_Y;\n\n\t\t// Unskew.\n\t\tfloat t = (xi \u002B yi) * (float)UNSKEW_2D;\n\t\tfloat dx0 = xi \u002B t, dy0 = yi \u002B t;\n\n\t\t// First vertex.\n\t\tfloat a0 = RSQUARED_2D - dx0 * dx0 - dy0 * dy0;\n\t\tfloat value = (a0 * a0) * (a0 * a0) * Grad( seed, xsbp, ysbp, dx0, dy0 );\n\n\t\t// Second vertex.\n\t\tfloat a1 = (float)(2 * (1 \u002B 2 * UNSKEW_2D) * (1 / UNSKEW_2D \u002B 2)) * t \u002B ((float)(-2 * (1 \u002B 2 * UNSKEW_2D) * (1 \u002B 2 * UNSKEW_2D)) \u002B a0);\n\t\tfloat dx1 = dx0 - (float)(1 \u002B 2 * UNSKEW_2D);\n\t\tfloat dy1 = dy0 - (float)(1 \u002B 2 * UNSKEW_2D);\n\t\tvalue \u002B= (a1 * a1) * (a1 * a1) * Grad( seed, xsbp \u002B PRIME_X, ysbp \u002B PRIME_Y, dx1, dy1 );\n\n\t\t// Third and fourth vertices.\n\t\t// Nested conditionals were faster than compact bit logic/arithmetic.\n\t\tfloat xmyi = xi - yi;\n\t\tif ( t \u003C UNSKEW_2D )\n\t\t{\n\t\t\tif ( xi \u002B xmyi \u003E 1 )\n\t\t\t{\n\t\t\t\tfloat dx2 = dx0 - (float)(3 * UNSKEW_2D \u002B 2);\n\t\t\t\tfloat dy2 = dy0 - (float)(3 * UNSKEW_2D \u002B 1);\n\t\t\t\tfloat a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;\n\t\t\t\tif ( a2 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a2 * a2) * (a2 * a2) * Grad( seed, xsbp \u002B (PRIME_X \u003C\u003C 1), ysbp \u002B PRIME_Y, dx2, dy2 );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfloat dx2 = dx0 - (float)UNSKEW_2D;\n\t\t\t\tfloat dy2 = dy0 - (float)(UNSKEW_2D \u002B 1);\n\t\t\t\tfloat a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;\n\t\t\t\tif ( a2 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a2 * a2) * (a2 * a2) * Grad( seed, xsbp, ysbp \u002B PRIME_Y, dx2, dy2 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( yi - xmyi \u003E 1 )\n\t\t\t{\n\t\t\t\tfloat dx3 = dx0 - (float)(3 * UNSKEW_2D \u002B 1);\n\t\t\t\tfloat dy3 = dy0 - (float)(3 * UNSKEW_2D \u002B 2);\n\t\t\t\tfloat a3 = RSQUARED_2D - dx3 * dx3 - dy3 * dy3;\n\t\t\t\tif ( a3 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a3 * a3) * (a3 * a3) * Grad( seed, xsbp \u002B PRIME_X, ysbp \u002B (PRIME_Y \u003C\u003C 1), dx3, dy3 );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfloat dx3 = dx0 - (float)(UNSKEW_2D \u002B 1);\n\t\t\t\tfloat dy3 = dy0 - (float)UNSKEW_2D;\n\t\t\t\tfloat a3 = RSQUARED_2D - dx3 * dx3 - dy3 * dy3;\n\t\t\t\tif ( a3 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a3 * a3) * (a3 * a3) * Grad( seed, xsbp \u002B PRIME_X, ysbp, dx3, dy3 );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif ( xi \u002B xmyi \u003C 0 )\n\t\t\t{\n\t\t\t\tfloat dx2 = dx0 \u002B (float)(1 \u002B UNSKEW_2D);\n\t\t\t\tfloat dy2 = dy0 \u002B (float)UNSKEW_2D;\n\t\t\t\tfloat a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;\n\t\t\t\tif ( a2 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a2 * a2) * (a2 * a2) * Grad( seed, xsbp - PRIME_X, ysbp, dx2, dy2 );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfloat dx2 = dx0 - (float)(UNSKEW_2D \u002B 1);\n\t\t\t\tfloat dy2 = dy0 - (float)UNSKEW_2D;\n\t\t\t\tfloat a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;\n\t\t\t\tif ( a2 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a2 * a2) * (a2 * a2) * Grad( seed, xsbp \u002B PRIME_X, ysbp, dx2, dy2 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( yi \u003C xmyi )\n\t\t\t{\n\t\t\t\tfloat dx2 = dx0 \u002B (float)UNSKEW_2D;\n\t\t\t\tfloat dy2 = dy0 \u002B (float)(UNSKEW_2D \u002B 1);\n\t\t\t\tfloat a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;\n\t\t\t\tif ( a2 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a2 * a2) * (a2 * a2) * Grad( seed, xsbp, ysbp - PRIME_Y, dx2, dy2 );\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfloat dx2 = dx0 - (float)UNSKEW_2D;\n\t\t\t\tfloat dy2 = dy0 - (float)(UNSKEW_2D \u002B 1);\n\t\t\t\tfloat a2 = RSQUARED_2D - dx2 * dx2 - dy2 * dy2;\n\t\t\t\tif ( a2 \u003E 0 )\n\t\t\t\t{\n\t\t\t\t\tvalue \u002B= (a2 * a2) * (a2 * a2) * Grad( seed, xsbp, ysbp \u002B PRIME_Y, dx2, dy2 );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn value;\n\t}\n\n\t/**\n     * 3D OpenSimplex2S/SuperSimplex noise, with better visual isotropy in (X, Y).\n     * Recommended for 3D terrain and time-varied animations.\n     * The Z coordinate should always be the \u0022different\u0022 coordinate in whatever your use case is.\n     * If Y is vertical in world coordinates, call Noise3_ImproveXZ(x, z, Y) or use Noise3_XZBeforeY.\n     * If Z is vertical in world coordinates, call Noise3_ImproveXZ(x, y, Z).\n     * For a time varied animation, call Noise3_ImproveXY(x, y, T).\n     */\n\tpublic static float Noise3_ImproveXY( long seed, double x, double y, double z )\n\t{\n\t\t// Re-orient the cubic lattices without skewing, so Z points up the main lattice diagonal,\n\t\t// and the planes formed by XY are moved far out of alignment with the cube faces.\n\t\t// Orthonormal rotation. Not a skew transform.\n\t\tdouble xy = x \u002B y;\n\t\tdouble s2 = xy * ROTATE3_ORTHOGONALIZER;\n\t\tdouble zz = z * ROOT3OVER3;\n\t\tdouble xr = x \u002B s2 \u002B zz;\n\t\tdouble yr = y \u002B s2 \u002B zz;\n\t\tdouble zr = xy * -ROOT3OVER3 \u002B zz;\n\n\t\t// Evaluate both lattices to form a BCC lattice.\n\t\treturn Noise3_UnrotatedBase( seed, xr, yr, zr );\n\t}\n\n\t/**\n     * 3D OpenSimplex2S/SuperSimplex noise, with better visual isotropy in (X, Z).\n     * Recommended for 3D terrain and time-varied animations.\n     * The Y coordinate should always be the \u0022different\u0022 coordinate in whatever your use case is.\n     * If Y is vertical in world coordinates, call Noise3_ImproveXZ(x, Y, z).\n     * If Z is vertical in world coordinates, call Noise3_ImproveXZ(x, Z, y) or use Noise3_ImproveXY.\n     * For a time varied animation, call Noise3_ImproveXZ(x, T, y) or use Noise3_ImproveXY.\n     */\n\tpublic static float Noise3_ImproveXZ( long seed, double x, double y, double z )\n\t{\n\t\t// Re-orient the cubic lattices without skewing, so Y points up the main lattice diagonal,\n\t\t// and the planes formed by XZ are moved far out of alignment with the cube faces.\n\t\t// Orthonormal rotation. Not a skew transform.\n\t\tdouble xz = x \u002B z;\n\t\tdouble s2 = xz * -0.211324865405187;\n\t\tdouble yy = y * ROOT3OVER3;\n\t\tdouble xr = x \u002B s2 \u002B yy;\n\t\tdouble zr = z \u002B s2 \u002B yy;\n\t\tdouble yr = xz * -ROOT3OVER3 \u002B yy;\n\n\t\t// Evaluate both lattices to form a BCC lattice.\n\t\treturn Noise3_UnrotatedBase( seed, xr, yr, zr );\n\t}\n\n\t/**\n     * 3D OpenSimplex2S/SuperSimplex noise, fallback rotation option\n     * Use Noise3_ImproveXY or Noise3_ImproveXZ instead, wherever appropriate.\n     * They have less diagonal bias. This function\u0027s best use is as a fallback.\n     */\n\tpublic static float Noise3_Fallback( long seed, double x, double y, double z )\n\t{\n\t\t// Re-orient the cubic lattices via rotation, to produce a familiar look.\n\t\t// Orthonormal rotation. Not a skew transform.\n\t\tdouble r = FALLBACK_ROTATE3 * (x \u002B y \u002B z);\n\t\tdouble xr = r - x, yr = r - y, zr = r - z;\n\n\t\t// Evaluate both lattices to form a BCC lattice.\n\t\treturn Noise3_UnrotatedBase( seed, xr, yr, zr );\n\t}\n\n\t/**\n     * Generate overlapping cubic lattices for 3D Re-oriented BCC noise.\n     * Lookup table implementation inspired by DigitalShadow.\n     * It was actually faster to narrow down the points in the loop itself,\n     * than to build up the index with enough info to isolate 8 points.\n     */\n\tprivate static float Noise3_UnrotatedBase( long seed, double xr, double yr, double zr )\n\t{\n\t\t// Get base points and offsets.\n\t\tint xrb = FastFloor( xr ), yrb = FastFloor( yr ), zrb = FastFloor( zr );\n\t\tfloat xi = (float)(xr - xrb), yi = (float)(yr - yrb), zi = (float)(zr - zrb);\n\n\t\t// Prime pre-multiplication for hash. Also flip seed for second lattice copy.\n\t\tlong xrbp = xrb * PRIME_X, yrbp = yrb * PRIME_Y, zrbp = zrb * PRIME_Z;\n\t\tlong seed2 = seed ^ -0x52D547B2E96ED629L;\n\n\t\t// -1 if positive, 0 if negative.\n\t\tint xNMask = (int)(-0.5f - xi), yNMask = (int)(-0.5f - yi), zNMask = (int)(-0.5f - zi);\n\n\t\t// First vertex.\n\t\tfloat x0 = xi \u002B xNMask;\n\t\tfloat y0 = yi \u002B yNMask;\n\t\tfloat z0 = zi \u002B zNMask;\n\t\tfloat a0 = RSQUARED_3D - x0 * x0 - y0 * y0 - z0 * z0;\n\t\tfloat value = (a0 * a0) * (a0 * a0) * Grad( seed,\n\t\t\txrbp \u002B (xNMask \u0026 PRIME_X), yrbp \u002B (yNMask \u0026 PRIME_Y), zrbp \u002B (zNMask \u0026 PRIME_Z), x0, y0, z0 );\n\n\t\t// Second vertex.\n\t\tfloat x1 = xi - 0.5f;\n\t\tfloat y1 = yi - 0.5f;\n\t\tfloat z1 = zi - 0.5f;\n\t\tfloat a1 = RSQUARED_3D - x1 * x1 - y1 * y1 - z1 * z1;\n\t\tvalue \u002B= (a1 * a1) * (a1 * a1) * Grad( seed2,\n\t\t\txrbp \u002B PRIME_X, yrbp \u002B PRIME_Y, zrbp \u002B PRIME_Z, x1, y1, z1 );\n\n\t\t// Shortcuts for building the remaining falloffs.\n\t\t// Derived by subtracting the polynomials with the offsets plugged in.\n\t\tfloat xAFlipMask0 = ((xNMask | 1) \u003C\u003C 1) * x1;\n\t\tfloat yAFlipMask0 = ((yNMask | 1) \u003C\u003C 1) * y1;\n\t\tfloat zAFlipMask0 = ((zNMask | 1) \u003C\u003C 1) * z1;\n\t\tfloat xAFlipMask1 = (-2 - (xNMask \u003C\u003C 2)) * x1 - 1.0f;\n\t\tfloat yAFlipMask1 = (-2 - (yNMask \u003C\u003C 2)) * y1 - 1.0f;\n\t\tfloat zAFlipMask1 = (-2 - (zNMask \u003C\u003C 2)) * z1 - 1.0f;\n\n\t\tbool skip5 = false;\n\t\tfloat a2 = xAFlipMask0 \u002B a0;\n\t\tif ( a2 \u003E 0 )\n\t\t{\n\t\t\tfloat x2 = x0 - (xNMask | 1);\n\t\t\tfloat y2 = y0;\n\t\t\tfloat z2 = z0;\n\t\t\tvalue \u002B= (a2 * a2) * (a2 * a2) * Grad( seed,\n\t\t\t\txrbp \u002B (~xNMask \u0026 PRIME_X), yrbp \u002B (yNMask \u0026 PRIME_Y), zrbp \u002B (zNMask \u0026 PRIME_Z), x2, y2, z2 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat a3 = yAFlipMask0 \u002B zAFlipMask0 \u002B a0;\n\t\t\tif ( a3 \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat x3 = x0;\n\t\t\t\tfloat y3 = y0 - (yNMask | 1);\n\t\t\t\tfloat z3 = z0 - (zNMask | 1);\n\t\t\t\tvalue \u002B= (a3 * a3) * (a3 * a3) * Grad( seed,\n\t\t\t\t\txrbp \u002B (xNMask \u0026 PRIME_X), yrbp \u002B (~yNMask \u0026 PRIME_Y), zrbp \u002B (~zNMask \u0026 PRIME_Z), x3, y3, z3 );\n\t\t\t}\n\n\t\t\tfloat a4 = xAFlipMask1 \u002B a1;\n\t\t\tif ( a4 \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat x4 = (xNMask | 1) \u002B x1;\n\t\t\t\tfloat y4 = y1;\n\t\t\t\tfloat z4 = z1;\n\t\t\t\tvalue \u002B= (a4 * a4) * (a4 * a4) * Grad( seed2,\n\t\t\t\t\txrbp \u002B (xNMask \u0026 unchecked(PRIME_X * 2)), yrbp \u002B PRIME_Y, zrbp \u002B PRIME_Z, x4, y4, z4 );\n\t\t\t\tskip5 = true;\n\t\t\t}\n\t\t}\n\n\t\tbool skip9 = false;\n\t\tfloat a6 = yAFlipMask0 \u002B a0;\n\t\tif ( a6 \u003E 0 )\n\t\t{\n\t\t\tfloat x6 = x0;\n\t\t\tfloat y6 = y0 - (yNMask | 1);\n\t\t\tfloat z6 = z0;\n\t\t\tvalue \u002B= (a6 * a6) * (a6 * a6) * Grad( seed,\n\t\t\t\txrbp \u002B (xNMask \u0026 PRIME_X), yrbp \u002B (~yNMask \u0026 PRIME_Y), zrbp \u002B (zNMask \u0026 PRIME_Z), x6, y6, z6 );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat a7 = xAFlipMask0 \u002B zAFlipMask0 \u002B a0;\n\t\t\tif ( a7 \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat x7 = x0 - (xNMask | 1);\n\t\t\t\tfloat y7 = y0;\n\t\t\t\tfloat z7 = z0 - (zNMask | 1);\n\t\t\t\tvalue \u002B= (a7 * a7) * (a7 * a7) * Grad( seed,\n\t\t\t\t\txrbp \u002B (~xNMask \u0026 PRIME_X), yrbp \u002B (yNMask \u0026 PRIME_Y), zrbp \u002B (~zNMask \u0026 PRIME_Z), x7, y7, z7 );\n\t\t\t}\n\n\t\t\tfloat a8 = yAFlipMask1 \u002B a1;\n\t\t\tif ( a8 \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat x8 = x1;\n\t\t\t\tfloat y8 = (yNMask | 1) \u002B y1;\n\t\t\t\tfloat z8 = z1;\n\t\t\t\tvalue \u002B= (a8 * a8) * (a8 * a8) * Grad( seed2,\n\t\t\t\t\txrbp \u002B PRIME_X, yrbp \u002B (yNMask \u0026 (PRIME_Y \u003C\u003C 1)), zrbp \u002B PRIME_Z, x8, y8, z8 );\n\t\t\t\tskip9 = true;\n\t\t\t}\n\t\t}\n\n\t\tbool skipD = false;\n\t\tfloat aA = zAFlipMask0 \u002B a0;\n\t\tif ( aA \u003E 0 )\n\t\t{\n\t\t\tfloat xA = x0;\n\t\t\tfloat yA = y0;\n\t\t\tfloat zA = z0 - (zNMask | 1);\n\t\t\tvalue \u002B= (aA * aA) * (aA * aA) * Grad( seed,\n\t\t\t\txrbp \u002B (xNMask \u0026 PRIME_X), yrbp \u002B (yNMask \u0026 PRIME_Y), zrbp \u002B (~zNMask \u0026 PRIME_Z), xA, yA, zA );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tfloat aB = xAFlipMask0 \u002B yAFlipMask0 \u002B a0;\n\t\t\tif ( aB \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat xB = x0 - (xNMask | 1);\n\t\t\t\tfloat yB = y0 - (yNMask | 1);\n\t\t\t\tfloat zB = z0;\n\t\t\t\tvalue \u002B= (aB * aB) * (aB * aB) * Grad( seed,\n\t\t\t\t\txrbp \u002B (~xNMask \u0026 PRIME_X), yrbp \u002B (~yNMask \u0026 PRIME_Y), zrbp \u002B (zNMask \u0026 PRIME_Z), xB, yB, zB );\n\t\t\t}\n\n\t\t\tfloat aC = zAFlipMask1 \u002B a1;\n\t\t\tif ( aC \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat xC = x1;\n\t\t\t\tfloat yC = y1;\n\t\t\t\tfloat zC = (zNMask | 1) \u002B z1;\n\t\t\t\tvalue \u002B= (aC * aC) * (aC * aC) * Grad( seed2,\n\t\t\t\t\txrbp \u002B PRIME_X, yrbp \u002B PRIME_Y, zrbp \u002B (zNMask \u0026 (PRIME_Z \u003C\u003C 1)), xC, yC, zC );\n\t\t\t\tskipD = true;\n\t\t\t}\n\t\t}\n\n\t\tif ( !skip5 )\n\t\t{\n\t\t\tfloat a5 = yAFlipMask1 \u002B zAFlipMask1 \u002B a1;\n\t\t\tif ( a5 \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat x5 = x1;\n\t\t\t\tfloat y5 = (yNMask | 1) \u002B y1;\n\t\t\t\tfloat z5 = (zNMask | 1) \u002B z1;\n\t\t\t\tvalue \u002B= (a5 * a5) * (a5 * a5) * Grad( seed2,\n\t\t\t\t\txrbp \u002B PRIME_X, yrbp \u002B (yNMask \u0026 (PRIME_Y \u003C\u003C 1)), zrbp \u002B (zNMask \u0026 (PRIME_Z \u003C\u003C 1)), x5, y5, z5 );\n\t\t\t}\n\t\t}\n\n\t\tif ( !skip9 )\n\t\t{\n\t\t\tfloat a9 = xAFlipMask1 \u002B zAFlipMask1 \u002B a1;\n\t\t\tif ( a9 \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat x9 = (xNMask | 1) \u002B x1;\n\t\t\t\tfloat y9 = y1;\n\t\t\t\tfloat z9 = (zNMask | 1) \u002B z1;\n\t\t\t\tvalue \u002B= (a9 * a9) * (a9 * a9) * Grad( seed2,\n\t\t\t\t\txrbp \u002B (xNMask \u0026 unchecked(PRIME_X * 2)), yrbp \u002B PRIME_Y, zrbp \u002B (zNMask \u0026 (PRIME_Z \u003C\u003C 1)), x9, y9, z9 );\n\t\t\t}\n\t\t}\n\n\t\tif ( !skipD )\n\t\t{\n\t\t\tfloat aD = xAFlipMask1 \u002B yAFlipMask1 \u002B a1;\n\t\t\tif ( aD \u003E 0 )\n\t\t\t{\n\t\t\t\tfloat xD = (xNMask | 1) \u002B x1;\n\t\t\t\tfloat yD = (yNMask | 1) \u002B y1;\n\t\t\t\tfloat zD = z1;\n\t\t\t\tvalue \u002B= (aD * aD) * (aD * aD) * Grad( seed2,\n\t\t\t\t\txrbp \u002B (xNMask \u0026 (PRIME_X \u003C\u003C 1)), yrbp \u002B (yNMask \u0026 (PRIME_Y \u003C\u003C 1)), zrbp \u002B PRIME_Z, xD, yD, zD );\n\t\t\t}\n\t\t}\n\n\t\treturn value;\n\t}\n\n\t/**\n     * 4D SuperSimplex noise, with XYZ oriented like Noise3_ImproveXY\n     * and W for an extra degree of freedom. W repeats eventually.\n     * Recommended for time-varied animations which texture a 3D object (W=time)\n     * in a space where Z is vertical\n     */\n\tpublic static float Noise4_ImproveXYZ_ImproveXY( long seed, double x, double y, double z, double w )\n\t{\n\t\tdouble xy = x \u002B y;\n\t\tdouble s2 = xy * -0.21132486540518699998;\n\t\tdouble zz = z * 0.28867513459481294226;\n\t\tdouble ww = w * 1.118033988749894;\n\t\tdouble xr = x \u002B (zz \u002B ww \u002B s2), yr = y \u002B (zz \u002B ww \u002B s2);\n\t\tdouble zr = xy * -0.57735026918962599998 \u002B (zz \u002B ww);\n\t\tdouble wr = z * -0.866025403784439 \u002B ww;\n\n\t\treturn Noise4_UnskewedBase( seed, xr, yr, zr, wr );\n\t}\n\n\t/**\n     * 4D SuperSimplex noise, with XYZ oriented like Noise3_ImproveXZ\n     * and W for an extra degree of freedom. W repeats eventually.\n     * Recommended for time-varied animations which texture a 3D object (W=time)\n     * in a space where Y is vertical\n     */\n\tpublic static float Noise4_ImproveXYZ_ImproveXZ( long seed, double x, double y, double z, double w )\n\t{\n\t\tdouble xz = x \u002B z;\n\t\tdouble s2 = xz * -0.21132486540518699998;\n\t\tdouble yy = y * 0.28867513459481294226;\n\t\tdouble ww = w * 1.118033988749894;\n\t\tdouble xr = x \u002B (yy \u002B ww \u002B s2), zr = z \u002B (yy \u002B ww \u002B s2);\n\t\tdouble yr = xz * -0.57735026918962599998 \u002B (yy \u002B ww);\n\t\tdouble wr = y * -0.866025403784439 \u002B ww;\n\n\t\treturn Noise4_UnskewedBase( seed, xr, yr, zr, wr );\n\t}\n\n\t/**\n     * 4D SuperSimplex noise, with XYZ oriented like Noise3_Fallback\n     * and W for an extra degree of freedom. W repeats eventually.\n     * Recommended for time-varied animations which texture a 3D object (W=time)\n     * where there isn\u0027t a clear distinction between horizontal and vertical\n     */\n\tpublic static float Noise4_ImproveXYZ( long seed, double x, double y, double z, double w )\n\t{\n\t\tdouble xyz = x \u002B y \u002B z;\n\t\tdouble ww = w * 1.118033988749894;\n\t\tdouble s2 = xyz * -0.16666666666666666 \u002B ww;\n\t\tdouble xs = x \u002B s2, ys = y \u002B s2, zs = z \u002B s2, ws = -0.5 * xyz \u002B ww;\n\n\t\treturn Noise4_UnskewedBase( seed, xs, ys, zs, ws );\n\t}\n\n\t/**\n     * 4D SuperSimplex noise, fallback lattice orientation.\n     */\n\tpublic static float Noise4_Fallback( long seed, double x, double y, double z, double w )\n\t{\n\t\t// Get points for A4 lattice\n\t\tdouble s = SKEW_4D * (x \u002B y \u002B z \u002B w);\n\t\tdouble xs = x \u002B s, ys = y \u002B s, zs = z \u002B s, ws = w \u002B s;\n\n\t\treturn Noise4_UnskewedBase( seed, xs, ys, zs, ws );\n\t}\n\n\t/**\n     * 4D SuperSimplex noise base.\n     * Using ultra-simple 4x4x4x4 lookup partitioning.\n     * This isn\u0027t as elegant or SIMD/GPU/etc. portable as other approaches,\n     * but it competes performance-wise with optimized 2014 OpenSimplex.\n     */\n\tprivate static float Noise4_UnskewedBase( long seed, double xs, double ys, double zs, double ws )\n\t{\n\t\t// Get base points and offsets\n\t\tint xsb = FastFloor( xs ), ysb = FastFloor( ys ), zsb = FastFloor( zs ), wsb = FastFloor( ws );\n\t\tfloat xsi = (float)(xs - xsb), ysi = (float)(ys - ysb), zsi = (float)(zs - zsb), wsi = (float)(ws - wsb);\n\n\t\t// Unskewed offsets\n\t\tfloat ssi = (xsi \u002B ysi \u002B zsi \u002B wsi) * UNSKEW_4D;\n\t\tfloat xi = xsi \u002B ssi, yi = ysi \u002B ssi, zi = zsi \u002B ssi, wi = wsi \u002B ssi;\n\n\t\t// Prime pre-multiplication for hash.\n\t\tlong xsvp = xsb * PRIME_X, ysvp = ysb * PRIME_Y, zsvp = zsb * PRIME_Z, wsvp = wsb * PRIME_W;\n\n\t\t// Index into initial table.\n\t\tint index = ((FastFloor( xs * 4 ) \u0026 3) \u003C\u003C 0)\n\t\t\t| ((FastFloor( ys * 4 ) \u0026 3) \u003C\u003C 2)\n\t\t\t| ((FastFloor( zs * 4 ) \u0026 3) \u003C\u003C 4)\n\t\t\t| ((FastFloor( ws * 4 ) \u0026 3) \u003C\u003C 6);\n\n\t\t// Point contributions\n\t\tfloat value = 0;\n\t\t(int secondaryIndexStart, int secondaryIndexStop) = LOOKUP_4D_A[index];\n\t\tfor ( int i = secondaryIndexStart; i \u003C secondaryIndexStop; i\u002B\u002B )\n\t\t{\n\t\t\tLatticeVertex4D c = LOOKUP_4D_B[i];\n\t\t\tfloat dx = xi \u002B c.dx, dy = yi \u002B c.dy, dz = zi \u002B c.dz, dw = wi \u002B c.dw;\n\t\t\tfloat a = (dx * dx \u002B dy * dy) \u002B (dz * dz \u002B dw * dw);\n\t\t\tif ( a \u003C RSQUARED_4D )\n\t\t\t{\n\t\t\t\ta -= RSQUARED_4D;\n\t\t\t\ta *= a;\n\t\t\t\tvalue \u002B= a * a * Grad( seed, xsvp \u002B c.xsvp, ysvp \u002B c.ysvp, zsvp \u002B c.zsvp, wsvp \u002B c.wsvp, dx, dy, dz, dw );\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n\n\t/*\n     * Utility\n     */\n\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\n\tprivate static float Grad( long seed, long xsvp, long ysvp, float dx, float dy )\n\t{\n\t\tlong hash = seed ^ xsvp ^ ysvp;\n\t\thash *= HASH_MULTIPLIER;\n\t\thash ^= hash \u003E\u003E (64 - N_GRADS_2D_EXPONENT \u002B 1);\n\t\tint gi = (int)hash \u0026 ((N_GRADS_2D - 1) \u003C\u003C 1);\n\t\treturn GRADIENTS_2D[gi | 0] * dx \u002B GRADIENTS_2D[gi | 1] * dy;\n\t}\n\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\n\tprivate static float Grad( long seed, long xrvp, long yrvp, long zrvp, float dx, float dy, float dz )\n\t{\n\t\tlong hash = (seed ^ xrvp) ^ (yrvp ^ zrvp);\n\t\thash *= HASH_MULTIPLIER;\n\t\thash ^= hash \u003E\u003E (64 - N_GRADS_3D_EXPONENT \u002B 2);\n\t\tint gi = (int)hash \u0026 ((N_GRADS_3D - 1) \u003C\u003C 2);\n\t\treturn GRADIENTS_3D[gi | 0] * dx \u002B GRADIENTS_3D[gi | 1] * dy \u002B GRADIENTS_3D[gi | 2] * dz;\n\t}\n\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\n\tprivate static float Grad( long seed, long xsvp, long ysvp, long zsvp, long wsvp, float dx, float dy, float dz, float dw )\n\t{\n\t\tlong hash = seed ^ (xsvp ^ ysvp) ^ (zsvp ^ wsvp);\n\t\thash *= HASH_MULTIPLIER;\n\t\thash ^= hash \u003E\u003E (64 - N_GRADS_4D_EXPONENT \u002B 2);\n\t\tint gi = (int)hash \u0026 ((N_GRADS_4D - 1) \u003C\u003C 2);\n\t\treturn (GRADIENTS_4D[gi | 0] * dx \u002B GRADIENTS_4D[gi | 1] * dy) \u002B (GRADIENTS_4D[gi | 2] * dz \u002B GRADIENTS_4D[gi | 3] * dw);\n\t}\n\n\t[MethodImpl( MethodImplOptions.AggressiveInlining )]\n\tprivate static int FastFloor( double x )\n\t{\n\t\tint xi = (int)x;\n\t\treturn x \u003C xi ? xi - 1 : xi;\n\t}\n\n\t/*\n     * Lookup Tables \u0026 Gradients\n     */\n\n\tprivate static readonly float[] GRADIENTS_2D;\n\tprivate static readonly float[] GRADIENTS_3D;\n\tprivate static readonly float[] GRADIENTS_4D;\n\tprivate static readonly (short SecondaryIndexStart, short SecondaryIndexStop)[] LOOKUP_4D_A;\n\tprivate static readonly LatticeVertex4D[] LOOKUP_4D_B;\n\n\tstatic OpenSimplex2S()\n\t{\n\n\t\tGRADIENTS_2D = new float[N_GRADS_2D * 2];\n\t\tfloat[] grad2 = {\n\t\t\t\t 0.38268343236509f,   0.923879532511287f,\n\t\t\t\t 0.923879532511287f,  0.38268343236509f,\n\t\t\t\t 0.923879532511287f, -0.38268343236509f,\n\t\t\t\t 0.38268343236509f,  -0.923879532511287f,\n\t\t\t\t-0.38268343236509f,  -0.923879532511287f,\n\t\t\t\t-0.923879532511287f, -0.38268343236509f,\n\t\t\t\t-0.923879532511287f,  0.38268343236509f,\n\t\t\t\t-0.38268343236509f,   0.923879532511287f,\n                //-------------------------------------//\n                 0.130526192220052f,  0.99144486137381f,\n\t\t\t\t 0.608761429008721f,  0.793353340291235f,\n\t\t\t\t 0.793353340291235f,  0.608761429008721f,\n\t\t\t\t 0.99144486137381f,   0.130526192220051f,\n\t\t\t\t 0.99144486137381f,  -0.130526192220051f,\n\t\t\t\t 0.793353340291235f, -0.60876142900872f,\n\t\t\t\t 0.608761429008721f, -0.793353340291235f,\n\t\t\t\t 0.130526192220052f, -0.99144486137381f,\n\t\t\t\t-0.130526192220052f, -0.99144486137381f,\n\t\t\t\t-0.608761429008721f, -0.793353340291235f,\n\t\t\t\t-0.793353340291235f, -0.608761429008721f,\n\t\t\t\t-0.99144486137381f,  -0.130526192220052f,\n\t\t\t\t-0.99144486137381f,   0.130526192220051f,\n\t\t\t\t-0.793353340291235f,  0.608761429008721f,\n\t\t\t\t-0.608761429008721f,  0.793353340291235f,\n\t\t\t\t-0.130526192220052f,  0.99144486137381f,\n\t\t};\n\t\tfor ( int i = 0; i \u003C grad2.Length; i\u002B\u002B )\n\t\t{\n\t\t\tgrad2[i] = (float)(grad2[i] / NORMALIZER_2D);\n\t\t}\n\t\tfor ( int i = 0, j = 0; i \u003C GRADIENTS_2D.Length; i\u002B\u002B, j\u002B\u002B )\n\t\t{\n\t\t\tif ( j == grad2.Length ) j = 0;\n\t\t\tGRADIENTS_2D[i] = grad2[j];\n\t\t}\n\n\t\tGRADIENTS_3D = new float[N_GRADS_3D * 4];\n\t\tfloat[] grad3 = {\n\t\t\t 2.22474487139f,       2.22474487139f,      -1.0f,                 0.0f,\n\t\t\t 2.22474487139f,       2.22474487139f,       1.0f,                 0.0f,\n\t\t\t 3.0862664687972017f,  1.1721513422464978f,  0.0f,                 0.0f,\n\t\t\t 1.1721513422464978f,  3.0862664687972017f,  0.0f,                 0.0f,\n\t\t\t-2.22474487139f,       2.22474487139f,      -1.0f,                 0.0f,\n\t\t\t-2.22474487139f,       2.22474487139f,       1.0f,                 0.0f,\n\t\t\t-1.1721513422464978f,  3.0862664687972017f,  0.0f,                 0.0f,\n\t\t\t-3.0862664687972017f,  1.1721513422464978f,  0.0f,                 0.0f,\n\t\t\t-1.0f,                -2.22474487139f,      -2.22474487139f,       0.0f,\n\t\t\t 1.0f,                -2.22474487139f,      -2.22474487139f,       0.0f,\n\t\t\t 0.0f,                -3.0862664687972017f, -1.1721513422464978f,  0.0f,\n\t\t\t 0.0f,                -1.1721513422464978f, -3.0862664687972017f,  0.0f,\n\t\t\t-1.0f,                -2.22474487139f,       2.22474487139f,       0.0f,\n\t\t\t 1.0f,                -2.22474487139f,       2.22474487139f,       0.0f,\n\t\t\t 0.0f,                -1.1721513422464978f,  3.0862664687972017f,  0.0f,\n\t\t\t 0.0f,                -3.0862664687972017f,  1.1721513422464978f,  0.0f,\n            //--------------------------------------------------------------------//\n            -2.22474487139f,      -2.22474487139f,      -1.0f,                 0.0f,\n\t\t\t-2.22474487139f,      -2.22474487139f,       1.0f,                 0.0f,\n\t\t\t-3.0862664687972017f, -1.1721513422464978f,  0.0f,                 0.0f,\n\t\t\t-1.1721513422464978f, -3.0862664687972017f,  0.0f,                 0.0f,\n\t\t\t-2.22474487139f,      -1.0f,                -2.22474487139f,       0.0f,\n\t\t\t-2.22474487139f,       1.0f,                -2.22474487139f,       0.0f,\n\t\t\t-1.1721513422464978f,  0.0f,                -3.0862664687972017f,  0.0f,\n\t\t\t-3.0862664687972017f,  0.0f,                -1.1721513422464978f,  0.0f,\n\t\t\t-2.22474487139f,      -1.0f,                 2.22474487139f,       0.0f,\n\t\t\t-2.22474487139f,       1.0f,                 2.22474487139f,       0.0f,\n\t\t\t-3.0862664687972017f,  0.0f,                 1.1721513422464978f,  0.0f,\n\t\t\t-1.1721513422464978f,  0.0f,                 3.0862664687972017f,  0.0f,\n\t\t\t-1.0f,                 2.22474487139f,      -2.22474487139f,       0.0f,\n\t\t\t 1.0f,                 2.22474487139f,      -2.22474487139f,       0.0f,\n\t\t\t 0.0f,                 1.1721513422464978f, -3.0862664687972017f,  0.0f,\n\t\t\t 0.0f,                 3.0862664687972017f, -1.1721513422464978f,  0.0f,\n\t\t\t-1.0f,                 2.22474487139f,       2.22474487139f,       0.0f,\n\t\t\t 1.0f,                 2.22474487139f,       2.22474487139f,       0.0f,\n\t\t\t 0.0f,                 3.0862664687972017f,  1.1721513422464978f,  0.0f,\n\t\t\t 0.0f,                 1.1721513422464978f,  3.0862664687972017f,  0.0f,\n\t\t\t 2.22474487139f,      -2.22474487139f,      -1.0f,                 0.0f,\n\t\t\t 2.22474487139f,      -2.22474487139f,       1.0f,                 0.0f,\n\t\t\t 1.1721513422464978f, -3.0862664687972017f,  0.0f,                 0.0f,\n\t\t\t 3.0862664687972017f, -1.1721513422464978f,  0.0f,                 0.0f,\n\t\t\t 2.22474487139f,      -1.0f,                -2.22474487139f,       0.0f,\n\t\t\t 2.22474487139f,       1.0f,                -2.22474487139f,       0.0f,\n\t\t\t 3.0862664687972017f,  0.0f,                -1.1721513422464978f,  0.0f,\n\t\t\t 1.1721513422464978f,  0.0f,                -3.0862664687972017f,  0.0f,\n\t\t\t 2.22474487139f,      -1.0f,                 2.22474487139f,       0.0f,\n\t\t\t 2.22474487139f,       1.0f,                 2.22474487139f,       0.0f,\n\t\t\t 1.1721513422464978f,  0.0f,                 3.0862664687972017f,  0.0f,\n\t\t\t 3.0862664687972017f,  0.0f,                 1.1721513422464978f,  0.0f,\n\t\t};\n\t\tfor ( int i = 0; i \u003C grad3.Length; i\u002B\u002B )\n\t\t{\n\t\t\tgrad3[i] = (float)(grad3[i] / NORMALIZER_3D);\n\t\t}\n\t\tfor ( int i = 0, j = 0; i \u003C GRADIENTS_3D.Length; i\u002B\u002B, j\u002B\u002B )\n\t\t{\n\t\t\tif ( j == grad3.Length ) j = 0;\n\t\t\tGRADIENTS_3D[i] = grad3[j];\n\t\t}\n\n\t\tGRADIENTS_4D = new float[N_GRADS_4D * 4];\n\t\tfloat[] grad4 = {\n\t\t\t-0.6740059517812944f,   -0.3239847771997537f,   -0.3239847771997537f,    0.5794684678643381f,\n\t\t\t-0.7504883828755602f,   -0.4004672082940195f,    0.15296486218853164f,   0.5029860367700724f,\n\t\t\t-0.7504883828755602f,    0.15296486218853164f,  -0.4004672082940195f,    0.5029860367700724f,\n\t\t\t-0.8828161875373585f,    0.08164729285680945f,   0.08164729285680945f,   0.4553054119602712f,\n\t\t\t-0.4553054119602712f,   -0.08164729285680945f,  -0.08164729285680945f,   0.8828161875373585f,\n\t\t\t-0.5029860367700724f,   -0.15296486218853164f,   0.4004672082940195f,    0.7504883828755602f,\n\t\t\t-0.5029860367700724f,    0.4004672082940195f,   -0.15296486218853164f,   0.7504883828755602f,\n\t\t\t-0.5794684678643381f,    0.3239847771997537f,    0.3239847771997537f,    0.6740059517812944f,\n\t\t\t-0.6740059517812944f,   -0.3239847771997537f,    0.5794684678643381f,   -0.3239847771997537f,\n\t\t\t-0.7504883828755602f,   -0.4004672082940195f,    0.5029860367700724f,    0.15296486218853164f,\n\t\t\t-0.7504883828755602f,    0.15296486218853164f,   0.5029860367700724f,   -0.4004672082940195f,\n\t\t\t-0.8828161875373585f,    0.08164729285680945f,   0.4553054119602712f,    0.08164729285680945f,\n\t\t\t-0.4553054119602712f,   -0.08164729285680945f,   0.8828161875373585f,   -0.08164729285680945f,\n\t\t\t-0.5029860367700724f,   -0.15296486218853164f,   0.7504883828755602f,    0.4004672082940195f,\n\t\t\t-0.5029860367700724f,    0.4004672082940195f,    0.7504883828755602f,   -0.15296486218853164f,\n\t\t\t-0.5794684678643381f,    0.3239847771997537f,    0.6740059517812944f,    0.3239847771997537f,\n\t\t\t-0.6740059517812944f,    0.5794684678643381f,   -0.3239847771997537f,   -0.3239847771997537f,\n\t\t\t-0.7504883828755602f,    0.5029860367700724f,   -0.4004672082940195f,    0.15296486218853164f,\n\t\t\t-0.7504883828755602f,    0.5029860367700724f,    0.15296486218853164f,  -0.4004672082940195f,\n\t\t\t-0.8828161875373585f,    0.4553054119602712f,    0.08164729285680945f,   0.08164729285680945f,\n\t\t\t-0.4553054119602712f,    0.8828161875373585f,   -0.08164729285680945f,  -0.08164729285680945f,\n\t\t\t-0.5029860367700724f,    0.7504883828755602f,   -0.15296486218853164f,   0.4004672082940195f,\n\t\t\t-0.5029860367700724f,    0.7504883828755602f,    0.4004672082940195f,   -0.15296486218853164f,\n\t\t\t-0.5794684678643381f,    0.6740059517812944f,    0.3239847771997537f,    0.3239847771997537f,\n\t\t\t 0.5794684678643381f,   -0.6740059517812944f,   -0.3239847771997537f,   -0.3239847771997537f,\n\t\t\t 0.5029860367700724f,   -0.7504883828755602f,   -0.4004672082940195f,    0.15296486218853164f,\n\t\t\t 0.5029860367700724f,   -0.7504883828755602f,    0.15296486218853164f,  -0.4004672082940195f,\n\t\t\t 0.4553054119602712f,   -0.8828161875373585f,    0.08164729285680945f,   0.08164729285680945f,\n\t\t\t 0.8828161875373585f,   -0.4553054119602712f,   -0.08164729285680945f,  -0.08164729285680945f,\n\t\t\t 0.7504883828755602f,   -0.5029860367700724f,   -0.15296486218853164f,   0.4004672082940195f,\n\t\t\t 0.7504883828755602f,   -0.5029860367700724f,    0.4004672082940195f,   -0.15296486218853164f,\n\t\t\t 0.6740059517812944f,   -0.5794684678643381f,    0.3239847771997537f,    0.3239847771997537f,\n            //------------------------------------------------------------------------------------------//\n            -0.753341017856078f,    -0.37968289875261624f,  -0.37968289875261624f,  -0.37968289875261624f,\n\t\t\t-0.7821684431180708f,   -0.4321472685365301f,   -0.4321472685365301f,    0.12128480194602098f,\n\t\t\t-0.7821684431180708f,   -0.4321472685365301f,    0.12128480194602098f,  -0.4321472685365301f,\n\t\t\t-0.7821684431180708f,    0.12128480194602098f,  -0.4321472685365301f,   -0.4321472685365301f,\n\t\t\t-0.8586508742123365f,   -0.508629699630796f,     0.044802370851755174f,  0.044802370851755174f,\n\t\t\t-0.8586508742123365f,    0.044802370851755174f, -0.508629699630796f,     0.044802370851755174f,\n\t\t\t-0.8586508742123365f,    0.044802370851755174f,  0.044802370851755174f, -0.508629699630796f,\n\t\t\t-0.9982828964265062f,   -0.03381941603233842f,  -0.03381941603233842f,  -0.03381941603233842f,\n\t\t\t-0.37968289875261624f,  -0.753341017856078f,    -0.37968289875261624f,  -0.37968289875261624f,\n\t\t\t-0.4321472685365301f,   -0.7821684431180708f,   -0.4321472685365301f,    0.12128480194602098f,\n\t\t\t-0.4321472685365301f,   -0.7821684431180708f,    0.12128480194602098f,  -0.4321472685365301f,\n\t\t\t 0.12128480194602098f,  -0.7821684431180708f,   -0.4321472685365301f,   -0.4321472685365301f,\n\t\t\t-0.508629699630796f,    -0.8586508742123365f,    0.044802370851755174f,  0.044802370851755174f,\n\t\t\t 0.044802370851755174f, -0.8586508742123365f,   -0.508629699630796f,     0.044802370851755174f,\n\t\t\t 0.044802370851755174f, -0.8586508742123365f,    0.044802370851755174f, -0.508629699630796f,\n\t\t\t-0.03381941603233842f,  -0.9982828964265062f,   -0.03381941603233842f,  -0.03381941603233842f,\n\t\t\t-0.37968289875261624f,  -0.37968289875261624f,  -0.753341017856078f,    -0.37968289875261624f,\n\t\t\t-0.4321472685365301f,   -0.4321472685365301f,   -0.7821684431180708f,    0.12128480194602098f,\n\t\t\t-0.4321472685365301f,    0.12128480194602098f,  -0.7821684431180708f,   -0.4321472685365301f,\n\t\t\t 0.12128480194602098f,  -0.4321472685365301f,   -0.7821684431180708f,   -0.4321472685365301f,\n\t\t\t-0.508629699630796f,     0.044802370851755174f, -0.8586508742123365f,    0.044802370851755174f,\n\t\t\t 0.044802370851755174f, -0.508629699630796f,    -0.8586508742123365f,    0.044802370851755174f,\n\t\t\t 0.044802370851755174f,  0.044802370851755174f, -0.8586508742123365f,   -0.508629699630796f,\n\t\t\t-0.03381941603233842f,  -0.03381941603233842f,  -0.9982828964265062f,   -0.03381941603233842f,\n\t\t\t-0.37968289875261624f,  -0.37968289875261624f,  -0.37968289875261624f,  -0.753341017856078f,\n\t\t\t-0.4321472685365301f,   -0.4321472685365301f,    0.12128480194602098f,  -0.7821684431180708f,\n\t\t\t-0.4321472685365301f,    0.12128480194602098f,  -0.4321472685365301f,   -0.7821684431180708f,\n\t\t\t 0.12128480194602098f,  -0.4321472685365301f,   -0.4321472685365301f,   -0.7821684431180708f,\n\t\t\t-0.508629699630796f,     0.044802370851755174f,  0.044802370851755174f, -0.8586508742123365f,\n\t\t\t 0.044802370851755174f, -0.508629699630796f,     0.044802370851755174f, -0.8586508742123365f,\n\t\t\t 0.044802370851755174f,  0.044802370851755174f, -0.508629699630796f,    -0.8586508742123365f,\n\t\t\t-0.03381941603233842f,  -0.03381941603233842f,  -0.03381941603233842f,  -0.9982828964265062f,\n\t\t\t-0.3239847771997537f,   -0.6740059517812944f,   -0.3239847771997537f,    0.5794684678643381f,\n\t\t\t-0.4004672082940195f,   -0.7504883828755602f,    0.15296486218853164f,   0.5029860367700724f,\n\t\t\t 0.15296486218853164f,  -0.7504883828755602f,   -0.4004672082940195f,    0.5029860367700724f,\n\t\t\t 0.08164729285680945f,  -0.8828161875373585f,    0.08164729285680945f,   0.4553054119602712f,\n\t\t\t-0.08164729285680945f,  -0.4553054119602712f,   -0.08164729285680945f,   0.8828161875373585f,\n\t\t\t-0.15296486218853164f,  -0.5029860367700724f,    0.4004672082940195f,    0.7504883828755602f,\n\t\t\t 0.4004672082940195f,   -0.5029860367700724f,   -0.15296486218853164f,   0.7504883828755602f,\n\t\t\t 0.3239847771997537f,   -0.5794684678643381f,    0.3239847771997537f,    0.6740059517812944f,\n\t\t\t-0.3239847771997537f,   -0.3239847771997537f,   -0.6740059517812944f,    0.5794684678643381f,\n\t\t\t-0.4004672082940195f,    0.15296486218853164f,  -0.7504883828755602f,    0.5029860367700724f,\n\t\t\t 0.15296486218853164f,  -0.4004672082940195f,   -0.7504883828755602f,    0.5029860367700724f,\n\t\t\t 0.08164729285680945f,   0.08164729285680945f,  -0.8828161875373585f,    0.4553054119602712f,\n\t\t\t-0.08164729285680945f,  -0.08164729285680945f,  -0.4553054119602712f,    0.8828161875373585f,\n\t\t\t-0.15296486218853164f,   0.4004672082940195f,   -0.5029860367700724f,    0.7504883828755602f,\n\t\t\t 0.4004672082940195f,   -0.15296486218853164f,  -0.5029860367700724f,    0.7504883828755602f,\n\t\t\t 0.3239847771997537f,    0.3239847771997537f,   -0.5794684678643381f,    0.6740059517812944f,\n\t\t\t-0.3239847771997537f,   -0.6740059517812944f,    0.5794684678643381f,   -0.3239847771997537f,\n\t\t\t-0.4004672082940195f,   -0.7504883828755602f,    0.5029860367700724f,    0.15296486218853164f,\n\t\t\t 0.15296486218853164f,  -0.7504883828755602f,    0.5029860367700724f,   -0.4004672082940195f,\n\t\t\t 0.08164729285680945f,  -0.8828161875373585f,    0.4553054119602712f,    0.08164729285680945f,\n\t\t\t-0.08164729285680945f,  -0.4553054119602712f,    0.8828161875373585f,   -0.08164729285680945f,\n\t\t\t-0.15296486218853164f,  -0.5029860367700724f,    0.7504883828755602f,    0.4004672082940195f,\n\t\t\t 0.4004672082940195f,   -0.5029860367700724f,    0.7504883828755602f,   -0.15296486218853164f,\n\t\t\t 0.3239847771997537f,   -0.5794684678643381f,    0.6740059517812944f,    0.3239847771997537f,\n\t\t\t-0.3239847771997537f,   -0.3239847771997537f,    0.5794684678643381f,   -0.6740059517812944f,\n\t\t\t-0.4004672082940195f,    0.15296486218853164f,   0.5029860367700724f,   -0.7504883828755602f,\n\t\t\t 0.15296486218853164f,  -0.4004672082940195f,    0.5029860367700724f,   -0.7504883828755602f,\n\t\t\t 0.08164729285680945f,   0.08164729285680945f,   0.4553054119602712f,   -0.8828161875373585f,\n\t\t\t-0.08164729285680945f,  -0.08164729285680945f,   0.8828161875373585f,   -0.4553054119602712f,\n\t\t\t-0.15296486218853164f,   0.4004672082940195f,    0.7504883828755602f,   -0.5029860367700724f,\n\t\t\t 0.4004672082940195f,   -0.15296486218853164f,   0.7504883828755602f,   -0.5029860367700724f,\n\t\t\t 0.3239847771997537f,    0.3239847771997537f,    0.6740059517812944f,   -0.5794684678643381f,\n\t\t\t-0.3239847771997537f,    0.5794684678643381f,   -0.6740059517812944f,   -0.3239847771997537f,\n\t\t\t-0.4004672082940195f,    0.5029860367700724f,   -0.7504883828755602f,    0.15296486218853164f,\n\t\t\t 0.15296486218853164f,   0.5029860367700724f,   -0.7504883828755602f,   -0.4004672082940195f,\n\t\t\t 0.08164729285680945f,   0.4553054119602712f,   -0.8828161875373585f,    0.08164729285680945f,\n\t\t\t-0.08164729285680945f,   0.8828161875373585f,   -0.4553054119602712f,   -0.08164729285680945f,\n\t\t\t-0.15296486218853164f,   0.7504883828755602f,   -0.5029860367700724f,    0.4004672082940195f,\n\t\t\t 0.4004672082940195f,    0.7504883828755602f,   -0.5029860367700724f,   -0.15296486218853164f,\n\t\t\t 0.3239847771997537f,    0.6740059517812944f,   -0.5794684678643381f,    0.3239847771997537f,\n\t\t\t-0.3239847771997537f,    0.5794684678643381f,   -0.3239847771997537f,   -0.6740059517812944f,\n\t\t\t-0.4004672082940195f,    0.5029860367700724f,    0.15296486218853164f,  -0.7504883828755602f,\n\t\t\t 0.15296486218853164f,   0.5029860367700724f,   -0.4004672082940195f,   -0.7504883828755602f,\n\t\t\t 0.08164729285680945f,   0.4553054119602712f,    0.08164729285680945f,  -0.8828161875373585f,\n\t\t\t-0.08164729285680945f,   0.8828161875373585f,   -0.08164729285680945f,  -0.4553054119602712f,\n\t\t\t-0.15296486218853164f,   0.7504883828755602f,    0.4004672082940195f,   -0.5029860367700724f,\n\t\t\t 0.4004672082940195f,    0.7504883828755602f,   -0.15296486218853164f,  -0.5029860367700724f,\n\t\t\t 0.3239847771997537f,    0.6740059517812944f,    0.3239847771997537f,   -0.5794684678643381f,\n\t\t\t 0.5794684678643381f,   -0.3239847771997537f,   -0.6740059517812944f,   -0.3239847771997537f,\n\t\t\t 0.5029860367700724f,   -0.4004672082940195f,   -0.7504883828755602f,    0.15296486218853164f,\n\t\t\t 0.5029860367700724f,    0.15296486218853164f,  -0.7504883828755602f,   -0.4004672082940195f,\n\t\t\t 0.4553054119602712f,    0.08164729285680945f,  -0.8828161875373585f,    0.08164729285680945f,\n\t\t\t 0.8828161875373585f,   -0.08164729285680945f,  -0.4553054119602712f,   -0.08164729285680945f,\n\t\t\t 0.7504883828755602f,   -0.15296486218853164f,  -0.5029860367700724f,    0.4004672082940195f,\n\t\t\t 0.7504883828755602f,    0.4004672082940195f,   -0.5029860367700724f,   -0.15296486218853164f,\n\t\t\t 0.6740059517812944f,    0.3239847771997537f,   -0.5794684678643381f,    0.3239847771997537f,\n\t\t\t 0.5794684678643381f,   -0.3239847771997537f,   -0.3239847771997537f,   -0.6740059517812944f,\n\t\t\t 0.5029860367700724f,   -0.4004672082940195f,    0.15296486218853164f,  -0.7504883828755602f,\n\t\t\t 0.5029860367700724f,    0.15296486218853164f,  -0.4004672082940195f,   -0.7504883828755602f,\n\t\t\t 0.4553054119602712f,    0.08164729285680945f,   0.08164729285680945f,  -0.8828161875373585f,\n\t\t\t 0.8828161875373585f,   -0.08164729285680945f,  -0.08164729285680945f,  -0.4553054119602712f,\n\t\t\t 0.7504883828755602f,   -0.15296486218853164f,   0.4004672082940195f,   -0.5029860367700724f,\n\t\t\t 0.7504883828755602f,    0.4004672082940195f,   -0.15296486218853164f,  -0.5029860367700724f,\n\t\t\t 0.6740059517812944f,    0.3239847771997537f,    0.3239847771997537f,   -0.5794684678643381f,\n\t\t\t 0.03381941603233842f,   0.03381941603233842f,   0.03381941603233842f,   0.9982828964265062f,\n\t\t\t-0.044802370851755174f, -0.044802370851755174f,  0.508629699630796f,     0.8586508742123365f,\n\t\t\t-0.044802370851755174f,  0.508629699630796f,    -0.044802370851755174f,  0.8586508742123365f,\n\t\t\t-0.12128480194602098f,   0.4321472685365301f,    0.4321472685365301f,    0.7821684431180708f,\n\t\t\t 0.508629699630796f,    -0.044802370851755174f, -0.044802370851755174f,  0.8586508742123365f,\n\t\t\t 0.4321472685365301f,   -0.12128480194602098f,   0.4321472685365301f,    0.7821684431180708f,\n\t\t\t 0.4321472685365301f,    0.4321472685365301f,   -0.12128480194602098f,   0.7821684431180708f,\n\t\t\t 0.37968289875261624f,   0.37968289875261624f,   0.37968289875261624f,   0.753341017856078f,\n\t\t\t 0.03381941603233842f,   0.03381941603233842f,   0.9982828964265062f,    0.03381941603233842f,\n\t\t\t-0.044802370851755174f,  0.044802370851755174f,  0.8586508742123365f,    0.508629699630796f,\n\t\t\t-0.044802370851755174f,  0.508629699630796f,     0.8586508742123365f,   -0.044802370851755174f,\n\t\t\t-0.12128480194602098f,   0.4321472685365301f,    0.7821684431180708f,    0.4321472685365301f,\n\t\t\t 0.508629699630796f,    -0.044802370851755174f,  0.8586508742123365f,   -0.044802370851755174f,\n\t\t\t 0.4321472685365301f,   -0.12128480194602098f,   0.7821684431180708f,    0.4321472685365301f,\n\t\t\t 0.4321472685365301f,    0.4321472685365301f,    0.7821684431180708f,   -0.12128480194602098f,\n\t\t\t 0.37968289875261624f,   0.37968289875261624f,   0.753341017856078f,     0.37968289875261624f,\n\t\t\t 0.03381941603233842f,   0.9982828964265062f,    0.03381941603233842f,   0.03381941603233842f,\n\t\t\t-0.044802370851755174f,  0.8586508742123365f,   -0.044802370851755174f,  0.508629699630796f,\n\t\t\t-0.044802370851755174f,  0.8586508742123365f,    0.508629699630796f,    -0.044802370851755174f,\n\t\t\t-0.12128480194602098f,   0.7821684431180708f,    0.4321472685365301f,    0.4321472685365301f,\n\t\t\t 0.508629699630796f,     0.8586508742123365f,   -0.044802370851755174f, -0.044802370851755174f,\n\t\t\t 0.4321472685365301f,    0.7821684431180708f,   -0.12128480194602098f,   0.4321472685365301f,\n\t\t\t 0.4321472685365301f,    0.7821684431180708f,    0.4321472685365301f,   -0.12128480194602098f,\n\t\t\t 0.37968289875261624f,   0.753341017856078f,     0.37968289875261624f,   0.37968289875261624f,\n\t\t\t 0.9982828964265062f,    0.03381941603233842f,   0.03381941603233842f,   0.03381941603233842f,\n\t\t\t 0.8586508742123365f,   -0.044802370851755174f, -0.044802370851755174f,  0.508629699630796f,\n\t\t\t 0.8586508742123365f,   -0.044802370851755174f,  0.508629699630796f,    -0.044802370851755174f,\n\t\t\t 0.7821684431180708f,   -0.12128480194602098f,   0.4321472685365301f,    0.4321472685365301f,\n\t\t\t 0.8586508742123365f,    0.508629699630796f,    -0.044802370851755174f, -0.044802370851755174f,\n\t\t\t 0.7821684431180708f,    0.4321472685365301f,   -0.12128480194602098f,   0.4321472685365301f,\n\t\t\t 0.7821684431180708f,    0.4321472685365301f,    0.4321472685365301f,   -0.12128480194602098f,\n\t\t\t 0.753341017856078f,     0.37968289875261624f,   0.37968289875261624f,   0.37968289875261624f,\n\t\t};\n\t\tfor ( int i = 0; i \u003C grad4.Length; i\u002B\u002B )\n\t\t{\n\t\t\tgrad4[i] = (float)(grad4[i] / NORMALIZER_4D);\n\t\t}\n\t\tfor ( int i = 0, j = 0; i \u003C GRADIENTS_4D.Length; i\u002B\u002B, j\u002B\u002B )\n\t\t{\n\t\t\tif ( j == grad4.Length ) j = 0;\n\t\t\tGRADIENTS_4D[i] = grad4[j];\n\t\t}\n\n\t\tint[][] lookup4DVertexCodes = {\n\t\t\tnew int[] { 0x15, 0x45, 0x51, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x01, 0x05, 0x11, 0x15, 0x41, 0x45, 0x51, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x01, 0x15, 0x16, 0x45, 0x46, 0x51, 0x52, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x15, 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xAA },\n\t\t\tnew int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xAA },\n\t\t\tnew int[] { 0x05, 0x15, 0x16, 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x04, 0x05, 0x14, 0x15, 0x44, 0x45, 0x54, 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xAA },\n\t\t\tnew int[] { 0x05, 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x9A, 0xAA },\n\t\t\tnew int[] { 0x05, 0x15, 0x16, 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x5B, 0x6A, 0x9A, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x04, 0x15, 0x19, 0x45, 0x49, 0x54, 0x55, 0x58, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x05, 0x15, 0x19, 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x05, 0x15, 0x19, 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x5E, 0x6A, 0x9A, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x05, 0x15, 0x1A, 0x45, 0x4A, 0x55, 0x56, 0x59, 0x5A, 0x5B, 0x5E, 0x6A, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x15, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x96, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x11, 0x15, 0x16, 0x51, 0x52, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x96, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x14, 0x15, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x9A, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x15, 0x16, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x6B, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x14, 0x15, 0x54, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x99, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x9A, 0xAA },\n\t\t\tnew int[] { 0x15, 0x16, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x6B, 0x9A, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x14, 0x15, 0x19, 0x54, 0x55, 0x58, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x99, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x19, 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x6E, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x19, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x6E, 0x9A, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x1A, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x6B, 0x6E, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x10, 0x11, 0x14, 0x15, 0x50, 0x51, 0x54, 0x55, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x11, 0x15, 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x11, 0x15, 0x16, 0x51, 0x52, 0x55, 0x56, 0x65, 0x66, 0x67, 0x6A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x14, 0x15, 0x54, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x15, 0x16, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x6B, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x14, 0x15, 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xAA },\n\t\t\tnew int[] { 0x15, 0x16, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6B, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x14, 0x15, 0x19, 0x54, 0x55, 0x58, 0x59, 0x65, 0x69, 0x6A, 0x6D, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x19, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x6E, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x19, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6E, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x69, 0x6A, 0x6B, 0x6E, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x10, 0x15, 0x25, 0x51, 0x54, 0x55, 0x61, 0x64, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x11, 0x15, 0x25, 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x11, 0x15, 0x25, 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x6A, 0x76, 0xA6, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x11, 0x15, 0x26, 0x51, 0x55, 0x56, 0x62, 0x65, 0x66, 0x67, 0x6A, 0x76, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x14, 0x15, 0x25, 0x54, 0x55, 0x59, 0x64, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x25, 0x55, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x25, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xA6, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x26, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x6B, 0x7A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x14, 0x15, 0x25, 0x54, 0x55, 0x59, 0x64, 0x65, 0x69, 0x6A, 0x79, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x25, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x25, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x7A, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6B, 0x7A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x14, 0x15, 0x29, 0x54, 0x55, 0x59, 0x65, 0x68, 0x69, 0x6A, 0x6D, 0x79, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x15, 0x29, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x6E, 0x7A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x15, 0x55, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6E, 0x7A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x6B, 0x6E, 0x7A, 0xAA, 0xAB, 0xAE, 0xBA, 0xBF },\n\t\t\tnew int[] { 0x45, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x5A, 0x66, 0x95, 0x96, 0x9A, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x41, 0x45, 0x46, 0x51, 0x52, 0x55, 0x56, 0x5A, 0x66, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x44, 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x45, 0x46, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x5A, 0x69, 0x95, 0x99, 0x9A, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xAA },\n\t\t\tnew int[] { 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x96, 0x9A, 0x9B, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x44, 0x45, 0x49, 0x54, 0x55, 0x58, 0x59, 0x5A, 0x69, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x49, 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x99, 0x9A, 0x9E, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x4A, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x9A, 0x9B, 0x9E, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x95, 0x96, 0x99, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x59, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x51, 0x52, 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xA7, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x56, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x45, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x58, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAD, 0xAE },\n\t\t\tnew int[] { 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x66, 0x69, 0x95, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x51, 0x52, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x96, 0xA6, 0xA7, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xBA },\n\t\t\tnew int[] { 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xBA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x9A, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x58, 0x59, 0x65, 0x69, 0x6A, 0x99, 0xA9, 0xAA, 0xAD, 0xAE },\n\t\t\tnew int[] { 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x69, 0x6A, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x61, 0x64, 0x65, 0x66, 0x69, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x61, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x6A, 0xA5, 0xA6, 0xAA, 0xB6, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x62, 0x65, 0x66, 0x6A, 0xA6, 0xA7, 0xAA, 0xAB, 0xB6, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x54, 0x55, 0x64, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA },\n\t\t\tnew int[] { 0x55, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x55, 0x56, 0x65, 0x66, 0x6A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x64, 0x65, 0x69, 0x6A, 0xA5, 0xA9, 0xAA, 0xB9, 0xBA },\n\t\t\tnew int[] { 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x15, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x68, 0x69, 0x6A, 0xA9, 0xAA, 0xAD, 0xAE, 0xB9, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x55, 0x59, 0x65, 0x69, 0x6A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x15, 0x55, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0xAA, 0xAB, 0xAE, 0xBA, 0xBF },\n\t\t\tnew int[] { 0x40, 0x41, 0x44, 0x45, 0x50, 0x51, 0x54, 0x55, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x95, 0x96, 0x9A, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x41, 0x45, 0x46, 0x51, 0x52, 0x55, 0x56, 0x95, 0x96, 0x97, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x45, 0x46, 0x55, 0x56, 0x5A, 0x95, 0x96, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x95, 0x99, 0x9A, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xAA },\n\t\t\tnew int[] { 0x45, 0x46, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9B, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x44, 0x45, 0x49, 0x54, 0x55, 0x58, 0x59, 0x95, 0x99, 0x9A, 0x9D, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x49, 0x55, 0x59, 0x5A, 0x95, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x49, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9E, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x96, 0x99, 0x9A, 0x9B, 0x9E, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x95, 0x96, 0x99, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x51, 0x52, 0x55, 0x56, 0x66, 0x95, 0x96, 0x9A, 0xA6, 0xA7, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x51, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xEA },\n\t\t\tnew int[] { 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x54, 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x58, 0x59, 0x69, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAD, 0xAE },\n\t\t\tnew int[] { 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x96, 0x99, 0x9A, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x95, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xAA },\n\t\t\tnew int[] { 0x51, 0x52, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x54, 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB },\n\t\t\tnew int[] { 0x54, 0x55, 0x58, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x66, 0x69, 0x6A, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xAF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x61, 0x64, 0x65, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xB5, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x61, 0x65, 0x66, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x61, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xAA, 0xB6, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x96, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB, 0xB6, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x54, 0x55, 0x64, 0x65, 0x69, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA },\n\t\t\tnew int[] { 0x55, 0x65, 0x66, 0x69, 0x6A, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x6A, 0x96, 0xA5, 0xA6, 0xAA, 0xAB, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x64, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xB9, 0xBA },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA },\n\t\t\tnew int[] { 0x55, 0x56, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xBA, 0xBB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x99, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE, 0xB9, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x6A, 0x99, 0xA5, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x55, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xBA, 0xBE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xBA },\n\t\t\tnew int[] { 0x40, 0x45, 0x51, 0x54, 0x55, 0x85, 0x91, 0x94, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x85, 0x91, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x85, 0x91, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xD6, 0xEA },\n\t\t\tnew int[] { 0x41, 0x45, 0x51, 0x55, 0x56, 0x86, 0x92, 0x95, 0x96, 0x97, 0x9A, 0xA6, 0xAA, 0xAB, 0xD6, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x85, 0x94, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xDA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xDA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x86, 0x95, 0x96, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB, 0xDA, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x85, 0x94, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xD9, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x59, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xDA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x85, 0x95, 0x96, 0x99, 0x9A, 0xAA, 0xDA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9B, 0xA6, 0xAA, 0xAB, 0xDA, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x44, 0x45, 0x54, 0x55, 0x59, 0x89, 0x95, 0x98, 0x99, 0x9A, 0x9D, 0xA9, 0xAA, 0xAE, 0xD9, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x45, 0x55, 0x59, 0x89, 0x95, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE, 0xDA, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x45, 0x55, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9E, 0xA9, 0xAA, 0xAE, 0xDA, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0x9B, 0x9E, 0xAA, 0xAB, 0xAE, 0xDA, 0xEA, 0xEF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x91, 0x94, 0x95, 0x96, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x91, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xE6, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x91, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xE6, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x92, 0x95, 0x96, 0x9A, 0xA6, 0xA7, 0xAA, 0xAB, 0xE6, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x54, 0x55, 0x94, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xE9, 0xEA },\n\t\t\tnew int[] { 0x55, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x55, 0x56, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x55, 0x56, 0x95, 0x96, 0x9A, 0xA6, 0xAA, 0xAB, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x94, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xE9, 0xEA },\n\t\t\tnew int[] { 0x55, 0x59, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x45, 0x55, 0x56, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xAA, 0xAB, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x95, 0x98, 0x99, 0x9A, 0xA9, 0xAA, 0xAD, 0xAE, 0xE9, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x55, 0x59, 0x95, 0x99, 0x9A, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x45, 0x55, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x95, 0x96, 0x99, 0x9A, 0xAA, 0xAB, 0xAE, 0xEA, 0xEF },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x91, 0x94, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xE5, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x65, 0x91, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xE6, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x91, 0x95, 0x96, 0xA5, 0xA6, 0xAA, 0xE6, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x66, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB, 0xE6, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x54, 0x55, 0x65, 0x94, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xE9, 0xEA },\n\t\t\tnew int[] { 0x55, 0x65, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x66, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xAA, 0xAB, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x94, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xE9, 0xEA },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x65, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xEA },\n\t\t\tnew int[] { 0x55, 0x56, 0x5A, 0x66, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xEA, 0xEB },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x69, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE, 0xE9, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x69, 0x95, 0x99, 0x9A, 0xA5, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x55, 0x59, 0x5A, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xEA, 0xEE },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xEA },\n\t\t\tnew int[] { 0x50, 0x51, 0x54, 0x55, 0x65, 0x95, 0xA1, 0xA4, 0xA5, 0xA6, 0xA9, 0xAA, 0xB5, 0xBA, 0xE5, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x51, 0x55, 0x65, 0x95, 0xA1, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA, 0xE6, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x51, 0x55, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xB6, 0xBA, 0xE6, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x51, 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA7, 0xAA, 0xAB, 0xB6, 0xBA, 0xE6, 0xEA, 0xFB },\n\t\t\tnew int[] { 0x54, 0x55, 0x65, 0x95, 0xA4, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA, 0xE9, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x55, 0x65, 0x95, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x51, 0x55, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x55, 0x56, 0x65, 0x66, 0x95, 0x96, 0xA5, 0xA6, 0xAA, 0xAB, 0xBA, 0xEA, 0xFB },\n\t\t\tnew int[] { 0x54, 0x55, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xB9, 0xBA, 0xE9, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x54, 0x55, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x55, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xBA, 0xEA, 0xFA },\n\t\t\tnew int[] { 0x55, 0x56, 0x65, 0x66, 0x6A, 0x95, 0x96, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xBA, 0xEA },\n\t\t\tnew int[] { 0x54, 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xAD, 0xAE, 0xB9, 0xBA, 0xE9, 0xEA, 0xFE },\n\t\t\tnew int[] { 0x55, 0x59, 0x65, 0x69, 0x95, 0x99, 0xA5, 0xA9, 0xAA, 0xAE, 0xBA, 0xEA, 0xFE },\n\t\t\tnew int[] { 0x55, 0x59, 0x65, 0x69, 0x6A, 0x95, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAE, 0xBA, 0xEA },\n\t\t\tnew int[] { 0x55, 0x56, 0x59, 0x5A, 0x65, 0x66, 0x69, 0x6A, 0x95, 0x96, 0x99, 0x9A, 0xA5, 0xA6, 0xA9, 0xAA, 0xAB, 0xAE, 0xBA, 0xEA },\n\t\t};\n\t\tLatticeVertex4D[] latticeVerticesByCode = new LatticeVertex4D[256];\n\t\tfor ( int i = 0; i \u003C 256; i\u002B\u002B )\n\t\t{\n\t\t\tint cx = ((i \u003E\u003E 0) \u0026 3) - 1;\n\t\t\tint cy = ((i \u003E\u003E 2) \u0026 3) - 1;\n\t\t\tint cz = ((i \u003E\u003E 4) \u0026 3) - 1;\n\t\t\tint cw = ((i \u003E\u003E 6) \u0026 3) - 1;\n\t\t\tlatticeVerticesByCode[i] = new LatticeVertex4D( cx, cy, cz, cw );\n\t\t}\n\t\tint nLatticeVerticesTotal = 0;\n\t\tfor ( int i = 0; i \u003C 256; i\u002B\u002B )\n\t\t{\n\t\t\tnLatticeVerticesTotal \u002B= lookup4DVertexCodes[i].Length;\n\t\t}\n\t\tLOOKUP_4D_A = new (short SecondaryIndexStart, short SecondaryIndexStop)[256];\n\t\tLOOKUP_4D_B = new LatticeVertex4D[nLatticeVerticesTotal];\n\t\tfor ( int i = 0, j = 0; i \u003C 256; i\u002B\u002B )\n\t\t{\n\t\t\tLOOKUP_4D_A[i] = ((short)j, (short)(j \u002B lookup4DVertexCodes[i].Length));\n\t\t\tfor ( int k = 0; k \u003C lookup4DVertexCodes[i].Length; k\u002B\u002B )\n\t\t\t{\n\t\t\t\tLOOKUP_4D_B[j\u002B\u002B] = latticeVerticesByCode[lookup4DVertexCodes[i][k]];\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate class LatticeVertex4D\n\t{\n\t\tpublic readonly float dx, dy, dz, dw;\n\t\tpublic readonly long xsvp, ysvp, zsvp, wsvp;\n\t\tpublic LatticeVertex4D( int xsv, int ysv, int zsv, int wsv )\n\t\t{\n\t\t\tthis.xsvp = xsv * PRIME_X; this.ysvp = ysv * PRIME_Y;\n\t\t\tthis.zsvp = zsv * PRIME_Z; this.wsvp = wsv * PRIME_W;\n\t\t\tfloat ssv = (xsv \u002B ysv \u002B zsv \u002B wsv) * UNSKEW_4D;\n\t\t\tthis.dx = -xsv - ssv;\n\t\t\tthis.dy = -ysv - ssv;\n\t\t\tthis.dz = -zsv - ssv;\n\t\t\tthis.dw = -wsv - ssv;\n\t\t}\n\t}\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainShapes/Islands.cs","FileName":"Islands.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using Editor;\nusing Sandbox;\nusing System;\n\nnamespace Sturnus.TerrainGenerationTool;\npublic static class Islands\n{\n\tpublic static float Default( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize = 0.1f, float warpStrength = 0.5f )\n\t{\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\t\tfloat warpX;\n\t\tfloat warpY;\n\t\tfloat warpedNx;\n\t\tfloat warpedNy;\n\t\tfloat noise;\n\t\tif ( warp )\n\t\t{\n\t\t\t// Generate warp offsets using additional noise\n\t\t\twarpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\twarpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\n\t\t\t// Apply domain warping\n\t\t\twarpedNx = nx \u002B warpX;\n\t\t\twarpedNy = ny \u002B warpY;\n\t\t}\n\t\telse\n\t\t{\n\t\t\twarpedNx = nx;\n\t\t\twarpedNy = ny;\n\t\t}\n\t\t\n\t\t// Radial distance from the center\n\t\tfloat distance = (float)Math.Sqrt( nx * nx \u002B ny * ny );\n\t\tfloat falloff = 1.0f - Math.Clamp( distance, 0.25f, 1 ); // Smooth taper from center to edge\n\n\t\t// Central mountain shape (parabolic for smooth curvature)\n\t\tfloat centralMountain = (1.0f - distance * distance) * falloff;\n\n\t\t// Beach-style taper near the edges\n\t\tfloat beachStart = 0.5f; // Start of the beach region (distance normalized)\n\t\tfloat beachEnd = 0.98f;   // End of the beach region (ocean level)\n\t\tfloat beachFalloff = Math.Clamp( (distance - beachStart) / (beachEnd - beachStart), 0.1f, 1 );\n\t\tfloat beachTaper = (1.0f - beachFalloff) * 0.25f; // Smooth transition to flat region\n\n\t\t// Add subtle noise for terrain variation\n\t\tif ( warp )\n\t\t{\n\t\t\tnoise = OpenSimplex2S.Noise2( seed, warpedNx * 2, warpedNy * 2 ) * 0.4f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnoise = OpenSimplex2S.Noise2( seed, nx * 6, ny * 6 ) * 0.05f; // Low-frequency noise\n\t\t}\n\t\t\n\t\t// Combine components: central mountain, beach taper, and noise\n\t\tfloat output = centralMountain * (1.0f - beachFalloff) \u002B beachTaper \u002B noise;\n\n\t\t// Combine all effects\n\t\tfloat heightValue = output;\n\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\tfloat baseline = minHeight; // Minimum height\n\t\tfloat heightValueCombined = MathF.Max( heightValue, baseline );\n\n\t\t// Clamp the final height to valid range\n\t\treturn heightValueCombined;\n\t}\n\n\tpublic static float Archipelagos( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1;\n\t\tfloat ny = (y / (float)height) * 2 - 1;\n\n\t\t// Apply domain warping\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Use noise layers to create clusters of small islands\n\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed, nx * 1.5f, ny * 1.5f );\n\t\tfloat secondaryNoise = OpenSimplex2S.Noise2( seed \u002B 1, nx * 3.0f, ny * 3.0f ) * 0.5f;\n\n\t\tfloat archipelagoHeight = baseNoise \u002B secondaryNoise;\n\n\t\t// Apply radial falloff to form rounded island clusters\n\t\tfloat distance = MathF.Sqrt( nx * nx \u002B ny * ny );\n\t\tfloat falloff = Math.Clamp( 1 - distance * 1.2f, 0, 1 );\n\t\tfloat heightValue = Math.Clamp( archipelagoHeight * falloff, 0, 1 );\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\tfloat baseline = minHeight; // Minimum height\n\t\tfloat heightValueCombined = MathF.Max( heightValue, baseline );\n\n\t\t// Clamp the final height to valid range\n\t\treturn heightValueCombined;\n\t}\n\n\tpublic static float Atoll( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\n\t\t// Apply domain warping\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 20, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 21, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Calculate distance from the center\n\t\tfloat distance = MathF.Sqrt( nx * nx \u002B ny * ny );\n\n\t\t// Define parameters for the single ring\n\t\tfloat ringCenter = 0.6f; // Center of the ring\n\t\tfloat ringWidth = 0.1f;  // Width of the ring\n\n\t\t// Create a single ring using a Gaussian-like function\n\t\tfloat ring = MathF.Exp( -MathF.Pow( (distance - ringCenter) / ringWidth, 2 ) );\n\n\t\t// Add some noise for variation\n\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed, nx * 1.0f, ny * 1.0f ) * 0.4f;\n\n\t\t// Introduce a beach-like area (reduce noise and height for one side of the map)\n\t\tfloat beachEffect = Math.Clamp( (1 - nx) * 0.5f, 0.2f, 1.0f ); // Reduces height on one side of the map\n\t\tfloat beachNoise = OpenSimplex2S.Noise2( seed \u002B 30, nx * 2.0f, ny * 2.0f ) * 0.2f;\n\n\t\t// Combine the ring, noise, and beach effect\n\t\tfloat heightValue = (ring \u002B baseNoise * beachEffect \u002B beachNoise) * beachEffect;\n\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\tfloat baseline = minHeight; // Minimum height\n\t\tfloat heightValueCombined = MathF.Max( heightValue, baseline );\n\n\t\t// Clamp the final height to valid range\n\t\treturn heightValueCombined;\n\t}\n\n\n\n\n\tpublic static float Islets( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1;\n\t\tfloat ny = (y / (float)height) * 2 - 1;\n\n\t\t// Apply domain warping\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 30, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 31, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Generate scattered small islands\n\t\tfloat scatterNoise = OpenSimplex2S.Noise2( seed, nx * 6.0f, ny * 6.0f );\n\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed \u002B 1, nx * 3.0f, ny * 3.0f ) * 0.5f;\n\n\t\tfloat isletHeight = scatterNoise \u002B baseNoise;\n\n\t\t// Apply distance falloff to create isolated islets\n\t\tfloat distance = MathF.Sqrt( nx * nx \u002B ny * ny );\n\t\tfloat falloff = Math.Clamp( 1 - distance * 1.5f, 0, 1 );\n\t\tfloat heightValue = Math.Clamp( isletHeight * falloff, 0, 1 );\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\tfloat baseline = minHeight; // Minimum height\n\t\tfloat heightValueCombined = MathF.Max( heightValue, baseline );\n\n\t\t// Clamp the final height to valid range\n\t\treturn heightValueCombined;\n\t}\n\n\tpublic static float Oceanic( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize, float warpStrength )\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1;\n\t\tfloat ny = (y / (float)height) * 2 - 1;\n\n\t\t// Apply domain warping\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 40, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 41, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Generate large, continuous landmass with a few scattered features\n\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed, nx * 1.0f, ny * 1.0f );\n\t\tfloat featureNoise = OpenSimplex2S.Noise2( seed \u002B 1, nx * 2.0f, ny * 2.0f ) * 0.5f;\n\n\t\tfloat oceanicHeight = baseNoise \u002B featureNoise;\n\n\t\t// Apply radial falloff for a natural ocean/land mix\n\t\tfloat distance = MathF.Sqrt( nx * nx \u002B ny * ny );\n\t\tfloat falloff = Math.Clamp( 1 - distance * 1.0f, 0, 1 );\n\n\t\tfloat heightValue = Math.Clamp( oceanicHeight * falloff, 0, 1 );\n\n\t\tfloat baseline = minHeight; // Minimum height\n\t\tfloat heightValueCombined = MathF.Max( heightValue, baseline );\n\n\t\t// Clamp the final height to valid range\n\t\treturn heightValueCombined;\n\t}\n\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainShapes/Planetary.cs","FileName":"Planetary.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using Editor;\nusing Sandbox;\nusing System;\nusing System.Collections.Generic;\n\nnamespace Sturnus.TerrainGenerationTool;\npublic static class Planetary\n{\n\tpublic static float Sharded(\n\t\tint x,\n\t\tint y,\n\t\tint width,\n\t\tint height,\n\t\tlong seed,\n\t\tfloat minHeight,\n\t\tbool warp,              // Apply domain warping\n\t\tfloat warpSize,         // Warp scale\n\t\tfloat warpStrength      // Warp strength\n\t)\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\n\t\tfloat shardHeight = 10f;\n\t\tfloat crackDepth = 1f;\n\t\tfloat noiseStrength = 0.05f;\n\t\tint cellCount = 5;\n\n\t\t// Apply domain warping for irregularity\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Generate cracks\n\t\tfloat minDist = float.MaxValue;\n\t\tfloat secondaryDist = float.MaxValue;\n\t\tfloat cellSize = 2.0f / cellCount; // Normalize the cell size\n\t\tfor ( int i = 0; i \u003C cellCount; i\u002B\u002B )\n\t\t{\n\t\t\tfor ( int j = 0; j \u003C cellCount; j\u002B\u002B )\n\t\t\t{\n\t\t\t\tfloat cellX = -1 \u002B i * cellSize \u002B OpenSimplex2S.Noise2( seed \u002B 20, i, j ) * cellSize * 0.5f;\n\t\t\t\tfloat cellY = -1 \u002B j * cellSize \u002B OpenSimplex2S.Noise2( seed \u002B 21, i, j ) * cellSize * 0.5f;\n\n\t\t\t\tfloat distance = MathF.Sqrt( (nx - cellX) * (nx - cellX) \u002B (ny - cellY) * (ny - cellY) );\n\n\t\t\t\tif ( distance \u003C minDist )\n\t\t\t\t{\n\t\t\t\t\tsecondaryDist = minDist;\n\t\t\t\t\tminDist = distance;\n\t\t\t\t}\n\t\t\t\telse if ( distance \u003C secondaryDist )\n\t\t\t\t{\n\t\t\t\t\tsecondaryDist = distance;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Compute shard height based on the secondary distance\n\t\tfloat shardNoise = OpenSimplex2S.Noise2( seed \u002B 30, nx, ny ) * noiseStrength;\n\t\tfloat heightValue = MathF.Max( secondaryDist - minDist, 0f ) * shardHeight \u002B shardNoise;\n\n\t\t// Apply crack depth at borders between shards\n\t\tif ( secondaryDist - minDist \u003C 0.03f ) // Control the width of cracks\n\t\t{\n\t\t\theightValue -= crackDepth;\n\t\t}\n\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\t//heightValue = MathF.Max( heightValue, minHeight );\n\n\t\t// Clamp height to avoid negative values\n\t\theightValue = Math.Clamp( heightValue, 0, 1 );\n\n\t\tfloat baseline = minHeight; // Minimum height\n\t\tfloat heightValueCombined = MathF.Max( heightValue, baseline );\n\n\t\t// Clamp the final height to valid range\n\t\treturn heightValueCombined;\n\t}\n\n\tpublic static float Craters(\n\t\tint x,\n\t\tint y,\n\t\tint width,\n\t\tint height,\n\t\tlong seed,\n\t\tfloat minHeight,\n\t\tbool warp,             // Apply domain warping for irregularity\n\t\tfloat warpSize,        // Warp scale\n\t\tfloat warpStrength     // Warp strength\n\t)\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\n\t\tint craterCount = 100;\n\t\tfloat minCraterSize = 0.1f;\n        float maxCraterSize = 0.3f;\n        float craterDepth = 0.05f;\n        float rimHeight = 0.05f;\n        float rimWidthRatio = 0.2f;\n        float noiseStrength = 0.05f;\n        float largeCraterRatio = 0.2f;\n\t\tfloat slopeFalloff = 0.9f;  // Controls smoothness of ramps\n\n\n\t\t// Apply domain warping for irregularity\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Base terrain noise\n\t\tfloat baseTerrain = OpenSimplex2S.Noise2( seed \u002B 1, nx * 2.0f, ny * 2.0f ) * noiseStrength;\n\t\tbaseTerrain = (baseTerrain \u002B 1) * 0.5f; // Normalize to [0, 1]\n\n\t\tfloat heightValue = baseTerrain;\n\n\t\t// Iterate through craters in reverse order to overwrite previous craters\n\t\tfor ( int i = craterCount - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\t// Randomize crater properties\n\t\t\tfloat craterX = random.Next( -100000, 100000 ) / 50000.0f;\n\t\t\tfloat craterY = random.Next( -100000, 100000 ) / 50000.0f;\n\t\t\tfloat craterRadius = (i \u003C craterCount * largeCraterRatio)\n\t\t\t\t? random.Next( (int)(maxCraterSize * 500), (int)(maxCraterSize * 1000) ) / 1000.0f // Large craters\n\t\t\t\t: random.Next( (int)(minCraterSize * 500), (int)(minCraterSize * 1000) ) / 1000.0f; // Small craters\n\n\t\t\t// Distance from the current point to the crater center\n\t\t\tfloat distance = MathF.Sqrt( (nx - craterX) * (nx - craterX) \u002B (ny - craterY) * (ny - craterY) );\n\n\t\t\tif ( distance \u003C craterRadius )\n\t\t\t{\n\t\t\t\tfloat rimStart = craterRadius * (1f - rimWidthRatio);\n\t\t\t\tfloat rimEnd = craterRadius;\n\n\t\t\t\t// Inside the pit\n\t\t\t\tif ( distance \u003C rimStart )\n\t\t\t\t{\n\t\t\t\t\tfloat pitFalloff = Math.Clamp( 1f - (distance / rimStart), 0f, 1f );\n\t\t\t\t\theightValue = baseTerrain - MathF.Pow( pitFalloff, slopeFalloff ) * craterDepth; // Smooth ramp to the center\n\t\t\t\t}\n\t\t\t\t// Raised rim\n\t\t\t\telse if ( distance \u003E= rimStart \u0026\u0026 distance \u003C rimEnd )\n\t\t\t\t{\n\t\t\t\t\tfloat rimFalloff = Math.Clamp( (distance - rimStart) / (rimEnd - rimStart), 0f, 1f );\n\t\t\t\t\theightValue = baseTerrain \u002B MathF.Pow( 1f - rimFalloff, slopeFalloff ) * rimHeight; // Rounded rim\n\t\t\t\t}\n\n\t\t\t\t// Reset terrain below the rim to prevent intersecting ridges\n\t\t\t\tif ( distance \u003E= rimEnd )\n\t\t\t\t{\n\t\t\t\t\theightValue = baseTerrain;\n\t\t\t\t}\n\n\t\t\t\t// Exit the loop once the current crater is applied\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Ensure height values are clamped to [0, 1]\n\t\theightValue = Math.Clamp( heightValue, 0, 1 );\n\n\t\treturn heightValue;\n\t}\n\n\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainBrushes/ExtendedTerrainTool.cs","FileName":"ExtendedTerrainTool.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using System;\nusing System.Collections.Generic;\nusing Editor;\nusing Editor.TerrainEditor;\nusing Sandbox;\n\nnamespace Sturnus.TerrainGenerationTool.EditorTools;\n\n/// \u003Csummary\u003E\n/// A terrain editor tool that includes every stock brush from the built-in terrain tool,\n/// plus any custom brushes defined in this library. Register your own brushes by returning\n/// them from \u003Csee cref=\u0022GetSubtools\u0022/\u003E.\n/// \u003C/summary\u003E\n[EditorTool]\n[Title( \u0022Terrain Pro\u0022 )]\n[Icon( \u0022landscape\u0022 )]\n[Alias( \u0022tools.terrain-pro\u0022 )]\n[Group( \u0022Scene\u0022 )]\npublic class ExtendedTerrainTool : TerrainEditorTool\n{\n\tpublic override IEnumerable\u003CEditorTool\u003E GetSubtools()\n\t{\n\t\t// Stock brushes, minus the Hole tool (we don\u0027t want holes in Terrain Pro)\n\t\tforeach ( var tool in base.GetSubtools() )\n\t\t{\n\t\t\tif ( tool is HoleTool )\n\t\t\t\tcontinue;\n\n\t\t\tyield return tool;\n\t\t}\n\n\t\t// Custom brushes from this library\n\t\tyield return new BulgeBrushTool( this );\n\t\tyield return new CraterBrushTool( this );\n\t\tyield return new TerraceBrushTool( this );\n\t\tyield return new NoiseBrushTool( this );\n\t}\n}\n\n/// \u003Csummary\u003E\n/// Base for CPU-side sculpt brushes. The stock tools sculpt with a GPU compute shader;\n/// these do the same on the CPU by directly editing the terrain\u0027s heightmap array, which\n/// lets us invent entirely new brush behaviour without shipping a new shader.\n/// Also keeps the painted region highlighted while the mouse is held down.\n/// \u003C/summary\u003E\npublic abstract class CpuSculptBrushTool : BaseBrushTool\n{\n\tushort[] _strokeBefore;\n\tRectInt _strokeRegion;\n\tbool _strokeActive;\n\n\t// Brush footprints stamped so far this stroke, for the circle highlight\n\tList\u003C(int cx, int cy, int size)\u003E _strokeCircles;\n\n\t// One terrain-projected decal per stamped circle, like the stock brush preview\n\tList\u003CBrushPreviewSceneObject\u003E _highlightObjects;\n\n\t// Deferred-apply selection: per-texel max falloff weight covered by the stroke\n\tfloat[] _selectionWeights;\n\tfloat _selectionOpacity;\n\n\t/// \u003Csummary\u003E\n\t/// When true, painting only records the brush footprint (a selection) instead of sculpting.\n\t/// The whole selection is sculpted at once in \u003Csee cref=\u0022ApplySelection\u0022/\u003E when the mouse is\n\t/// released, so the effect lands across the entire dragged area simultaneously.\n\t/// \u003C/summary\u003E\n\tprotected virtual bool ApplyOnRelease =\u003E false;\n\n\tpublic override bool PaintMode { get; set; } = true;\n\n\tprotected CpuSculptBrushTool( TerrainEditorTool terrainEditorTool ) : base( terrainEditorTool )\n\t{\n\t}\n\n\tpublic override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\t// Keep the brush circles we\u0027ve painted highlighted until the mouse is released\n\t\tif ( _strokeActive )\n\t\t{\n\t\t\tvar terrain = GetSelectedComponent\u003CTerrain\u003E() ?? Scene.Get\u003CTerrain\u003E();\n\t\t\tif ( terrain.IsValid() )\n\t\t\t\tUpdateStrokeHighlight( terrain );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Renders a translucent brush-preview decal for every circle stamped during the stroke.\n\t/// Uses the same terrain-projected \u003Csee cref=\u0022BrushPreviewSceneObject\u0022/\u003E the stock terrain\n\t/// tool shows for its brush preview, so the highlight hugs the terrain surface.\n\t/// \u003C/summary\u003E\n\tvoid UpdateStrokeHighlight( Terrain terrain )\n\t{\n\t\tint res = terrain.Storage.Resolution;\n\t\tif ( res \u003C= 0 || _strokeCircles == null ) return;\n\n\t\t// Grow/shrink the decal list to match the number of stamped circles\n\t\tif ( _highlightObjects == null )\n\t\t\t_highlightObjects = new List\u003CBrushPreviewSceneObject\u003E();\n\n\t\twhile ( _highlightObjects.Count \u003C _strokeCircles.Count )\n\t\t\t_highlightObjects.Add( new BrushPreviewSceneObject( Gizmo.World ) );\n\n\t\twhile ( _highlightObjects.Count \u003E _strokeCircles.Count )\n\t\t{\n\t\t\tvar extra = _highlightObjects[^1];\n\t\t\textra.Delete();\n\t\t\t_highlightObjects.RemoveAt( _highlightObjects.Count - 1 );\n\t\t}\n\n\t\tvar tx = terrain.WorldTransform;\n\t\tfloat heightScale = terrain.Storage.TerrainHeight / 65535f;\n\t\tfloat unitsPerTexel = terrain.Storage.TerrainSize / (float)res;\n\n\t\tfor ( int i = 0; i \u003C _strokeCircles.Count; i\u002B\u002B )\n\t\t{\n\t\t\tvar (cx, cy, size) = _strokeCircles[i];\n\n\t\t\tint hx = Math.Clamp( cx, 0, res - 1 );\n\t\t\tint hy = Math.Clamp( cy, 0, res - 1 );\n\t\t\tfloat h = terrain.Storage.HeightMap[hy * res \u002B hx] * heightScale;\n\n\t\t\tvar obj = _highlightObjects[i];\n\t\t\tobj.RenderLayer = SceneRenderLayer.OverlayWithDepth;\n\t\t\tobj.Bounds = BBox.FromPositionAndSize( 0, float.MaxValue );\n\t\t\tobj.Transform = new Transform( tx.PointToWorld( new Vector3( cx * unitsPerTexel, cy * unitsPerTexel, h ) ), tx.Rotation );\n\t\t\tobj.Radius = size * 0.5f * unitsPerTexel;\n\t\t\tobj.Texture = TerrainEditorTool.Brush?.Texture;\n\t\t\tobj.Color = Color.FromBytes( 255, 165, 0 ).WithAlpha( 0.5f );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Deletes the highlight decals, called when the stroke ends.\n\t/// \u003C/summary\u003E\n\tvoid ClearStrokeHighlight()\n\t{\n\t\tif ( _highlightObjects != null )\n\t\t{\n\t\t\tforeach ( var obj in _highlightObjects )\n\t\t\t\tobj.Delete();\n\t\t\t_highlightObjects.Clear();\n\t\t}\n\t}\n\n\tprotected override void OnPaint( Terrain terrain, TerrainPaintParameters paint )\n\t{\n\t\tint res = terrain.Storage.Resolution;\n\n\t\t// Brush footprint in texels\n\t\tint size = (int)Math.Floor( paint.BrushSettings.Size * 2.0f / terrain.Storage.TerrainSize * res );\n\t\tsize = Math.Max( size, 1 );\n\n\t\tint cx = (int)Math.Floor( paint.HitUV.x * res );\n\t\tint cy = (int)Math.Floor( paint.HitUV.y * res );\n\n\t\tvar region = new RectInt( cx - size / 2, cy - size / 2, size \u002B 1, size \u002B 1 );\n\n\t\t// On the first paint of a stroke, snapshot the entire heightmap so undo can restore it\n\t\t// no matter how far the stroke drags.\n\t\tif ( !_strokeActive )\n\t\t{\n\t\t\t_strokeActive = true;\n\t\t\t_strokeRegion = region;\n\t\t\t_strokeBefore = (ushort[])terrain.Storage.HeightMap.Clone();\n\t\t\t_strokeCircles = new List\u003C(int, int, int)\u003E();\n\n\t\t\tif ( ApplyOnRelease )\n\t\t\t{\n\t\t\t\t_selectionWeights = new float[res * res];\n\t\t\t\t_selectionOpacity = paint.BrushSettings.Opacity;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Expand the dirty region to cover this frame\u0027s footprint\n\t\t\tint left = Math.Min( _strokeRegion.Left, region.Left );\n\t\t\tint top = Math.Min( _strokeRegion.Top, region.Top );\n\t\t\tint right = Math.Max( _strokeRegion.Right, region.Right );\n\t\t\tint bottom = Math.Max( _strokeRegion.Bottom, region.Bottom );\n\t\t\t_strokeRegion = new RectInt( left, top, right - left, bottom - top );\n\t\t}\n\n\t\t_strokeCircles.Add( (cx, cy, size) );\n\n\t\tif ( ApplyOnRelease )\n\t\t{\n\t\t\t// Only mark the selection - no height changes until the mouse is released\n\t\t\tStampSelection( terrain, paint, res, cx, cy, size );\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Let the brush decide how to edit each texel\n\t\t\tSculpt( terrain, paint, res, cx, cy, size );\n\n\t\t\t// Upload CPU -\u003E GPU and refresh collision so the sculpt shows live\n\t\t\tterrain.SyncGPUTexture();\n\t\t\tterrain.UpdateCollision( Terrain.SyncFlags.Height, _strokeRegion );\n\t\t}\n\t}\n\n\tprotected abstract void Sculpt( Terrain terrain, TerrainPaintParameters paint, int res, int centerX, int centerY, int size );\n\n\t/// \u003Csummary\u003E\n\t/// Records the brush\u0027s falloff weight for every texel in the footprint. The strongest weight\n\t/// any stamp leaves on a texel wins, so overlapping circles blend into one clean selection.\n\t/// \u003C/summary\u003E\n\tvoid StampSelection( Terrain terrain, TerrainPaintParameters paint, int res, int centerX, int centerY, int size )\n\t{\n\t\tint radius = size / 2;\n\n\t\tfor ( int y = -radius; y \u003C= radius; y\u002B\u002B )\n\t\t{\n\t\t\tfor ( int x = -radius; x \u003C= radius; x\u002B\u002B )\n\t\t\t{\n\t\t\t\tint tx = centerX \u002B x;\n\t\t\t\tint ty = centerY \u002B y;\n\t\t\t\tif ( tx \u003C 0 || ty \u003C 0 || tx \u003E= res || ty \u003E= res ) continue;\n\n\t\t\t\tfloat w = SampleBrush( paint, x \u002B radius, y \u002B radius, size );\n\t\t\t\tif ( w \u003C= 0.001f ) continue;\n\n\t\t\t\tint index = ty * res \u002B tx;\n\t\t\t\tif ( w \u003E _selectionWeights[index] )\n\t\t\t\t\t_selectionWeights[index] = w;\n\t\t\t}\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Applies the brush over the whole recorded selection at once. Called on mouse release when\n\t/// \u003Csee cref=\u0022ApplyOnRelease\u0022/\u003E is true. The weights array holds the max falloff weight for\n\t/// every texel touched by the stroke.\n\t/// \u003C/summary\u003E\n\tprotected virtual void ApplySelection( Terrain terrain, int res, float[] weights, float opacity )\n\t{\n\t}\n\n\tprotected override void OnPaintEnded( Terrain terrain )\n\t{\n\t\tif ( _strokeActive )\n\t\t{\n\t\t\tint res = terrain.Storage.Resolution;\n\n\t\t\t// Clamp the unioned dirty region to the terrain bounds\n\t\t\t_strokeRegion.Left = Math.Clamp( _strokeRegion.Left, 0, res - 1 );\n\t\t\t_strokeRegion.Right = Math.Clamp( _strokeRegion.Right, 0, res - 1 );\n\t\t\t_strokeRegion.Top = Math.Clamp( _strokeRegion.Top, 0, res - 1 );\n\t\t\t_strokeRegion.Bottom = Math.Clamp( _strokeRegion.Bottom, 0, res - 1 );\n\n\t\t\tif ( ApplyOnRelease \u0026\u0026 _selectionWeights != null )\n\t\t\t{\n\t\t\t\t// Sculpt the entire dragged selection in one go, then sync once\n\t\t\t\tApplySelection( terrain, res, _selectionWeights, _selectionOpacity );\n\t\t\t\tterrain.SyncGPUTexture();\n\t\t\t\tterrain.UpdateCollision( Terrain.SyncFlags.Height, _strokeRegion );\n\t\t\t}\n\n\t\t\tushort[] after = (ushort[])terrain.Storage.HeightMap.Clone();\n\t\t\tvar region = _strokeRegion;\n\n\t\t\tAction Restore( ushort[] data ) =\u003E () =\u003E\n\t\t\t{\n\t\t\t\tif ( !terrain.IsValid() ) return;\n\t\t\t\tWriteHeightRegion( terrain.Storage.HeightMap, res, region, data );\n\t\t\t\tterrain.SyncGPUTexture();\n\t\t\t\tterrain.UpdateCollision( Terrain.SyncFlags.Height, region );\n\t\t\t};\n\n\t\t\tSceneEditorSession.Active.UndoSystem.Insert( $\u0022Terrain {DisplayInfo.For( this ).Name}\u0022, Restore( _strokeBefore ), Restore( after ) );\n\n\t\t\t_strokeBefore = null;\n\t\t\t_strokeActive = false;\n\t\t\t_strokeCircles = null;\n\t\t\t_selectionWeights = null;\n\t\t\tClearStrokeHighlight();\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Sample the selected brush\u0027s falloff at a local footprint coordinate.\n\t/// Returns 0..1, 1 in the middle, 0 at the edges.\n\t/// \u003C/summary\u003E\n\tprotected float SampleBrush( TerrainPaintParameters paint, int localX, int localY, int size )\n\t{\n\t\tif ( size \u003C= 0 ) return 0f;\n\n\t\tvar pixmap = paint.Brush?.Pixmap;\n\t\tif ( pixmap != null \u0026\u0026 pixmap.Width \u003E 0 \u0026\u0026 pixmap.Height \u003E 0 )\n\t\t{\n\t\t\tfloat u = (localX \u002B 0.5f) / size;\n\t\t\tfloat v = (localY \u002B 0.5f) / size;\n\n\t\t\tint px = Math.Clamp( (int)(u * pixmap.Width), 0, pixmap.Width - 1 );\n\t\t\tint py = Math.Clamp( (int)(v * pixmap.Height), 0, pixmap.Height - 1 );\n\n\t\t\tvar c = pixmap.GetPixel( px, py );\n\t\t\treturn Math.Clamp( c.r, 0f, 1f );\n\t\t}\n\n\t\t// Fallback: soft radial falloff\n\t\tfloat nx = (localX \u002B 0.5f - size * 0.5f) / (size * 0.5f);\n\t\tfloat ny = (localY \u002B 0.5f - size * 0.5f) / (size * 0.5f);\n\t\tfloat d = MathF.Sqrt( nx * nx \u002B ny * ny );\n\t\treturn Math.Clamp( 1f - d, 0f, 1f );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Writes a full heightmap snapshot into the given dirty region. The snapshot may be a full\n\t/// map clone, but we only copy the region we actually painted so collision updates stay cheap.\n\t/// \u003C/summary\u003E\n\tstatic void WriteHeightRegion( ushort[] heightmap, int res, RectInt region, ushort[] data )\n\t{\n\t\tfor ( int y = 0; y \u003C region.Height; y\u002B\u002B )\n\t\t{\n\t\t\tfor ( int x = 0; x \u003C region.Width; x\u002B\u002B )\n\t\t\t{\n\t\t\t\theightmap[region.Left \u002B x \u002B (region.Top \u002B y) * res] = data[region.Left \u002B x \u002B (region.Top \u002B y) * res];\n\t\t\t}\n\t\t}\n\t}\n}\n\n/// \u003Csummary\u003E\n/// Raises a smooth dome inside the brush footprint.\n/// \u003C/summary\u003E\n[Title( \u0022Bulge\u0022 )]\n[Icon( \u0022bubble_chart\u0022 )]\n[Alias( \u0022tools.terrain.bulge\u0022 )]\n[Group( \u00221\u0022 )]\n[Order( 1 )]\npublic class BulgeBrushTool : CpuSculptBrushTool\n{\n\tpublic BulgeBrushTool( TerrainEditorTool terrainEditorTool ) : base( terrainEditorTool )\n\t{\n\t}\n\n\tprotected override void Sculpt( Terrain terrain, TerrainPaintParameters paint, int res, int centerX, int centerY, int size )\n\t{\n\t\tvar heightmap = terrain.Storage.HeightMap;\n\t\tfloat opacity = paint.BrushSettings.Opacity;\n\t\tint radius = size / 2;\n\n\t\tfor ( int y = -radius; y \u003C= radius; y\u002B\u002B )\n\t\t{\n\t\t\tfor ( int x = -radius; x \u003C= radius; x\u002B\u002B )\n\t\t\t{\n\t\t\t\tint tx = centerX \u002B x;\n\t\t\t\tint ty = centerY \u002B y;\n\t\t\t\tif ( tx \u003C 0 || ty \u003C 0 || tx \u003E= res || ty \u003E= res ) continue;\n\n\t\t\t\tfloat brush = SampleBrush( paint, x \u002B radius, y \u002B radius, size );\n\t\t\t\tif ( brush \u003C= 0.001f ) continue;\n\n\t\t\t\tint index = ty * res \u002B tx;\n\t\t\t\tfloat current = heightmap[index] / 65535f;\n\n\t\t\t\t// Parabolic dome: 1 at centre, 0 at the edge\n\t\t\t\tfloat dome = brush * brush;\n\t\t\t\tfloat target = current \u002B dome * opacity;\n\n\t\t\t\theightmap[index] = (ushort)Math.Clamp( target * 65535f, 0, 65535 );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/// \u003Csummary\u003E\n/// Digs a rounded depression with a slightly raised rim, like an impact crater.\n/// \u003C/summary\u003E\n[Title( \u0022Crater\u0022 )]\n[Icon( \u0022brightness_low\u0022 )]\n[Alias( \u0022tools.terrain.crater\u0022 )]\n[Group( \u00221\u0022 )]\n[Order( 1 )]\npublic class CraterBrushTool : CpuSculptBrushTool\n{\n\tpublic CraterBrushTool( TerrainEditorTool terrainEditorTool ) : base( terrainEditorTool )\n\t{\n\t}\n\n\tprotected override void Sculpt( Terrain terrain, TerrainPaintParameters paint, int res, int centerX, int centerY, int size )\n\t{\n\t\tvar heightmap = terrain.Storage.HeightMap;\n\t\tfloat opacity = paint.BrushSettings.Opacity;\n\t\tint radius = size / 2;\n\n\t\tfor ( int y = -radius; y \u003C= radius; y\u002B\u002B )\n\t\t{\n\t\t\tfor ( int x = -radius; x \u003C= radius; x\u002B\u002B )\n\t\t\t{\n\t\t\t\tint tx = centerX \u002B x;\n\t\t\t\tint ty = centerY \u002B y;\n\t\t\t\tif ( tx \u003C 0 || ty \u003C 0 || tx \u003E= res || ty \u003E= res ) continue;\n\n\t\t\t\tfloat brush = SampleBrush( paint, x \u002B radius, y \u002B radius, size );\n\t\t\t\tif ( brush \u003C= 0.001f ) continue;\n\n\t\t\t\tint index = ty * res \u002B tx;\n\t\t\t\tfloat current = heightmap[index] / 65535f;\n\n\t\t\t\t// Depression with a raised rim: dip in the middle, bump near the edge\n\t\t\t\tfloat rim = brush \u003C 0.75f ? -brush : (brush - 0.75f) / 0.25f;\n\t\t\t\tfloat target = current \u002B rim * opacity;\n\n\t\t\t\theightmap[index] = (ushort)Math.Clamp( target * 65535f, 0, 65535 );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/// \u003Csummary\u003E\n/// Snaps heights to evenly spaced terraced steps within the brush footprint.\n/// \u003C/summary\u003E\n[Title( \u0022Terrace\u0022 )]\n[Icon( \u0022stairs\u0022 )]\n[Alias( \u0022tools.terrain.terrace\u0022 )]\n[Group( \u00221\u0022 )]\n[Order( 1 )]\npublic class TerraceBrushTool : CpuSculptBrushTool\n{\n\tpublic TerraceBrushTool( TerrainEditorTool terrainEditorTool ) : base( terrainEditorTool )\n\t{\n\t}\n\n\tprotected override void Sculpt( Terrain terrain, TerrainPaintParameters paint, int res, int centerX, int centerY, int size )\n\t{\n\t\tvar heightmap = terrain.Storage.HeightMap;\n\t\tfloat opacity = paint.BrushSettings.Opacity;\n\t\tint radius = size / 2;\n\n\t\t// Step every 8% of full height - you could expose this as a setting later\n\t\tconst float stepSize = 0.08f;\n\n\t\tfor ( int y = -radius; y \u003C= radius; y\u002B\u002B )\n\t\t{\n\t\t\tfor ( int x = -radius; x \u003C= radius; x\u002B\u002B )\n\t\t\t{\n\t\t\t\tint tx = centerX \u002B x;\n\t\t\t\tint ty = centerY \u002B y;\n\t\t\t\tif ( tx \u003C 0 || ty \u003C 0 || tx \u003E= res || ty \u003E= res ) continue;\n\n\t\t\t\tfloat brush = SampleBrush( paint, x \u002B radius, y \u002B radius, size );\n\t\t\t\tif ( brush \u003C= 0.001f ) continue;\n\n\t\t\t\tint index = ty * res \u002B tx;\n\t\t\t\tfloat current = heightmap[index] / 65535f;\n\n\t\t\t\tfloat stepped = MathF.Round( current / stepSize ) * stepSize;\n\t\t\t\tfloat target = MathX.LerpTo( current, stepped, opacity * brush );\n\n\t\t\t\theightmap[index] = (ushort)Math.Clamp( target * 65535f, 0, 65535 );\n\t\t\t}\n\t\t}\n\t}\n}\n\n/// \u003Csummary\u003E\n/// Adds adjustable simplex noise to the terrain, driven by the library\u0027s OpenSimplex2S\n/// generator. Frequency, strength and seed can be tuned with the toolbar sliders.\n/// \u003C/summary\u003E\n[Title( \u0022Noise\u0022 )]\n[Icon( \u0022shuffle\u0022 )]\n[Alias( \u0022tools.terrain.noise\u0022 )]\n[Group( \u00221\u0022 )]\n[Order( 1 )]\npublic class NoiseBrushTool : CpuSculptBrushTool\n{\n\t/// \u003Csummary\u003ENoise frequency - higher = more, smaller bumps.\u003C/summary\u003E\n\t[Property, Range( 0.5f, 20f ), Step( 0.1f ), WideMode] public float Frequency { get; set; } = 4f;\n\n\t/// \u003Csummary\u003EHow strongly the noise displaces the height (0..1, fraction of full height).\u003C/summary\u003E\n\t[Property, Range( 0.001f, 0.05f ), Step( 0.001f ), WideMode] public float Strength { get; set; } = 0.008f;\n\n\t/// \u003Csummary\u003ERandom seed for the noise field.\u003C/summary\u003E\n\t[Property, Range( 0, 100000 ), Step( 1 ), WideMode] public int NoiseSeed { get; set; } = 1337;\n\n\tpublic NoiseBrushTool( TerrainEditorTool terrainEditorTool ) : base( terrainEditorTool )\n\t{\n\t}\n\n\t// Noise is applied once across the whole dragged selection when the mouse is released,\n\t// not per-frame while painting.\n\tprotected override bool ApplyOnRelease =\u003E true;\n\n\t/// \u003Csummary\u003E\n\t/// Shows the stock terrain brush settings plus a \u0022Noise Settings\u0022 group bound to the\n\t/// noise properties, so Frequency / Strength / Seed can be tuned in the sidebar.\n\t/// \u003C/summary\u003E\n\tpublic override Widget CreateToolSidebar()\n\t{\n\t\tif ( _parent is null ) return null;\n\n\t\tvar sidebar = (ToolSidebarWidget)_parent.CreateToolSidebar();\n\t\tif ( sidebar is null ) return null;\n\n\t\tvar so = EditorTypeLibrary.GetSerializedObject( this );\n\t\tvar group = sidebar.AddGroup( \u0022Noise Settings\u0022 );\n\n\t\tvar sheet = new ControlSheet();\n\t\tsheet.AddObject( so, prop =\u003E\n\t\t\tprop.Name is nameof( Frequency ) or nameof( Strength ) or nameof( NoiseSeed ) );\n\t\tgroup.Add( sheet );\n\n\t\treturn sidebar;\n\t}\n\n\t// Not used - ApplyOnRelease routes painting through ApplySelection instead.\n\tprotected override void Sculpt( Terrain terrain, TerrainPaintParameters paint, int res, int centerX, int centerY, int size )\n\t{\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Applies the noise field across every texel covered by the stroke, using the strongest\n\t/// brush falloff weight recorded for each texel.\n\t/// \u003C/summary\u003E\n\tprotected override void ApplySelection( Terrain terrain, int res, float[] weights, float opacity )\n\t{\n\t\tvar heightmap = terrain.Storage.HeightMap;\n\n\t\tfor ( int i = 0; i \u003C weights.Length; i\u002B\u002B )\n\t\t{\n\t\t\tfloat w = weights[i];\n\t\t\tif ( w \u003C= 0.001f ) continue;\n\n\t\t\tint tx = i % res;\n\t\t\tint ty = i / res;\n\n\t\t\t// Sample simplex noise at the texel, in [0,1], then remap to [-1,1]\n\t\t\t// so the noise can both add and subtract height.\n\t\t\tfloat noise = OpenSimplex2S.Noise2( NoiseSeed, tx * Frequency, ty * Frequency );\n\t\t\tnoise = noise * 2f - 1f;\n\n\t\t\tfloat current = heightmap[i] / 65535f;\n\t\t\tfloat target = current \u002B noise * Strength * opacity * w;\n\n\t\t\theightmap[i] = (ushort)Math.Clamp( target * 65535f, 0, 65535 );\n\t\t}\n\t}\n\n\tpublic override Widget CreateToolbarWidget()\n\t{\n\t\tvar group = new Widget();\n\t\tgroup.FixedHeight = Theme.RowHeight;\n\t\tgroup.Layout = Layout.Row();\n\t\tgroup.Layout.Spacing = 6;\n\n\t\tgroup.Layout.Add( new Label( \u0022Frequency\u0022 ) );\n\t\tvar freq = new FloatSlider( group );\n\t\tfreq.Minimum = 0.5f;\n\t\tfreq.Maximum = 20f;\n\t\tfreq.Step = 0.1f;\n\t\tfreq.Value = Frequency;\n\t\tfreq.OnValueEdited = () =\u003E Frequency = freq.Value;\n\t\tgroup.Layout.Add( freq, 1 );\n\n\t\tgroup.Layout.Add( new Label( \u0022Strength\u0022 ) );\n\t\tvar strength = new FloatSlider( group );\n\t\tstrength.Minimum = 0.001f;\n\t\tstrength.Maximum = 0.05f;\n\t\tstrength.Step = 0.001f;\n\t\tstrength.Value = Strength;\n\t\tstrength.OnValueEdited = () =\u003E Strength = strength.Value;\n\t\tgroup.Layout.Add( strength, 1 );\n\n\t\tgroup.Layout.Add( new Label( \u0022Seed\u0022 ) );\n\t\tvar seed = new FloatSlider( group );\n\t\tseed.Minimum = 0;\n\t\tseed.Maximum = 100000;\n\t\tseed.Step = 1;\n\t\tseed.Value = NoiseSeed;\n\t\tseed.OnValueEdited = () =\u003E NoiseSeed = (int)seed.Value;\n\t\tgroup.Layout.Add( seed, 1 );\n\n\t\tgroup.OnPaintOverride = () =\u003E\n\t\t{\n\t\t\tPaint.ClearPen();\n\t\t\tPaint.SetBrush( Theme.ControlBackground );\n\t\t\tPaint.DrawRect( group.LocalRect, Theme.ControlRadius );\n\t\t\treturn true;\n\t\t};\n\n\t\treturn group;\n\t}\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":".obj/__compiler_extra.cs","FileName":"__compiler_extra.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":339832,"Code":"global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonTitle\u0022, \u0022Terrain Pro\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022terraingenerationtool\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022sturnus\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022sturnus.terraingenerationtool\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002228\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00222026-08-06T23:17:13.0077717Z\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.327.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.327.0\u0022)]"},{"Ident":"sturnus.terraingenerationtool","Path":"Code/File.cs","FileName":"File.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":339832,"Code":"using Sandbox;\n\n"},{"Ident":"sturnus.terraingenerationtool","Path":"File.cs","FileName":"File.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":339832,"Code":"using Sandbox;\n\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/Noise/Lanczos.cs","FileName":"Lanczos.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Drawing;\nusing System.Text;\n\nnamespace Sturnus.TerrainGenerationTool.Noise.Lanczos\n{\n\t//A version of value-noise using Lanczos-Resampling. Also has classic billiniar-noise\n\t//Made by Zomare\n\n\tclass ValueNoise\n\t{\n\t\t//Hash-Function for rng, outputs in range [-1, 1]\n\t\tstatic float Random( int x1, int y1 )\n\t\t{\n\t\t\tbyte[] table = {151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7,\n\t\t\t\t\t\t\t225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247,\n\t\t\t\t\t\t\t120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,\n\t\t\t\t\t\t\t88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134,\n\t\t\t\t\t\t\t139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220,\n\t\t\t\t\t\t\t105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80,\n\t\t\t\t\t\t\t73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,\n\t\t\t\t\t\t\t164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38,\n\t\t\t\t\t\t\t147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,\n\t\t\t\t\t\t\t28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101,\n\t\t\t\t\t\t\t155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232,\n\t\t\t\t\t\t\t178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12,\n\t\t\t\t\t\t\t191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181,\n\t\t\t\t\t\t\t199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236,\n\t\t\t\t\t\t\t205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180};\n\n\t\t\treturn ((float)table[(x1 \u002B table[y1 \u0026 255]) \u0026 255] / 255f) * 2 - 1;\n\t\t}\n\n\n\t\tpublic static float Compute( float x, float y )\n\t\t{\n\t\t\tint ix = (int)x;\n\t\t\tint iy = (int)y;\n\n\t\t\tfloat dx = x - ix;\n\t\t\tfloat dy = y - iy;\n\n\t\t\t//averages used for normalizing\n\t\t\tfloat avgY = 0;\n\t\t\tfloat avgX = 0;\n\n\t\t\t//Calculating lookup table for faster horizontal interpolation\n\t\t\tfloat[] lanczosX = new float[6];\n\n\t\t\tfor ( int px = -2; px \u003C 4; px\u002B\u002B )\n\t\t\t{\n\t\t\t\tfloat f = Lanczos( dx - px );\n\t\t\t\tavgX \u002B= f;\n\t\t\t\tlanczosX[px \u002B 2] = f;\n\t\t\t}\n\n\t\t\tfloat n = 0;\n\n\t\t\tfor ( int py = -2; py \u003C 4; py\u002B\u002B )\n\t\t\t{\n\t\t\t\tfloat a = 0;\n\n\t\t\t\tfor ( int px = -2; px \u003C 4; px\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\ta \u002B= Random( ix \u002B px, iy \u002B py ) * lanczosX[px \u002B 2];\n\t\t\t\t}\n\n\t\t\t\ta /= avgX;\n\t\t\t\tn \u002B= a * Lanczos( dy - py );\n\t\t\t\tavgY \u002B= Lanczos( dy - py );\n\t\t\t}\n\n\t\t\t//!Not correctly normalized!\n\t\t\treturn smoothstep( -1, 1, (n / avgY / 1.25f \u002B 1) / 2f );\n\t\t}\n\n\t\t//Lanczos function used for interpolation\n\t\t//L(x)=sinc(x)sinc(x/a)\n\t\tstatic float Lanczos( float t )\n\t\t{\n\t\t\tif ( t == 0 )\n\t\t\t{\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\telse if ( t \u003E 4 || t \u003C -4 )\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn 3 * (float)((Math.Sin( Math.PI * t ) * Math.Sin( Math.PI * (t / 3) )) / (Math.PI * Math.PI * t * t));\n\t\t}\n\n\n\t\t//Left in for the purpose of maybe using it later\n\t\tstatic float Sinc( float x )\n\t\t{\n\t\t\treturn (float)(Math.Sin( Math.PI * x ) / (Math.PI * x));\n\t\t}\n\n\t\t//Outputs an low frequency octave of noise as a png\n\t\t/*static public void Test( int d )\n\t\t{\n\t\t\tvar bm = new Bitmap( d, d );\n\n\t\t\tint off = new Random().Next( -1000, 1000 );\n\n\t\t\tfor ( int y = 0; y \u003C d; y\u002B\u002B )\n\t\t\t{\n\t\t\t\tfor ( int x = 0; x \u003C d; x\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tint c = (int)(255 * (Compute( x * 0.025f, y * 0.025f \u002B off ) \u002B 1) / 2f);\n\n\t\t\t\t\tbm.SetPixel( x, y, Color.FromArgb( c, c, c ) );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbm.Save( \u0022test.png\u0022 );\n\t\t}*/\n\n\t\t//Generates a worldmap and outputs it as a png\n\t\t/*static public void Generate( int d )\n\t\t{\n\t\t\tvar bm = new Bitmap( d, d );\n\n\t\t\tint off = new Random().Next( -1000, 1000 );\n\n\t\t\tfor ( int y = 0; y \u003C d; y\u002B\u002B )\n\t\t\t{\n\t\t\t\tfor ( int x = 0; x \u003C d; x\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tfloat a = Math.Abs( ComputeFractal( x * 0.02f, y * 0.02f \u002B off, 4 ) );\n\n\t\t\t\t\tif ( a \u003E 0.2f )\n\t\t\t\t\t{\n\t\t\t\t\t\ta = 0.2f;\n\t\t\t\t\t}\n\t\t\t\t\ta /= 0.2f;\n\n\n\t\t\t\t\ta *= ComputeFractal( x * 0.025f, y * 0.025f \u002B 1000 \u002B off, 8 ) * 0.5f \u002B 0.5f;\n\n\t\t\t\t\tColor c = Color.Aqua;\n\n\n\t\t\t\t\tif ( a \u003E 0.9f )\n\t\t\t\t\t{\n\t\t\t\t\t\tc = Color.White;\n\t\t\t\t\t}\n\t\t\t\t\telse if ( a \u003E 0.75f )\n\t\t\t\t\t{\n\t\t\t\t\t\tc = Color.LightGray;\n\t\t\t\t\t}\n\t\t\t\t\telse if ( a \u003E 0.4f )\n\t\t\t\t\t{\n\t\t\t\t\t\tc = Color.ForestGreen;\n\t\t\t\t\t}\n\n\t\t\t\t\tbm.SetPixel( x, y, c );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbm.Save( \u0022map.png\u0022 );\n\t\t}*/\n\n\t\t//Fractal Noise; n: amount of octaves\n\t\tstatic public float ComputeFractal( float x, float y, int n )\n\t\t{\n\t\t\tfloat a = 0;\n\t\t\tfloat avg = 0;\n\t\t\tfloat w = 1;\n\t\t\tfloat frq = 1;\n\n\t\t\tfor ( int i = 0; i \u003C n; i\u002B\u002B )\n\t\t\t{\n\t\t\t\ta \u002B= Compute( x * frq, y * frq ) * w;\n\t\t\t\tavg \u002B= w;\n\t\t\t\tw *= 0.25f;\n\t\t\t\tfrq *= 3;\n\t\t\t}\n\n\t\t\treturn a / avg;\n\t\t}\n\n\t\t//\u0022Normal\u0022 Value-Noise\n\t\tstatic public float ComputeLinear( float x, float y )\n\t\t{\n\t\t\tint ix = (int)x;\n\t\t\tint iy = (int)y;\n\n\t\t\tfloat dx = x - ix;\n\t\t\tfloat dy = y - iy;\n\n\t\t\tfloat fx1 = lerp( Random( ix, iy ), Random( ix \u002B 1, iy ), dx );\n\t\t\tfloat fx2 = lerp( Random( ix, iy \u002B 1 ), Random( ix \u002B 1, iy \u002B 1 ), dx );\n\n\t\t\treturn lerp( fx1, fx2, dy );\n\t\t}\n\n\n\t\t//Basic interpolation Functions\n\n\t\t//7th order smoothstep\n\t\tstatic float smootherstep( float a1, float a2, float t )\n\t\t{\n\t\t\treturn lerp( a1, a2, t * t * t * t * (t * (t * (70 - 20 * t) - 84) \u002B 35) );\n\t\t}\n\n\t\t//Normal smoothstep\n\t\tstatic float smoothstep( float a1, float a2, float t )\n\t\t{\n\t\t\treturn lerp( a1, a2, t * t * (3 - 2 * t) );\n\t\t}\n\n\t\tstatic float lerp( float a1, float a2, float t )\n\t\t{\n\t\t\tt = Math.Clamp( t, 0f, 1f );\n\n\t\t\treturn (1 - t) * a1 \u002B t * a2;\n\t\t}\n\t}\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainShapes/Volcanic.cs","FileName":"Volcanic.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using Editor;\nusing Sandbox;\nusing Sandbox.UI;\nusing System;\n\nnamespace Sturnus.TerrainGenerationTool;\npublic static class Volcanic\n{\n\tpublic static float Default(\n\t\tint x,\n\t\tint y,\n\t\tint width,\n\t\tint height,\n\t\tlong seed,\n\t\tfloat minHeight,\n\t\tbool warp,\n\t\tfloat warpSize,\n\t\tfloat warpStrength\n\t)\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\n\t\tfloat craterRadius = 0.3f;        // Average radius of the central crater\n\t\tfloat rimHeight = 0.25f;          // Height of the crater rim\n\t\tfloat rimWidth = 0f;           // Width of the rim\n\t\tfloat outerSlopeStrength = 0.5f;  // Strength of the gradient for the exterior slope\n\t\tfloat innerSlopeStrength = 2.0f;  // Strength of the gradient for the interior slope\n\t\tfloat baseHeight = 0.1f;          // Minimum base height\n\t\tfloat noiseStrength = 0.02f; // General noise strength\n\t\tfloat distance = MathF.Sqrt( nx * nx \u002B ny * ny );\n\n\t\t// Calculate distance from the center of the heightmap\n\t\tfloat centerX = 0f, centerY = 0f; // Center of the heightmap\n\t\tfloat distanceToCenter = MathF.Sqrt( (nx - centerX) * (nx - centerX) \u002B (ny - centerY) * (ny - centerY) );\n\n\t\t// Apply domain warping for irregularity\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Introduce irregularity to the crater radius\n\t\tfloat craterIrregularity = OpenSimplex2S.Noise2( seed \u002B 20, nx * 4.0f, ny * 4.0f ) * 0.1f;\n\t\tfloat dynamicCraterRadius = craterRadius \u002B craterIrregularity;\n\n\t\t// Initialize height components\n\t\tfloat crater = 0f;\n\t\tfloat rim = 0f;\n\t\tfloat outerSlope = 0f;\n\n\t\t// Crater: Smooth inward slope towards the center of the crater\n\t\tif ( distanceToCenter \u003C dynamicCraterRadius )\n\t\t{\n\t\t\tfloat craterDepth = (1f - (distanceToCenter));\n\t\t\tcrater = MathF.Pow( craterDepth, innerSlopeStrength ) ; // Inward slope\n\t\t}\n\n\t\t// Rim: Uneven ridge around the crater\n\t\tif ( distanceToCenter \u003E= dynamicCraterRadius \u0026\u0026 distanceToCenter \u003C dynamicCraterRadius \u002B rimWidth )\n\t\t{\n\t\t\tfloat rimFalloff = (distanceToCenter - dynamicCraterRadius) / rimWidth;\n\t\t\tfloat rimNoise = OpenSimplex2S.Noise2( seed \u002B 30, nx * 8.0f, ny * 8.0f ) * noiseStrength;\n\t\t\trim = (1f - rimFalloff) * rimHeight \u002B rimNoise; // Add noise for unevenness\n\t\t}\n\n\t\t// Outer slope: Smooth gradient with noise towards the edges\n\t\tif ( distanceToCenter \u003E= dynamicCraterRadius \u002B rimWidth )\n\t\t{\n\t\t\tfloat slopeDistance = 1f - distanceToCenter; // Decrease height as we approach the edge\n\t\t\tfloat slopeNoise = OpenSimplex2S.Noise2( seed \u002B 40, nx * 4.0f, ny * 4.0f ) * noiseStrength;\n\t\t\touterSlope = MathF.Max( 0, slopeDistance ) * outerSlopeStrength \u002B slopeNoise;\n\t\t}\n\n\t\t// Combine components\n\t\tfloat heightValue = baseHeight \u002B crater \u002B rim \u002B outerSlope;\n\n\t\t// Smooth transition towards the crater center for a more natural look\n\t\tif ( distanceToCenter \u003C dynamicCraterRadius )\n\t\t{\n\t\t\tfloat centerFalloff = MathF.Pow( 1f - (distanceToCenter / dynamicCraterRadius), 2f );\n\t\t\theightValue = centerFalloff * baseHeight; // Slight bump for a smoother slope\n\t\t}\n\n\n\t\theightValue = Math.Max( heightValue, baseHeight );\n\n\t\tvar heightValueBase = Math.Max( heightValue, minHeight );\n\n\t\t// Clamp the final height\n\t\treturn Math.Clamp( heightValueBase, 0, 1 );\n\t}\n\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainShapes/Mountainous.cs","FileName":"Mountainous.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using Editor;\nusing Sandbox;\nusing System;\n\nnamespace Sturnus.TerrainGenerationTool;\npublic static class Mountainous\n{\n\tpublic static float Default( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize = 0.1f, float warpStrength = 0.5f )\n\t{\n\t\tfloat nx = x / (float)width; // Normalize x to range [0, 1]\n\t\tfloat ny = y / (float)height; // Normalize y to range [0, 1]\n\t\tfloat warpX;\n\t\tfloat warpY;\n\t\tfloat warpedNx;\n\t\tfloat warpedNy;\n\n\t\tif( warp )\n\t\t{\n\t\t\t// Generate warp offsets using noise\n\t\t\twarpX = OpenSimplex2S.Noise2( seed \u002B 20, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\twarpY = OpenSimplex2S.Noise2( seed \u002B 21, nx * warpSize, ny * warpSize ) * warpStrength;\n\n\t\t\t// Apply domain warping to the coordinates\n\t\t\twarpedNx = nx \u002B warpX;\n\t\t\twarpedNy = ny \u002B warpY;\n\t\t}\n\t\telse\n\t\t{\n\t\t\twarpedNx = nx;\n\t\t\twarpedNy = ny;\n\t\t}\n\t\t\n\n\t\t// Base noise for ridge structure (using warped coordinates)\n\t\tfloat ridgeNoise = Math.Abs( OpenSimplex2S.Noise2( seed, warpedNx * 5, warpedNy * 0.5f ) ) * 0.8f;\n\n\t\t// Add distortion to the ridge line to make it less uniform\n\t\tfloat distortion = OpenSimplex2S.Noise2( seed \u002B 2, warpedNx * 2, warpedNy * 2 ) * 0.3f;\n\t\tridgeNoise \u002B= distortion;\n\n\t\t// Add fine detail to the mountains with higher frequency noise\n\t\tfloat detailNoise = OpenSimplex2S.Noise2( seed \u002B 1, warpedNx * 20, warpedNy * 20 ) * 0.2f;\n\n\t\t// Combine ridge, distortion, and detail noise\n\t\tfloat combinedNoise = ridgeNoise \u002B detailNoise;\n\n\t\t// Apply a falloff effect to keep the edges lower\n\t\tfloat edgeFalloff = 1.0f - Math.Clamp( Math.Abs( nx - 0.5f ) \u002B Math.Abs( ny - 0.5f ), 0, 1 );\n\n\t\t// Combine all effects\n\t\tfloat heightValue = combinedNoise * edgeFalloff;\n\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\tfloat baseline = minHeight; // Minimum height\n\t\theightValue = MathF.Max( heightValue, baseline );\n\n\t\t// Combine everything with edge falloff\n\t\treturn Math.Clamp( heightValue, 0, 1 );\n\t}\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainShapes/Sea.cs","FileName":"Sea.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using Editor;\nusing Sandbox;\nusing System;\n\nnamespace Sturnus.TerrainGenerationTool;\npublic static class Sea\n{\n\tpublic static float SeaBed(\n\t\tint x,\n\t\tint y,\n\t\tint width,\n\t\tint height,\n\t\tlong seed,\n\t\tfloat minHeight,\n\t\tbool domainWarping,\n\t\tfloat domainWarpingSize,\n\t\tfloat domainWarpingStrength\n\t)\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat depthScale = 0.4f; // Adjust the overall depth\n\t\tfloat waveFrequency = 0.5f; // Frequency of base ripples\n\t\tfloat waveAmplitude = 0.1f; // Height of ripples\n\t\tfloat randomVariation = 0.02f; // Subtle randomness\n\t\tfloat distortionFrequency = 0.03f; // Frequency for distortion\n\t\tfloat distortionStrength = 0.05f ;\t// Strength of distortion\n\t\t// Normalize coordinates to [0, 1]\n\t\tfloat nx = x / (float)width;\n\t\tfloat ny = y / (float)height;\n\n\t\t// Generate base ripple effect\n\t\tfloat baseRipple = MathF.Sin( nx * waveFrequency * MathF.PI * 2 ) * waveAmplitude\n\t\t\t\t\t\t \u002B MathF.Sin( ny * waveFrequency * MathF.PI * 2 ) * waveAmplitude;\n\n\t\t// Add distortion to break uniformity\n\t\tfloat distortion = OpenSimplex2S.Noise2( seed \u002B 1, nx * distortionFrequency, ny * distortionFrequency )\n\t\t\t\t\t\t   * distortionStrength;\n\n\t\t// Add random noise for natural variation\n\t\tfloat randomNoise = (float)(random.NextDouble() - 0.5) * randomVariation;\n\n\t\t// Combine all effects\n\t\tfloat heightValue = baseRipple \u002B distortion \u002B randomNoise;\n\t\t\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\tfloat baseline = minHeight; // Minimum height\n\t\theightValue = MathF.Max( heightValue, baseline );\n\n\t\t// Scale to depth and clamp\n\t\theightValue = heightValue * depthScale;\n\t\treturn Math.Clamp( heightValue, 0.0f, 1.0f );\n\t}\n\n\tpublic static float Cliff(\n\tint x,\n\tint y,\n\tint width,\n\tint height,\n\tlong seed,\n\tfloat minHeight,\n\tbool warp,\n\tfloat warpSize,\n\tfloat warpStrength\n)\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\t\tfloat hillHeight = 0.9f;    // Height of the cliff\n\t\tfloat slopeWidth = 0.2f;     // Width of the slope transition\n\t\tfloat wideningFactor = 0.9f;\n\t\t// Apply domain warping for cliff irregularity\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Calculate distance for the hill gradient\n\t\tfloat distance = nx \u003E= 0 ? MathF.Abs( nx ) : MathF.Abs( nx ) * (1 - wideningFactor); // Widen on one side\n\n\t\t// Generate hill gradient using a smooth transition\n\t\tfloat hill = Math.Clamp( 1.0f - MathF.Pow( distance / slopeWidth, 2.0f ), 0, 1 ); // Quadratic falloff for smoother slope\n\t\thill *= hillHeight; // Scale the hill to the desired height\n\n\t\t// Add base noise for texture\n\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed, nx * 6.0f, ny * 6.0f ) * 0.2f;\n\n\t\t// Add finer noise for additional detail\n\t\tfloat fineNoise = OpenSimplex2S.Noise2( seed \u002B 1, nx * 12.0f, ny * 12.0f ) * (0.2f / 2);\n\n\t\t// Combine hill gradient with noise\n\t\tfloat heightValue = hill \u002B baseNoise \u002B fineNoise;\n\n\t\tfloat baseValue = Math.Max( baseNoise, minHeight );\n\t\theightValue = Math.Max( heightValue, baseValue );\n\n\t\t// Clamp the height value to ensure valid results\n\t\treturn Math.Clamp( heightValue, 0, 1 );\n\t}\n\n\n\n\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainGenerationTool.cs","FileName":"TerrainGenerationTool.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel.DataAnnotations.Schema;\r\nusing System.Drawing;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing Editor;\r\nusing Editor.ShaderGraph.Nodes;\r\nusing Editor.Widgets;\r\nusing Sandbox;\r\nusing SkiaSharp;\r\nusing static Sandbox.Gradient;\r\n\r\nusing Sturnus.TerrainGenerationTool;\r\nusing Sandbox.Utility;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing Parallel = System.Threading.Tasks.Parallel;\r\nusing Sturnus.TerrainGenerationTool.RiverStream;\r\nusing Sandbox.Services;\r\nusing System.Reflection;\r\nusing static TerrainGenerationTool;\r\n\r\n[EditorApp( \u0022Terrain Pro Generation\u0022, \u0022terrain\u0022, \u0022Generate procedural terrain with a realtime 3D preview\u0022 )]\r\npublic class TerrainGenerationTool : BaseWindow\r\n{\r\n\tpublic string GenerationPath { get; set; } = Editor.FileSystem.Content.GetFullPath( \u0022\u0022 ) \u002B \u0022\\\\TerrainGenerationTool\\\\\u0022;\r\n\tpublic string GenerationLocalPath { get; set; } = \u0022\\\\TerrainGenerationTool\\\\\u0022;\r\n\tpublic string ExportPath { get; set; } = Project.Current.RootDirectory \u002B \u0022\\\\Assets\\\\\u0022;\r\n\r\n\tHashSet\u003Cstring\u003E TerrainCategoryArray { get; set; } = new HashSet\u003Cstring\u003E();\r\n\tHashSet\u003Cstring\u003E TerrainShapeArray { get; set; } = new HashSet\u003Cstring\u003E();\r\n\r\n\t// Per-tile category/shape selections for the tile grid (index = ty * grid \u002B tx)\r\n\tstring[] _tileCategories = new string[1];\r\n\tstring[] _tileShapes = new string[1];\r\n\r\n\t// Per-tile height/scale/seed values (index = ty * grid \u002B tx)\r\n\tfloat[] _tileMinHeights = new float[1];\r\n\tfloat[] _tileMaxHeights = new float[1];\r\n\tfloat[] _tilePlaneScales = new float[1];\r\n\tlong[] _tileSeeds = new long[1];\r\n\r\n\t// Per-tile smoothing/noise values (index = ty * grid \u002B tx)\r\n\tint[] _tileSmoothingPasses = new int[1];\r\n\tint[] _tileNoiseLayerStacks = new int[1];\r\n\r\n\t// Per-tile domain warping values (index = ty * grid \u002B tx)\r\n\tbool[] _tileDomainWarping = new bool[1];\r\n\tfloat[] _tileDomainWarpingSizes = new float[1];\r\n\tfloat[] _tileDomainWarpingStrengths = new float[1];\r\n\r\n\t// Per-tile splatmap settings (index = ty * grid \u002B tx)\r\n\tint[] _tileSplatLayerCounts = new int[1];\r\n\tint[] _tileSplatMapCounts = new int[1];\r\n\tSplatDispersionMode[] _tileSplatDispersions = new SplatDispersionMode[1];\r\n\tfloat[] _tileSplatBlendStrengths = new float[1];\r\n\r\n\t// The tile currently being edited by the Terrain Type page\u0027s Category/Shape selectors\r\n\tint _selectedTileIndex = 0;\r\n\tbool _syncingTileSelectors;\r\n\tList\u003CTileGridBox\u003E _tileBoxes = new();\r\n\r\n\tList\u003CType\u003E terrainCategoryClassesTypes = new List\u003CType\u003E { typeof( Islands ), typeof( Mountainous ), typeof( Planetary ), typeof( Realistic ), typeof( Sea ), typeof( Volcanic ) };\r\n\tList\u003CType\u003E terrainShapeMethodTypes { get; set; }\r\n\r\n\r\n\tenum TerrainDimensions : int\r\n\t{\r\n\t\tx512 = 512,\r\n\t\tx1024 = 1024,\r\n\t\tx2048 = 2048,\r\n\t\tx4096 = 4096,\r\n\t\tX8192 = 8192\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// How the grid is stored once generated.\r\n\t/// Combined stitches every cell into one full-res map; PerCell keeps each cell as its own\r\n\t/// full-resolution heightmap/splatmap (for per-terrain apply, per-cell export and preview).\r\n\t/// \u003C/summary\u003E\r\n\tpublic enum GridStorageMode\r\n\t{\r\n\t\tCombined,\r\n\t\tPerCell\r\n\t}\r\n\r\n\t[Step( 1 )] GridStorageMode GridStorage { get; set; } = GridStorageMode.Combined;\r\n\r\n\t// Per-cell full-resolution maps (index = ty * grid \u002B tx). Only filled in PerCell mode.\r\n\tList\u003Cfloat[,]\u003E _cellHeightmaps = new();\r\n\tList\u003Cfloat[,]\u003E _cellSplatmaps = new();\r\n\r\n\tpublic enum SplatDispersionMode\r\n\t{\r\n\t\tEvenly,\r\n\t\tNatural\r\n\t}\r\n\r\n\t//enum TerrainCategoryEnum;\r\n\tDynamicEnum TerrainCategoryEnum = new DynamicEnum();\r\n\tDynamicEnum TerrainShapeEnum = new DynamicEnum();\r\n\r\n\tTerrainDimensions TerrainDimensionsEnum { get; set; } = TerrainDimensions.x512;\r\n\t[Step( 1 ), MinMax( 1, 4 )] int TerrainGridSize { get; set; } = 1;\r\n\t//TerrainCategoryEnum TerrainShapeEnumSelect { get; set; }\r\n\t[Step( 0.01f ),MinMax(0.1f,1f)] float TerrainMinHeight { get; set; } = 0.2f;\r\n\t[Step( 0.01f),MinMax(0.1f,1f)] float TerrainMaxHeight { get; set; } = 0.5f;\r\n\t[Step( 0.01f),MinMax(0.1f,1f)] float TerrainPlaneScale { get; set; } = 0.5f;\r\n\tlong TerrainSeed { get; set; } = 1234567890;\r\n\t[Step( 1 ), MinMax( 1,20 )] int SmoothingPasses { get; set; } = 10;\r\n\t[Group( \u0022Domain Warping\u0022 )] bool DomainWarping { get; set; } = true;\r\n\t[Group( \u0022Domain Warping\u0022 )][Step( 0.01f ),MinMax(0.1f,1f)] float DomainWarpingSize { get; set; } = 0.25f;\r\n\t[Group( \u0022Domain Warping\u0022 )][Step( 0.01f ),MinMax(0.1f,1f)] float DomainWarpingStrength { get; set; } = 0.15f;\r\n\tbool ErosionSimulation { get; set; } = false;\r\n\t[Step( 1f),MinMax(1f,25f)] int NoiseLayerStacks { get; set; } = 1;\r\n\r\n\t///\r\n\t/// River Carving Variables\r\n\t///\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )] bool RiverCarvingBool { get; set; } = true;\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )][Step( 0.1f), MinMax( 0.5f, 10f )] float RiverCarvingFrequency { get; set; } = 1.5f;\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )][Step( 0.01f),MinMax(0.01f,5f)] float RiverCarvingStrength { get; set; } = 0.3f;\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )][Step( 0.001f),MinMax(0.01f,0.25f)] float RiverCarvingDepth { get; set; } = 0.01f;\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )][Step( 0.01f),MinMax(0.001f,2f)] float RiverCarvingWidth { get; set; } = 0.25f;\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )][Step( 0.01f),MinMax(0.05f,1f)] float RiverCarvingSpacing { get; set; } = 0.05f;\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )][Step( 0.01f),MinMax(0.01f,1f)] float RiverCarvingTurbulenceStrength { get; set; } = 0.01f;\r\n\t[Property][Group( \u0022River \u0026 Stream Carving\u0022 )][Step( 0.01f),MinMax(0.01f,10f)] float RiverCarvingTurbulenceFrequency { get; set; } = 0.01f;\r\n\t\r\n\r\n\t///\r\n\t/// Tool Placement Square\r\n\t///\r\n\t[Group( \u0022Tool Placement\u0022 )] bool StagingArea { get; set; } = true;\r\n\t[Group( \u0022Tool Placement\u0022 )][Step( 1),MinMax( 1, 100 )] int StagingAreaSize { get; set; } = 10; // Size of the square (in grid units)\r\n\t[Group( \u0022Tool Placement\u0022 )][Step(0.01f),MinMax(0,1)] float StagingAreaHeight { get; set; } = 0.1f; // Height of the flat square\r\n\t[Group( \u0022Tool Placement\u0022 )][Step(0.01f),MinMax(0,1)] float StagingAreaX { get; set; } = 0.1f; // X-center of the square as a ratio\r\n\t[Group( \u0022Tool Placement\u0022 )][Step(0.01f),MinMax(0,1)] float StagingAreaY { get; set; } = 0.1f; // Y-center of the square as a ratio\r\n\r\n\tGradient SplatMapGradient = new Gradient( new Gradient.ColorFrame( 0.0f, Color.Cyan ), new Gradient.ColorFrame( 0.25f, Color.Red ), new Gradient.ColorFrame( 0.5f, Color.Yellow ), new Gradient.ColorFrame( 0.75f, Color.Green ) );\r\n\tSKColor[] _splatcolors { get; set; }\r\n\r\n\t[Step( 1 ), MinMax( 2, 32 )] int SplatLayerCount { get; set; } = 8;\r\n\t[Step( 1 ), MinMax( 1, 8 )] int SplatMapCount { get; set; } = 1;\r\n\tSplatDispersionMode SplatDispersion { get; set; } = SplatDispersionMode.Evenly;\r\n\t[Step( 0.05f ), MinMax( 0f, 1f )] float SplatBlendStrength { get; set; } = 0.35f;\r\n\r\n\t[Property] bool PreviewSplatMaterials { get; set; } = false;\r\n\r\n\tfloat[] _splatthresholds = { 0f, 0.25f, 0.50f, 0.75f };\r\n\tfloat[,] _heightmap;\r\n\tfloat[,] _splatmap;\r\n\tfloat[,] _previewHeightmap;\r\n\r\n\tTerrainMaterial[] _previewMaterials;\r\n\tint _previewMaterialsGeneration = 0;\r\n\tList\u003CEditor.Asset\u003E _localTmatAssets;\r\n\r\n\tTexture _preview_image_texture;\r\n\tEditor.TextureWidget PreviewImage;\r\n\tTexture _preview_splatmap_texture;\r\n\tEditor.TextureWidget PreviewSplatmap;\r\n\r\n\tSceneRenderingWidget RenderCanvas;\r\n\tCameraComponent Camera;\r\n\tGizmo.Instance GizmoInstance;\r\n\tGameObject _previewGO;\r\n\tTerrain _previewTerrain;\r\n\tTerrainStorage _previewStorage;\r\n\tGameObject _splatOverlayGO;\r\n\tModelRenderer _splatOverlayRenderer;\r\n\tMesh _overlayMesh;\r\n\tfloat[] _overlayTargetHeights;\r\n\tfloat[] _overlayCurrentHeights;\r\n\tColor32[] _overlayCurrentColors;\r\n\tfloat[,] _overlaySplatmap;\r\n\tbool _overlayUseSplatColors;\r\n\tbool _overlayAnimating;\r\n\tbool _overlayColorAnimating;\r\n\tList\u003CColor\u003E _splatColorCache = new();\r\n\tList\u003Cfloat\u003E _currentFrameTimes;\r\n\tList\u003Cfloat\u003E _targetFrameTimes;\r\n\tbool _gradientAnimating;\r\n\tbool _isAnimatingGradient;\r\n\tconst float PreviewMorphSpeed = 8f;\r\n\tconst float MeshMorphSpeed = PreviewMorphSpeed * 0.25f;\r\n\tfloat _orbitDistance = 20000f;\r\n\tfloat _orbitAngle = 0f;\r\n\tfloat _orbitPitch = 30f;\r\n\tbool _autoSpin = true;\r\n\tFloatSlider ZoomSlider;\r\n\tconst float SpinSpeed = 8f; // degrees per second\r\n\tconst int PreviewResolution = 512;\r\n\tconst float PreviewTerrainSize = 20000f;\r\n\tconst float PreviewTerrainHeight = 5000f;\r\n\r\n\tSerializedObject _serialized;\r\n\tbool _previewDirty;\r\n\tfloat _lastPreviewRegen = float.MinValue;\r\n\tbool _isGenerating = false;\r\n\tint _generationToken = 0;\r\n\r\n\tList\u003CWidget\u003E _domainWarpingWidgets = new();\r\n\tList\u003CWidget\u003E _riverCarvingWidgets = new();\r\n\tList\u003CWidget\u003E _stagingAreaWidgets = new();\r\n\r\n\tDictionary\u003Cstring, Widget\u003E _propsPages = new();\r\n\tSegmentedControl _propsTabBar;\r\n\tWidget _propsContent;\r\n\tstring _activePropsTab;\r\n\tWidget _tilesContainer;\r\n\tButton _randomizeMaterialsButton;\r\n\tLabel _materialLoadingLabel;\r\n\tGradientControlWidget _gradientControlWidget;\r\n\r\n\tWrapSelector ShapeArray;\r\n\tWrapSelector CategoryArray;\r\n\r\n\tpublic class DynamicEnum\r\n\t{\r\n\t\tprivate readonly Dictionary\u003Cstring, int\u003E _values = new Dictionary\u003Cstring, int\u003E();\r\n\t\tprivate int _nextValue = 0;\r\n\r\n\t\tpublic void Add( string name )\r\n\t\t{\r\n\t\t\tif ( !_values.ContainsKey( name ) )\r\n\t\t\t{\r\n\t\t\t\t_values[name] = _nextValue\u002B\u002B;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tpublic int GetValue( string name )\r\n\t\t{\r\n\t\t\tif ( string.IsNullOrEmpty( name ) ) return -1;\r\n\t\t\treturn _values.TryGetValue( name, out var value ) ? value : -1; // Return -1 if not found\r\n\t\t}\r\n\r\n\t\tpublic string GetName( int key )\r\n\t\t{\r\n\t\t\treturn _values.FirstOrDefault( pair =\u003E pair.Value == key ).Key ?? \u0022Unknown\u0022; // Return \u0022Unknown\u0022 if not found\r\n\t\t}\r\n\r\n\t\tpublic string[] GetNames()\r\n\t\t{\r\n\t\t\treturn _values.Keys.ToArray();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static string[] GetMethodsFromClass( string className )\r\n\t{\r\n\t\t// Attempt to get the Type from the class name (fully qualified)\r\n\t\tType classType = Type.GetType( className );\r\n\r\n\t\tif ( classType == null )\r\n\t\t{\r\n\t\t\tthrow new ArgumentException( $\u0022Class \u0027{className}\u0027 could not be found. Ensure the namespace is included.\u0022 );\r\n\t\t}\r\n\r\n\t\tList\u003Cstring\u003E methodNames = new List\u003Cstring\u003E();\r\n\r\n\t\t// Get all public methods (static and instance) from the class\r\n\t\tMethodInfo[] methods = classType.GetMethods( BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static );\r\n\r\n\t\tforeach ( var method in methods )\r\n\t\t{\r\n\t\t\t// Exclude methods not declared in this class\r\n\t\t\tif ( method.DeclaringType == classType )\r\n\t\t\t{\r\n\t\t\t\tmethodNames.Add( method.Name );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn methodNames.ToArray();\r\n\t}\r\n\r\n\tpublic string[] GetTerrainCategoryClasses( params Type[] CategoryClasses )\r\n\t{\r\n\t\tHashSet\u003Cstring\u003E methodNames = new HashSet\u003Cstring\u003E();\r\n\r\n\t\tforeach ( Type CategoryClass in CategoryClasses )\r\n\t\t{\r\n\t\t\t// Get all public static methods from the class\r\n\t\t\tMethodInfo[] methods = CategoryClass.GetMethods( BindingFlags.Public | BindingFlags.Static );\r\n\r\n\t\t\tforeach ( MethodInfo method in methods )\r\n\t\t\t{\r\n\t\t\t\t// Exclude inherited methods or non-relevant ones\r\n\t\t\t\tif ( method.DeclaringType == CategoryClass )\r\n\t\t\t\t{\r\n\t\t\t\t\tmethodNames.Add( $\u0022{CategoryClass.Name}.{method.Name}\u0022 );\r\n\t\t\t\t\tTerrainCategoryEnum.Add( CategoryClass.Name);\r\n\t\t\t\t\tTerrainCategoryArray.Add( CategoryClass.Name );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn methodNames.ToArray();\r\n\t}\r\n\r\n\tpublic string[] GetTerrainShapeMethods( Type shapeClass )\r\n\t{\r\n\t\tHashSet\u003Cstring\u003E methodNames = new HashSet\u003Cstring\u003E();\r\n\t\tMethodInfo[] methods = shapeClass.GetMethods( BindingFlags.Public | BindingFlags.Static );\r\n\r\n\t\t\tforeach ( MethodInfo method in methods )\r\n\t\t\t{\r\n\t\t\t\t// Exclude inherited methods or non-relevant ones\r\n\t\t\t\tif ( method.DeclaringType == shapeClass )\r\n\t\t\t\t{\r\n\t\t\t\t\t//TerrainShapeArray.Add( shapeClass.Name );\r\n\t\t\t\t\t//TerrainShapeEnum.Add( shapeClass.Name );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\treturn methodNames.ToArray();\r\n\t}\r\n\r\n\tpublic static object CallMethod( string className, string methodName, object[] parameters = null )\r\n\t{\r\n\t\t// Get the class type\r\n\t\tType classType = Type.GetType( className );\r\n\t\tif ( classType == null )\r\n\t\t{\r\n\t\t\tthrow new ArgumentException( $\u0022Class \u0027{className}\u0027 could not be found.\u0022 );\r\n\t\t}\r\n\r\n\t\t// Get the method info\r\n\t\tMethodInfo method = classType.GetMethod( methodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance );\r\n\t\tif ( method == null )\r\n\t\t{\r\n\t\t\tthrow new ArgumentException( $\u0022Method \u0027{methodName}\u0027 could not be found in class \u0027{className}\u0027.\u0022 );\r\n\t\t}\r\n\r\n\t\t// Check if the method is static or instance\r\n\t\tobject instance = null;\r\n\t\tif ( !method.IsStatic )\r\n\t\t{\r\n\t\t\tinstance = Activator.CreateInstance( classType );\r\n\t\t}\r\n\r\n\t\t// Invoke the method\r\n\t\tobject result = method.Invoke( instance, parameters );\r\n\r\n\t\t// Ensure the return type is compatible\r\n\t\tif ( result is not float )\r\n\t\t{\r\n\t\t\tthrow new InvalidOperationException( $\u0022Method \u0027{methodName}\u0027 does not return a float.\u0022 );\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\tpublic void InitialShapes()\r\n\t{\r\n\t\tShapeArray.DestroyChildren();\r\n\t\tTerrainShapeArray.Clear();\r\n\r\n\t\tstring className = $\u0022Sturnus.TerrainGenerationTool.Islands\u0022; // Fully qualified name\r\n\t\tstring[] methods = GetMethodsFromClass( className );\r\n\r\n\t\t// Print the methods\r\n\t\tforeach ( string method in methods )\r\n\t\t{\r\n\t\t\tTerrainShapeArray.Add( method );\r\n\t\t\tTerrainShapeEnum.Add( method );\r\n\t\t}\r\n\r\n\t\tforeach ( var shape in TerrainShapeArray )\r\n\t\t{\r\n\t\t\tShapeArray.AddOption( shape );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic TerrainGenerationTool() : base()\r\n\t{\r\n\t\tWindowTitle = \u0022Terrain Generation Tool\u0022;\r\n\t\tSetWindowIcon( \u0022terrain\u0022 );\r\n\t\tMinimumSize = new Vector2( 1000, 700 );\r\n\t\tSize = new Vector2( 1500, 900 );\r\n\t\tStartCentered = true;\r\n\r\n\t\t_serialized = this.GetSerialized();\r\n\t\t_serialized.OnPropertyChanged \u002B= OnSerializedPropertyChanged;\r\n\r\n\t\tstring[] terrainCategoryClasses = GetTerrainCategoryClasses( terrainCategoryClassesTypes.ToArray() );\r\n\t\tstring[] terrainShapeMethods = GetTerrainShapeMethods( typeof(Islands) );\r\n\t\t\r\n\t\t//Create TerrainGenerationTool folder if it doesn\u0027t exist.\r\n\t\tDirectory.CreateDirectory( GenerationPath );\r\n\r\n\t\tSplatMapGradient.Blending = Gradient.BlendMode.Stepped;\r\n\r\n\t\tLayout = Layout.Row();\r\n\t\tLayout.Margin = 0;\r\n\t\tLayout.Spacing = 0;\r\n\r\n\t\t// ---------- Left: options panel ----------\r\n\t\tvar scroll = new ScrollArea( this );\r\n\t\tscroll.Canvas = new Widget( scroll );\r\n\t\tscroll.Canvas.Layout = Layout.Column();\r\n\t\tscroll.Canvas.Layout.Margin = 10;\r\n\t\tscroll.Canvas.Layout.Spacing = 5;\r\n\t\tscroll.MinimumWidth = 400;\r\n\t\tscroll.MaximumWidth = 480;\r\n\t\tLayout.Add( scroll );\r\n\r\n\t\tvar body = scroll.Canvas.Layout;\r\n\r\n\t\t// ---------- Left: grouped property tabs ----------\r\n\t\tvar propsRoot = body.Add( new Widget( null ), 1 );\r\n\t\tpropsRoot.Layout = Layout.Column();\r\n\t\tpropsRoot.Layout.Spacing = 5;\r\n\r\n\t\t_propsTabBar = propsRoot.Layout.Add( new SegmentedControl() );\r\n\t\t_propsTabBar.ShowText = true;\r\n\t\t_propsTabBar.FixedHeight = Theme.RowHeight * 1.6f;\r\n\t\t_propsTabBar.OnSelectedChanged \u002B= ( name ) =\u003E SelectPropsTab( name );\r\n\r\n\t\t_propsContent = propsRoot.Layout.Add( new Widget( null ), 1 );\r\n\t\t_propsContent.Layout = Layout.Column();\r\n\t\t_propsContent.Layout.Margin = 0;\r\n\t\t_propsContent.Layout.Alignment = TextFlag.Top;\r\n\r\n\t\t// --- Terrain Type tab ---\r\n\t\tvar typePage = CreatePropsPage();\r\n\r\n\t\ttypePage.Layout.Add( new Label( \u0022Terrain Dimensions\u0022 ) );\r\n\t\ttypePage.Layout.Add( new EnumControlWidget( _serialized.GetProperty( nameof( TerrainDimensionsEnum ) ) ) );\r\n\r\n\t\ttypePage.Layout.Add( new Label( \u0022Terrain Category\u0022 ) );\r\n\t\tCategoryArray = typePage.Layout.Add( new WrapSelector() );\r\n\t\tfor ( int i = 0; i \u003C TerrainCategoryArray.ToArray().GetLength( 0 ); i\u002B\u002B )\r\n\t\t{\r\n\t\t\tList\u003Cstring\u003E rowValues = new List\u003Cstring\u003E();\r\n\t\t\trowValues.Add( TerrainCategoryArray.ToArray()[i] );\r\n\t\t\tCategoryArray.AddOption( rowValues[0] );\r\n\t\t}\r\n\t\ttypePage.Layout.Add( new Label( \u0022Terrain Shape\u0022 ) );\r\n\t\tShapeArray = typePage.Layout.Add( new WrapSelector() );\r\n\t\tInitialShapes();\r\n\t\tCategoryArray.OnSelectedChanged \u002B= ( _ ) =\u003E\r\n\t\t{\r\n\t\t\tRebuildShapes();\r\n\t\t\tApplySelectedCategory();\r\n\t\t\t_previewDirty = true;\r\n\t\t};\r\n\t\tShapeArray.OnSelectedChanged \u002B= ( _ ) =\u003E\r\n\t\t{\r\n\t\t\tApplySelectedShape();\r\n\t\t\t_previewDirty = true;\r\n\t\t};\r\n\t\tif ( CategoryArray.Children.Count() \u003E 0 )\r\n\t\t{\r\n\t\t\tCategoryArray.SelectedIndex = 0;\r\n\t\t\tCategoryArray.Selected = CategoryArray.Children.First().Name;\r\n\t\t}\r\n\t\tRebuildShapes();\r\n\t\tif ( ShapeArray.Children.Count() \u003E 0 )\r\n\t\t{\r\n\t\t\tShapeArray.SelectedIndex = 0;\r\n\t\t\tShapeArray.Selected = ShapeArray.Children.First().Name;\r\n\t\t}\r\n\t\tAddPropsTab( \u0022Terrain Type\u0022, \u0022terrain\u0022, typePage, \u0022Terrain dimensions, category and shape\u0022 );\r\n\r\n\t\t// --- Height / Scale tab ---\r\n\t\tvar heightPage = CreatePropsPage();\r\n\r\n\t\theightPage.Layout.Add( new Label( \u0022Min Height (relative)\u0022 ) );\r\n\t\theightPage.Layout.Add( FloatSlider( nameof( TerrainMinHeight ) ) );\r\n\t\theightPage.Layout.Add( new Label( \u0022Max Height (relative)\u0022 ) );\r\n\t\theightPage.Layout.Add( FloatSlider( nameof( TerrainMaxHeight ) ) );\r\n\t\theightPage.Layout.Add( new Label( \u0022Terrain Plane Scale\u0022 ) );\r\n\t\theightPage.Layout.Add( FloatSlider( nameof( TerrainPlaneScale ) ) );\r\n\t\theightPage.Layout.Add( new Label( \u0022Terrain Seed\u0022 ) );\r\n\t\tvar seedRow = heightPage.Layout.AddRow();\r\n\t\tseedRow.Spacing = 4;\r\n\t\tvar seedControl = seedRow.Add( new IntegerControlWidget( _serialized.GetProperty( nameof( TerrainSeed ) ) ), 1 );\r\n\t\tseedRow.Add( new IconButton( \u0022casino\u0022, RandomizeSeed, this )\r\n\t\t{\r\n\t\t\tToolTip = \u0022Randomize seed\u0022,\r\n\t\t\tIconSize = 16,\r\n\t\t\tFixedSize = new Vector2( 26, 26 )\r\n\t\t} );\r\n\t\tAddPropsTab( \u0022Height/Scale\u0022, \u0022straighten\u0022, heightPage, \u0022Terrain height, plane scale and seed\u0022 );\r\n\r\n\t\t// --- Smooth / Noise tab ---\r\n\t\tvar noisePage = CreatePropsPage();\r\n\r\n\t\tnoisePage.Layout.Add( new Label( \u0022Smoothing Passes\u0022 ) );\r\n\t\tnoisePage.Layout.Add( IntSlider( nameof( SmoothingPasses ) ) );\r\n\t\tnoisePage.Layout.Add( new Label( \u0022Noise Layer Stacks\u0022 ) );\r\n\t\tnoisePage.Layout.Add( IntSlider( nameof( NoiseLayerStacks ) ) );\r\n\t\tAddPropsTab( \u0022Smooth/Noise\u0022, \u0022grain\u0022, noisePage, \u0022Terrain smoothing and noise layers\u0022 );\r\n\r\n\t\t// --- Splat tab ---\r\n\t\tvar splatPage = CreatePropsPage();\r\n\r\n\t\tsplatPage.Layout.Add( new Label( \u0022Splat Layer Count\u0022 ) );\r\n\t\tsplatPage.Layout.Add( IntSlider( nameof( SplatLayerCount ) ) );\r\n\t\tsplatPage.Layout.Add( new Label( \u0022Splat Map Count\u0022 ) );\r\n\t\tsplatPage.Layout.Add( IntSlider( nameof( SplatMapCount ) ) );\r\n\t\tsplatPage.Layout.Add( new Label( \u0022Dispersion\u0022 ) );\r\n\t\tsplatPage.Layout.Add( new EnumControlWidget( _serialized.GetProperty( nameof( SplatDispersion ) ) ) );\r\n\t\tsplatPage.Layout.Add( new Label( \u0022Blend Strength\u0022 ) );\r\n\t\tsplatPage.Layout.Add( FloatSlider( nameof( SplatBlendStrength ) ) );\r\n\t\tsplatPage.Layout.Add( new Label( \u0022Splatmap Colors/Threshold\u0022 ) );\r\n\t\t_gradientControlWidget = new GradientControlWidget( _serialized.GetProperty( nameof( SplatMapGradient ) ) );\r\n\t\tsplatPage.Layout.Add( _gradientControlWidget );\r\n\t\tsplatPage.Layout.Add( new Label( \u0022Preview Materials\u0022 ) );\r\n\t\tsplatPage.Layout.Add( new BoolControlWidget( _serialized.GetProperty( nameof( PreviewSplatMaterials ) ) ) );\r\n\t\tvar materialHint = new Label( \u0022Assigns random local .tmat terrain materials from your project\u0027s assets to the splat layers so you can preview the material blending on the terrain.\u0022 );\r\n\t\tmaterialHint.SetStyles( \u0022font-size: 10px; color: #888;\u0022 );\r\n\t\tmaterialHint.WordWrap = true;\r\n\t\tmaterialHint.MaximumWidth = 260;\r\n\t\tsplatPage.Layout.Add( materialHint );\r\n\t\tvar randomizeRow = splatPage.Layout.AddRow();\r\n\t\t_randomizeMaterialsButton = randomizeRow.Add( new Button( \u0022Randomize Materials\u0022, \u0022casino\u0022 ) );\r\n\t\t_randomizeMaterialsButton.Clicked \u002B= RandomizeMaterials;\r\n\t\t_materialLoadingLabel = randomizeRow.Add( new Label( \u0022Loading...\u0022 ) );\r\n\t\t_materialLoadingLabel.SetStyles( \u0022font-size: 10px; color: #888;\u0022 );\r\n\t\t_materialLoadingLabel.Visible = false;\r\n\t\t_materialLoadingLabel.WordWrap = true;\r\n\t\t_materialLoadingLabel.MaximumWidth = 120;\r\n\t\tAddPropsTab( \u0022Splat\u0022, \u0022palette\u0022, splatPage, \u0022Splatmap layers, maps, colors and dispersion\u0022 );\r\n\r\n\t\t// --- Warping tab ---\r\n\t\tvar warpPage = CreatePropsPage();\r\n\r\n\t\twarpPage.Layout.Add( new Label( \u0022Domain Warping\u0022 ) );\r\n\t\twarpPage.Layout.Add( new BoolControlWidget( _serialized.GetProperty( nameof( DomainWarping ) ) ) );\r\n\t\tvar DomainWarpingSizeLabel = warpPage.Layout.Add( new Label( \u0022Domain Warping (Size)\u0022 ) );\r\n\t\tvar DomainWarpingSizeFloat = warpPage.Layout.Add( FloatSlider( nameof( DomainWarpingSize ) ) );\r\n\t\tvar DomainWarpingStrengthLabel = warpPage.Layout.Add( new Label( \u0022Domain Warping (Strength)\u0022 ) );\r\n\t\tvar DomainWarpingStrengthFloat = warpPage.Layout.Add( FloatSlider( nameof( DomainWarpingStrength ) ) );\r\n\t\t_domainWarpingWidgets.AddRange( new Widget[] { DomainWarpingSizeLabel, DomainWarpingSizeFloat, DomainWarpingStrengthLabel, DomainWarpingStrengthFloat } );\r\n\t\tAddPropsTab( \u0022Warping\u0022, \u0022blur_on\u0022, warpPage, \u0022Domain warping options\u0022 );\r\n\r\n\t\t// --- River tab ---\r\n\t\tvar riverPage = CreatePropsPage();\r\n\r\n\t\triverPage.Layout.Add( new Label( \u0022River Carving\u0022 ) );\r\n\t\triverPage.Layout.Add( new BoolControlWidget( _serialized.GetProperty( nameof( RiverCarvingBool ) ) ) );\r\n\t\tvar RiverCarvingFrequencyLabel = riverPage.Layout.Add( new Label( \u0022RiverCarvingFrequency\u0022 ) );\r\n\t\tvar RiverCarvingFrequencyFloat = riverPage.Layout.Add( FloatSlider( nameof( RiverCarvingFrequency ) ) );\r\n\t\t/*var RiverCarvingStrength = riverPage.Layout.Add( new Label(\u0022RiverCarvingStrength\u0022));\r\n\t\tvar RiverCarvingStrengthFloat = riverPage.Layout.Add( FloatSlider( nameof(RiverCarvingStrength) ) );*/\r\n\t\tvar RiverCarvingDepthLabel = riverPage.Layout.Add( new Label( \u0022RiverCarvingDepth\u0022 ) );\r\n\t\tvar RiverCarvingDepthFloat = riverPage.Layout.Add( FloatSlider( nameof( RiverCarvingDepth ) ) );\r\n\t\tvar RiverCarvingWidthLabel = riverPage.Layout.Add( new Label( \u0022RiverCarvingWidth\u0022 ) );\r\n\t\tvar RiverCarvingWidthFloat = riverPage.Layout.Add( FloatSlider( nameof( RiverCarvingWidth ) ) );\r\n\t\tvar RiverCarvingSpacingLabel = riverPage.Layout.Add( new Label( \u0022RiverCarvingSpacing\u0022 ) );\r\n\t\tvar RiverCarvingSpacingFloat = riverPage.Layout.Add( FloatSlider( nameof( RiverCarvingSpacing ) ) );\r\n\t\tvar RiverCarvingTurbulenceStrengthLabel = riverPage.Layout.Add( new Label( \u0022RiverCarvingTurbulenceStrength\u0022 ) );\r\n\t\tvar RiverCarvingTurbulenceStrengthFloat = riverPage.Layout.Add( FloatSlider( nameof( RiverCarvingTurbulenceStrength ) ) );\r\n\t\tvar RiverCarvingTurbulenceFrequencyLabel = riverPage.Layout.Add( new Label( \u0022RiverCarvingTurbulenceFrequency\u0022 ) );\r\n\t\tvar RiverCarvingTurbulenceFrequencyFloat = riverPage.Layout.Add( FloatSlider( nameof( RiverCarvingTurbulenceFrequency ) ) );\r\n\t\t_riverCarvingWidgets.AddRange( new Widget[]\r\n\t\t{\r\n\t\t\tRiverCarvingFrequencyLabel, RiverCarvingFrequencyFloat,\r\n\t\t\t/*RiverCarvingStrength, RiverCarvingStrengthFloat,*/\r\n\t\t\tRiverCarvingDepthLabel, RiverCarvingDepthFloat,\r\n\t\t\tRiverCarvingWidthLabel, RiverCarvingWidthFloat,\r\n\t\t\tRiverCarvingSpacingLabel, RiverCarvingSpacingFloat,\r\n\t\t\tRiverCarvingTurbulenceStrengthLabel, RiverCarvingTurbulenceStrengthFloat,\r\n\t\t\tRiverCarvingTurbulenceFrequencyLabel, RiverCarvingTurbulenceFrequencyFloat\r\n\t\t} );\r\n\t\tAddPropsTab( \u0022River\u0022, \u0022water\u0022, riverPage, \u0022River carving options\u0022 );\r\n\r\n\t\t// --- Staging tab ---\r\n\t\tvar stagingPage = CreatePropsPage();\r\n\r\n\t\tstagingPage.Layout.Add( new Label( \u0022Staging Area\u0022 ) );\r\n\t\tstagingPage.Layout.Add( new BoolControlWidget( _serialized.GetProperty( nameof( StagingArea ) ) ) );\r\n\t\tvar StagingAreaSizeLabel = stagingPage.Layout.Add( new Label( \u0022Staging Area (Size)\u0022 ) );\r\n\t\tvar StagingAreaSizeFloat = stagingPage.Layout.Add( IntSlider( nameof( StagingAreaSize ) ) );\r\n\t\tvar StagingAreaHeightLabel = stagingPage.Layout.Add( new Label( \u0022Staging Area (Height)\u0022 ) );\r\n\t\tvar StagingAreaHeightFloat = stagingPage.Layout.Add( FloatSlider( nameof( StagingAreaHeight ) ) );\r\n\t\tvar StagingAreaXLabel = stagingPage.Layout.Add( new Label( \u0022Staging Area (X)\u0022 ) );\r\n\t\tvar StagingAreaXFloat = stagingPage.Layout.Add( FloatSlider( nameof( StagingAreaX ) ) );\r\n\t\tvar StagingAreaYLabel = stagingPage.Layout.Add( new Label( \u0022Staging Area (Y)\u0022 ) );\r\n\t\tvar StagingAreaYFloat = stagingPage.Layout.Add( FloatSlider( nameof( StagingAreaY ) ) );\r\n\t\t_stagingAreaWidgets.AddRange( new Widget[]\r\n\t\t{\r\n\t\t\tStagingAreaSizeLabel, StagingAreaSizeFloat,\r\n\t\t\tStagingAreaHeightLabel, StagingAreaHeightFloat,\r\n\t\t\tStagingAreaXLabel, StagingAreaXFloat,\r\n\t\t\tStagingAreaYLabel, StagingAreaYFloat\r\n\t\t} );\r\n\t\tAddPropsTab( \u0022Staging\u0022, \u0022square_foot\u0022, stagingPage, \u0022Staging area placement\u0022 );\r\n\r\n\t\tbody.AddSpacingCell( 5 );\r\n\r\n\t\t// ---------- Tile grid section (docked between props and actions) ----------\r\n\t\tvar tileGridSection = body.Add( new Widget( null ) );\r\n\t\ttileGridSection.Layout = Layout.Column();\r\n\t\ttileGridSection.Layout.Spacing = 4;\r\n\r\n\t\ttileGridSection.Layout.Add( new Label( \u0022Tile Grid\u0022 ) );\r\n\t\ttileGridSection.Layout.Add( IntSlider( nameof( TerrainGridSize ) ) );\r\n\t\ttileGridSection.Layout.Add( new Label( \u0022Storage\u0022 ) );\r\n\t\ttileGridSection.Layout.Add( new EnumControlWidget( _serialized.GetProperty( nameof( GridStorage ) ) ) );\r\n\t\tvar tileGridHint = new Label( \u0022Click a tile to select which cell the Terrain Type category and shape apply to. Click \u0027All\u0027 to set every tile at once.\u0022 );\r\n\t\ttileGridHint.SetStyles( \u0022font-size: 10px; color: #888;\u0022 );\r\n\t\ttileGridHint.WordWrap = true;\r\n\t\ttileGridHint.MaximumWidth = 260;\r\n\t\ttileGridSection.Layout.Add( tileGridHint );\r\n\r\n\t\t_tilesContainer = new Widget( null );\r\n\t\t_tilesContainer.Layout = Layout.Column();\r\n\t\t_tilesContainer.Layout.Spacing = 4;\r\n\t\ttileGridSection.Layout.Add( _tilesContainer );\r\n\r\n\t\tbody.AddSpacingCell( 5 );\r\n\r\n\t\tvar GenerateButton = body.Add( new Button.Primary( \u0022Generate\u0022, \u0022auto_awesome\u0022, this ) );\r\n\r\n\t\tvar ExportButton = body.Add( new Button( \u0022Export\u0022, \u0022file_download\u0022, this ) );\r\n\t\tExportButton.Tint = \u0022#41AF20\u0022;\r\n\r\n\t\tvar ApplyButton = body.Add( new Button( \u0022Apply To Terrain\u0022, \u0022file_upload\u0022, this ) );\r\n\t\tApplyButton.Tint = \u0022#AF2020\u0022;\r\n\r\n\t\tif ( _heightmap == null )\r\n\t\t{\r\n\t\t\tExportButton.Enabled = false;\r\n\t\t\tApplyButton.Enabled = false;\r\n\r\n\t\t}\r\n\r\n\t\tGenerateButton.Clicked \u002B= () =\u003E\r\n\t\t{\r\n\t\t\tBuildSplatColors();\r\n\r\n\t\t\tint fullRes = (int)TerrainDimensionsEnum;\r\n\r\n\t\t\tif ( GridStorage == GridStorageMode.PerCell )\r\n\t\t\t{\r\n\t\t\t\t// Each cell is its own full-resolution map - no stitching.\r\n\t\t\t\t_cellHeightmaps = BuildPerCellHeightmaps(\r\n\t\t\t\t\tfullRes, fullRes,\r\n\t\t\t\t\t(string[])_tileCategories.Clone(), (string[])_tileShapes.Clone(),\r\n\t\t\t\t\t(long[])_tileSeeds.Clone(), (int[])_tileNoiseLayerStacks.Clone(),\r\n\t\t\t\t\t(float[])_tileMinHeights.Clone(), (float[])_tileMaxHeights.Clone(),\r\n\t\t\t\t\t(bool[])_tileDomainWarping.Clone(), (float[])_tileDomainWarpingSizes.Clone(), (float[])_tileDomainWarpingStrengths.Clone(),\r\n\t\t\t\t\t(int[])_tileSmoothingPasses.Clone(), (float[])_tilePlaneScales.Clone(),\r\n\t\t\t\t\tRiverCarvingBool, RiverCarvingFrequency, RiverCarvingWidth, RiverCarvingDepth,\r\n\t\t\t\t\tRiverCarvingTurbulenceFrequency, RiverCarvingTurbulenceStrength, RiverCarvingSpacing,\r\n\t\t\t\t\tStagingArea, StagingAreaSize, StagingAreaHeight, StagingAreaX, StagingAreaY );\r\n\r\n\t\t\t\tif ( _cellHeightmaps.Count == 0 )\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Error( \u0022No per-cell heightmaps generated. Aborting.\u0022 );\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t_cellSplatmaps = BuildPerCellSplatmaps( _cellHeightmaps,\r\n\t\t\t\t\t(int[])_tileSplatLayerCounts.Clone(), (SplatDispersionMode[])_tileSplatDispersions.Clone(), (float[])_tileSplatBlendStrengths.Clone() );\r\n\r\n\t\t\t\t// A stitched preview map so the 3D preview still shows the whole grid tiled.\r\n\t\t\t\t_heightmap = BuildHeightmap(\r\n\t\t\t\t\tfullRes, fullRes,\r\n\t\t\t\t\t(string[])_tileCategories.Clone(), (string[])_tileShapes.Clone(), TerrainGridSize,\r\n\t\t\t\t\t(long[])_tileSeeds.Clone(), (int[])_tileNoiseLayerStacks.Clone(),\r\n\t\t\t\t\t(float[])_tileMinHeights.Clone(), (float[])_tileMaxHeights.Clone(),\r\n\t\t\t\t\t(bool[])_tileDomainWarping.Clone(), (float[])_tileDomainWarpingSizes.Clone(), (float[])_tileDomainWarpingStrengths.Clone(),\r\n\t\t\t\t\t(int[])_tileSmoothingPasses.Clone(), (float[])_tilePlaneScales.Clone(),\r\n\t\t\t\t\tRiverCarvingBool, RiverCarvingFrequency, RiverCarvingWidth, RiverCarvingDepth,\r\n\t\t\t\t\tRiverCarvingTurbulenceFrequency, RiverCarvingTurbulenceStrength, RiverCarvingSpacing,\r\n\t\t\t\t\tStagingArea, StagingAreaSize, StagingAreaHeight, StagingAreaX, StagingAreaY );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t_heightmap = BuildHeightmap(\r\n\t\t\t\t\tfullRes, fullRes,\r\n\t\t\t\t\t(string[])_tileCategories.Clone(), (string[])_tileShapes.Clone(), TerrainGridSize,\r\n\t\t\t\t\t(long[])_tileSeeds.Clone(), (int[])_tileNoiseLayerStacks.Clone(),\r\n\t\t\t\t\t(float[])_tileMinHeights.Clone(), (float[])_tileMaxHeights.Clone(),\r\n\t\t\t\t\t(bool[])_tileDomainWarping.Clone(), (float[])_tileDomainWarpingSizes.Clone(), (float[])_tileDomainWarpingStrengths.Clone(),\r\n\t\t\t\t\t(int[])_tileSmoothingPasses.Clone(), (float[])_tilePlaneScales.Clone(),\r\n\t\t\t\t\tRiverCarvingBool, RiverCarvingFrequency, RiverCarvingWidth, RiverCarvingDepth,\r\n\t\t\t\t\tRiverCarvingTurbulenceFrequency, RiverCarvingTurbulenceStrength, RiverCarvingSpacing,\r\n\t\t\t\t\tStagingArea, StagingAreaSize, StagingAreaHeight, StagingAreaX, StagingAreaY );\r\n\r\n\t\t\t\t_cellHeightmaps = null;\r\n\t\t\t\t_cellSplatmaps = null;\r\n\t\t\t}\r\n\r\n\t\t\tif ( _heightmap == null )\r\n\t\t\t{\r\n\t\t\t\tLog.Error( \u0022Heightmap is not generated. Aborting.\u0022 );\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t_splatmap = BuildTileGridSplatmap( _heightmap, TerrainGridSize,\r\n\t\t\t\t(int[])_tileSplatLayerCounts.Clone(), (SplatDispersionMode[])_tileSplatDispersions.Clone(), (float[])_tileSplatBlendStrengths.Clone() );\r\n\r\n\t\t\t// Write the preview files for the asset folder, and build fresh textures for the widgets\r\n\t\t\tGeneratePreviewFile( GenerationPath, out var previewBitmap, out var splatBitmap );\r\n\r\n\t\t\t_preview_image_texture = TextureFromBitmap( previewBitmap );\r\n\t\t\tPreviewImage.Texture = _preview_image_texture;\r\n\r\n\t\t\t_preview_splatmap_texture = TextureFromBitmap( splatBitmap );\r\n\t\t\tPreviewSplatmap.Texture = _preview_splatmap_texture;\r\n\r\n\t\t\tExportButton.Enabled = true;\r\n\t\t\tApplyButton.Enabled = true;\r\n\r\n\t\t\tUpdatePreviewTerrain( _heightmap );\r\n\r\n\t\t};\r\n\r\n\t\tExportButton.Clicked \u002B= () =\u003E\r\n\t\t{\r\n\t\t\tGenerateImageFiles( ExportPath );\r\n\t\t\tvar PopUp = new PopupWindow( \u0022Export Complete\u0022, $\u0022Files exported to {ExportPath}\u0022, \u0022Okay\u0022 );\r\n\t\t\tPopUp.Show();\r\n\t\t};\r\n\r\n\t\tApplyButton.Clicked \u002B= () =\u003E\r\n\t\t{\r\n\t\t\tIDictionary\u003Cstring, Action\u003E WarnDiaglog = new Dictionary\u003Cstring, Action\u003E(); ;\r\n\t\t\tWarnDiaglog.Add( \u0022Apply\u0022, GridStorage == GridStorageMode.PerCell ? UpdatePerCellTerrains : UpdateTerrain );\r\n\t\t\tvar PopUpWarn = new PopupWindow( \u0022Warning: Terrain Override\u0022, \u0022This will override your current scene\u0027s terrain data.\u0022,\u0022Cancel\u0022, WarnDiaglog );\r\n\t\t\tPopUpWarn.Show();\r\n\t\t};\r\n\t\t\r\n\t\tbody.AddStretchCell();\r\n\r\n\t\t// ---------- Right: tabbed preview panel ----------\r\n\t\tvar rightPanel = Layout.Add( new Widget( null ), 1 );\r\n\t\trightPanel.Layout = Layout.Column();\r\n\t\trightPanel.Layout.Spacing = 0;\r\n\r\n\t\tvar PreviewTabs = rightPanel.Layout.Add( new VerticalTabWidget( this ), 1 );\r\n\t\tPreviewTabs.StateCookie = \u0022TerrainGenerationTool.PreviewTabs\u0022;\r\n\r\n\t\tRenderCanvas = new SceneRenderingWidget( this );\r\n\t\tRenderCanvas.OnPreFrame \u002B= OnPreFrame;\r\n\t\tRenderCanvas.FocusMode = FocusMode.Click;\r\n\t\tRenderCanvas.Scene = Scene.CreateEditorScene();\r\n\t\tRenderCanvas.Scene.SceneWorld.AmbientLightColor = Color.FromBytes( 135, 206, 235 ) * 0.45f;\r\n\r\n\t\t// 3D preview tab\r\n\t\tPreviewTabs.AddPage( \u00223D Preview\u0022, \u0022landscape\u0022, RenderCanvas, \u00223D terrain preview\u0022 );\r\n\r\n\t\t// Height/Color maps tab\r\n\t\tvar mapsPage = new Widget( null );\r\n\t\tmapsPage.Layout = Layout.Row();\r\n\t\tmapsPage.Layout.Spacing = 5;\r\n\r\n\t\tvar _image_preview = new Editor.TextureWidget();\r\n\t\t_image_preview.Texture = _preview_image_texture;\r\n\t\t_image_preview.Size = new Vector2( 512, 512 );\r\n\t\tPreviewImage = mapsPage.Layout.Add( _image_preview, 50 );\r\n\r\n\t\tvar _splatmap_preview = new Editor.TextureWidget();\r\n\t\t_splatmap_preview.Texture = _preview_splatmap_texture;\r\n\t\t_splatmap_preview.Size = new Vector2( 512, 512 );\r\n\t\tPreviewSplatmap = mapsPage.Layout.Add( _splatmap_preview, 50 );\r\n\r\n\t\tPreviewTabs.AddPage( \u0022Height/Color Maps\u0022, \u0022grid_view\u0022, mapsPage, \u0022Heightmap and splatmap preview\u0022 );\r\n\r\n\t\tusing ( RenderCanvas.Scene.Push() )\r\n\t\t{\r\n\t\t\tCamera = new GameObject( true, \u0022camera\u0022 ).GetOrAddComponent\u003CCameraComponent\u003E( false );\r\n\t\t\tCamera.BackgroundColor = Color.FromBytes( 135, 206, 235 );\r\n\t\t\tCamera.ZFar = 100000;\r\n\t\t\tCamera.Enabled = true;\r\n\t\t\tPositionCameraForOrbit();\r\n\t\t\tRenderCanvas.Camera = Camera;\r\n\r\n\t\t\tvar sun = new GameObject( true, \u0022sun\u0022 ).GetOrAddComponent\u003CDirectionalLight\u003E( false );\r\n\t\t\tsun.WorldRotation = Rotation.From( 45, 45, 0 );\r\n\t\t\tsun.LightColor = Color.White;\r\n\t\t\tsun.SkyColor = Color.FromBytes( 135, 206, 235 );\r\n\t\t\tsun.Enabled = true;\r\n\r\n\t\t\tvar sun2 = new GameObject( true, \u0022sun2\u0022 ).GetOrAddComponent\u003CDirectionalLight\u003E( false );\r\n\t\t\tsun2.WorldRotation = Rotation.From( -30, 135, 0 );\r\n\t\t\tsun2.LightColor = Color.White * 0.3f;\r\n\t\t\tsun2.SkyColor = Color.FromBytes( 135, 206, 235 );\r\n\t\t\tsun2.Enabled = true;\r\n\t\t}\r\n\r\n\t\tGizmoInstance = RenderCanvas.GizmoInstance;\r\n\r\n\t\t// Create the preview terrain - a real Terrain component in the preview scene\r\n\t\tusing ( RenderCanvas.Scene.Push() )\r\n\t\t{\r\n\t\t\t_previewGO = new GameObject( true, \u0022terrain preview\u0022 );\r\n\t\t\t_previewTerrain = _previewGO.AddComponent\u003CTerrain\u003E( false );\r\n\t\t\t_previewStorage = new TerrainStorage();\r\n\t\t\t_previewStorage.EmbeddedResource = new Sandbox.Resources.EmbeddedResource { ResourceCompiler = \u0022embed\u0022 };\r\n\t\t\t_previewStorage.SetResolution( PreviewResolution );\r\n\t\t\t_previewStorage.TerrainSize = PreviewTerrainSize;\r\n\t\t\t_previewStorage.TerrainHeight = PreviewTerrainHeight;\r\n\t\t\t_previewTerrain.Storage = _previewStorage;\r\n\t\t\t_previewTerrain.TerrainSize = PreviewTerrainSize;\r\n\t\t\t_previewTerrain.TerrainHeight = PreviewTerrainHeight;\r\n\t\t\t// Terrain spans [0, TerrainSize] from its origin - shift it so it\u0027s centered on the origin\r\n\t\t\t_previewGO.WorldPosition = new Vector3( -PreviewTerrainSize * 0.5f, -PreviewTerrainSize * 0.5f, 0f );\r\n\t\t\t_previewTerrain.Enabled = true;\r\n\r\n\t\t\t// Overlay mesh that shows the splatmap colors when the material preview is off.\r\n\t\t\t// Slightly offset above the terrain so it doesn\u0027t z-fight with the terrain surface.\r\n\t\t\t// Parented to the terrain GO (which is centered at origin), so the mesh uses local coords.\r\n\t\t\t_splatOverlayGO = new GameObject( true, \u0022splat overlay\u0022 );\r\n\t\t\t_splatOverlayGO.Parent = _previewGO;\r\n\t\t\t_splatOverlayGO.LocalPosition = new Vector3( 0, 0, 1f );\r\n\t\t\t_splatOverlayRenderer = _splatOverlayGO.AddComponent\u003CModelRenderer\u003E();\r\n\t\t\t_splatOverlayRenderer.MaterialOverride = Material.Load( \u0022materials/default/vertex_color.vmat\u0022 );\r\n\t\t\t_splatOverlayGO.Enabled = false;\r\n\t\t}\r\n\r\n\t\t// Zoom slider at the bottom of the preview panel\r\n\t\tvar zoomRow = rightPanel.Layout.AddRow();\r\n\t\tzoomRow.Margin = new Sandbox.UI.Margin( 8, 4, 8, 6 );\r\n\t\tzoomRow.Spacing = 8;\r\n\r\n\t\tzoomRow.Add( new IconButton( \u0022zoom_out\u0022, () =\u003E ZoomSlider.Value = MathF.Max( ZoomSlider.Minimum, ZoomSlider.Value - 500f ), this ) { IconSize = 16, FixedSize = new Vector2( 22, 22 ) } );\r\n\t\tZoomSlider = zoomRow.Add( new FloatSlider( this ), 1 );\r\n\t\tZoomSlider.Minimum = 10000f;\r\n\t\tZoomSlider.Maximum = 40000f;\r\n\t\tZoomSlider.Step = 500f;\r\n\t\tZoomSlider.Value = 40000f - _orbitDistance \u002B 10000f;\r\n\t\tZoomSlider.OnValueEdited = UpdateOrbitFromZoom;\r\n\t\tzoomRow.Add( new IconButton( \u0022zoom_in\u0022, () =\u003E ZoomSlider.Value = MathF.Min( ZoomSlider.Maximum, ZoomSlider.Value \u002B 500f ), this ) { IconSize = 16, FixedSize = new Vector2( 22, 22 ) } );\r\n\r\n\t\tApplyConditionalVisibility();\r\n\t\tRebuildTileGridUI();\r\n\t\tLoadFacepunchMaterialsAsync();\r\n\t\tRegeneratePreview();\r\n\t\tShow();\r\n\t}\r\n\r\n\tvoid OnSerializedPropertyChanged( SerializedProperty prop )\r\n\t{\r\n\t\t// Intermediate frames written during gradient animation shouldn\u0027t retrigger a regen\r\n\t\tif ( !_isAnimatingGradient )\r\n\t\t\t_previewDirty = true;\r\n\r\n\t\tif ( prop is null ) return;\r\n\r\n\t\tswitch ( prop.Name )\r\n\t\t{\r\n\t\t\tcase nameof( TerrainGridSize ):\r\n\t\t\t\tRebuildTileGridUI();\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( GridStorage ):\r\n\t\t\t\t// Reset stored per-cell maps when the storage mode changes so stale data isn\u0027t applied/exported\r\n\t\t\t\t_cellHeightmaps = null;\r\n\t\t\t\t_cellSplatmaps = null;\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( TerrainMinHeight ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( minHeight: TerrainMinHeight );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( TerrainMaxHeight ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( maxHeight: TerrainMaxHeight );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( TerrainPlaneScale ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( planeScale: TerrainPlaneScale );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( TerrainSeed ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( seed: TerrainSeed );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( SmoothingPasses ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( smoothing: SmoothingPasses );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( NoiseLayerStacks ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( noiseLayers: NoiseLayerStacks );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( DomainWarping ):\r\n\t\t\t\tSetWidgetsVisible( _domainWarpingWidgets, DomainWarping );\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( warp: DomainWarping );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( DomainWarpingSize ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( warpSize: DomainWarpingSize );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( DomainWarpingStrength ):\r\n\t\t\t\tif ( !_syncingTileSelectors ) WriteSelectedValues( warpStrength: DomainWarpingStrength );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( StagingArea ):\n\t\t\t\tSetWidgetsVisible( _stagingAreaWidgets, StagingArea );\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( SplatLayerCount ):\r\n\t\t\tcase nameof( SplatDispersion ):\r\n\t\t\tcase nameof( SplatBlendStrength ):\r\n\t\t\tcase nameof( SplatMapCount ):\r\n\t\t\t\tif ( !_syncingTileSelectors )\r\n\t\t\t\t{\r\n\t\t\t\t\tWriteSelectedValues(\r\n\t\t\t\t\t\tsplatLayers: prop.Name == nameof( SplatLayerCount ) ? SplatLayerCount : (int?)null,\r\n\t\t\t\t\t\tsplatMaps: prop.Name == nameof( SplatMapCount ) ? SplatMapCount : (int?)null,\r\n\t\t\t\t\t\tsplatDispersion: prop.Name == nameof( SplatDispersion ) ? SplatDispersion : (SplatDispersionMode?)null,\r\n\t\t\t\t\t\tsplatBlend: prop.Name == nameof( SplatBlendStrength ) ? SplatBlendStrength : (float?)null );\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Resample the gradient into evenly spaced stops so the colors/thresholds match the layer count\r\n\t\t\t\tResampleSplatGradient();\r\n\t\t\t\tif ( PreviewSplatMaterials )\r\n\t\t\t\t{\r\n\t\t\t\t\t_previewMaterials = null;\r\n\t\t\t\t\tRandomizeMaterialsAsync();\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( SplatMapGradient ):\r\n\t\t\t\t// The user edited the gradient colors in the widget - make that the source of\r\n\t\t\t\t// truth so later resamples keep their colors instead of falling back to the\r\n\t\t\t\t// stale random cache.\r\n\t\t\t\tif ( !_isAnimatingGradient )\r\n\t\t\t\t{\r\n\t\t\t\t\t_splatColorCache.Clear();\r\n\t\t\t\t\tif ( SplatMapGradient.Colors != null )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tforeach ( var frame in SplatMapGradient.Colors )\r\n\t\t\t\t\t\t\t_splatColorCache.Add( frame.Value );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\tcase nameof( PreviewSplatMaterials ):\r\n\t\t\t\tif ( PreviewSplatMaterials \u0026\u0026 ( _previewMaterials == null || _previewMaterials.Length \u003C Math.Max( SplatLayerCount, 2 ) ) )\r\n\t\t\t\t{\r\n\t\t\t\t\tRandomizeMaterials();\r\n\t\t\t\t}\r\n\t\t\t\tif ( !PreviewSplatMaterials )\r\n\t\t\t\t{\r\n\t\t\t\t\t_previewMaterials = null;\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// The largest splat layer count across all tiles (so shared resources like the preview\r\n\t/// material list and gradient cover every tile). Falls back to the global value.\r\n\t/// \u003C/summary\u003E\r\n\tint MaxTileSplatLayers()\r\n\t{\r\n\t\tint max = Math.Max( SplatLayerCount, 2 );\r\n\t\tif ( _tileSplatLayerCounts != null )\r\n\t\t{\r\n\t\t\tforeach ( var lc in _tileSplatLayerCounts )\r\n\t\t\t\tmax = Math.Max( max, lc );\r\n\t\t}\r\n\t\treturn max;\r\n\t}\r\n\r\n\tvoid ResampleSplatGradient()\r\n\t{\r\n\t\tint layerCount = MaxTileSplatLayers();\r\n\r\n\t\t// Seed the persistent color cache from the current gradient first so user edits are kept.\r\n\t\tif ( _splatColorCache.Count == 0 \u0026\u0026 SplatMapGradient.Colors != null \u0026\u0026 SplatMapGradient.Colors.Count() \u003E 0 )\r\n\t\t{\r\n\t\t\tforeach ( var frame in SplatMapGradient.Colors )\r\n\t\t\t\t_splatColorCache.Add( frame.Value );\r\n\t\t}\r\n\r\n\t\t// Add new colors to the end as layers grow - existing colors keep their index.\r\n\t\twhile ( _splatColorCache.Count \u003C layerCount )\r\n\t\t\t_splatColorCache.Add( RandomBrightColor() );\r\n\r\n\t\t// Compute the target stop positions.\r\n\t\tfloat[] thresholds;\r\n\t\tvar source = _previewHeightmap ?? _heightmap;\r\n\t\tif ( SplatDispersion == SplatDispersionMode.Natural \u0026\u0026 source != null )\r\n\t\t{\r\n\t\t\tthresholds = ComputeNaturalThresholds( source, layerCount );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthresholds = new float[layerCount];\r\n\t\t\tfor ( int i = 0; i \u003C layerCount; i\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tthresholds[i] = (float)i / (layerCount - 1);\r\n\t\t\t}\r\n\t\t}\r\n\t\t_splatthresholds = thresholds;\r\n\r\n\t\t// First build: set the gradient directly.\r\n\t\tif ( _currentFrameTimes is null )\r\n\t\t{\r\n\t\t\t_currentFrameTimes = thresholds.ToList();\r\n\t\t\t_targetFrameTimes = thresholds.ToList();\r\n\t\t\tApplyGradientFromFrames();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tint oldCount = _currentFrameTimes.Count;\r\n\r\n\t\t// New frames (added layers) enter from the right and slide left to their target.\r\n\t\tif ( layerCount \u003E oldCount )\r\n\t\t{\r\n\t\t\tfor ( int i = oldCount; i \u003C layerCount; i\u002B\u002B )\r\n\t\t\t\t_currentFrameTimes.Add( 1f );\r\n\t\t\t_targetFrameTimes = thresholds.ToList();\r\n\t\t}\r\n\t\t// Removed frames slide out to the right (target 1.0) and get dropped when they arrive.\r\n\t\telse if ( layerCount \u003C oldCount )\r\n\t\t{\r\n\t\t\t// Existing frames keep their current positions; the extra ones head right.\r\n\t\t\tvar newTargets = thresholds.ToList();\r\n\t\t\twhile ( newTargets.Count \u003C oldCount )\r\n\t\t\t\tnewTargets.Add( 1f );\r\n\t\t\t_targetFrameTimes = newTargets;\r\n\t\t}\r\n\t\t// Same count - just retarget the existing frames.\r\n\t\telse\r\n\t\t{\r\n\t\t\t_targetFrameTimes = thresholds.ToList();\r\n\t\t}\r\n\r\n\t\t_gradientAnimating = true;\r\n\t}\r\n\r\n\tvoid ApplyGradientFromFrames()\r\n\t{\r\n\t\t// Only include frames that are still \u0022in play\u0022 (haven\u0027t slid off the right edge yet).\r\n\t\tvar frames = new List\u003CGradient.ColorFrame\u003E();\r\n\t\tfor ( int i = 0; i \u003C _currentFrameTimes.Count \u0026\u0026 i \u003C _splatColorCache.Count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tfloat t = Math.Clamp( _currentFrameTimes[i], 0f, 1f );\r\n\t\t\tframes.Add( new Gradient.ColorFrame( t, _splatColorCache[i] ) );\r\n\t\t}\r\n\r\n\t\tSplatMapGradient = new Gradient( frames.ToArray() );\r\n\t\tSplatMapGradient.Blending = Gradient.BlendMode.Stepped;\r\n\r\n\t\t_isAnimatingGradient = true;\r\n\t\ttry\r\n\t\t{\r\n\t\t\t_serialized.GetProperty( nameof( SplatMapGradient ) )?.SetValue( SplatMapGradient );\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\t_isAnimatingGradient = false;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Eases the gradient color stops toward their target positions so palette changes\r\n\t/// slide in/out on the scale instead of snapping.\r\n\t/// \u003C/summary\u003E\r\n\tvoid UpdateGradientAnimation()\r\n\t{\r\n\t\tif ( !_gradientAnimating || _currentFrameTimes is null || _targetFrameTimes is null ) return;\r\n\r\n\t\tfloat t = 1f - MathF.Exp( -PreviewMorphSpeed * RealTime.Delta );\r\n\r\n\t\tfloat maxDelta = 0f;\r\n\t\tfor ( int i = 0; i \u003C _currentFrameTimes.Count \u0026\u0026 i \u003C _targetFrameTimes.Count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tfloat delta = _targetFrameTimes[i] - _currentFrameTimes[i];\r\n\t\t\t_currentFrameTimes[i] \u002B= delta * t;\r\n\t\t\tmaxDelta = MathF.Max( maxDelta, MathF.Abs( delta ) );\r\n\t\t}\r\n\r\n\t\t// Drop frames that have slid off the right edge (removed layers)\r\n\t\tif ( _currentFrameTimes.Count \u003E _targetFrameTimes.Count )\r\n\t\t{\r\n\t\t\twhile ( _currentFrameTimes.Count \u003E _targetFrameTimes.Count )\r\n\t\t\t{\r\n\t\t\t\tint last = _currentFrameTimes.Count - 1;\r\n\t\t\t\tif ( _currentFrameTimes[last] \u003E= 0.999f )\r\n\t\t\t\t{\r\n\t\t\t\t\t_currentFrameTimes.RemoveAt( last );\r\n\t\t\t\t\tif ( _splatColorCache.Count \u003E _targetFrameTimes.Count )\r\n\t\t\t\t\t\t_splatColorCache.RemoveAt( _splatColorCache.Count - 1 );\r\n\t\t\t\t}\r\n\t\t\t\telse break;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tApplyGradientFromFrames();\r\n\r\n\t\t// Force the gradient widget to repaint this frame so the motion is smooth\r\n\t\tif ( _gradientControlWidget != null \u0026\u0026 _gradientControlWidget.IsValid() )\r\n\t\t\t_gradientControlWidget.Update();\r\n\r\n\t\t// The overlay mesh samples the live gradient, so rebuild it while the palette animates\r\n\t\tBuildOverlayMesh();\r\n\r\n\t\tif ( maxDelta \u003C 0.001f )\r\n\t\t{\r\n\t\t\t_currentFrameTimes = _targetFrameTimes.ToList();\r\n\t\t\t_gradientAnimating = false;\r\n\t\t}\r\n\t}\r\n\r\n\tstatic Color RandomBrightColor()\r\n\t{\r\n\t\t// Pick a hue at random, keep saturation/value high so it stands out\r\n\t\tfloat hue = Random.Shared.NextSingle() * 360f;\r\n\t\treturn new ColorHsv( hue, 0.8f, 1.0f ).ToColor();\r\n\t}\r\n\r\n\tvoid SetWidgetsVisible( List\u003CWidget\u003E widgets, bool visible )\r\n\t{\r\n\t\tforeach ( var widget in widgets )\r\n\t\t{\r\n\t\t\twidget.Visible = visible;\r\n\t\t\twidget.Enabled = visible;\r\n\t\t}\r\n\t}\r\n\r\n\tWidget CreatePropsPage()\r\n\t{\r\n\t\tvar page = new Widget( null );\r\n\t\tpage.VerticalSizeMode = SizeMode.CanShrink;\r\n\t\tpage.HorizontalSizeMode = SizeMode.Flexible;\r\n\t\tpage.Layout = Layout.Column();\r\n\t\tpage.Layout.Margin = 10;\r\n\t\tpage.Layout.Spacing = 5;\r\n\t\tpage.Layout.Alignment = TextFlag.Top;\r\n\t\treturn page;\r\n\t}\r\n\r\n\tFloatControlWidget FloatSlider( string propertyName )\r\n\t{\r\n\t\tvar property = _serialized.GetProperty( propertyName );\r\n\t\tvar control = new FloatControlWidget( property );\r\n\t\tMakeRanged( control, property );\r\n\t\treturn control;\r\n\t}\r\n\r\n\tIntegerControlWidget IntSlider( string propertyName )\r\n\t{\r\n\t\tvar property = _serialized.GetProperty( propertyName );\r\n\t\tvar control = new IntegerControlWidget( property );\r\n\t\tMakeRanged( control, property );\r\n\t\treturn control;\r\n\t}\r\n\r\n\tvoid MakeRanged( FloatControlWidget control, SerializedProperty property )\r\n\t{\r\n\t\tif ( property is null ) return;\r\n\r\n\t\tproperty.TryGetAttribute\u003CMinMaxAttribute\u003E( out var minMax );\r\n\t\tif ( minMax is null ) return;\r\n\r\n\t\tfloat step = 0.01f;\r\n\t\tif ( property.TryGetAttribute\u003CStepAttribute\u003E( out var stepAttr ) )\r\n\t\t{\r\n\t\t\tstep = stepAttr.Step;\r\n\t\t}\r\n\r\n\t\tcontrol.MakeRanged( new Vector2( minMax.MinValue, minMax.MaxValue ), step, true, true );\r\n\t}\r\n\r\n\tvoid RandomizeSeed()\r\n\t{\r\n\t\tvar property = _serialized.GetProperty( nameof( TerrainSeed ) );\r\n\t\tif ( property is null ) return;\r\n\r\n\t\tTerrainSeed = Random.Shared.NextInt64();\r\n\t\tproperty.SetValue( TerrainSeed );\r\n\t\t_previewDirty = true;\r\n\t}\r\n\r\n\tvoid LoadFacepunchMaterialsAsync()\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Find all local tmat assets in the project\u0027s assets folder (not cloud ones).\r\n\t\t\t// Prefer 1K variants when a material has multiple resolutions.\r\n\t\t\tvar allLocal = Editor.AssetSystem.All\r\n\t\t\t\t.Where( a =\u003E a is not null \u0026\u0026 !a.IsDeleted \u0026\u0026 !a.IsCloud )\r\n\t\t\t\t.Where( a =\u003E (a.RelativePath?.EndsWith( \u0022.tmat\u0022 ) ?? false) )\r\n\t\t\t\t.ToList();\r\n\r\n\t\t\tvar with1k = allLocal.Where( a =\u003E a.RelativePath.Contains( \u0022_1k\u0022 ) ).ToList();\r\n\r\n\t\t\t_localTmatAssets = with1k.Count \u003E 0 ? with1k : allLocal;\r\n\r\n\t\t\tif ( _localTmatAssets.Count == 0 )\r\n\t\t\t\tLog.Warning( \u0022No local .tmat terrain materials found in the project\u0027s assets folder\u0022 );\r\n\t\t}\r\n\t\tcatch ( System.Exception e )\r\n\t\t{\r\n\t\t\tLog.Error( $\u0022Failed to find local terrain materials: {e.Message}\u0022 );\r\n\t\t}\r\n\t}\r\n\r\n\tvoid RandomizeMaterials()\r\n\t{\r\n\t\tif ( !PreviewSplatMaterials ) return;\r\n\r\n\t\tif ( _localTmatAssets is null || _localTmatAssets.Count == 0 )\r\n\t\t{\r\n\t\t\t// Load the local tmat list first, then randomize once it\u0027s available\r\n\t\t\t_ = LoadFacepunchMaterialsAndRandomize();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tRandomizeMaterialsAsync();\r\n\t}\r\n\r\n\tasync Task LoadFacepunchMaterialsAndRandomize()\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// Find all local tmat assets in the project\u0027s assets folder (not cloud ones).\r\n\t\t\t// Prefer 1K variants when a material has multiple resolutions.\r\n\t\t\tvar allLocal = Editor.AssetSystem.All\r\n\t\t\t\t.Where( a =\u003E a is not null \u0026\u0026 !a.IsDeleted \u0026\u0026 !a.IsCloud )\r\n\t\t\t\t.Where( a =\u003E (a.RelativePath?.EndsWith( \u0022.tmat\u0022 ) ?? false) )\r\n\t\t\t\t.ToList();\r\n\r\n\t\t\tvar with1k = allLocal.Where( a =\u003E a.RelativePath.Contains( \u0022_1k\u0022 ) ).ToList();\r\n\r\n\t\t\t_localTmatAssets = with1k.Count \u003E 0 ? with1k : allLocal;\r\n\r\n\t\t\tif ( PreviewSplatMaterials \u0026\u0026 _localTmatAssets.Count \u003E 0 )\r\n\t\t\t\tRandomizeMaterialsAsync();\r\n\t\t}\r\n\t\tcatch ( System.Exception e )\r\n\t\t{\r\n\t\t\tLog.Error( $\u0022Failed to find local terrain materials: {e.Message}\u0022 );\r\n\t\t}\r\n\t}\r\n\r\n\tasync void RandomizeMaterialsAsync()\r\n\t{\r\n\t\tif ( _materialLoadingLabel != null )\r\n\t\t{\r\n\t\t\t_materialLoadingLabel.Text = \u0022Loading materials...\u0022;\r\n\t\t\t_materialLoadingLabel.Visible = true;\r\n\t\t}\r\n\r\n\t\tint layerCount = MaxTileSplatLayers();\r\n\t\tint gen = \u002B\u002B_previewMaterialsGeneration;\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar pool = new List\u003CEditor.Asset\u003E( _localTmatAssets );\r\n\r\n\t\t\tvar materials = new List\u003CTerrainMaterial\u003E();\r\n\r\n\t\t\t// Pull from the pool until we have layerCount usable materials (skipping any\r\n\t\t\t// whose textures fail to compile) or the pool runs out.\r\n\t\t\twhile ( materials.Count \u003C layerCount \u0026\u0026 pool.Count \u003E 0 )\r\n\t\t\t{\r\n\t\t\t\tint idx = Random.Shared.Next( pool.Count );\r\n\t\t\t\tvar asset = pool[idx];\r\n\t\t\t\tpool.RemoveAt( idx );\r\n\r\n\t\t\t\tif ( !asset.TryLoadResource\u003CTerrainMaterial\u003E( out var found ) || found is null )\r\n\t\t\t\t{\r\n\t\t\t\t\tLog.Warning( $\u0022Failed to load TerrainMaterial from \u0027{asset.Path}\u0027\u0022 );\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Only accept materials whose compiled BCR/NHO textures actually exist -\r\n\t\t\t\t// otherwise the terrain renders a pink checkerboard.\r\n\t\t\t\tif ( !IsMaterialUsable( found, asset.Path ) )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tmaterials.Add( found );\r\n\t\t\t}\r\n\r\n\t\t\tif ( gen != _previewMaterialsGeneration ) return;\r\n\r\n\t\t\tif ( materials.Count == 0 )\r\n\t\t\t{\r\n\t\t\t\t_previewMaterials = null;\r\n\t\t\t\tLog.Warning( \u0022No local terrain materials could be loaded\u0022 );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\t_previewMaterials = materials.ToArray();\r\n\t\t\t}\r\n\r\n\t\t\t_previewDirty = true;\r\n\t\t}\r\n\t\tcatch ( System.Exception e )\r\n\t\t{\r\n\t\t\tLog.Error( $\u0022Failed to load preview materials: {e.Message}\u0022 );\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\tif ( gen == _previewMaterialsGeneration \u0026\u0026 _materialLoadingLabel != null )\r\n\t\t\t\t_materialLoadingLabel.Visible = false;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Checks that a terrain material\u0027s compiled BCR/NHO textures are usable. The terrain shader\r\n\t/// samples these bindlessly, and a missing/failed compile shows up as a pink checkerboard.\r\n\t/// \u003C/summary\u003E\r\n\tbool IsMaterialUsable( TerrainMaterial material, string ident )\r\n\t{\r\n\t\tif ( material is null ) return false;\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\tvar bcr = material.BCRTexture;\r\n\t\t\tif ( bcr is null || bcr.IsError || !bcr.IsValid )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\u0022Skipping \u0027{ident}\u0027: BCR texture missing or failed to compile\u0022 );\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\tvar nho = material.NHOTexture;\r\n\t\t\tif ( nho is null || nho.IsError || !nho.IsValid )\r\n\t\t\t{\r\n\t\t\t\tLog.Warning( $\u0022Skipping \u0027{ident}\u0027: NHO texture missing or failed to compile\u0022 );\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\tcatch ( System.Exception e )\r\n\t\t{\r\n\t\t\tLog.Warning( $\u0022Skipping \u0027{ident}\u0027: {e.Message}\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid AddPropsTab( string name, string icon, Widget page, string tooltip )\r\n\t{\r\n\t\t_propsTabBar.AddOption( name, icon );\r\n\t\t_propsPages[name] = page;\r\n\t\t_propsContent.Layout.Add( page );\r\n\r\n\t\tpage.Visible = false;\r\n\t\tpage.ToolTip = tooltip;\r\n\r\n\t\tif ( _propsPages.Count == 1 )\r\n\t\t{\r\n\t\t\tSelectPropsTab( name );\r\n\t\t}\r\n\t}\r\n\r\n\tvoid SelectPropsTab( string name )\r\n\t{\r\n\t\tforeach ( var entry in _propsPages )\r\n\t\t{\r\n\t\t\tentry.Value.Visible = entry.Key == name;\r\n\t\t}\r\n\r\n\t\tif ( _activePropsTab != name )\r\n\t\t{\r\n\t\t\t_activePropsTab = name;\r\n\t\t\t_previewDirty = true;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid ApplyConditionalVisibility()\r\n\t{\r\n\t\tSetWidgetsVisible( _domainWarpingWidgets, DomainWarping );\r\n\t\tSetWidgetsVisible( _riverCarvingWidgets, RiverCarvingBool );\r\n\t\tSetWidgetsVisible( _stagingAreaWidgets, StagingArea );\r\n\t}\r\n\r\n\t[EditorEvent.Frame]\r\n\tpublic void FrameUpdate()\r\n\t{\r\n\t\t// Tick the preview scene so the terrain clipmap builds and updates\r\n\t\tif ( RenderCanvas != null \u0026\u0026 RenderCanvas.Scene.IsValid() )\r\n\t\t\tRenderCanvas.Scene.EditorTick( RealTime.Now, RealTime.Delta );\r\n\r\n\t\t// Morph the overlay mesh toward its target shape every frame\r\n\t\tUpdateOverlayAnimation();\r\n\r\n\t\t// If a tile was just selected/deselected, keep rebuilding the overlay mesh so the\r\n\t\t// grey-out smoothly eases in until the colors settle.\r\n\t\tif ( _overlayColorAnimating )\r\n\t\t{\r\n\t\t\tBuildOverlayMesh();\r\n\t\t}\r\n\r\n\t\t// Animate the gradient color stops sliding in/out\r\n\t\tUpdateGradientAnimation();\r\n\r\n\t\tif ( !_previewDirty ) return;\r\n\t\tif ( RealTime.Now - _lastPreviewRegen \u003C 0.1f ) return;\r\n\t\tif ( _isGenerating ) return;\r\n\r\n\t\t_previewDirty = false;\r\n\t\t_lastPreviewRegen = RealTime.Now;\r\n\r\n\t\tRegeneratePreviewAsync();\r\n\t}\r\n\r\n\tasync void RegeneratePreviewAsync()\r\n\t{\r\n\t\tif ( _previewTerrain is null || !_previewTerrain.IsValid() ) return;\r\n\t\tif ( string.IsNullOrEmpty( CategoryArray?.Selected ) || string.IsNullOrEmpty( ShapeArray?.Selected ) ) return;\r\n\r\n\t\tif ( _isGenerating ) return;\r\n\t\t_isGenerating = true;\r\n\t\tint token = \u002B\u002B_generationToken;\r\n\r\n\t\t// Splat colors are read on the main thread into the shared arrays\r\n\t\tBuildSplatColors();\r\n\r\n\t\t// Snapshot UI-driven values on the main thread so the background task doesn\u0027t touch widgets\r\n\t\tvar tileCategories = (string[])_tileCategories.Clone();\r\n\t\tvar tileShapes = (string[])_tileShapes.Clone();\r\n\t\tvar tileSeeds = (long[])_tileSeeds.Clone();\r\n\t\tvar tileMinHeights = (float[])_tileMinHeights.Clone();\r\n\t\tvar tileMaxHeights = (float[])_tileMaxHeights.Clone();\r\n\t\tvar tilePlaneScales = (float[])_tilePlaneScales.Clone();\r\n\t\tvar tileSmoothing = (int[])_tileSmoothingPasses.Clone();\r\n\t\tvar tileNoiseLayers = (int[])_tileNoiseLayerStacks.Clone();\r\n\t\tvar tileWarping = (bool[])_tileDomainWarping.Clone();\r\n\t\tvar tileWarpingSizes = (float[])_tileDomainWarpingSizes.Clone();\r\n\t\tvar tileWarpingStrengths = (float[])_tileDomainWarpingStrengths.Clone();\r\n\t\tvar tileSplatLayerCounts = (int[])_tileSplatLayerCounts.Clone();\r\n\t\tvar tileSplatDispersions = (SplatDispersionMode[])_tileSplatDispersions.Clone();\r\n\t\tvar tileSplatBlends = (float[])_tileSplatBlendStrengths.Clone();\r\n\t\tint gridSize = TerrainGridSize;\r\n\t\tbool rivers = RiverCarvingBool;\r\n\t\tfloat riverFrequency = RiverCarvingFrequency;\r\n\t\tfloat riverWidth = RiverCarvingWidth;\r\n\t\tfloat riverDepth = RiverCarvingDepth;\r\n\t\tfloat riverTurbFreq = RiverCarvingTurbulenceFrequency;\r\n\t\tfloat riverTurbStrength = RiverCarvingTurbulenceStrength;\r\n\t\tfloat riverSpacing = RiverCarvingSpacing;\r\n\t\tbool staging = StagingArea;\r\n\t\tint stagingSize = StagingAreaSize;\r\n\t\tfloat stagingHeight = StagingAreaHeight;\r\n\t\tfloat stagingX = StagingAreaX;\r\n\t\tfloat stagingY = StagingAreaY;\r\n\r\n\t\ttry\r\n\t\t{\r\n\t\t\t// CPU-heavy work (noise, smoothing, rivers, splatmap) runs off the main thread\r\n\t\t\tfloat[,] heightmap = await Task.Run( () =\u003E BuildHeightmap(\r\n\t\t\t\tPreviewResolution, PreviewResolution,\r\n\t\t\t\ttileCategories, tileShapes, gridSize,\r\n\t\t\t\ttileSeeds, tileNoiseLayers, tileMinHeights, tileMaxHeights,\r\n\t\t\t\ttileWarping, tileWarpingSizes, tileWarpingStrengths,\r\n\t\t\t\ttileSmoothing, tilePlaneScales,\r\n\t\t\t\trivers, riverFrequency, riverWidth, riverDepth,\r\n\t\t\t\triverTurbFreq, riverTurbStrength, riverSpacing,\r\n\t\t\t\tstaging, stagingSize, stagingHeight, stagingX, stagingY ) );\r\n\r\n\t\t\tif ( token != _generationToken ) return;\r\n\t\t\tif ( heightmap is null ) return;\r\n\r\n\t\t\t_previewHeightmap = heightmap;\r\n\r\n\t\t\t// GPU/scene updates must happen back on the main thread\r\n\t\t\tUpdatePreviewTerrain( heightmap );\r\n\t\t}\r\n\t\tfinally\r\n\t\t{\r\n\t\t\t_isGenerating = false;\r\n\r\n\t\t\t// If more changes came in while we were busy, regenerate again\r\n\t\t\tif ( _previewDirty \u0026\u0026 token == _generationToken )\r\n\t\t\t{\r\n\t\t\t\t_previewDirty = false;\r\n\t\t\t\tRegeneratePreviewAsync();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvoid OnPreFrame()\r\n\t{\r\n\t\tGizmoInstance.Input.IsHovered = IsActiveWindow \u0026\u0026 RenderCanvas.IsUnderMouse;\r\n\r\n\t\tvar isAltHeld = Editor.Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Alt );\r\n\t\tvar isLeftDown = Editor.Application.MouseButtons.HasFlag( MouseButtons.Left );\r\n\r\n\t\tvar isInteracting = false;\r\n\r\n\t\tif ( GizmoInstance.OrbitCamera( Camera, RenderCanvas, ref _orbitDistance ) )\r\n\t\t{\r\n\t\t\t// User is manually orbiting - don\u0027t auto-spin this frame\r\n\t\t\tisInteracting = true;\r\n\t\t\tGizmoInstance.Input.IsHovered = false;\r\n\t\t}\r\n\t\telse if ( isAltHeld )\r\n\t\t{\r\n\t\t\tisInteracting = true;\r\n\t\t}\r\n\t\telse if ( isLeftDown \u0026\u0026 GizmoInstance.Input.IsHovered )\r\n\t\t{\r\n\t\t\t// Click and drag in the preview to adjust pitch/yaw\r\n\t\t\tisInteracting = true;\r\n\r\n\t\t\tvar delta = Editor.Application.CursorDelta * 0.1f;\r\n\r\n\t\t\t_orbitPitch = Math.Clamp( _orbitPitch \u002B delta.y, 5f, 85f );\r\n\t\t\t_orbitAngle \u002B= delta.x;\r\n\t\t\tif ( _orbitAngle \u003E= 360f ) _orbitAngle -= 360f;\r\n\t\t\tif ( _orbitAngle \u003C 0f ) _orbitAngle \u002B= 360f;\r\n\r\n\t\t\tPositionCameraForOrbit();\r\n\t\t}\r\n\r\n\t\tif ( !isInteracting \u0026\u0026 _autoSpin )\r\n\t\t{\r\n\t\t\t// Slowly rotate the camera around the terrain\r\n\t\t\t_orbitAngle \u002B= SpinSpeed * RealTime.Delta;\r\n\t\t\tif ( _orbitAngle \u003E= 360f ) _orbitAngle -= 360f;\r\n\t\t\tPositionCameraForOrbit();\r\n\t\t}\r\n\r\n\t\t// Scroll wheel over the preview controls zoom\r\n\t\tif ( !isAltHeld \u0026\u0026 GizmoInstance.Input.IsHovered \u0026\u0026 MathF.Abs( Editor.Application.MouseWheelDelta.y ) \u003E 0.001f )\r\n\t\t{\r\n\t\t\tvar wheelDelta = Editor.Application.MouseWheelDelta.y;\r\n\t\t\tZoomSlider.Value = Math.Clamp( ZoomSlider.Value \u002B wheelDelta * 500f, ZoomSlider.Minimum, ZoomSlider.Maximum );\r\n\t\t\tUpdateOrbitFromZoom();\r\n\t\t}\r\n\r\n\t\tRenderCanvas.UpdateGizmoInputs( GizmoInstance.Input.IsHovered );\r\n\t}\r\n\r\n\tvoid PositionCameraForOrbit()\r\n\t{\r\n\t\tif ( Camera is null || !Camera.IsValid() ) return;\r\n\r\n\t\tfloat pitch = _orbitPitch;\r\n\t\tfloat yaw = _orbitAngle;\r\n\r\n\t\tvar offset = new Vector3(\r\n\t\t\tMathF.Sin( MathX.DegreeToRadian( yaw ) ) * MathF.Cos( MathX.DegreeToRadian( pitch ) ),\r\n\t\t\tMathF.Cos( MathX.DegreeToRadian( yaw ) ) * MathF.Cos( MathX.DegreeToRadian( pitch ) ),\r\n\t\t\tMathF.Sin( MathX.DegreeToRadian( pitch ) )\r\n\t\t) * _orbitDistance;\r\n\r\n\t\tCamera.WorldPosition = Vector3.Zero \u002B offset;\r\n\t\tCamera.WorldRotation = Rotation.LookAt( (Vector3.Zero - offset).Normal, Vector3.Up );\r\n\t}\r\n\r\n\tvoid UpdateOrbitFromZoom()\r\n\t{\r\n\t\t// Higher slider value = closer to terrain (zoom in)\r\n\t\t_orbitDistance = ZoomSlider.Maximum \u002B ZoomSlider.Minimum - ZoomSlider.Value;\r\n\t\tPositionCameraForOrbit();\r\n\t}\r\n\r\n\tvoid BuildSplatColors()\r\n\t{\r\n\t\t// The colors must be sampled at the ACTUAL threshold positions the splatmap uses, not at\r\n\t\t// evenly spaced positions. In Natural dispersion the layers sit at slope-weighted stops,\r\n\t\t// so even sampling would skip/misalign colors. The gradient\u0027s frame times ARE the\r\n\t\t// thresholds (ResampleSplatGradient positions them there), so read them directly.\r\n\t\tint layerCount = MaxTileSplatLayers();\r\n\r\n\t\tvar frames = SplatMapGradient.Colors;\r\n\t\tif ( frames != null \u0026\u0026 frames.Count() == layerCount \u0026\u0026 layerCount \u003E 0 )\r\n\t\t{\r\n\t\t\t// Frames are ordered by time - use their exact positions and colors so the splatmap\r\n\t\t\t// and shader reflect exactly what the user set in the gradient widget.\r\n\t\t\tvar times = new float[layerCount];\r\n\t\t\tvar colors = new SKColor[layerCount];\r\n\t\t\tint i = 0;\r\n\t\t\tforeach ( var frame in frames )\r\n\t\t\t{\r\n\t\t\t\ttimes[i] = Math.Clamp( frame.Time, 0f, 1f );\r\n\t\t\t\tvar c = frame.Value.ToColor32();\r\n\t\t\t\tcolors[i] = new SKColor( c.r, c.g, c.b, c.a );\r\n\t\t\t\ti\u002B\u002B;\r\n\t\t\t}\r\n\t\t\t_splatthresholds = times;\r\n\t\t\t_splatcolors = colors;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Fallback: no matching frame count yet (e.g. first build) - sample the gradient at the\r\n\t\t// same threshold positions the splatmap will use.\r\n\t\tfloat[] stops;\r\n\t\tvar source = _previewHeightmap ?? _heightmap;\r\n\t\tif ( SplatDispersion == SplatDispersionMode.Natural \u0026\u0026 source != null )\r\n\t\t\tstops = ComputeNaturalThresholds( source, layerCount );\r\n\t\telse\r\n\t\t\tstops = MakeEvenThresholds( layerCount );\r\n\r\n\t\tvar thresholdtime = new List\u003Cfloat\u003E();\r\n\t\tvar mapgradients = new List\u003CSKColor\u003E();\r\n\t\tfor ( int i = 0; i \u003C layerCount; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tthresholdtime.Add( stops[i] );\r\n\r\n\t\t\tvar color = SplatMapGradient.Evaluate( Math.Clamp( stops[i], 0f, 1f ) ).ToColor32();\r\n\t\t\tmapgradients.Add( new SKColor( color.r, color.g, color.b, color.a ) );\r\n\t\t}\r\n\t\t_splatthresholds = thresholdtime.ToArray();\r\n\t\t_splatcolors = mapgradients.ToArray();\r\n\t}\r\n\r\n\tvoid RegeneratePreview()\r\n\t{\r\n\t\tif ( _previewTerrain is null || !_previewTerrain.IsValid() ) return;\r\n\t\tif ( string.IsNullOrEmpty( CategoryArray?.Selected ) || string.IsNullOrEmpty( ShapeArray?.Selected ) ) return;\r\n\r\n\t\tBuildSplatColors();\r\n\r\n\t\tvar heightmap = BuildHeightmap(\r\n\t\t\tPreviewResolution, PreviewResolution,\r\n\t\t\t(string[])_tileCategories.Clone(), (string[])_tileShapes.Clone(), TerrainGridSize,\r\n\t\t\t(long[])_tileSeeds.Clone(), (int[])_tileNoiseLayerStacks.Clone(),\r\n\t\t\t(float[])_tileMinHeights.Clone(), (float[])_tileMaxHeights.Clone(),\r\n\t\t\t(bool[])_tileDomainWarping.Clone(), (float[])_tileDomainWarpingSizes.Clone(), (float[])_tileDomainWarpingStrengths.Clone(),\r\n\t\t\t(int[])_tileSmoothingPasses.Clone(), (float[])_tilePlaneScales.Clone(),\r\n\t\t\tRiverCarvingBool, RiverCarvingFrequency, RiverCarvingWidth, RiverCarvingDepth,\r\n\t\t\tRiverCarvingTurbulenceFrequency, RiverCarvingTurbulenceStrength, RiverCarvingSpacing,\r\n\t\t\tStagingArea, StagingAreaSize, StagingAreaHeight, StagingAreaX, StagingAreaY );\r\n\t\tif ( heightmap is null ) return;\r\n\r\n\t\t_previewHeightmap = heightmap;\r\n\t\tUpdatePreviewTerrain( heightmap );\r\n\t}\r\n\r\n\tvoid UpdatePreviewTerrain( float[,] heightmap )\r\n\t{\r\n\t\tif ( _previewTerrain is null || !_previewTerrain.IsValid() ) return;\r\n\t\tif ( _previewStorage is null ) return;\r\n\r\n\t\tint res = heightmap.GetLength( 0 );\r\n\r\n\t\t// Resize the storage to match the incoming heightmap so Generate (full res) and the\r\n\t\t// live preview (PreviewResolution) both work without a buffer size mismatch.\r\n\t\tif ( _previewStorage.Resolution != res )\r\n\t\t\t_previewStorage.SetResolution( res );\r\n\r\n\t\t// Write the heightmap into the terrain storage (0..65535 maps across TerrainHeight)\r\n\t\tushort[] heightArray = new ushort[res * res];\r\n\t\tfor ( int y = 0; y \u003C res; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C res; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat h = Math.Clamp( heightmap[x, y], 0f, 1f );\r\n\t\t\t\theightArray[y * res \u002B x] = (ushort)Math.Clamp( (int)(h * 65535f), 0, 65535 );\r\n\t\t\t}\r\n\t\t}\r\n\t\t_previewStorage.HeightMap = heightArray;\r\n\r\n\t\t// Build the control map (which materials go where). When the material preview is\r\n\t\t// enabled and we have assigned bluedock materials, blend them by the splat map\r\n\t\t// exactly like the exported terrain would. Otherwise use the single default material.\r\n\t\tuint[] controlMap = new uint[res * res];\r\n\r\n\t\tbool useMaterials = _activePropsTab == \u0022Splat\u0022 \u0026\u0026 PreviewSplatMaterials \u0026\u0026 _previewMaterials != null \u0026\u0026 _previewMaterials.Length \u003E 0;\r\n\r\n\t\tif ( useMaterials )\r\n\t\t{\r\n\t\t\tfloat[,] splatmap = BuildTileGridSplatmap( heightmap, TerrainGridSize,\r\n\t\t\t\t(int[])_tileSplatLayerCounts.Clone(), (SplatDispersionMode[])_tileSplatDispersions.Clone(), (float[])_tileSplatBlendStrengths.Clone() );\r\n\r\n\t\t\tint matCount = _previewMaterials.Length;\r\n\t\t\tfor ( int y = 0; y \u003C res; y\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfor ( int x = 0; x \u003C res; x\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat layerPos = Math.Clamp( splatmap[x, y], 0f, matCount - 1f );\r\n\t\t\t\t\tint baseId = (int)MathF.Floor( layerPos );\r\n\t\t\t\t\tint overlayId = Math.Min( baseId \u002B 1, matCount - 1 );\r\n\t\t\t\t\tbyte blend = (byte)Math.Clamp( (int)((layerPos - baseId) * 255f), 0, 255 );\r\n\r\n\t\t\t\t\tcontrolMap[y * res \u002B x] = new CompactTerrainMaterial( (byte)baseId, (byte)overlayId, blend, false ).Packed;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Default: single material, no blending\r\n\t\t\tfor ( int i = 0; i \u003C controlMap.Length; i\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tcontrolMap[i] = new CompactTerrainMaterial( 0, 0, 0, false ).Packed;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t_previewStorage.ControlMap = controlMap;\r\n\r\n\t\t// Assign the materials and push everything to the GPU\r\n\t\tif ( _previewMaterials != null )\r\n\t\t{\r\n\t\t\t_previewStorage.Materials.Clear();\r\n\t\t\t_previewStorage.Materials.AddRange( _previewMaterials );\r\n\t\t}\r\n\r\n\t\t// Only the terrain shows when we\u0027re on the Splat tab previewing the real materials.\r\n\t\t// The terrain must be enabled before touching its GPU state, otherwise SyncGPUTexture\r\n\t\t// throws - so sync only when it\u0027s going to be visible.\r\n\t\tif ( _previewTerrain != null ) _previewTerrain.Enabled = useMaterials;\r\n\r\n\t\tif ( useMaterials \u0026\u0026 _previewTerrain != null \u0026\u0026 _previewTerrain.IsValid() )\r\n\t\t{\r\n\t\t\t_previewTerrain.Create();\r\n\t\t\t_previewTerrain.SyncGPUTexture();\r\n\t\t\t_previewTerrain.UpdateMaterialsBuffer();\r\n\t\t}\r\n\r\n\t\t// Overlay logic: on the Splat tab with the material preview off, overlay the splatmap\r\n\t\t// colors so you can see the layer layout. On any other tab, overlay the original\r\n\t\t// height-based color we used to paint the mesh. When materials are previewing, no overlay.\r\n\t\tbool showSplatOverlay = _activePropsTab == \u0022Splat\u0022 \u0026\u0026 !useMaterials;\r\n\t\tUpdateSplatOverlay( heightmap, !useMaterials, showSplatOverlay );\r\n\t}\r\n\r\n\tvoid UpdateSplatOverlay( float[,] heightmap, bool visible, bool splatColors )\r\n\t{\r\n\t\tif ( _splatOverlayRenderer is null || !_splatOverlayRenderer.IsValid() ) return;\r\n\r\n\t\tint res = heightmap.GetLength( 0 );\r\n\r\n\t\t// Store the target heightmap and the desired colors. The mesh itself is animated\r\n\t\t// toward this target in FrameUpdate so changes morph smoothly instead of snapping.\r\n\t\tif ( _overlayTargetHeights == null || _overlayTargetHeights.Length != res * res )\r\n\t\t{\r\n\t\t\t_overlayTargetHeights = new float[res * res];\r\n\t\t\t_overlayCurrentHeights = new float[res * res];\r\n\t\t}\r\n\r\n\t\tbool first = _splatOverlayRenderer.Model is null;\r\n\r\n\t\tfor ( int y = 0; y \u003C res; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C res; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\t_overlayTargetHeights[y * res \u002B x] = Math.Clamp( heightmap[x, y], 0f, 1f );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// If we\u0027ve never built the mesh, snap to the current values so the first frame is correct.\r\n\t\tif ( first )\r\n\t\t{\r\n\t\t\tArray.Copy( _overlayTargetHeights, _overlayCurrentHeights, _overlayTargetHeights.Length );\r\n\t\t\t_overlayAnimating = false;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_overlayAnimating = true;\r\n\t\t}\r\n\r\n\t\t// Cache the splatmap (only changes when the heightmap regenerates). The vertex colors\r\n\t\t// are evaluated from the LIVE gradient each frame so they stay in sync with the widget.\r\n\t\t_overlayUseSplatColors = splatColors;\r\n\t\tif ( splatColors )\r\n\t\t\t_overlaySplatmap = BuildTileGridSplatmap( heightmap, TerrainGridSize,\r\n\t\t\t\t(int[])_tileSplatLayerCounts.Clone(), (SplatDispersionMode[])_tileSplatDispersions.Clone(), (float[])_tileSplatBlendStrengths.Clone() );\r\n\r\n\t\tif ( first )\r\n\t\t\tBuildOverlayMesh();\r\n\r\n\t\t// Toggle visibility\r\n\t\tif ( _splatOverlayGO != null ) _splatOverlayGO.Enabled = visible;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Called every frame - eases the overlay mesh from its current shape toward the target shape.\r\n\t/// \u003C/summary\u003E\r\n\tvoid UpdateOverlayAnimation()\r\n\t{\r\n\t\tif ( _splatOverlayRenderer is null || !_splatOverlayRenderer.IsValid() ) return;\r\n\t\tif ( !_overlayAnimating || _overlayTargetHeights is null || _overlayCurrentHeights is null ) return;\r\n\r\n\t\tint count = _overlayTargetHeights.Length;\r\n\t\tif ( _overlayCurrentHeights.Length != count ) return;\r\n\r\n\t\t// Exponential approach - fast at first, settles smoothly\r\n\t\tfloat t = 1f - MathF.Exp( -PreviewMorphSpeed * RealTime.Delta );\r\n\r\n\t\tfloat maxDelta = 0f;\r\n\t\tfor ( int i = 0; i \u003C count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tfloat delta = _overlayTargetHeights[i] - _overlayCurrentHeights[i];\r\n\t\t\t_overlayCurrentHeights[i] \u002B= delta * t;\r\n\t\t\tmaxDelta = MathF.Max( maxDelta, MathF.Abs( delta ) );\r\n\t\t}\r\n\r\n\t\t// Rebuild the mesh from the current (eased) heights. Colors are sampled from the\r\n\t\t// live gradient inside BuildOverlayMesh so they animate as smoothly as the widget.\r\n\t\tBuildOverlayMesh();\r\n\r\n\t\t// Stop once we\u0027re close enough\r\n\t\tif ( maxDelta \u003C 0.001f )\r\n\t\t{\r\n\t\t\tArray.Copy( _overlayTargetHeights, _overlayCurrentHeights, count );\r\n\t\t\t_overlayAnimating = false;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid BuildOverlayMesh()\r\n\t{\r\n\t\tif ( _splatOverlayRenderer is null || !_splatOverlayRenderer.IsValid() ) return;\r\n\t\tif ( _overlayCurrentHeights is null ) return;\r\n\r\n\t\tint res = (int)MathF.Sqrt( _overlayCurrentHeights.Length );\r\n\t\tint vertexCount = res * res;\r\n\r\n\t\tconst float worldSize = PreviewTerrainSize;\r\n\t\tconst float worldHeight = PreviewTerrainHeight;\r\n\t\tfloat cellX = worldSize / res;\r\n\t\tfloat cellY = worldSize / res;\r\n\r\n\t\t// Color ease factor - the mesh morphs at half the gradient speed so the color\r\n\t\t// swipe across the terrain is smoother. First build snaps immediately.\r\n\t\t// Tile-selection grey/blue changes use a much faster rate so they snap quicker.\r\n\t\tfloat colorMorphSpeed = _overlayColorAnimating ? PreviewMorphSpeed * 4f : MeshMorphSpeed;\r\n\t\tfloat colorT = _overlayCurrentColors is null ? 1f : 1f - MathF.Exp( -colorMorphSpeed * RealTime.Delta );\r\n\r\n\t\tvar vertices = new Vertex[vertexCount];\r\n\t\tvar colors = _overlayCurrentColors ?? new Color32[vertexCount];\r\n\r\n\t\tbool useSplat = _overlayUseSplatColors \u0026\u0026 _overlaySplatmap != null;\r\n\r\n\t\t// When a specific tile is selected, only that tile keeps its colors - the rest go grey\r\n\t\tint grid = Math.Max( TerrainGridSize, 1 );\r\n\t\tint tileW = res / grid;\r\n\t\tint tileH = res / grid;\r\n\r\n\t\t// Track how far the colors moved so the refresh can stop once they settle\r\n\t\tfloat[] maxColorDelta = new float[1];\r\n\r\n\t\tParallel.For( 0, res, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C res; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tint index = y * res \u002B x;\r\n\t\t\t\tfloat h = _overlayCurrentHeights[index];\r\n\r\n\t\t\t\t// Local space - the overlay GO is parented to the centered terrain GO\r\n\t\t\t\tVector3 position = new Vector3( x * cellX, y * cellY, h * worldHeight );\r\n\r\n\t\t\t\tfloat hL = _overlayCurrentHeights[y * res \u002B Math.Max( x - 1, 0 )];\r\n\t\t\t\tfloat hR = _overlayCurrentHeights[y * res \u002B Math.Min( x \u002B 1, res - 1 )];\r\n\t\t\t\tfloat hD = _overlayCurrentHeights[Math.Max( y - 1, 0 ) * res \u002B x];\r\n\t\t\t\tfloat hU = _overlayCurrentHeights[Math.Min( y \u002B 1, res - 1 ) * res \u002B x];\r\n\r\n\t\t\t\tfloat dx = (hR - hL) * worldHeight / (2.0f * cellX);\r\n\t\t\t\tfloat dy = (hU - hD) * worldHeight / (2.0f * cellY);\r\n\r\n\t\t\t\tVector3 normal = new Vector3( -dx, -dy, 1.0f ).Normal;\r\n\r\n\t\t\t\t// Which grid tile does this vertex belong to?\r\n\t\t\t\tint tileX = Math.Min( x / Math.Max( tileW, 1 ), grid - 1 );\r\n\t\t\t\tint tileY = Math.Min( y / Math.Max( tileH, 1 ), grid - 1 );\r\n\t\t\t\tint tileIndex = tileY * grid \u002B tileX;\r\n\t\t\t\tbool isSelectedTile = _selectedTileIndex \u003C 0 || tileIndex == _selectedTileIndex;\r\n\r\n\t\t\t\t// Sample the target color from the LIVE gradient each frame, then ease the\r\n\t\t\t\t// mesh color toward it so the swipe lags behind the widget and looks smooth.\r\n\t\t\t\tColor targetColor;\r\n\t\t\t\tif ( !isSelectedTile )\r\n\t\t\t\t{\r\n\t\t\t\t\t// Wash out everything outside the selected tile\r\n\t\t\t\t\ttargetColor = Color.FromBytes( 235, 235, 235 );\r\n\t\t\t\t}\r\n\t\t\t\telse if ( useSplat )\r\n\t\t\t\t{\r\n\t\t\t\t\tint tileLayers = _tileSplatLayerCounts != null \u0026\u0026 tileIndex \u003C _tileSplatLayerCounts.Length\r\n\t\t\t\t\t\t? Math.Max( _tileSplatLayerCounts[tileIndex], 2 ) : Math.Max( SplatLayerCount, 2 );\r\n\r\n\t\t\t\t\t// Sample the color from the same threshold-aligned color table the splatmap\r\n\t\t\t\t\t// image uses, so the 3D preview and the exported splatmap always agree.\r\n\t\t\t\t\tfloat layerPos = Math.Clamp( _overlaySplatmap[x, y], 0f, tileLayers - 1f );\r\n\t\t\t\t\tint layer0 = (int)MathF.Floor( layerPos );\r\n\t\t\t\t\tint layer1 = Math.Min( layer0 \u002B 1, tileLayers - 1 );\r\n\t\t\t\t\tfloat t = layerPos - layer0;\r\n\r\n\t\t\t\t\tif ( _splatcolors != null \u0026\u0026 _splatcolors.Length \u003E layer1 )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar col0 = _splatcolors[layer0];\r\n\t\t\t\t\t\tvar col1 = _splatcolors[layer1];\r\n\t\t\t\t\t\ttargetColor = new Color(\r\n\t\t\t\t\t\t\tMathX.LerpTo( col0.Red / 255f, col1.Red / 255f, t ),\r\n\t\t\t\t\t\t\tMathX.LerpTo( col0.Green / 255f, col1.Green / 255f, t ),\r\n\t\t\t\t\t\t\tMathX.LerpTo( col0.Blue / 255f, col1.Blue / 255f, t ) );\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tColor c0 = SplatMapGradient.Evaluate( Math.Clamp( layer0 / (float)Math.Max( tileLayers - 1, 1 ), 0f, 1f ) );\r\n\t\t\t\t\t\tColor c1 = SplatMapGradient.Evaluate( Math.Clamp( layer1 / (float)Math.Max( tileLayers - 1, 1 ), 0f, 1f ) );\r\n\t\t\t\t\t\ttargetColor = Color.Lerp( c0, c1, t );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t// The original height-based material color we painted on the mesh\r\n\t\t\t\t\ttargetColor = Color.Lerp( Color.FromBytes( 30, 90, 200 ), Color.FromBytes( 200, 185, 150 ), h );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar target32 = targetColor.ToColor32();\r\n\t\t\t\tvar current = colors[index];\r\n\r\n\t\t\t\tbyte r = (byte)MathX.LerpTo( current.r, target32.r, colorT );\r\n\t\t\t\tbyte g = (byte)MathX.LerpTo( current.g, target32.g, colorT );\r\n\t\t\t\tbyte b = (byte)MathX.LerpTo( current.b, target32.b, colorT );\r\n\t\t\t\tbyte a = (byte)MathX.LerpTo( current.a, target32.a, colorT );\r\n\t\t\t\tcolors[index] = new Color32( r, g, b, a );\r\n\r\n\t\t\t\tfloat delta = MathF.Abs( r - target32.r ) \u002B MathF.Abs( g - target32.g ) \u002B MathF.Abs( b - target32.b );\r\n\t\t\t\tmaxColorDelta[0] = MathF.Max( maxColorDelta[0], delta );\r\n\r\n\t\t\t\tvertices[index] = new Vertex( position, normal, normal, new Vector4( 0, 0, 0, 1 ) );\r\n\t\t\t\tvertices[index].Color = colors[index];\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t_overlayCurrentColors = colors;\r\n\r\n\t\t// Stop the color-only refresh once everything has eased to its target\r\n\t\tif ( _overlayColorAnimating \u0026\u0026 maxColorDelta[0] \u003C 1f )\r\n\t\t{\r\n\t\t\t_overlayColorAnimating = false;\r\n\t\t}\r\n\r\n\t\t// Build indices once - the grid topology never changes\r\n\t\tvar indices = new List\u003Cint\u003E();\r\n\t\tif ( _overlayMesh is null )\r\n\t\t{\r\n\t\t\tfor ( int y = 0; y \u003C res - 1; y\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfor ( int x = 0; x \u003C res - 1; x\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tint a = x \u002B y * res;\r\n\t\t\t\t\tint b = (x \u002B 1) \u002B y * res;\r\n\t\t\t\t\tint c = (x \u002B 1) \u002B (y \u002B 1) * res;\r\n\t\t\t\t\tint d = x \u002B (y \u002B 1) * res;\r\n\r\n\t\t\t\t\tindices.Add( a );\r\n\t\t\t\t\tindices.Add( b );\r\n\t\t\t\t\tindices.Add( c );\r\n\t\t\t\t\tindices.Add( a );\r\n\t\t\t\t\tindices.Add( c );\r\n\t\t\t\t\tindices.Add( d );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( _overlayMesh is null || !_overlayMesh.IsValid() )\r\n\t\t{\r\n\t\t\t_overlayMesh = new Mesh( _splatOverlayRenderer.MaterialOverride );\r\n\t\t\t_overlayMesh.CreateVertexBuffer( vertices.Length, vertices );\r\n\t\t\t_overlayMesh.CreateIndexBuffer( indices.Count, indices );\r\n\t\t\t_overlayMesh.Bounds = BBox.FromPositionAndSize( new Vector3( worldSize * 0.5f, worldSize * 0.5f, worldHeight * 0.5f ), new Vector3( worldSize, worldSize, worldHeight ) );\r\n\r\n\t\t\t_splatOverlayRenderer.Model = Model.Builder.AddMesh( _overlayMesh ).Create();\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Update the existing vertex buffer in place - much faster than rebuilding the model\r\n\t\t\t_overlayMesh.SetVertexBufferData( vertices );\r\n\t\t}\r\n\t}\r\n\r\n\tfloat[,] BuildHeightmap( int width, int height,\r\n\t\tstring[] tileCategories,\r\n\t\tstring[] tileShapes,\r\n\t\tint gridSize,\r\n\t\tlong[] tileSeeds,\r\n\t\tint[] tileNoiseLayerStacks,\r\n\t\tfloat[] tileMinHeights,\r\n\t\tfloat[] tileMaxHeights,\r\n\t\tbool[] tileDomainWarping,\r\n\t\tfloat[] tileDomainWarpingSizes,\r\n\t\tfloat[] tileDomainWarpingStrengths,\r\n\t\tint[] tileSmoothingPasses,\r\n\t\tfloat[] tilePlaneScales,\r\n\t\tbool riverCarving,\r\n\t\tfloat riverFrequency,\r\n\t\tfloat riverWidth,\r\n\t\tfloat riverDepth,\r\n\t\tfloat riverTurbulenceFrequency,\r\n\t\tfloat riverTurbulenceStrength,\r\n\t\tfloat minRiverSpacing,\r\n\t\tbool stagingArea,\r\n\t\tint stagingAreaSize,\r\n\t\tfloat stagingAreaHeight,\r\n\t\tfloat stagingAreaX,\r\n\t\tfloat stagingAreaY )\r\n\t{\r\n\t\tint grid = Math.Max( gridSize, 1 );\r\n\t\tint tileW = width / grid;\r\n\t\tint tileH = height / grid;\r\n\r\n\t\t// Build each tile\u0027s heightmap, then stitch them together averaging overlapping edges.\r\n\t\tfloat[,] heightmap = BuildTileGrid( width, height, tileW, tileH, grid, tileCategories, tileShapes, tileSeeds, tileNoiseLayerStacks, tileMinHeights, tileMaxHeights, tileDomainWarping, tileDomainWarpingSizes, tileDomainWarpingStrengths, tileSmoothingPasses, tilePlaneScales );\r\n\r\n\t\tif ( ErosionSimulation )\r\n\t\t{\r\n\r\n\t\t}\r\n\r\n\t\tif ( heightmap == null )\r\n\t\t{\r\n\t\t\tLog.Error( \u0022Heightmap is not generated. Aborting.\u0022 );\r\n\t\t\treturn null;\r\n\t\t}\r\n\r\n\t\t// Rivers are carved across the whole combined map so they flow continuously through the tiles\r\n\t\tif ( riverCarving )\r\n\t\t{\r\n\t\t\theightmap = AddTurbulenceForRivers(\r\n\t\t\theightmap,\r\n\t\t\tseed: tileSeeds != null \u0026\u0026 tileSeeds.Length \u003E 0 ? tileSeeds[0] : 0,\r\n\t\t\triverFrequency: riverFrequency,\r\n\t\t\triverWidth: riverWidth,\r\n\t\t\triverDepth: riverDepth,\r\n\t\t\tturbulenceFrequency: riverTurbulenceFrequency,\r\n\t\t\tturbulenceStrength: riverTurbulenceStrength,\r\n\t\t\tminRiverSpacing: minRiverSpacing,\r\n\t\t\tslopeSteepness:10f,\r\n\t\t\tterrainNoiseFrequency: 2.0f,\r\n\t\t\tterrainNoiseAmplitude: 0.5f\r\n\t\t);\r\n\t\t}\r\n\r\n\t\tif ( stagingArea )\r\n\t\t{\r\n\t\t\theightmap = AddStagingSquare(\r\n\t\t\theightmap,\r\n\t\t\tstagingAreaSize,\r\n\t\t\tstagingAreaHeight,\r\n\t\t\tstagingAreaX,\r\n\t\t\tstagingAreaY );\r\n\t\t}\r\n\r\n\t\treturn heightmap;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Generates each tile with its own category/shape/height/scale/seed, then stitches them into\r\n\t/// one heightmap. Adjacent tiles share a blend band so their edges are averaged and look continuous.\r\n\t/// \u003C/summary\u003E\r\n\tfloat[,] BuildTileGrid( int width, int height, int tileW, int tileH, int grid, string[] categories, string[] shapes, long[] seeds, int[] noiseLayersArr, float[] minHeights, float[] maxHeights, bool[] warpingArr, float[] warpingSizesArr, float[] warpingStrengthsArr, int[] smoothingArr, float[] planeScales )\r\n\t{\r\n\t\tfloat[,] result = new float[width, height];\r\n\r\n\t\t// Single tile - just generate it directly at the requested size, matching the old behavior exactly.\r\n\t\tif ( grid \u003C= 1 )\r\n\t\t{\r\n\t\t\tstring category = categories != null \u0026\u0026 categories.Length \u003E 0 ? categories[0] : null;\r\n\t\t\tstring shape = shapes != null \u0026\u0026 shapes.Length \u003E 0 ? shapes[0] : null;\r\n\t\t\tlong seed = seeds != null \u0026\u0026 seeds.Length \u003E 0 ? seeds[0] : 0;\r\n\t\t\tint layerCount = noiseLayersArr != null \u0026\u0026 noiseLayersArr.Length \u003E 0 ? noiseLayersArr[0] : 1;\r\n\t\t\tfloat minHeight = minHeights != null \u0026\u0026 minHeights.Length \u003E 0 ? minHeights[0] : 0.2f;\r\n\t\t\tfloat maxHeight = maxHeights != null \u0026\u0026 maxHeights.Length \u003E 0 ? maxHeights[0] : 0.5f;\r\n\t\t\tint smoothingPasses = smoothingArr != null \u0026\u0026 smoothingArr.Length \u003E 0 ? smoothingArr[0] : 0;\r\n\t\t\tfloat planeScale = planeScales != null \u0026\u0026 planeScales.Length \u003E 0 ? planeScales[0] : 0.5f;\r\n\t\t\tbool domainWarping = warpingArr != null \u0026\u0026 warpingArr.Length \u003E 0 ? warpingArr[0] : true;\r\n\t\t\tfloat domainWarpingSize = warpingSizesArr != null \u0026\u0026 warpingSizesArr.Length \u003E 0 ? warpingSizesArr[0] : 0.25f;\r\n\t\t\tfloat domainWarpingStrength = warpingStrengthsArr != null \u0026\u0026 warpingStrengthsArr.Length \u003E 0 ? warpingStrengthsArr[0] : 0.15f;\r\n\r\n\t\t\tif ( string.IsNullOrEmpty( category ) ) category = \u0022Islands\u0022;\r\n\t\t\tif ( string.IsNullOrEmpty( shape ) ) shape = \u0022Default\u0022;\r\n\r\n\t\t\tvar fullclass = Type.GetType( $\u0022Sturnus.TerrainGenerationTool.{category}\u0022 );\r\n\t\t\tif ( fullclass is null || fullclass.GetMethod( shape ) is null ) return null;\r\n\r\n\t\t\treturn GenerateStackedNoise(\r\n\t\t\t\twidth, height,\r\n\t\t\t\tseed,\r\n\t\t\t\tlayerCount,\r\n\t\t\t\t1.0f, 2.0f, 1.0f, 0.5f,\r\n\t\t\t\t( x, y ) =\u003E (float)CallMethod( $\u0022Sturnus.TerrainGenerationTool.{category}\u0022, shape, new object[] {\r\n\t\t\t\tx, y,\r\n\t\t\t\twidth, height,\r\n\t\t\t\tseed,\r\n\t\t\t\tminHeight,\r\n\t\t\t\tdomainWarping,\r\n\t\t\t\tdomainWarpingSize,\r\n\t\t\t\tdomainWarpingStrength\r\n\t\t\t\t} ),\r\n\t\t\t\tmaxHeight,\r\n\t\t\t\tsmoothingPasses,\r\n\t\t\t\tplaneScale\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tfloat[,] weight = new float[width, height];\r\n\r\n\t\t// Overlap width for edge blending - a fraction of the tile size so seams blend smoothly\r\n\t\tint blend = Math.Max( 2, Math.Min( tileW, tileH ) / 8 );\r\n\r\n\t\tfor ( int ty = 0; ty \u003C grid; ty\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int tx = 0; tx \u003C grid; tx\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tint index = ty * grid \u002B tx;\r\n\r\n\t\t\t\tstring category = categories != null \u0026\u0026 index \u003C categories.Length \u0026\u0026 !string.IsNullOrEmpty( categories[index] ) ? categories[index] : \u0022Islands\u0022;\r\n\t\t\t\tstring shape = shapes != null \u0026\u0026 index \u003C shapes.Length \u0026\u0026 !string.IsNullOrEmpty( shapes[index] ) ? shapes[index] : \u0022Default\u0022;\r\n\t\t\t\tlong tileSeed = seeds != null \u0026\u0026 index \u003C seeds.Length ? seeds[index] : 0;\r\n\t\t\t\tint layerCount = noiseLayersArr != null \u0026\u0026 index \u003C noiseLayersArr.Length ? noiseLayersArr[index] : 1;\r\n\t\t\t\tfloat minHeight = minHeights != null \u0026\u0026 index \u003C minHeights.Length ? minHeights[index] : 0.2f;\r\n\t\t\t\tfloat maxHeight = maxHeights != null \u0026\u0026 index \u003C maxHeights.Length ? maxHeights[index] : 0.5f;\r\n\t\t\t\tint smoothingPasses = smoothingArr != null \u0026\u0026 index \u003C smoothingArr.Length ? smoothingArr[index] : 0;\r\n\t\t\t\tfloat planeScale = planeScales != null \u0026\u0026 index \u003C planeScales.Length ? planeScales[index] : 0.5f;\r\n\t\t\t\tbool domainWarping = warpingArr != null \u0026\u0026 index \u003C warpingArr.Length ? warpingArr[index] : true;\r\n\t\t\t\tfloat domainWarpingSize = warpingSizesArr != null \u0026\u0026 index \u003C warpingSizesArr.Length ? warpingSizesArr[index] : 0.25f;\r\n\t\t\t\tfloat domainWarpingStrength = warpingStrengthsArr != null \u0026\u0026 index \u003C warpingStrengthsArr.Length ? warpingStrengthsArr[index] : 0.15f;\r\n\r\n\t\t\t\tvar fullclass = Type.GetType( $\u0022Sturnus.TerrainGenerationTool.{category}\u0022 );\r\n\t\t\t\tif ( fullclass is null || fullclass.GetMethod( shape ) is null )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tfloat[,] tile = GenerateStackedNoise(\r\n\t\t\t\t\ttileW \u002B blend * 2,\r\n\t\t\t\t\ttileH \u002B blend * 2,\r\n\t\t\t\t\ttileSeed,\r\n\t\t\t\t\tlayerCount,\r\n\t\t\t\t\t1.0f,\r\n\t\t\t\t\t2.0f,\r\n\t\t\t\t\t1.0f,\r\n\t\t\t\t\t0.5f,\r\n\t\t\t\t\t( x, y ) =\u003E (float)CallMethod( $\u0022Sturnus.TerrainGenerationTool.{category}\u0022, shape, new object[] {\r\n\t\t\t\t\tx, y,\r\n\t\t\t\t\ttileW \u002B blend * 2,\r\n\t\t\t\t\ttileH \u002B blend * 2,\r\n\t\t\t\t\ttileSeed,\r\n\t\t\t\t\tminHeight,\r\n\t\t\t\t\tdomainWarping,\r\n\t\t\t\t\tdomainWarpingSize,\r\n\t\t\t\t\tdomainWarpingStrength\r\n\t\t\t\t\t} ),\r\n\t\t\t\t\tmaxHeight,\r\n\t\t\t\t\tsmoothingPasses,\r\n\t\t\t\t\tplaneScale\r\n\t\t\t\t);\r\n\r\n\t\t\t\tif ( tile is null ) continue;\r\n\r\n\t\t\t\t// Place the tile into the result with a weighted blend on the edges.\r\n\t\t\t\t// The tile is generated slightly larger than its slot (tileW \u002B blend*2) so the\r\n\t\t\t\t// overlap regions between neighbors are averaged.\r\n\t\t\t\tint slotX = tx * tileW;\r\n\t\t\t\tint slotY = ty * tileH;\r\n\r\n\t\t\t\tfor ( int y = 0; y \u003C tileH \u002B blend * 2; y\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tint outY = slotY - blend \u002B y;\r\n\t\t\t\t\tif ( outY \u003C 0 || outY \u003E= height ) continue;\r\n\r\n\t\t\t\t\tfor ( int x = 0; x \u003C tileW \u002B blend * 2; x\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tint outX = slotX - blend \u002B x;\r\n\t\t\t\t\t\tif ( outX \u003C 0 || outX \u003E= width ) continue;\r\n\r\n\t\t\t\t\t\t// Edge weight: 1 in the core, fading to 0 across the blend band at each edge\r\n\t\t\t\t\t\tfloat wx = EdgeBlendWeight( x, tileW \u002B blend * 2, blend );\r\n\t\t\t\t\t\tfloat wy = EdgeBlendWeight( y, tileH \u002B blend * 2, blend );\r\n\t\t\t\t\t\tfloat w = wx * wy;\r\n\r\n\t\t\t\t\t\tresult[outX, outY] \u002B= tile[x, y] * w;\r\n\t\t\t\t\t\tweight[outX, outY] \u002B= w;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Normalize by the accumulated weights\r\n\t\tfor ( int y = 0; y \u003C height; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( weight[x, y] \u003E 0.0001f )\r\n\t\t\t\t\tresult[x, y] /= weight[x, y];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Generates each grid cell as its own full-resolution heightmap using that cell\u0027s own\r\n\t/// category/shape/height/scale/seed/smoothing/noise/warp settings. No stitching - each cell\r\n\t/// is a complete map of width x height. Rivers and staging are applied per cell.\r\n\t/// \u003C/summary\u003E\r\n\tList\u003Cfloat[,]\u003E BuildPerCellHeightmaps( int width, int height,\r\n\t\tstring[] categories, string[] shapes, long[] seeds, int[] noiseLayersArr,\r\n\t\tfloat[] minHeights, float[] maxHeights, bool[] warpingArr, float[] warpingSizesArr,\r\n\t\tfloat[] warpingStrengthsArr, int[] smoothingArr, float[] planeScales,\r\n\t\tbool riverCarving, float riverFrequency, float riverWidth, float riverDepth,\r\n\t\tfloat riverTurbulenceFrequency, float riverTurbulenceStrength, float minRiverSpacing,\r\n\t\tbool stagingArea, int stagingAreaSize, float stagingAreaHeight, float stagingAreaX, float stagingAreaY )\r\n\t{\r\n\t\tvar cells = new List\u003Cfloat[,]\u003E();\r\n\r\n\t\tint grid = Math.Max( TerrainGridSize, 1 );\r\n\t\tint count = grid * grid;\r\n\r\n\t\tfor ( int index = 0; index \u003C count; index\u002B\u002B )\r\n\t\t{\r\n\t\t\tstring category = categories != null \u0026\u0026 index \u003C categories.Length \u0026\u0026 !string.IsNullOrEmpty( categories[index] ) ? categories[index] : \u0022Islands\u0022;\r\n\t\t\tstring shape = shapes != null \u0026\u0026 index \u003C shapes.Length \u0026\u0026 !string.IsNullOrEmpty( shapes[index] ) ? shapes[index] : \u0022Default\u0022;\r\n\t\t\tlong cellSeed = seeds != null \u0026\u0026 index \u003C seeds.Length ? seeds[index] : 0;\r\n\t\t\tint layerCount = noiseLayersArr != null \u0026\u0026 index \u003C noiseLayersArr.Length ? noiseLayersArr[index] : 1;\r\n\t\t\tfloat minHeight = minHeights != null \u0026\u0026 index \u003C minHeights.Length ? minHeights[index] : 0.2f;\r\n\t\t\tfloat maxHeight = maxHeights != null \u0026\u0026 index \u003C maxHeights.Length ? maxHeights[index] : 0.5f;\r\n\t\t\tbool domainWarping = warpingArr != null \u0026\u0026 index \u003C warpingArr.Length ? warpingArr[index] : true;\r\n\t\t\tfloat domainWarpingSize = warpingSizesArr != null \u0026\u0026 index \u003C warpingSizesArr.Length ? warpingSizesArr[index] : 0.25f;\r\n\t\t\tfloat domainWarpingStrength = warpingStrengthsArr != null \u0026\u0026 index \u003C warpingStrengthsArr.Length ? warpingStrengthsArr[index] : 0.15f;\r\n\t\t\tint smoothingPasses = smoothingArr != null \u0026\u0026 index \u003C smoothingArr.Length ? smoothingArr[index] : 0;\r\n\t\t\tfloat planeScale = planeScales != null \u0026\u0026 index \u003C planeScales.Length ? planeScales[index] : 0.5f;\r\n\r\n\t\t\tvar fullclass = Type.GetType( $\u0022Sturnus.TerrainGenerationTool.{category}\u0022 );\r\n\t\t\tif ( fullclass is null || fullclass.GetMethod( shape ) is null )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tfloat[,] map = GenerateStackedNoise(\r\n\t\t\t\twidth, height,\r\n\t\t\t\tcellSeed,\r\n\t\t\t\tlayerCount,\r\n\t\t\t\t1.0f, 2.0f, 1.0f, 0.5f,\r\n\t\t\t\t( x, y ) =\u003E (float)CallMethod( $\u0022Sturnus.TerrainGenerationTool.{category}\u0022, shape, new object[] {\r\n\t\t\t\t\tx, y,\r\n\t\t\t\t\twidth, height,\r\n\t\t\t\t\tcellSeed,\r\n\t\t\t\t\tminHeight,\r\n\t\t\t\t\tdomainWarping,\r\n\t\t\t\t\tdomainWarpingSize,\r\n\t\t\t\t\tdomainWarpingStrength\r\n\t\t\t\t} ),\r\n\t\t\t\tmaxHeight,\r\n\t\t\t\tsmoothingPasses,\r\n\t\t\t\tplaneScale\r\n\t\t\t);\r\n\r\n\t\t\tif ( map is null ) continue;\r\n\r\n\t\t\tif ( riverCarving )\r\n\t\t\t{\r\n\t\t\t\tmap = AddTurbulenceForRivers( map, cellSeed, riverFrequency, riverWidth, riverDepth,\r\n\t\t\t\t\triverTurbulenceFrequency, riverTurbulenceStrength, minRiverSpacing, 10f, 2.0f, 0.5f );\r\n\t\t\t}\r\n\r\n\t\t\tif ( stagingArea )\r\n\t\t\t{\r\n\t\t\t\tmap = AddStagingSquare( map, stagingAreaSize, stagingAreaHeight, stagingAreaX, stagingAreaY );\r\n\t\t\t}\r\n\r\n\t\t\tcells.Add( map );\r\n\t\t}\r\n\r\n\t\treturn cells;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Generates the splatmap cache for per-cell mode - one full-res splatmap per cell using that\r\n\t/// cell\u0027s own layer count / dispersion / blend strength.\r\n\t/// \u003C/summary\u003E\r\n\tList\u003Cfloat[,]\u003E BuildPerCellSplatmaps( List\u003Cfloat[,]\u003E cellHeightmaps, int[] layerCounts, SplatDispersionMode[] dispersions, float[] blendStrengths )\r\n\t{\r\n\t\tvar splats = new List\u003Cfloat[,]\u003E();\r\n\t\tif ( cellHeightmaps is null ) return splats;\r\n\r\n\t\tfor ( int i = 0; i \u003C cellHeightmaps.Count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tint layers = layerCounts != null \u0026\u0026 i \u003C layerCounts.Length ? Math.Max( layerCounts[i], 2 ) : 2;\r\n\t\t\tvar dispersion = dispersions != null \u0026\u0026 i \u003C dispersions.Length ? dispersions[i] : SplatDispersionMode.Evenly;\r\n\t\t\tfloat blend = blendStrengths != null \u0026\u0026 i \u003C blendStrengths.Length ? blendStrengths[i] : 0.35f;\r\n\r\n\t\t\tsplats.Add( GenerateSplatmap( cellHeightmaps[i], MakeEvenThresholds( layers ), TerrainMaxHeight, layers, dispersion, blend ) );\r\n\t\t}\r\n\r\n\t\treturn splats;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Generates a splatmap where each grid tile uses its own layer count, dispersion mode and\r\n\t/// blend strength. Each tile\u0027s splatmap is computed over its padded region (with the blend\r\n\t/// overlap) and stitched together using the same edge weights as the heightmap.\r\n\t/// \u003C/summary\u003E\r\n\tfloat[,] BuildTileGridSplatmap( float[,] heightmap, int gridSize, int[] layerCounts, SplatDispersionMode[] dispersions, float[] blendStrengths )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\r\n\t\tint grid = Math.Max( gridSize, 1 );\r\n\t\tint tileW = width / grid;\r\n\t\tint tileH = height / grid;\r\n\r\n\t\tint maxLayers = 2;\r\n\t\tif ( layerCounts != null )\r\n\t\t{\r\n\t\t\tforeach ( var lc in layerCounts )\r\n\t\t\t\tmaxLayers = Math.Max( maxLayers, lc );\r\n\t\t}\r\n\r\n\t\t// Single tile - same as before, just uses the tile\u0027s settings.\r\n\t\tif ( grid \u003C= 1 )\r\n\t\t{\r\n\t\t\tint layers = layerCounts != null \u0026\u0026 layerCounts.Length \u003E 0 ? Math.Max( layerCounts[0], 2 ) : maxLayers;\r\n\t\t\tvar dispersion = dispersions != null \u0026\u0026 dispersions.Length \u003E 0 ? dispersions[0] : SplatDispersionMode.Evenly;\r\n\t\t\tfloat blendStrength = blendStrengths != null \u0026\u0026 blendStrengths.Length \u003E 0 ? blendStrengths[0] : 0.35f;\r\n\r\n\t\t\treturn GenerateSplatmap( heightmap, MakeEvenThresholds( layers ), TerrainMaxHeight, layers, dispersion, blendStrength );\r\n\t\t}\r\n\r\n\t\tfloat[,] result = new float[width, height];\r\n\t\tfloat[,] weight = new float[width, height];\r\n\r\n\t\tint blend = Math.Max( 2, Math.Min( tileW, tileH ) / 8 );\r\n\r\n\t\tfor ( int ty = 0; ty \u003C grid; ty\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int tx = 0; tx \u003C grid; tx\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tint index = ty * grid \u002B tx;\r\n\r\n\t\t\t\tint layers = layerCounts != null \u0026\u0026 index \u003C layerCounts.Length ? Math.Max( layerCounts[index], 2 ) : maxLayers;\r\n\t\t\t\tvar dispersion = dispersions != null \u0026\u0026 index \u003C dispersions.Length ? dispersions[index] : SplatDispersionMode.Evenly;\r\n\t\t\t\tfloat blendStrength = blendStrengths != null \u0026\u0026 index \u003C blendStrengths.Length ? blendStrengths[index] : 0.35f;\r\n\r\n\t\t\t\t// Extract the tile\u0027s heightmap region (with blend padding) so its splatmap\r\n\t\t\t\t// normalizes against the tile\u0027s own height range.\r\n\t\t\t\tint tw = tileW \u002B blend * 2;\r\n\t\t\t\tint th = tileH \u002B blend * 2;\r\n\t\t\t\tfloat[,] tileHeight = new float[tw, th];\r\n\r\n\t\t\t\tint slotX = tx * tileW;\r\n\t\t\t\tint slotY = ty * tileH;\r\n\r\n\t\t\t\tfor ( int y = 0; y \u003C th; y\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tint srcY = slotY - blend \u002B y;\r\n\t\t\t\t\tif ( srcY \u003C 0 || srcY \u003E= height ) continue;\r\n\r\n\t\t\t\t\tfor ( int x = 0; x \u003C tw; x\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tint srcX = slotX - blend \u002B x;\r\n\t\t\t\t\t\tif ( srcX \u003C 0 || srcX \u003E= width ) continue;\r\n\r\n\t\t\t\t\t\ttileHeight[x, y] = heightmap[srcX, srcY];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tfloat[,] tileSplat = GenerateSplatmap( tileHeight, MakeEvenThresholds( layers ), TerrainMaxHeight, layers, dispersion, blendStrength );\r\n\r\n\t\t\t\t// Stitch with edge weights - same as the heightmap tiles\r\n\t\t\t\tfor ( int y = 0; y \u003C th; y\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tint outY = slotY - blend \u002B y;\r\n\t\t\t\t\tif ( outY \u003C 0 || outY \u003E= height ) continue;\r\n\r\n\t\t\t\t\tfor ( int x = 0; x \u003C tw; x\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tint outX = slotX - blend \u002B x;\r\n\t\t\t\t\t\tif ( outX \u003C 0 || outX \u003E= width ) continue;\r\n\r\n\t\t\t\t\t\tfloat wx = EdgeBlendWeight( x, tw, blend );\r\n\t\t\t\t\t\tfloat wy = EdgeBlendWeight( y, th, blend );\r\n\t\t\t\t\t\tfloat w = wx * wy;\r\n\r\n\t\t\t\t\t\tresult[outX, outY] \u002B= tileSplat[x, y] * w;\r\n\t\t\t\t\t\tweight[outX, outY] \u002B= w;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Normalize by the accumulated weights\r\n\t\tfor ( int y = 0; y \u003C height; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( weight[x, y] \u003E 0.0001f )\r\n\t\t\t\t\tresult[x, y] /= weight[x, y];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn result;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Builds evenly spaced color stop positions for the given layer count.\r\n\t/// \u003C/summary\u003E\r\n\tfloat[] MakeEvenThresholds( int layers )\r\n\t{\r\n\t\tvar t = new float[layers];\r\n\t\tfor ( int i = 0; i \u003C layers; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tt[i] = layers \u003C= 1 ? 0f : (float)i / (layers - 1);\r\n\t\t}\r\n\t\treturn t;\r\n\t}\r\n\r\n\tfloat EdgeBlendWeight( int coord, int size, int blend )\r\n\t{\r\n\t\tif ( blend \u003C= 0 ) return 1f;\r\n\t\tif ( coord \u003C blend ) return (float)coord / blend;\r\n\t\tif ( coord \u003E size - blend ) return (float)(size - coord) / blend;\r\n\t\treturn 1f;\r\n\t}\r\n\r\n\tpublic void RebuildShapes()\r\n\t{\r\n\t\tShapeArray.DestroyChildren();\r\n\t\tTerrainShapeArray.Clear();\r\n\r\n\t\tif ( CategoryArray?.Selected is null )\r\n\t\t{\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tstring className = $\u0022Sturnus.TerrainGenerationTool.{TerrainCategoryEnum.GetName( TerrainCategoryEnum.GetValue( CategoryArray.Selected ) )}\u0022; // Fully qualified name\r\n\t\tstring[] methods = GetMethodsFromClass( className );\r\n\r\n\t\t// Print the methods\r\n\t\tforeach ( string method in methods )\r\n\t\t{\r\n\t\t\tTerrainShapeArray.Add( method );\r\n\t\t\t//Log.Info( method );\r\n\t\t}\r\n\r\n\t\tforeach ( var shape in TerrainShapeArray )\r\n\t\t{\r\n\t\t\tShapeArray.AddOption( shape );\r\n\t\t}\r\n\r\n\t\tShapeArray.SelectedIndex = 0;\r\n\t\tShapeArray.Selected = ShapeArray.Children.FirstOrDefault().Name;\r\n\t\tforeach(var test in ShapeArray.Children )\r\n\t\t{\r\n\t\t\t//Log.Info( test.Name );\r\n\t\t}\r\n\t\t\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Rebuilds the tile grid section: an \u0022All\u0022 box plus one box per grid cell. Each box shows\r\n\t/// which terrain category that tile currently uses and is clickable to select which tile the\r\n\t/// Terrain Type page\u0027s category/shape selectors edit.\r\n\t/// \u003C/summary\u003E\r\n\tvoid RebuildTileGridUI()\r\n\t{\r\n\t\tif ( _tilesContainer is null || !_tilesContainer.IsValid() ) return;\r\n\r\n\t\t_tilesContainer.DestroyChildren();\r\n\t\t_tileBoxes.Clear();\r\n\r\n\t\tint grid = Math.Max( TerrainGridSize, 1 );\r\n\t\tint count = grid * grid;\r\n\r\n\t\t// Preserve existing selections, extend/trim to the new size\r\n\t\tvar oldCategories = _tileCategories;\r\n\t\tvar oldShapes = _tileShapes;\r\n\t\tvar oldMinHeights = _tileMinHeights;\r\n\t\tvar oldMaxHeights = _tileMaxHeights;\r\n\t\tvar oldPlaneScales = _tilePlaneScales;\r\n\t\tvar oldSeeds = _tileSeeds;\r\n\t\tvar oldSmoothing = _tileSmoothingPasses;\r\n\t\tvar oldNoiseLayers = _tileNoiseLayerStacks;\r\n\t\tvar oldWarping = _tileDomainWarping;\r\n\t\tvar oldWarpingSizes = _tileDomainWarpingSizes;\r\n\t\tvar oldWarpingStrengths = _tileDomainWarpingStrengths;\r\n\t\tvar oldSplatLayerCounts = _tileSplatLayerCounts;\r\n\t\tvar oldSplatMapCounts = _tileSplatMapCounts;\r\n\t\tvar oldSplatDispersions = _tileSplatDispersions;\r\n\t\tvar oldSplatBlendStrengths = _tileSplatBlendStrengths;\r\n\r\n\t\t_tileCategories = new string[count];\r\n\t\t_tileShapes = new string[count];\r\n\t\t_tileMinHeights = new float[count];\r\n\t\t_tileMaxHeights = new float[count];\r\n\t\t_tilePlaneScales = new float[count];\r\n\t\t_tileSeeds = new long[count];\r\n\t\t_tileSmoothingPasses = new int[count];\r\n\t\t_tileNoiseLayerStacks = new int[count];\r\n\t\t_tileDomainWarping = new bool[count];\r\n\t\t_tileDomainWarpingSizes = new float[count];\r\n\t\t_tileDomainWarpingStrengths = new float[count];\r\n\t\t_tileSplatLayerCounts = new int[count];\r\n\t\t_tileSplatMapCounts = new int[count];\r\n\t\t_tileSplatDispersions = new SplatDispersionMode[count];\r\n\t\t_tileSplatBlendStrengths = new float[count];\r\n\r\n\t\tstring defaultCategory = TerrainCategoryArray.Count \u003E 0 ? TerrainCategoryArray.First() : CategoryArray?.Selected;\r\n\t\tstring defaultShape = FirstShapeForCategory( defaultCategory );\r\n\t\tif ( string.IsNullOrEmpty( defaultShape ) )\r\n\t\t\tdefaultShape = ShapeArray?.Selected;\r\n\r\n\t\tfor ( int i = 0; i \u003C count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\t_tileCategories[i] = oldCategories != null \u0026\u0026 i \u003C oldCategories.Length \u0026\u0026 !string.IsNullOrEmpty( oldCategories[i] )\r\n\t\t\t\t? oldCategories[i] : defaultCategory;\r\n\t\t\t_tileShapes[i] = oldShapes != null \u0026\u0026 i \u003C oldShapes.Length \u0026\u0026 !string.IsNullOrEmpty( oldShapes[i] )\r\n\t\t\t\t? oldShapes[i] : defaultShape;\r\n\t\t\t_tileMinHeights[i] = oldMinHeights != null \u0026\u0026 i \u003C oldMinHeights.Length ? oldMinHeights[i] : TerrainMinHeight;\r\n\t\t\t_tileMaxHeights[i] = oldMaxHeights != null \u0026\u0026 i \u003C oldMaxHeights.Length ? oldMaxHeights[i] : TerrainMaxHeight;\r\n\t\t\t_tilePlaneScales[i] = oldPlaneScales != null \u0026\u0026 i \u003C oldPlaneScales.Length ? oldPlaneScales[i] : TerrainPlaneScale;\r\n\t\t\t_tileSeeds[i] = oldSeeds != null \u0026\u0026 i \u003C oldSeeds.Length ? oldSeeds[i] : TerrainSeed;\r\n\t\t\t_tileSmoothingPasses[i] = oldSmoothing != null \u0026\u0026 i \u003C oldSmoothing.Length ? oldSmoothing[i] : SmoothingPasses;\r\n\t\t\t_tileNoiseLayerStacks[i] = oldNoiseLayers != null \u0026\u0026 i \u003C oldNoiseLayers.Length ? oldNoiseLayers[i] : NoiseLayerStacks;\r\n\t\t\t_tileDomainWarping[i] = oldWarping != null \u0026\u0026 i \u003C oldWarping.Length ? oldWarping[i] : DomainWarping;\r\n\t\t\t_tileDomainWarpingSizes[i] = oldWarpingSizes != null \u0026\u0026 i \u003C oldWarpingSizes.Length ? oldWarpingSizes[i] : DomainWarpingSize;\r\n\t\t\t_tileDomainWarpingStrengths[i] = oldWarpingStrengths != null \u0026\u0026 i \u003C oldWarpingStrengths.Length ? oldWarpingStrengths[i] : DomainWarpingStrength;\r\n\t\t\t_tileSplatLayerCounts[i] = oldSplatLayerCounts != null \u0026\u0026 i \u003C oldSplatLayerCounts.Length ? oldSplatLayerCounts[i] : SplatLayerCount;\r\n\t\t\t_tileSplatMapCounts[i] = oldSplatMapCounts != null \u0026\u0026 i \u003C oldSplatMapCounts.Length ? oldSplatMapCounts[i] : SplatMapCount;\r\n\t\t\t_tileSplatDispersions[i] = oldSplatDispersions != null \u0026\u0026 i \u003C oldSplatDispersions.Length ? oldSplatDispersions[i] : SplatDispersion;\r\n\t\t\t_tileSplatBlendStrengths[i] = oldSplatBlendStrengths != null \u0026\u0026 i \u003C oldSplatBlendStrengths.Length ? oldSplatBlendStrengths[i] : SplatBlendStrength;\r\n\t\t}\r\n\r\n\t\t_selectedTileIndex = Math.Clamp( _selectedTileIndex, 0, count - 1 );\r\n\r\n\t\t// \u0022All tiles\u0022 box selects every tile at once\r\n\t\tvar allBox = new TileGridBox( null, -1, \u0022All\u0022, \u0022Select every tile\u0022 );\r\n\t\tallBox.IsSelected = _selectedTileIndex \u003C 0;\r\n\t\tallBox.OnClicked = () =\u003E SelectTile( -1 );\r\n\t\t_tileBoxes.Add( allBox );\r\n\t\t_tilesContainer.Layout.Add( allBox );\r\n\r\n\t\t// One box per grid cell\r\n\t\tfor ( int ty = 0; ty \u003C grid; ty\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar row = _tilesContainer.Layout.AddRow();\r\n\t\t\trow.Spacing = 4;\r\n\r\n\t\t\tfor ( int tx = 0; tx \u003C grid; tx\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tint index = ty * grid \u002B tx;\r\n\r\n\t\t\t\tvar box = new TileGridBox( null, index, $\u0022{_tileCategories[index]}:{_tileShapes[index]}\u0022, $\u0022Tile {tx},{ty}\u0022 );\r\n\t\t\t\tbox.IsSelected = index == _selectedTileIndex;\r\n\t\t\t\tbox.OnClicked = () =\u003E SelectTile( index );\r\n\t\t\t\t_tileBoxes.Add( box );\r\n\t\t\t\trow.Add( box, 1 );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Sync the category/shape selectors to whichever tile is selected\r\n\t\tSyncSelectorsToSelectedTile();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Marks the given tile as the one being edited. -1 selects all tiles.\r\n\t/// \u003C/summary\u003E\r\n\tvoid SelectTile( int index )\r\n\t{\r\n\t\tif ( _selectedTileIndex == index ) return;\r\n\r\n\t\t_selectedTileIndex = index;\r\n\t\tUpdateTileBoxSelection();\r\n\t\tSyncSelectorsToSelectedTile();\r\n\r\n\t\t// Ease the overlay mesh colors so only the selected tile stays colored\r\n\t\tif ( _overlayMesh != null \u0026\u0026 _overlayMesh.IsValid() )\r\n\t\t{\r\n\t\t\t_overlayColorAnimating = true;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid UpdateTileBoxSelection()\r\n\t{\r\n\t\tforeach ( var box in _tileBoxes )\r\n\t\t{\r\n\t\t\tif ( box.Index == _selectedTileIndex )\r\n\t\t\t{\r\n\t\t\t\tbox.IsSelected = true;\r\n\t\t\t\tbox.Update();\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tbox.IsSelected = false;\r\n\t\t\t\tbox.Update();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvoid UpdateTileBoxText()\r\n\t{\r\n\t\tforeach ( var box in _tileBoxes )\r\n\t\t{\r\n\t\t\tif ( box.Index \u003C 0 ) continue;\r\n\t\t\tif ( box.Index \u003C _tileCategories.Length \u0026\u0026 box.Index \u003C _tileShapes.Length )\r\n\t\t\t\tbox.Text = $\u0022{_tileCategories[box.Index]}:{_tileShapes[box.Index]}\u0022;\r\n\t\t\tbox.Update();\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Pushes the selected tile\u0027s category/shape/height/scale/seed into the Terrain Type and\r\n\t/// Height/Scale page controls.\r\n\t/// \u003C/summary\u003E\r\n\tvoid SyncSelectorsToSelectedTile()\r\n\t{\r\n\t\tif ( CategoryArray is null || ShapeArray is null ) return;\r\n\r\n\t\t_syncingTileSelectors = true;\r\n\r\n\t\tint refIndex = Math.Max( _selectedTileIndex, 0 );\r\n\t\tif ( refIndex \u003E= _tileCategories.Length ) refIndex = 0;\r\n\r\n\t\tstring category = _tileCategories[refIndex];\r\n\t\tif ( CategoryArray.HasOption( category ) )\r\n\t\t{\r\n\t\t\tCategoryArray.Selected = category;\r\n\t\t}\r\n\t\telse if ( CategoryArray.Children.Count() \u003E 0 )\r\n\t\t{\r\n\t\t\tCategoryArray.SelectedIndex = 0;\r\n\t\t}\r\n\r\n\t\tstring shape = _tileShapes[refIndex];\r\n\t\tif ( ShapeArray.HasOption( shape ) )\r\n\t\t{\r\n\t\t\tShapeArray.Selected = shape;\r\n\t\t}\r\n\t\telse if ( ShapeArray.Children.Count() \u003E 0 )\r\n\t\t{\r\n\t\t\tShapeArray.SelectedIndex = 0;\r\n\t\t}\r\n\r\n\t\t// Push the selected tile\u0027s height/scale/seed into the global props so the\r\n\t\t// Height/Scale page sliders show the selected tile\u0027s values.\r\n\t\t_serialized.GetProperty( nameof( TerrainMinHeight ) )?.SetValue( _tileMinHeights[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( TerrainMaxHeight ) )?.SetValue( _tileMaxHeights[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( TerrainPlaneScale ) )?.SetValue( _tilePlaneScales[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( TerrainSeed ) )?.SetValue( _tileSeeds[refIndex] );\r\n\r\n\t\t// Same for the Smooth/Noise page controls\r\n\t\t_serialized.GetProperty( nameof( SmoothingPasses ) )?.SetValue( _tileSmoothingPasses[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( NoiseLayerStacks ) )?.SetValue( _tileNoiseLayerStacks[refIndex] );\r\n\r\n\t\t// Domain warping page controls\r\n\t\t_serialized.GetProperty( nameof( DomainWarping ) )?.SetValue( _tileDomainWarping[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( DomainWarpingSize ) )?.SetValue( _tileDomainWarpingSizes[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( DomainWarpingStrength ) )?.SetValue( _tileDomainWarpingStrengths[refIndex] );\r\n\r\n\t\t// Splat page controls\r\n\t\t_serialized.GetProperty( nameof( SplatLayerCount ) )?.SetValue( _tileSplatLayerCounts[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( SplatMapCount ) )?.SetValue( _tileSplatMapCounts[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( SplatDispersion ) )?.SetValue( _tileSplatDispersions[refIndex] );\r\n\t\t_serialized.GetProperty( nameof( SplatBlendStrength ) )?.SetValue( _tileSplatBlendStrengths[refIndex] );\r\n\r\n\t\t_syncingTileSelectors = false;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Called when the Terrain Type page\u0027s category changes. Writes the new category to the\r\n\t/// selected tile (or every tile if All is selected).\r\n\t/// \u003C/summary\u003E\r\n\tvoid ApplySelectedCategory()\r\n\t{\r\n\t\tif ( _syncingTileSelectors ) return;\r\n\r\n\t\tstring category = CategoryArray.Selected;\r\n\t\tif ( string.IsNullOrEmpty( category ) ) return;\r\n\r\n\t\tif ( _selectedTileIndex \u003C 0 )\r\n\t\t{\r\n\t\t\tfor ( int i = 0; i \u003C _tileCategories.Length; i\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\t_tileCategories[i] = category;\r\n\t\t\t\t_tileShapes[i] = FirstShapeForCategory( category );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ( _selectedTileIndex \u003C _tileCategories.Length )\r\n\t\t{\r\n\t\t\t_tileCategories[_selectedTileIndex] = category;\r\n\t\t\t_tileShapes[_selectedTileIndex] = FirstShapeForCategory( category );\r\n\t\t}\r\n\r\n\t\t// Keep the shape selector in sync with the new category\u0027s first shape\r\n\t\tif ( ShapeArray.HasOption( _tileShapes[Math.Max( _selectedTileIndex, 0 )] ) )\r\n\t\t\tShapeArray.Selected = _tileShapes[Math.Max( _selectedTileIndex, 0 )];\r\n\r\n\t\tUpdateTileBoxText();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Called when the Terrain Type page\u0027s shape changes. Writes the new shape to the selected\r\n\t/// tile (or every tile if All is selected).\r\n\t/// \u003C/summary\u003E\r\n\tvoid ApplySelectedShape()\r\n\t{\r\n\t\tif ( _syncingTileSelectors ) return;\r\n\r\n\t\tstring shape = ShapeArray.Selected;\r\n\t\tif ( string.IsNullOrEmpty( shape ) ) return;\r\n\r\n\t\tif ( _selectedTileIndex \u003C 0 )\r\n\t\t{\r\n\t\t\tfor ( int i = 0; i \u003C _tileShapes.Length; i\u002B\u002B )\r\n\t\t\t\t_tileShapes[i] = shape;\r\n\t\t}\r\n\t\telse if ( _selectedTileIndex \u003C _tileShapes.Length )\r\n\t\t{\r\n\t\t\t_tileShapes[_selectedTileIndex] = shape;\r\n\t\t}\r\n\r\n\t\tUpdateTileBoxText();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Writes the given global height/scale/seed values into the selected tile (or every tile\r\n\t/// if All is selected). Nullable params mean \u0022leave unchanged\u0022.\r\n\t/// \u003C/summary\u003E\r\n\tvoid WriteSelectedValues( float? minHeight = null, float? maxHeight = null, float? planeScale = null, long? seed = null, int? smoothing = null, int? noiseLayers = null,\r\n\t\tbool? warp = null, float? warpSize = null, float? warpStrength = null,\r\n\t\tint? splatLayers = null, int? splatMaps = null, SplatDispersionMode? splatDispersion = null, float? splatBlend = null )\r\n\t{\r\n\t\tif ( _selectedTileIndex \u003C 0 )\r\n\t\t{\r\n\t\t\tfor ( int i = 0; i \u003C _tileMinHeights.Length; i\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( minHeight.HasValue ) _tileMinHeights[i] = minHeight.Value;\r\n\t\t\t\tif ( maxHeight.HasValue ) _tileMaxHeights[i] = maxHeight.Value;\r\n\t\t\t\tif ( planeScale.HasValue ) _tilePlaneScales[i] = planeScale.Value;\r\n\t\t\t\tif ( seed.HasValue ) _tileSeeds[i] = seed.Value;\r\n\t\t\t\tif ( smoothing.HasValue ) _tileSmoothingPasses[i] = smoothing.Value;\r\n\t\t\t\tif ( noiseLayers.HasValue ) _tileNoiseLayerStacks[i] = noiseLayers.Value;\r\n\t\t\t\tif ( warp.HasValue ) _tileDomainWarping[i] = warp.Value;\r\n\t\t\t\tif ( warpSize.HasValue ) _tileDomainWarpingSizes[i] = warpSize.Value;\r\n\t\t\t\tif ( warpStrength.HasValue ) _tileDomainWarpingStrengths[i] = warpStrength.Value;\r\n\t\t\t\tif ( splatLayers.HasValue ) _tileSplatLayerCounts[i] = splatLayers.Value;\r\n\t\t\t\tif ( splatMaps.HasValue ) _tileSplatMapCounts[i] = splatMaps.Value;\r\n\t\t\t\tif ( splatDispersion.HasValue ) _tileSplatDispersions[i] = splatDispersion.Value;\r\n\t\t\t\tif ( splatBlend.HasValue ) _tileSplatBlendStrengths[i] = splatBlend.Value;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ( _selectedTileIndex \u003C _tileMinHeights.Length )\r\n\t\t{\r\n\t\t\tif ( minHeight.HasValue ) _tileMinHeights[_selectedTileIndex] = minHeight.Value;\r\n\t\t\tif ( maxHeight.HasValue ) _tileMaxHeights[_selectedTileIndex] = maxHeight.Value;\r\n\t\t\tif ( planeScale.HasValue ) _tilePlaneScales[_selectedTileIndex] = planeScale.Value;\r\n\t\t\tif ( seed.HasValue ) _tileSeeds[_selectedTileIndex] = seed.Value;\r\n\t\t\tif ( smoothing.HasValue ) _tileSmoothingPasses[_selectedTileIndex] = smoothing.Value;\r\n\t\t\tif ( noiseLayers.HasValue ) _tileNoiseLayerStacks[_selectedTileIndex] = noiseLayers.Value;\r\n\t\t\tif ( warp.HasValue ) _tileDomainWarping[_selectedTileIndex] = warp.Value;\r\n\t\t\tif ( warpSize.HasValue ) _tileDomainWarpingSizes[_selectedTileIndex] = warpSize.Value;\r\n\t\t\tif ( warpStrength.HasValue ) _tileDomainWarpingStrengths[_selectedTileIndex] = warpStrength.Value;\r\n\t\t\tif ( splatLayers.HasValue ) _tileSplatLayerCounts[_selectedTileIndex] = splatLayers.Value;\r\n\t\t\tif ( splatMaps.HasValue ) _tileSplatMapCounts[_selectedTileIndex] = splatMaps.Value;\r\n\t\t\tif ( splatDispersion.HasValue ) _tileSplatDispersions[_selectedTileIndex] = splatDispersion.Value;\r\n\t\t\tif ( splatBlend.HasValue ) _tileSplatBlendStrengths[_selectedTileIndex] = splatBlend.Value;\r\n\t\t}\r\n\t}\r\n\r\n\tstring FirstShapeForCategory( string category )\r\n\t{\r\n\t\tvar options = ShapeOptionsForCategory( category );\r\n\t\treturn options.Length \u003E 0 ? options[0] : null;\r\n\t}\r\n\r\n\tstring[] ShapeOptionsForCategory( string category )\r\n\t{\r\n\t\tif ( string.IsNullOrEmpty( category ) ) return Array.Empty\u003Cstring\u003E();\r\n\r\n\t\tstring className = $\u0022Sturnus.TerrainGenerationTool.{category}\u0022;\r\n\t\ttry\r\n\t\t{\r\n\t\t\treturn GetMethodsFromClass( className );\r\n\t\t}\r\n\t\tcatch\r\n\t\t{\r\n\t\t\treturn Array.Empty\u003Cstring\u003E();\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void UpdateTerrain()\r\n\t{\r\n\t\tif ( _heightmap is null ) return;\r\n\r\n\t\tvar ActiveScene = Editor.SceneEditorSession.Active.Scene;\r\n\t\tvar FirstTerrain = ActiveScene.GetAllComponents\u003CTerrain\u003E().FirstOrDefault();\r\n\t\tif ( !FirstTerrain.IsValid() ) return;\r\n\r\n\t\tint res = _heightmap.GetLength( 0 );\r\n\r\n\t\t// Resize the scene terrain\u0027s storage to match the generated heightmap\r\n\t\tif ( FirstTerrain.Storage is null )\r\n\t\t\tFirstTerrain.Storage = new TerrainStorage { EmbeddedResource = new Sandbox.Resources.EmbeddedResource { ResourceCompiler = \u0022embed\u0022 } };\r\n\r\n\t\tFirstTerrain.Storage.SetResolution( res );\r\n\r\n\t\t// Write the heightmap with the same indexing the preview uses (heightArray[y * res \u002B x] =\r\n\t\t// heightmap[x, y]). ConvertFloatArrayToUShortArray stores a transpose, which would mirror\r\n\t\t// the terrain against the splatmap and misalign the materials on slopes.\r\n\t\tushort[] heightArray = new ushort[res * res];\r\n\t\tfor ( int y = 0; y \u003C res; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C res; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat h = Math.Clamp( _heightmap[x, y], 0f, 1f );\r\n\t\t\t\theightArray[y * res \u002B x] = (ushort)Math.Clamp( (int)(h * 65535f), 0, 65535 );\r\n\t\t\t}\r\n\t\t}\r\n\t\tFirstTerrain.Storage.HeightMap = heightArray;\r\n\r\n\t\t// Apply the splatmap as a control map so the material blending matches the generated\r\n\t\t// splatmap. SetResolution wipes the control map, so we always rewrite it here. The splatmap\r\n\t\t// is recomputed from the current settings so dispersion/layer changes made after Generate\r\n\t\t// are honoured.\r\n\t\t_splatmap = BuildTileGridSplatmap( _heightmap, TerrainGridSize,\r\n\t\t\t(int[])_tileSplatLayerCounts.Clone(), (SplatDispersionMode[])_tileSplatDispersions.Clone(), (float[])_tileSplatBlendStrengths.Clone() );\r\n\r\n\t\tif ( _splatmap != null )\r\n\t\t{\r\n\t\t\t// Resolve which materials to use: the assigned preview materials, else the terrain\u0027s\r\n\t\t\t// existing materials, else fall back to loading local tmats.\r\n\t\t\tvar materials = _previewMaterials;\r\n\t\t\tif ( materials == null || materials.Length == 0 )\r\n\t\t\t\tmaterials = FirstTerrain.Storage.Materials?.ToArray();\r\n\r\n\t\t\tif ( materials == null || materials.Length == 0 )\r\n\t\t\t\tmaterials = LoadTerrainMaterialsSync();\r\n\r\n\t\t\tif ( materials != null \u0026\u0026 materials.Length \u003E 0 )\r\n\t\t\t{\r\n\t\t\t\tuint[] controlMap = new uint[res * res];\r\n\t\t\t\tint matCount = materials.Length;\r\n\t\t\t\tfor ( int y = 0; y \u003C res; y\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfor ( int x = 0; x \u003C res; x\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfloat layerPos = Math.Clamp( _splatmap[x, y], 0f, matCount - 1f );\r\n\t\t\t\t\t\tint baseId = (int)MathF.Floor( layerPos );\r\n\t\t\t\t\t\tint overlayId = Math.Min( baseId \u002B 1, matCount - 1 );\r\n\t\t\t\t\t\tbyte blend = (byte)Math.Clamp( (int)((layerPos - baseId) * 255f), 0, 255 );\r\n\t\t\t\t\t\tcontrolMap[y * res \u002B x] = new CompactTerrainMaterial( (byte)baseId, (byte)overlayId, blend, false ).Packed;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tFirstTerrain.Storage.ControlMap = controlMap;\r\n\t\t\t\tFirstTerrain.Storage.Materials.Clear();\r\n\t\t\t\tFirstTerrain.Storage.Materials.AddRange( materials );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tFirstTerrain.Create();\r\n\t\tFirstTerrain.SyncGPUTexture();\r\n\t\tFirstTerrain.UpdateMaterialsBuffer();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Synchronously loads usable local .tmat terrain materials so Apply can set the splat\r\n\t/// control map even when the user never clicked \u0022Randomize Materials\u0022.\r\n\t/// \u003C/summary\u003E\r\n\tTerrainMaterial[] LoadTerrainMaterialsSync()\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tif ( _localTmatAssets is null || _localTmatAssets.Count == 0 )\r\n\t\t\t{\r\n\t\t\t\tvar allLocal = Editor.AssetSystem.All\r\n\t\t\t\t\t.Where( a =\u003E a is not null \u0026\u0026 !a.IsDeleted \u0026\u0026 !a.IsCloud )\r\n\t\t\t\t\t.Where( a =\u003E (a.RelativePath?.EndsWith( \u0022.tmat\u0022 ) ?? false) )\r\n\t\t\t\t\t.ToList();\r\n\t\t\t\tvar with1k = allLocal.Where( a =\u003E a.RelativePath.Contains( \u0022_1k\u0022 ) ).ToList();\r\n\t\t\t\t_localTmatAssets = with1k.Count \u003E 0 ? with1k : allLocal;\r\n\t\t\t}\r\n\r\n\t\t\tvar pool = new List\u003CEditor.Asset\u003E( _localTmatAssets );\r\n\t\t\tvar materials = new List\u003CTerrainMaterial\u003E();\r\n\r\n\t\t\tint layerCount = MaxTileSplatLayers();\r\n\t\t\twhile ( materials.Count \u003C layerCount \u0026\u0026 pool.Count \u003E 0 )\r\n\t\t\t{\r\n\t\t\t\tint idx = Random.Shared.Next( pool.Count );\r\n\t\t\t\tvar asset = pool[idx];\r\n\t\t\t\tpool.RemoveAt( idx );\r\n\r\n\t\t\t\tif ( !asset.TryLoadResource\u003CTerrainMaterial\u003E( out var found ) || found is null )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tif ( !IsMaterialUsable( found, asset.Path ) )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tmaterials.Add( found );\r\n\t\t\t}\r\n\r\n\t\t\treturn materials.Count \u003E 0 ? materials.ToArray() : null;\r\n\t\t}\r\n\t\tcatch ( System.Exception e )\r\n\t\t{\r\n\t\t\tLog.Error( $\u0022Failed to load terrain materials for apply: {e.Message}\u0022 );\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Applies per-cell mode: spawns one Terrain per grid cell, each at full resolution and\r\n\t/// positioned so they tile together in the scene.\r\n\t/// \u003C/summary\u003E\r\n\tprivate void UpdatePerCellTerrains()\r\n\t{\r\n\t\tif ( _cellHeightmaps == null || _cellHeightmaps.Count == 0 ) return;\r\n\r\n\t\tvar ActiveScene = Editor.SceneEditorSession.Active.Scene;\r\n\r\n\t\t// Match the size/height of an existing terrain in the scene so the cells line up,\r\n\t\t// falling back to the preview constants if the scene has no terrain yet.\r\n\t\tvar existing = ActiveScene.GetAllComponents\u003CTerrain\u003E().FirstOrDefault();\r\n\t\tfloat cellSize = existing.IsValid() ? existing.TerrainSize : PreviewTerrainSize;\r\n\t\tfloat terrainHeight = existing.IsValid() ? existing.TerrainHeight : PreviewTerrainHeight;\r\n\r\n\t\tint grid = Math.Max( TerrainGridSize, 1 );\r\n\r\n\t\t// Terrain size is a property on the component; size each cell so the whole grid spans cellSize*grid.\r\n\t\tfloat sizePerCell = cellSize;\r\n\r\n\t\tint cellRes = _cellHeightmaps[0].GetLength( 0 );\r\n\r\n\t\t// Recompute the per-cell splatmaps from the current settings so dispersion/layer changes\r\n\t\t// made after Generate are honoured.\r\n\t\t_cellSplatmaps = BuildPerCellSplatmaps( _cellHeightmaps,\r\n\t\t\t(int[])_tileSplatLayerCounts.Clone(), (SplatDispersionMode[])_tileSplatDispersions.Clone(), (float[])_tileSplatBlendStrengths.Clone() );\r\n\r\n\t\tusing ( ActiveScene.Push() )\r\n\t\t{\r\n\t\t\tfor ( int ty = 0; ty \u003C grid; ty\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfor ( int tx = 0; tx \u003C grid; tx\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tint index = ty * grid \u002B tx;\r\n\t\t\t\t\tif ( index \u003E= _cellHeightmaps.Count ) continue;\r\n\r\n\t\t\t\t\tvar go = new GameObject( true, $\u0022terrain cell {tx},{ty}\u0022 );\r\n\t\t\t\t\tvar terrain = go.AddComponent\u003CTerrain\u003E( false );\r\n\r\n\t\t\t\t\tvar storage = new TerrainStorage();\r\n\t\t\t\t\tstorage.EmbeddedResource = new Sandbox.Resources.EmbeddedResource { ResourceCompiler = \u0022embed\u0022 };\r\n\t\t\t\t\tstorage.SetResolution( cellRes );\r\n\t\t\t\t\tstorage.TerrainSize = sizePerCell;\r\n\t\t\t\t\tstorage.TerrainHeight = terrainHeight;\r\n\r\n\t\t\t\t\t// Match the preview\u0027s indexing (heightArray[y * res \u002B x] = map[x, y]) so the\r\n\t\t\t\t\t// heightmap and splatmap line up on slopes.\r\n\t\t\t\t\tushort[] cellHeight = new ushort[cellRes * cellRes];\r\n\t\t\t\t\tvar cellMap = _cellHeightmaps[index];\r\n\t\t\t\t\tfor ( int y = 0; y \u003C cellRes; y\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor ( int x = 0; x \u003C cellRes; x\u002B\u002B )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tfloat h = Math.Clamp( cellMap[x, y], 0f, 1f );\r\n\t\t\t\t\t\t\tcellHeight[y * cellRes \u002B x] = (ushort)Math.Clamp( (int)(h * 65535f), 0, 65535 );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tstorage.HeightMap = cellHeight;\r\n\r\n\t\t\t\t\t// Add the splat control map so the material blending matches the generated splatmap.\r\n\t\t\t\t\t// These are fresh cells, so resolve materials the same way as the combined apply.\r\n\t\t\t\t\tif ( _cellSplatmaps != null \u0026\u0026 index \u003C _cellSplatmaps.Count )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar materials = _previewMaterials;\r\n\t\t\t\t\t\tif ( materials == null || materials.Length == 0 )\r\n\t\t\t\t\t\t\tmaterials = LoadTerrainMaterialsSync();\r\n\r\n\t\t\t\t\t\tif ( materials != null \u0026\u0026 materials.Length \u003E 0 )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tuint[] controlMap = new uint[cellRes * cellRes];\r\n\t\t\t\t\t\t\tint matCount = materials.Length;\r\n\t\t\t\t\t\t\tvar splatmap = _cellSplatmaps[index];\r\n\t\t\t\t\t\t\tfor ( int y = 0; y \u003C cellRes; y\u002B\u002B )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tfor ( int x = 0; x \u003C cellRes; x\u002B\u002B )\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tfloat layerPos = Math.Clamp( splatmap[x, y], 0f, matCount - 1f );\r\n\t\t\t\t\t\t\t\t\tint baseId = (int)MathF.Floor( layerPos );\r\n\t\t\t\t\t\t\t\t\tint overlayId = Math.Min( baseId \u002B 1, matCount - 1 );\r\n\t\t\t\t\t\t\t\t\tbyte blend = (byte)Math.Clamp( (int)((layerPos - baseId) * 255f), 0, 255 );\r\n\t\t\t\t\t\t\t\t\tcontrolMap[y * cellRes \u002B x] = new CompactTerrainMaterial( (byte)baseId, (byte)overlayId, blend, false ).Packed;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tstorage.ControlMap = controlMap;\r\n\t\t\t\t\t\t\tstorage.Materials.Clear();\r\n\t\t\t\t\t\t\tstorage.Materials.AddRange( materials );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tterrain.Storage = storage;\r\n\t\t\t\t\tterrain.TerrainSize = sizePerCell;\r\n\t\t\t\t\tterrain.TerrainHeight = terrainHeight;\r\n\r\n\t\t\t\t\t// Each cell is sizePerCell wide - tile them so the whole grid spans cellSize*grid\r\n\t\t\t\t\tgo.WorldPosition = new Vector3( tx * sizePerCell, ty * sizePerCell, 0f );\r\n\r\n\t\t\t\t\tterrain.Create();\r\n\t\t\t\t\tterrain.SyncGPUTexture();\r\n\t\t\t\t\tterrain.UpdateMaterialsBuffer();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate float[,] AddStagingSquare( float[,] heightmap, int squareSize, float squareHeight, float centerX, float centerY )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\r\n\t\t// Calculate the center and bounds of the square\r\n\t\tint centerXPixel = (int)(centerX * width);\r\n\t\tint centerYPixel = (int)(centerY * height);\r\n\t\tint halfSize = squareSize / 2;\r\n\r\n\t\tint startX = Math.Max( centerXPixel - halfSize, 0 );\r\n\t\tint startY = Math.Max( centerYPixel - halfSize, 0 );\r\n\t\tint endX = Math.Min( centerXPixel \u002B halfSize, width - 1 );\r\n\t\tint endY = Math.Min( centerYPixel \u002B halfSize, height - 1 );\r\n\r\n\t\t// Set the height values inside the square to be completely flat\r\n\t\tfor ( int y = startY; y \u003C= endY; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = startX; x \u003C= endX; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\theightmap[x, y] = squareHeight;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Add the slope around the square\r\n\t\tfor ( int y = 0; y \u003C height; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\t// Skip the flat square area\r\n\t\t\t\tif ( x \u003E= startX \u0026\u0026 x \u003C= endX \u0026\u0026 y \u003E= startY \u0026\u0026 y \u003C= endY )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\t// Calculate the distance to the nearest edge of the square\r\n\t\t\t\tint dx = Math.Max( Math.Abs( x - centerXPixel ) - halfSize, 0 );\r\n\t\t\t\tint dy = Math.Max( Math.Abs( y - centerYPixel ) - halfSize, 0 );\r\n\t\t\t\tfloat distanceToSquare = MathF.Sqrt( dx * dx \u002B dy * dy );\r\n\r\n\t\t\t\t// Calculate the target height for the slope\r\n\t\t\t\tfloat slopeHeight = squareHeight - (distanceToSquare * 0.0038f); // 0.0038f is perfect for players\r\n\r\n\t\t\t\t// Ensure the slope transitions smoothly into the existing terrain\r\n\t\t\t\theightmap[x, y] = Math.Max( heightmap[x, y], slopeHeight );\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn heightmap;\r\n\t}\r\n\r\n\tprivate void GeneratePreviewFile( string path, out SKBitmap image, out SKBitmap splat )\r\n\t{\r\n\t\t//Create TerrainGenerationTool folder if it doesn\u0027t exist.\r\n\t\tDirectory.CreateDirectory( path );\r\n\t\tstring previewfile = Path.Combine( path, $\u0022TerrainGenerationUtility_preview.png\u0022 );\r\n\t\tstring splatfile = Path.Combine( path, $\u0022TerrainGenerationUtility_splat_preview.png\u0022 );\r\n\r\n\t\timage = HeightmapToBitMap( _heightmap );\r\n\t\tSaveImage( image, previewfile );\r\n\t\tsplat = SplatmapToBitMap( _splatmap, _splatcolors );\r\n\t\tSaveSplatmapAsPng( splat, splatfile );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Builds a fresh GPU texture from a bitmap so the preview widgets always get new data\r\n\t/// (the resource cache would otherwise return the same stale texture for the same path).\r\n\t/// \u003C/summary\u003E\r\n\tTexture TextureFromBitmap( SKBitmap bitmap )\r\n\t{\r\n\t\tif ( bitmap is null ) return Texture.Invalid;\r\n\r\n\t\tint width = bitmap.Width;\r\n\t\tint height = bitmap.Height;\r\n\t\tbyte[] rgba = new byte[width * height * 4];\r\n\r\n\t\tfor ( int y = 0; y \u003C height; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tvar c = bitmap.GetPixel( x, y );\r\n\t\t\t\tint i = (y * width \u002B x) * 4;\r\n\t\t\t\trgba[i \u002B 0] = c.Red;\r\n\t\t\t\trgba[i \u002B 1] = c.Green;\r\n\t\t\t\trgba[i \u002B 2] = c.Blue;\r\n\t\t\t\trgba[i \u002B 3] = c.Alpha;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn Texture.Create( width, height )\r\n\t\t\t.WithName( $\u0022TerrainGenerationPreview_{Environment.TickCount}\u0022 )\r\n\t\t\t.WithData( rgba )\r\n\t\t\t.Finish();\r\n\t}\r\n\r\n\tprivate void GenerateImageFiles( string output_path )\r\n\t{\r\n\t\tstring UsingDomainWarping = \u0022\u0022;\r\n\t\tstring UsingErosionEmulation = \u0022\u0022;\r\n\t\tstring UsingWaterCarving = \u0022\u0022;\r\n\r\n\t\tif ( DomainWarping )\r\n\t\t{\r\n\t\t\tUsingDomainWarping = \u0022_warp\u0022;\r\n\t\t}\r\n\r\n\t\tif ( ErosionSimulation )\r\n\t\t{\r\n\t\t\tUsingErosionEmulation = \u0022_erosion\u0022;\r\n\t\t}\r\n\r\n\t\tif ( RiverCarvingBool )\r\n\t\t{\r\n\t\t\tUsingWaterCarving = \u0022_watercarving\u0022;\r\n\t\t}\r\n\r\n\t\t//Create TerrainGenerationTool folder if it doesn\u0027t exist.\r\n\t\tDirectory.CreateDirectory( output_path );\r\n\r\n\t\tif ( GridStorage == GridStorageMode.PerCell \u0026\u0026 _cellHeightmaps != null \u0026\u0026 _cellHeightmaps.Count \u003E 0 )\r\n\t\t{\r\n\t\t\tGeneratePerCellFiles( output_path );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tstring rawfile = Path.Combine( output_path, $\u0022TerrainGenerationUtility_export_{/*TerrainShapeEnumSelect*/null}{UsingDomainWarping}{UsingErosionEmulation}{UsingWaterCarving}.raw\u0022 );\r\n\t\tstring previewfile = Path.Combine( output_path, $\u0022TerrainGenerationUtility_preview_{/*TerrainShapeEnumSelect*/null}{UsingDomainWarping}{UsingErosionEmulation}{UsingWaterCarving}.png\u0022 );\r\n\t\tstring splatfile = Path.Combine( output_path, $\u0022TerrainGenerationUtility_splat_export_{/*TerrainShapeEnumSelect*/null}{UsingDomainWarping}{UsingErosionEmulation}{UsingWaterCarving}.png\u0022 );\r\n\r\n\t\t//Export RAW HeightMap file\r\n\t\tSaveRaw( _heightmap, rawfile );\r\n\t\tLog.Info( $\u0022Raw file generated! - {rawfile}\u0022 );\r\n\t\t//Generate \u0026 Export Preview image for widget\r\n\t\tSKBitmap image = HeightmapToBitMap( _heightmap );\r\n\t\tSaveImage( image, previewfile );\r\n\t\tLog.Info( $\u0022HeightMap preview file generated! - {previewfile}\u0022 );\r\n\t\t//Generate \u0026 Export SplatMap image\r\n\t\tfloat[,] splatmap = BuildTileGridSplatmap( _heightmap, TerrainGridSize,\r\n\t\t\t(int[])_tileSplatLayerCounts.Clone(), (SplatDispersionMode[])_tileSplatDispersions.Clone(), (float[])_tileSplatBlendStrengths.Clone() );\r\n\t\tSKBitmap splat = SplatmapToBitMap( splatmap, _splatcolors );\r\n\t\tSaveSplatmapAsPng( splat, splatfile );\r\n\t\tLog.Info( $\u0022Splatmap file generated! - {splatfile}\u0022 );\r\n\r\n\t\t// Split the layers across the requested number of splat maps (based on the first tile\u0027s settings)\r\n\t\tint layerCount = _tileSplatLayerCounts != null \u0026\u0026 _tileSplatLayerCounts.Length \u003E 0 ? Math.Max( _tileSplatLayerCounts[0], 2 ) : 2;\r\n\t\tint mapCount = _tileSplatMapCounts != null \u0026\u0026 _tileSplatMapCounts.Length \u003E 0 ? Math.Max( _tileSplatMapCounts[0], 1 ) : 1;\r\n\t\tfor ( int m = 0; m \u003C mapCount; m\u002B\u002B )\r\n\t\t{\r\n\t\t\tint startLayer = m * layerCount / mapCount;\r\n\t\t\tint endLayer = (m \u002B 1) * layerCount / mapCount;\r\n\r\n\t\t\tvar mapBitmap = new SKBitmap( splatmap.GetLength( 0 ), splatmap.GetLength( 1 ) );\r\n\r\n\t\t\tfor ( int y = 0; y \u003C mapBitmap.Height; y\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfor ( int x = 0; x \u003C mapBitmap.Width; x\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat layerPos = Math.Clamp( splatmap[x, y], 0f, layerCount - 1f );\r\n\t\t\t\t\tint layer = (int)MathF.Round( layerPos );\r\n\r\n\t\t\t\t\tif ( layer \u003E= startLayer \u0026\u0026 layer \u003C endLayer )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfloat local = (layer - startLayer) / (float)Math.Max( endLayer - startLayer, 1 );\r\n\t\t\t\t\t\tvar color = SplatMapGradient.Evaluate( Math.Clamp( local, 0f, 1f ) ).ToColor32();\r\n\t\t\t\t\t\tmapBitmap.SetPixel( x, y, new SKColor( color.r, color.g, color.b, color.a ) );\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tmapBitmap.SetPixel( x, y, new SKColor( 0, 0, 0, 255 ) );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tstring mapFile = Path.Combine( output_path, $\u0022TerrainGenerationUtility_splatmap_{m}_{/*TerrainShapeEnumSelect*/null}{UsingDomainWarping}{UsingErosionEmulation}{UsingWaterCarving}.png\u0022 );\r\n\t\t\tSaveSplatmapAsPng( mapBitmap, mapFile );\r\n\t\t\tLog.Info( $\u0022Splatmap {m} file generated! - {mapFile}\u0022 );\r\n\t\t}\r\n\r\n\t\tLog.Info( $\u0022All export files saved! {output_path}\u0022 );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Exports each grid cell as its own full-resolution .raw heightmap and .png splatmap,\r\n\t/// named by grid coordinate.\r\n\t/// \u003C/summary\u003E\r\n\tprivate void GeneratePerCellFiles( string output_path )\r\n\t{\r\n\t\tDirectory.CreateDirectory( output_path );\r\n\r\n\t\tint grid = Math.Max( TerrainGridSize, 1 );\r\n\r\n\t\tfor ( int ty = 0; ty \u003C grid; ty\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int tx = 0; tx \u003C grid; tx\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tint index = ty * grid \u002B tx;\r\n\t\t\t\tif ( index \u003E= _cellHeightmaps.Count ) continue;\r\n\r\n\t\t\t\tvar heightmap = _cellHeightmaps[index];\r\n\r\n\t\t\t\tstring rawfile = Path.Combine( output_path, $\u0022TerrainGenerationUtility_cell_{tx}_{ty}.raw\u0022 );\r\n\t\t\t\tSaveRaw( heightmap, rawfile );\r\n\t\t\t\tLog.Info( $\u0022Cell {tx},{ty} raw file generated! - {rawfile}\u0022 );\r\n\r\n\t\t\t\tSKBitmap image = HeightmapToBitMap( heightmap );\r\n\t\t\t\tstring previewfile = Path.Combine( output_path, $\u0022TerrainGenerationUtility_cell_{tx}_{ty}_preview.png\u0022 );\r\n\t\t\t\tSaveImage( image, previewfile );\r\n\t\t\t\tLog.Info( $\u0022Cell {tx},{ty} preview generated! - {previewfile}\u0022 );\r\n\r\n\t\t\t\tif ( _cellSplatmaps != null \u0026\u0026 index \u003C _cellSplatmaps.Count )\r\n\t\t\t\t{\r\n\t\t\t\t\tSKBitmap splat = SplatmapToBitMap( _cellSplatmaps[index], _splatcolors );\r\n\t\t\t\t\tstring splatfile = Path.Combine( output_path, $\u0022TerrainGenerationUtility_cell_{tx}_{ty}_splat.png\u0022 );\r\n\t\t\t\t\tSaveSplatmapAsPng( splat, splatfile );\r\n\t\t\t\t\tLog.Info( $\u0022Cell {tx},{ty} splatmap generated! - {splatfile}\u0022 );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tLog.Info( $\u0022All per-cell export files saved! {output_path}\u0022 );\r\n\t}\r\n\r\n\tpublic float[,] GenerateHeightmap( int width, int height, Func\u003Cint, int, float\u003E generator, float maxHeight, int smoothpasses )\r\n\t{\r\n\t\tfloat[,] heightmap = new float[width, height];\r\n\t\tfloat actualMaxHeight = float.MinValue;\r\n\r\n\t\t// Use parallel processing to generate heightmap\r\n\t\tobject maxLock = new object(); // Lock object for thread safety\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat value = generator( x, y );\r\n\t\t\t\theightmap[x, y] = value;\r\n\r\n\t\t\t\t// Update actual max height (thread-safe)\r\n\t\t\t\tlock ( maxLock )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( value \u003E actualMaxHeight )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tactualMaxHeight = value;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t// Scale all values by the actual max height and up to the specified max height\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\theightmap[x, y] = (heightmap[x, y] / actualMaxHeight) * maxHeight;\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t// Apply smoothing if needed\r\n\t\tif ( smoothpasses \u003E 0 )\r\n\t\t{\r\n\t\t\treturn SmoothHeightmap( heightmap, smoothpasses );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn heightmap;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static float[,] GenerateStackedNoise(\r\n\t\tint width,\r\n\t\tint height,\r\n\t\tlong seed,\r\n\t\tint layers,\r\n\t\tfloat initialFrequency,\r\n\t\tfloat frequencyMultiplier,\r\n\t\tfloat initialAmplitude,\r\n\t\tfloat amplitudeMultiplier,\r\n\t\tFunc\u003Cint, int, float\u003E shapeFunction, // Shape function applied after stacking noise\r\n\t\tfloat maxHeight,\r\n\t\tint smoothingPasses,\r\n\t\tfloat terrainPlaneScale // New variable to scale the noise\r\n\r\n\t)\r\n\t{\r\n\t\t// Initialize the heightmap with zeros\r\n\t\tfloat[,] heightmap = new float[width, height];\r\n\r\n\t\t// Random offset generator for noise layers\r\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\r\n\t\tfloat[] xOffsets = new float[layers];\r\n\t\tfloat[] yOffsets = new float[layers];\r\n\r\n\t\tfor ( int i = 0; i \u003C layers; i\u002B\u002B )\r\n\t\t{\r\n\t\t\txOffsets[i] = random.Next( -100000, 100000 ) / 1000.0f;\r\n\t\t\tyOffsets[i] = random.Next( -100000, 100000 ) / 1000.0f;\r\n\t\t}\r\n\r\n\t\t// Adjust frequency based on TerrainPlaneScale\r\n\t\tfloat scaleFactor = Math.Clamp( terrainPlaneScale, 0.01f, 1.0f );\r\n\r\n\t\t// Multithreaded noise generation\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat value = 0.0f;\r\n\r\n\t\t\t\tfor ( int layer = 0; layer \u003C layers; layer\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat frequency = initialFrequency * MathF.Pow( frequencyMultiplier, layer ) / scaleFactor;\r\n\t\t\t\t\tfloat amplitude = initialAmplitude * MathF.Pow( amplitudeMultiplier, layer );\r\n\r\n\t\t\t\t\t// Normalized coordinates adjusted by scale factor\r\n\t\t\t\t\tfloat nx = (x / (float)width) * frequency;\r\n\t\t\t\t\tfloat ny = (y / (float)height) * frequency;\r\n\r\n\t\t\t\t\t// Apply random offsets\r\n\t\t\t\t\tnx \u002B= xOffsets[layer];\r\n\t\t\t\t\tny \u002B= yOffsets[layer];\r\n\r\n\t\t\t\t\t// Generate noise\r\n\t\t\t\t\tfloat noiseValue = OpenSimplex2S.Noise2( (seed \u002B layer) \u0026 0xFFFFFFFF, nx, ny );\r\n\t\t\t\t\tvalue \u002B= Math.Clamp( noiseValue, -1.0f, 1.0f ) * amplitude;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Save the computed value to the heightmap\r\n\t\t\t\t// (each Parallel.For iteration writes its own distinct row - no lock needed)\r\n\t\t\t\theightmap[x, y] \u002B= value;\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t// Normalize the heightmap to the range [0, 1]\r\n\t\theightmap = NormalizeHeightmap( heightmap );\r\n\r\n\t\t// Apply the shape function and amplify its contribution if needed\r\n\t\tfloat shapeAmplification = 1.2f; // Adjust for stronger shape effects\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\theightmap[x, y] *= MathF.Pow( shapeFunction( x, y ), shapeAmplification );\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t// Rescale the heightmap to the desired maxHeight\r\n\t\tfloat currentMax = FindMaxHeight( heightmap );\r\n\t\tif ( currentMax \u003E 0 )\r\n\t\t{\r\n\t\t\tParallel.For( 0, height, y =\u003E\r\n\t\t\t{\r\n\t\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\theightmap[x, y] = (heightmap[x, y] / currentMax) * maxHeight;\r\n\t\t\t\t}\r\n\t\t\t} );\r\n\t\t}\r\n\r\n\t\t// Apply smoothing\r\n\t\tif ( smoothingPasses \u003E 0 )\r\n\t\t{\r\n\t\t\theightmap = SmoothHeightmap( heightmap, smoothingPasses );\r\n\t\t}\r\n\r\n\t\treturn heightmap;\r\n\t}\r\n\r\n\r\n\t// Helper method to find the maximum height in a heightmap\r\n\tprivate static float FindMaxHeight( float[,] heightmap )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\r\n\t\tfloat max = float.MinValue;\r\n\t\tobject maxLock = new object();\r\n\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfloat rowMax = float.MinValue;\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( heightmap[x, y] \u003E rowMax )\r\n\t\t\t\t{\r\n\t\t\t\t\trowMax = heightmap[x, y];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( rowMax \u003E max )\r\n\t\t\t{\r\n\t\t\t\tlock ( maxLock )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( rowMax \u003E max ) max = rowMax;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\treturn max;\r\n\t}\r\n\r\n\r\n\tprivate static float[,] NormalizeHeightmap( float[,] heightmap )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\r\n\t\t// Find the min and max values (parallel, per-row reduce)\r\n\t\tobject lockObject = new object();\r\n\t\tfloat min = float.MaxValue;\r\n\t\tfloat max = float.MinValue;\r\n\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfloat rowMin = float.MaxValue;\r\n\t\t\tfloat rowMax = float.MinValue;\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat value = heightmap[x, y];\r\n\t\t\t\tif ( value \u003C rowMin ) rowMin = value;\r\n\t\t\t\tif ( value \u003E rowMax ) rowMax = value;\r\n\t\t\t}\r\n\r\n\t\t\tlock ( lockObject )\r\n\t\t\t{\r\n\t\t\t\tif ( rowMin \u003C min ) min = rowMin;\r\n\t\t\t\tif ( rowMax \u003E max ) max = rowMax;\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\tfloat range = max - min;\r\n\t\tif ( range \u003C= 0f ) range = 1f;\r\n\r\n\t\t// Normalize the values (parallel, independent writes)\r\n\t\tfloat[,] normalized = new float[width, height];\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tnormalized[x, y] = (heightmap[x, y] - min) / range;\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\treturn normalized;\r\n\t}\r\n\r\n\tpublic static float[,] AddTurbulenceForRivers(\r\n\t\tfloat[,] heightmap,\r\n\t\tlong seed,\r\n\t\tfloat riverFrequency, // Frequency for river placement\r\n\t\tfloat riverWidth, // Width of the rivers\r\n\t\tfloat riverDepth, // Depth of the rivers\r\n\t\tfloat turbulenceFrequency, // Turbulence frequency\r\n\t\tfloat turbulenceStrength, // Turbulence strength\r\n\t\tfloat minRiverSpacing, // Minimum spacing between rivers\r\n\t\tfloat slopeSteepness, // Controls the gradual slope of the riverbanks\r\n\t\tfloat terrainNoiseFrequency, // Matches terrain surface noise frequency\r\n\t\tfloat terrainNoiseAmplitude // Matches terrain surface noise amplitude\r\n\t)\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\t\tfloat[,] newHeightmap = (float[,])heightmap.Clone();\r\n\r\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\r\n\t\tfloat[,] riverPlacementNoise = new float[width, height];\r\n\r\n\t\t// Generate river placement noise\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat nx = x / (float)width;\r\n\t\t\t\tfloat ny = y / (float)height;\r\n\r\n\t\t\t\t// Noise for river placement\r\n\t\t\t\triverPlacementNoise[x, y] = OpenSimplex2S.Noise2( seed, nx * riverFrequency, ny * riverFrequency );\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t// Process heightmap with river carving\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat nx = x / (float)width;\r\n\t\t\t\tfloat ny = y / (float)height;\r\n\r\n\t\t\t\tfloat riverNoise = MathF.Abs( riverPlacementNoise[x, y] ); // Use absolute noise for placement\r\n\r\n\t\t\t\t// Determine if the point is within the river carving zone\r\n\t\t\t\tif ( riverNoise \u003C riverWidth )\r\n\t\t\t\t{\r\n\t\t\t\t\t// Calculate the smooth curve effect based on distance from the center\r\n\t\t\t\t\tfloat distanceFactor = 1.0f - (riverNoise / riverWidth); // 1 at center, 0 at edge\r\n\t\t\t\t\tfloat smoothDepthReduction = MathF.Pow( distanceFactor, slopeSteepness ) * riverDepth;\r\n\r\n\t\t\t\t\t// Add turbulence for a more organic flow\r\n\t\t\t\t\tfloat turbulence = OpenSimplex2S.Noise2( seed \u002B 1, nx * turbulenceFrequency, ny * turbulenceFrequency )\r\n\t\t\t\t\t\t\t\t\t   * turbulenceStrength;\r\n\r\n\t\t\t\t\t// Apply smooth depth reduction and turbulence\r\n\t\t\t\t\tfloat reducedHeight = newHeightmap[x, y] - smoothDepthReduction \u002B turbulence;\r\n\r\n\t\t\t\t\t// Clamp height to ensure it doesn\u0027t rise above the original\r\n\t\t\t\t\tnewHeightmap[x, y] = MathF.Max( 0, MathF.Min( newHeightmap[x, y], reducedHeight ) );\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Enforce minimum spacing between rivers\r\n\t\t\t\tif ( riverNoise \u003C minRiverSpacing )\r\n\t\t\t\t{\r\n\t\t\t\t\t// Slightly raise the terrain to enforce separation\r\n\t\t\t\t\tnewHeightmap[x, y] \u002B= (minRiverSpacing - riverNoise) * 0.05f;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t// Add base noise to the entire heightmap after carving\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat nx = x / (float)width;\r\n\t\t\t\tfloat ny = y / (float)height;\r\n\r\n\t\t\t\t// Generate base noise\r\n\t\t\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed \u002B 2, nx * 6f, ny * 6f )\r\n\t\t\t\t\t\t\t\t  * 0.02f;\r\n\r\n\t\t\t\t// Add noise to the heightmap\r\n\t\t\t\tnewHeightmap[x, y] = MathF.Max( 0, newHeightmap[x, y] \u002B baseNoise );\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\treturn newHeightmap;\r\n\t}\r\n\r\n\t// Smooths the heightmap using a simple box blur with adjustable strength\r\n\tprivate static float[,] SmoothHeightmap( float[,] heightmap, int smoothingPasses )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\t\tfloat[,] smoothed = new float[width, height];\r\n\r\n\t\tfor ( int pass = 0; pass \u003C smoothingPasses; pass\u002B\u002B )\r\n\t\t{\r\n\t\t\tParallel.For( 0, height, y =\u003E\r\n\t\t\t{\r\n\t\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfloat sum = 0;\r\n\t\t\t\t\tint count = 0;\r\n\r\n\t\t\t\t\t// Iterate through neighbors\r\n\t\t\t\t\tfor ( int dy = -1; dy \u003C= 1; dy\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfor ( int dx = -1; dx \u003C= 1; dx\u002B\u002B )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tint nx = x \u002B dx;\r\n\t\t\t\t\t\t\tint ny = y \u002B dy;\r\n\r\n\t\t\t\t\t\t\tif ( nx \u003E= 0 \u0026\u0026 nx \u003C width \u0026\u0026 ny \u003E= 0 \u0026\u0026 ny \u003C height )\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tsum \u002B= heightmap[nx, ny];\r\n\t\t\t\t\t\t\t\tcount\u002B\u002B;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tsmoothed[x, y] = sum / count;\r\n\t\t\t\t}\r\n\t\t\t} );\r\n\r\n\t\t\t// Copy smoothed values back to the original heightmap for the next pass\r\n\t\t\tParallel.For( 0, height, y =\u003E\r\n\t\t\t{\r\n\t\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\theightmap[x, y] = smoothed[x, y];\r\n\t\t\t\t}\r\n\t\t\t} );\r\n\t\t}\r\n\t\treturn smoothed;\r\n\t}\r\n\r\n\tpublic static ushort[] ConvertFloatArrayToUShortArray( float[,] input, float scale = 65535.0f )\r\n\t{\r\n\t\t// Get the dimensions of the 2D array\r\n\t\tint rows = input.GetLength( 0 );\r\n\t\tint cols = input.GetLength( 1 );\r\n\r\n\t\t// Initialize the 1D ushort array\r\n\t\tushort[] output = new ushort[rows * cols];\r\n\r\n\t\t// Iterate over the 2D array row by row\r\n\t\tint index = 0;\r\n\t\tfor ( int row = 0; row \u003C rows; row\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int col = 0; col \u003C cols; col\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\t// Convert the float to ushort, scaling if necessary\r\n\t\t\t\tfloat value = input[row, col];\r\n\t\t\t\tvalue = Math.Clamp( value, 0.0f, 1.0f ); // Ensure the float is in the 0 to 1 range\r\n\t\t\t\toutput[index\u002B\u002B] = (ushort)(value * scale);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn output;\r\n\t}\r\n\r\n\tpublic static byte[] ConvertRawFloatArrayToByteArray( float[,] rawData, float scale = 65535.0f )\r\n\t{\r\n\t\tif ( rawData == null )\r\n\t\t{\r\n\t\t\tthrow new ArgumentNullException( nameof( rawData ), \u0022Input rawData cannot be null.\u0022 );\r\n\t\t}\r\n\r\n\t\tint rows = rawData.GetLength( 0 );\r\n\t\tint cols = rawData.GetLength( 1 );\r\n\r\n\t\t// Create a byte array with 2 bytes per value\r\n\t\tbyte[] byteArray = new byte[rows * cols * 2]; // 2 bytes per ushort\r\n\r\n\t\tint index = 0;\r\n\t\tfor ( int row = 0; row \u003C rows; row\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int col = 0; col \u003C cols; col\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat value = rawData[row, col];\r\n\t\t\t\tvalue = Math.Clamp( value, 0.0f, 1.0f ); // Ensure value is in the range [0, 1]\r\n\r\n\t\t\t\t// Convert to 16-bit unsigned integer\r\n\t\t\t\tushort ushortValue = (ushort)(value * scale);\r\n\r\n\t\t\t\t// Store in byte array (little-endian order)\r\n\t\t\t\tbyteArray[index\u002B\u002B] = (byte)(ushortValue \u0026 0xFF);       // Lower byte\r\n\t\t\t\tbyteArray[index\u002B\u002B] = (byte)((ushortValue \u003E\u003E 8) \u0026 0xFF); // Upper byte\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn byteArray;\r\n\t}\r\n\r\n\t// Converts a heightmap to a grayscale image using SkiaSharp\r\n\tpublic static SKBitmap HeightmapToBitMap( float[,] heightmap )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\t\tSKBitmap bitmap = new SKBitmap( width, height );\r\n\r\n\t\tfor ( int y = 0; y \u003C height; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tint intensity = (int)(heightmap[x, y] * 255);\r\n\t\t\t\tintensity = Math.Clamp( intensity, 0, 255 );\r\n\t\t\t\tbitmap.SetPixel( x, y, new SKColor( (byte)intensity, (byte)intensity, (byte)intensity ) );\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn bitmap;\r\n\t}\r\n\r\n\tpublic byte[] ConvertSKBitmapToBytes( SKBitmap bitmap, SKEncodedImageFormat format, int quality = 100 )\r\n\t{\r\n\t\t// Create an SKImage from the SKBitmap\r\n\t\tusing ( var image = SKImage.FromBitmap( bitmap ) )\r\n\t\t{\r\n\t\t\t// Encode the image to the desired format (e.g., PNG, JPEG)\r\n\t\t\tusing ( var data = image.Encode( format, quality ) )\r\n\t\t\t{\r\n\t\t\t\t// Convert SKData to a byte array\r\n\t\t\t\treturn data.ToArray();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static void SaveImage(\r\n\tSKBitmap bitmap,\r\n\tstring filename,\r\n\tfloat rotationDegrees = 270f,\r\n\tbool reverseHorizontal = false,\r\n\tbool reverseVertical = true\r\n)\r\n\t{\r\n\t\tint width = bitmap.Width;\r\n\t\tint height = bitmap.Height;\r\n\r\n\t\t// Create a new bitmap to hold the transformed image\r\n\t\tusing var transformedBitmap = new SKBitmap( width, height );\r\n\r\n\t\t// Create a canvas to draw the transformed image\r\n\t\tusing var canvas = new SKCanvas( transformedBitmap );\r\n\r\n\t\t// Clear the canvas with transparency\r\n\t\tcanvas.Clear( SKColors.Transparent );\r\n\r\n\t\t// Apply transformations\r\n\t\tcanvas.Save();\r\n\r\n\t\t// Translate to the center of the canvas for rotation and flipping\r\n\t\tcanvas.Translate( width / 2f, height / 2f );\r\n\r\n\t\t// Apply flipping first\r\n\t\tfloat scaleX = reverseHorizontal ? -1f : 1f;\r\n\t\tfloat scaleY = reverseVertical ? -1f : 1f;\r\n\t\tcanvas.Scale( scaleX, scaleY );\r\n\r\n\t\t// Apply rotation\r\n\t\tif ( rotationDegrees != 0 )\r\n\t\t{\r\n\t\t\tcanvas.RotateDegrees( rotationDegrees );\r\n\t\t}\r\n\r\n\t\t// Translate back to ensure the image is drawn correctly\r\n\t\tcanvas.Translate( -width / 2f, -height / 2f );\r\n\r\n\t\t// Draw the original bitmap onto the transformed canvas\r\n\t\tcanvas.DrawBitmap( bitmap, 0, 0 );\r\n\r\n\t\t// Restore the canvas to finalize the transformations\r\n\t\tcanvas.Restore();\r\n\r\n\t\t// Flush the canvas\r\n\t\tcanvas.Flush();\r\n\r\n\t\t// Save the transformed bitmap as a PNG file\r\n\t\tusing var pixmap = transformedBitmap.PeekPixels();\r\n\t\tusing var image = SKImage.FromPixels( pixmap );\r\n\t\tusing var data = image.Encode( SKEncodedImageFormat.Png, 100 );\r\n\r\n\t\tusing var stream = File.OpenWrite( filename );\r\n\t\tdata.SaveTo( stream );\r\n\t}\r\n\r\n\tpublic static void SaveRaw( float[,] heightmap, string filename, int rotationDegrees = 270, bool reverseHorizontal = true, bool reverseVertical = false )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\r\n\t\t// Rotate the heightmap if requested\r\n\t\tif ( rotationDegrees != 0 )\r\n\t\t{\r\n\t\t\theightmap = RotateHeightmap( heightmap, rotationDegrees );\r\n\t\t\tif ( rotationDegrees == 90 || rotationDegrees == 270 )\r\n\t\t\t{\r\n\t\t\t\t// Swap width and height for 90\u00B0 or 270\u00B0 rotations\r\n\t\t\t\t(width, height) = (height, width);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Reverse the heightmap if requested\r\n\t\tif ( reverseHorizontal || reverseVertical )\r\n\t\t{\r\n\t\t\theightmap = ReverseHeightmap( heightmap, reverseHorizontal, reverseVertical );\r\n\t\t}\r\n\r\n\t\tusing var fileStream = new FileStream( filename, FileMode.Create, FileAccess.Write );\r\n\t\tusing var binaryWriter = new BinaryWriter( fileStream );\r\n\r\n\t\tfor ( int y = 0; y \u003C height; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\t// Scale image data to 16-bit\r\n\t\t\t\tushort value = (ushort)(Math.Clamp( heightmap[x, y], 0, 1 ) * 65535);\r\n\t\t\t\tbinaryWriter.Write( value );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Helper method to rotate the heightmap by 90\u00B0, 180\u00B0, or 270\u00B0\r\n\tprivate static float[,] RotateHeightmap( float[,] original, int rotationDegrees )\r\n\t{\r\n\t\tint originalWidth = original.GetLength( 0 );\r\n\t\tint originalHeight = original.GetLength( 1 );\r\n\r\n\t\tfloat[,] rotated;\r\n\r\n\t\tswitch ( rotationDegrees )\r\n\t\t{\r\n\t\t\tcase 90:\r\n\t\t\t\trotated = new float[originalHeight, originalWidth];\r\n\t\t\t\tfor ( int y = 0; y \u003C originalHeight; y\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfor ( int x = 0; x \u003C originalWidth; x\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\trotated[y, originalWidth - 1 - x] = original[x, y];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase 180:\r\n\t\t\t\trotated = new float[originalWidth, originalHeight];\r\n\t\t\t\tfor ( int y = 0; y \u003C originalHeight; y\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfor ( int x = 0; x \u003C originalWidth; x\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\trotated[originalWidth - 1 - x, originalHeight - 1 - y] = original[x, y];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase 270:\r\n\t\t\t\trotated = new float[originalHeight, originalWidth];\r\n\t\t\t\tfor ( int y = 0; y \u003C originalHeight; y\u002B\u002B )\r\n\t\t\t\t{\r\n\t\t\t\t\tfor ( int x = 0; x \u003C originalWidth; x\u002B\u002B )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\trotated[originalHeight - 1 - y, x] = original[x, y];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tdefault:\r\n\t\t\t\tthrow new ArgumentException( \u0022Rotation must be 0, 90, 180, or 270 degrees.\u0022 );\r\n\t\t}\r\n\r\n\t\treturn rotated;\r\n\t}\r\n\r\n\t// Helper method to reverse the heightmap horizontally and/or vertically\r\n\tprivate static float[,] ReverseHeightmap( float[,] original, bool reverseHorizontal, bool reverseVertical )\r\n\t{\r\n\t\tint width = original.GetLength( 0 );\r\n\t\tint height = original.GetLength( 1 );\r\n\r\n\t\tfloat[,] reversed = new float[width, height];\r\n\r\n\t\tfor ( int y = 0; y \u003C height; y\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tint targetX = reverseHorizontal ? width - 1 - x : x;\r\n\t\t\t\tint targetY = reverseVertical ? height - 1 - y : y;\r\n\t\t\t\treversed[targetX, targetY] = original[x, y];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn reversed;\r\n\t}\r\n\r\n\tpublic static float[,] GenerateSplatmap( float[,] heightmap, float[] thresholds, float maxHeight, int layerCount = -1, SplatDispersionMode dispersion = SplatDispersionMode.Evenly, float blendStrength = 0.35f )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\r\n\t\tint layers = layerCount \u003E 0 ? layerCount : Math.Max( thresholds.Length, 2 );\r\n\t\tfloat[,] splatmap = new float[width, height];\r\n\r\n\t\t// Build normalized height bounds from the data itself (robust to maxHeight being lower than peaks)\r\n\t\tfloat minH = float.MaxValue, maxH = float.MinValue;\r\n\t\tobject minMaxLock = new object();\r\n\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfloat rowMin = float.MaxValue;\r\n\t\t\tfloat rowMax = float.MinValue;\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( heightmap[x, y] \u003C rowMin ) rowMin = heightmap[x, y];\r\n\t\t\t\tif ( heightmap[x, y] \u003E rowMax ) rowMax = heightmap[x, y];\r\n\t\t\t}\r\n\r\n\t\t\tlock ( minMaxLock )\r\n\t\t\t{\r\n\t\t\t\tif ( rowMin \u003C minH ) minH = rowMin;\r\n\t\t\t\tif ( rowMax \u003E maxH ) maxH = rowMax;\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\tfloat range = MathF.Max( maxH - minH, 0.0001f );\r\n\r\n\t\t// Thresholds are the height positions of each color stop in [0,1].\r\n\t\t// Evenly mode uses equally spaced stops; Natural mode uses a slope-weighted\r\n\t\t// distribution so colors bunch on flat/common terrain and spread on steep slopes.\r\n\t\tfloat[] stops = thresholds;\r\n\t\tif ( dispersion == SplatDispersionMode.Natural )\r\n\t\t{\r\n\t\t\tstops = ComputeNaturalThresholds( heightmap, layers );\r\n\t\t}\r\n\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\t// Normalized height in [0,1]\r\n\t\t\t\tfloat normalizedHeight = Math.Clamp( (heightmap[x, y] - minH) / range, 0f, 1f );\r\n\r\n\t\t\t\t// Interpolated layer position from the color stop positions\r\n\t\t\t\tfloat layerPos = HeightToLayer( normalizedHeight, stops );\r\n\r\n\t\t\t\t// Soft snap to the nearest layer governed by blend strength\r\n\t\t\t\tfloat center = MathF.Round( layerPos );\r\n\t\t\t\tfloat distance = layerPos - center;\r\n\r\n\t\t\t\tfloat factor;\r\n\t\t\t\tif ( MathF.Abs( distance ) \u003C= blendStrength * 0.5f )\r\n\t\t\t\t{\r\n\t\t\t\t\tfactor = layerPos;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tfactor = center;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tsplatmap[x, y] = Math.Clamp( factor, 0f, layers - 1 );\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\treturn splatmap;\r\n\t}\r\n\r\n\tstatic float HeightToLayer( float normalizedHeight, float[] stops )\r\n\t{\r\n\t\tint count = stops.Length;\r\n\t\tif ( count \u003C= 1 ) return 0f;\r\n\t\tif ( normalizedHeight \u003C= stops[0] ) return 0f;\r\n\t\tif ( normalizedHeight \u003E= stops[count - 1] ) return count - 1f;\r\n\r\n\t\tfor ( int i = 0; i \u003C count - 1; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tif ( normalizedHeight \u003E= stops[i] \u0026\u0026 normalizedHeight \u003C= stops[i \u002B 1] )\r\n\t\t\t{\r\n\t\t\t\tfloat t = (normalizedHeight - stops[i]) / MathF.Max( stops[i \u002B 1] - stops[i], 0.0001f );\r\n\t\t\t\treturn i \u002B t;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn count - 1f;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Places color stop thresholds based on the terrain\u0027s slope-weighted height distribution.\r\n\t/// Flat, common heights get many stops (lots of color blending); steep, rare heights get few\r\n\t/// stops (few color changes), matching how terrain materials naturally appear.\r\n\t/// \u003C/summary\u003E\r\n\tstatic float[] ComputeNaturalThresholds( float[,] heightmap, int layerCount )\r\n\t{\r\n\t\tint width = heightmap.GetLength( 0 );\r\n\t\tint height = heightmap.GetLength( 1 );\r\n\r\n\t\tfloat minH = float.MaxValue, maxH = float.MinValue;\r\n\t\tobject minMaxLock = new object();\r\n\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfloat rowMin = float.MaxValue;\r\n\t\t\tfloat rowMax = float.MinValue;\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( heightmap[x, y] \u003C rowMin ) rowMin = heightmap[x, y];\r\n\t\t\t\tif ( heightmap[x, y] \u003E rowMax ) rowMax = heightmap[x, y];\r\n\t\t\t}\r\n\r\n\t\t\tlock ( minMaxLock )\r\n\t\t\t{\r\n\t\t\t\tif ( rowMin \u003C minH ) minH = rowMin;\r\n\t\t\t\tif ( rowMax \u003E maxH ) maxH = rowMax;\r\n\t\t\t}\r\n\t\t} );\r\n\t\tfloat range = MathF.Max( maxH - minH, 0.0001f );\r\n\r\n\t\t// Histogram of normalized heights, weighted by flatness (1 - slope).\r\n\t\t// Use a per-thread local histogram, then merge, to avoid lock contention.\r\n\t\tconst int bins = 128;\r\n\t\tfloat[] hist = new float[bins];\r\n\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfloat[] localHist = new float[bins];\r\n\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tfloat h = heightmap[x, y];\r\n\r\n\t\t\t\tfloat hL = heightmap[Math.Max( x - 1, 0 ), y];\r\n\t\t\t\tfloat hR = heightmap[Math.Min( x \u002B 1, width - 1 ), y];\r\n\t\t\t\tfloat hD = heightmap[x, Math.Max( y - 1, 0 )];\r\n\t\t\t\tfloat hU = heightmap[x, Math.Min( y \u002B 1, height - 1 )];\r\n\r\n\t\t\t\tfloat localDiff = (MathF.Abs( hR - hL ) \u002B MathF.Abs( hU - hD )) * 0.5f;\r\n\t\t\t\tfloat slope = Math.Clamp( localDiff / MathF.Max( range * 0.1f, 0.0001f ), 0f, 1f );\r\n\r\n\t\t\t\tfloat weight = MathF.Max( 1f - slope, 0.05f );\r\n\t\t\t\tfloat normalizedHeight = Math.Clamp( (h - minH) / range, 0f, 1f );\r\n\t\t\t\tint bin = Math.Clamp( (int)(normalizedHeight * (bins - 1)), 0, bins - 1 );\r\n\t\t\t\tlocalHist[bin] \u002B= weight;\r\n\t\t\t}\r\n\r\n\t\t\tlock ( minMaxLock )\r\n\t\t\t{\r\n\t\t\t\tfor ( int i = 0; i \u003C bins; i\u002B\u002B ) hist[i] \u002B= localHist[i];\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\t// Cumulative distribution\r\n\t\tfloat total = hist.Sum();\r\n\t\tif ( total \u003C= 0f )\r\n\t\t{\r\n\t\t\ttotal = 1f;\r\n\t\t\tfor ( int i = 0; i \u003C bins; i\u002B\u002B ) hist[i] = 1f;\r\n\t\t}\r\n\r\n\t\tfloat[] thresholds = new float[layerCount];\r\n\t\tthresholds[0] = 0f;\r\n\t\tthresholds[layerCount - 1] = 1f;\r\n\r\n\t\tfloat cum = 0f;\r\n\t\tint binIndex = 0;\r\n\t\tfor ( int i = 1; i \u003C layerCount - 1; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tfloat target = (i / (float)(layerCount - 1)) * total;\r\n\t\t\twhile ( binIndex \u003C bins - 1 \u0026\u0026 cum \u003C target )\r\n\t\t\t{\r\n\t\t\t\tcum \u002B= hist[binIndex];\r\n\t\t\t\tbinIndex\u002B\u002B;\r\n\t\t\t}\r\n\t\t\tthresholds[i] = binIndex / (float)(bins - 1);\r\n\t\t}\r\n\r\n\t\t// Ensure monotonic\r\n\t\tfor ( int i = 1; i \u003C layerCount; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tthresholds[i] = MathF.Max( thresholds[i], thresholds[i - 1] );\r\n\t\t}\r\n\r\n\t\treturn thresholds;\r\n\t}\r\n\r\n\tpublic static SKBitmap SplatmapToBitMap( float[,] splatmap, SKColor[] colors )\r\n\t{\r\n\t\tint width = splatmap.GetLength( 0 );\r\n\t\tint height = splatmap.GetLength( 1 );\r\n\t\tSKBitmap bitmap = new SKBitmap( width, height );\r\n\r\n\t\tParallel.For( 0, height, y =\u003E\r\n\t\t{\r\n\t\t\tfor ( int x = 0; x \u003C width; x\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\t// Map the splatmap value to a valid layer position\r\n\t\t\t\tfloat layerPos = Math.Clamp( splatmap[x, y], 0f, colors.Length - 1f );\r\n\t\t\t\tint layer0 = (int)MathF.Floor( layerPos );\r\n\t\t\t\tint layer1 = Math.Min( layer0 \u002B 1, colors.Length - 1 );\r\n\t\t\t\tfloat t = layerPos - layer0;\r\n\r\n\t\t\t\t// Blend between the two nearest layer colors\r\n\t\t\t\tvar c0 = colors[layer0];\r\n\t\t\t\tvar c1 = colors[layer1];\r\n\t\t\t\tvar color = new SKColor(\r\n\t\t\t\t\t(byte)MathX.LerpTo( c0.Red, c1.Red, t ),\r\n\t\t\t\t\t(byte)MathX.LerpTo( c0.Green, c1.Green, t ),\r\n\t\t\t\t\t(byte)MathX.LerpTo( c0.Blue, c1.Blue, t ),\r\n\t\t\t\t\t(byte)MathX.LerpTo( c0.Alpha, c1.Alpha, t ) );\r\n\r\n\t\t\t\tbitmap.SetPixel( x, y, color );\r\n\t\t\t}\r\n\t\t} );\r\n\r\n\t\treturn bitmap;\r\n\t}\r\n\r\n\tpublic static void SaveSplatmapAsPng(\r\n\tSKBitmap bitmap,\r\n\tstring filename,\r\n\tfloat rotationDegrees = 270f,\r\n\tbool reverseHorizontal = false,\r\n\tbool reverseVertical = true\r\n)\r\n\t{\r\n\t\tint width = bitmap.Width;\r\n\t\tint height = bitmap.Height;\r\n\t\tusing var transformedBitmap = new SKBitmap( width, height );\r\n\t\tusing var canvas = new SKCanvas( transformedBitmap );\r\n\r\n\t\t// Clear the canvas with transparency\r\n\t\tcanvas.Clear( SKColors.Transparent );\r\n\t\t// Apply transformations\r\n\t\tcanvas.Save();\r\n\t\t// Translate to the center of the canvas for rotation and flipping\r\n\t\tcanvas.Translate( width / 2f, height / 2f );\r\n\t\t// Apply flipping first\r\n\t\tfloat scaleX = reverseHorizontal ? -1f : 1f;\r\n\t\tfloat scaleY = reverseVertical ? -1f : 1f;\r\n\t\tcanvas.Scale( scaleX, scaleY );\r\n\r\n\t\t// Apply rotation\r\n\t\tif ( rotationDegrees != 0 )\r\n\t\t{\r\n\t\t\tcanvas.RotateDegrees( rotationDegrees );\r\n\t\t}\r\n\r\n\t\t// Translate back to ensure the image is drawn correctly\r\n\t\tcanvas.Translate( -width / 2f, -height / 2f );\r\n\t\t// Draw the original bitmap onto the transformed canvas\r\n\t\tcanvas.DrawBitmap( bitmap, 0, 0 );\r\n\t\t// Restore the canvas to finalize the transformations\r\n\t\tcanvas.Restore();\r\n\t\t// Flush the canvas\r\n\t\tcanvas.Flush();\r\n\r\n\t\t// Save the transformed bitmap as a PNG file\r\n\t\tusing var pixmap = transformedBitmap.PeekPixels();\r\n\t\tusing var image = SKImage.FromPixels( pixmap );\r\n\t\tusing var data = image.Encode( SKEncodedImageFormat.Png, 100 );\r\n\r\n\t\tusing var stream = File.OpenWrite( filename );\r\n\t\tdata.SaveTo( stream );\r\n\t}\r\n}\r\n\r\n/// \u003Csummary\u003E\r\n/// An icon \u002B label picker whose options wrap onto multiple lines.\r\n/// Mimics the interface of \u003Csee cref=\u0022Editor.SegmentedControl\u0022/\u003E (AddOption, Selected, SelectedIndex, OnSelectedChanged).\r\n/// \u003C/summary\u003E\r\npublic class WrapSelector : Widget\r\n{\r\n\treadonly List\u003CWrapOption\u003E _buttons = new();\r\n\treadonly List\u003Cstring\u003E _names = new();\r\n\r\n\tpublic string Selected\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tfor ( int i = 0; i \u003C _buttons.Count; i\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( _buttons[i].IsActive )\r\n\t\t\t\t\treturn _names[i];\r\n\t\t\t}\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tset\r\n\t\t{\r\n\t\t\tSetSelected( value );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic int SelectedIndex\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tfor ( int i = 0; i \u003C _buttons.Count; i\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( _buttons[i].IsActive )\r\n\t\t\t\t\treturn i;\r\n\t\t\t}\r\n\t\t\treturn -1;\r\n\t\t}\r\n\t\tset\r\n\t\t{\r\n\t\t\tif ( value \u003E= 0 \u0026\u0026 value \u003C _names.Count )\r\n\t\t\t\tSetSelected( _names[value] );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic Action\u003Cstring\u003E OnSelectedChanged { get; set; }\r\n\r\n\tpublic WrapSelector( Widget parent = null ) : base( parent )\r\n\t{\r\n\t\tLayout = Layout.Row();\r\n\t\tLayout.Spacing = 4;\r\n\t\tSetSizeMode( SizeMode.CanGrow, SizeMode.CanGrow );\r\n\t\tHorizontalSizeMode = SizeMode.Flexible;\r\n\t}\r\n\r\n\tpublic void AddOption( string name, string icon = null, int? count = null, string label = null )\r\n\t{\r\n\t\tif ( _names.Contains( name ) ) return;\r\n\r\n\t\tif ( string.IsNullOrEmpty( name ) )\r\n\t\t{\r\n\t\t\t// Special \u0022clear\u0022 option, shown with the close icon\r\n\t\t\ticon ??= \u0022close\u0022;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\ticon ??= IconFor( name );\r\n\t\t}\r\n\r\n\t\tvar option = new WrapOption( this, label ?? (string.IsNullOrEmpty( name ) ? \u0022Clear\u0022 : name), icon );\r\n\t\toption.IsActive = false;\r\n\t\toption.MouseLeftPress = () =\u003E SetSelected( name );\r\n\r\n\t\t_names.Add( name );\r\n\t\t_buttons.Add( option );\r\n\t\tLayout.Add( option );\r\n\t}\r\n\r\n\tpublic bool HasOption( string name ) =\u003E _names.Contains( name );\r\n\r\n\tpublic new void DestroyChildren()\r\n\t{\r\n\t\tforeach ( var b in _buttons )\r\n\t\t{\r\n\t\t\tif ( b.IsValid() )\r\n\t\t\t\tb.Destroy();\r\n\t\t}\r\n\t\t_buttons.Clear();\r\n\t\t_names.Clear();\r\n\t}\r\n\r\n\tvoid SetSelected( string name )\r\n\t{\r\n\t\tbool changed = Selected != name;\r\n\r\n\t\tfor ( int i = 0; i \u003C _buttons.Count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\t_buttons[i].IsActive = _names[i] == name;\r\n\t\t}\r\n\r\n\t\tif ( changed )\r\n\t\t{\r\n\t\t\tOnSelectedChanged?.Invoke( name );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic string IconFor( string name )\r\n\t{\r\n\t\tswitch ( name )\r\n\t\t{\r\n\t\t\tcase \u0022Islands\u0022: return \u0022landscape\u0022;\r\n\t\t\tcase \u0022Mountainous\u0022: return \u0022terrain\u0022;\r\n\t\t\tcase \u0022Planetary\u0022: return \u0022public\u0022;\r\n\t\t\tcase \u0022Realistic\u0022: return \u0022photo\u0022;\r\n\t\t\tcase \u0022Sea\u0022: return \u0022water\u0022;\r\n\t\t\tcase \u0022Volcanic\u0022: return \u0022volcano\u0022;\r\n\t\t\tcase \u0022Default\u0022: return \u0022shapes\u0022;\r\n\t\t\tcase \u0022Archipelagos\u0022: return \u0022scatter_plot\u0022;\r\n\t\t\tcase \u0022Atoll\u0022: return \u0022crop_square\u0022;\r\n\t\t\tcase \u0022Islets\u0022: return \u0022blur_on\u0022;\r\n\t\t\tcase \u0022Oceanic\u0022: return \u0022waves\u0022;\r\n\t\t\tcase \u0022Cliff\u0022: return \u0022terrain\u0022;\r\n\t\t\tcase \u0022Craters\u0022: return \u0022brightness_low\u0022;\r\n\t\t\tcase \u0022Hills\u0022: return \u0022landscape\u0022;\r\n\t\t\tcase \u0022Plateau\u0022: return \u0022square_foot\u0022;\r\n\t\t\tcase \u0022SeaBed\u0022: return \u0022water\u0022;\r\n\t\t\tcase \u0022Sharded\u0022: return \u0022dashboard\u0022;\r\n\t\t\tdefault: return \u0022shapes\u0022;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/// \u003Csummary\u003E\r\n/// A single option in a \u003Csee cref=\u0022WrapSelector\u0022/\u003E: an icon with a text label underneath.\r\n/// \u003C/summary\u003E\r\npublic class WrapOption : Widget\r\n{\r\n\tpublic string Icon { get; }\r\n\tpublic string Text { get; }\r\n\tpublic bool IsActive { get; set; }\r\n\r\n\tpublic WrapOption( Widget parent, string text, string icon ) : base( parent )\r\n\t{\r\n\t\tText = text;\r\n\t\tIcon = icon;\r\n\t\tCursor = CursorShape.Finger;\r\n\t\tToolTip = text;\r\n\t\tMinimumSize = new Vector2( 52, 44 );\r\n\t}\r\n\r\n\tprotected override Vector2 SizeHint()\r\n\t{\r\n\t\tPaint.SetDefaultFont( 7 );\r\n\t\tvar textRect = Paint.MeasureText( new Rect( 0, 0, 60, 100 ), Text, TextFlag.WordWrap );\r\n\t\treturn new Vector2( MathF.Max( textRect.Size.x \u002B 10, 52 ), textRect.Size.y \u002B 24 );\r\n\t}\r\n\r\n\tprotected override void OnPaint()\r\n\t{\r\n\t\tbase.OnPaint();\r\n\r\n\t\tPaint.Antialiasing = true;\r\n\t\tPaint.ClearPen();\r\n\r\n\t\tvar rect = LocalRect;\r\n\r\n\t\tvar background = IsActive ? Theme.Primary.WithAlpha( 0.25f ) : Theme.ControlBackground.WithAlpha( 0.6f );\r\n\t\tif ( Paint.HasMouseOver ) background = background.Lighten( 0.1f );\r\n\t\tPaint.SetBrush( background );\r\n\t\tPaint.DrawRect( rect, Theme.ControlRadius );\r\n\r\n\t\tvar iconRect = new Rect( rect.Left, rect.Top \u002B 4, rect.Width, rect.Height * 0.55f );\r\n\t\tvar color = IsActive ? Theme.Primary : Theme.Text.WithAlpha( 0.8f );\r\n\t\tPaint.SetPen( color );\r\n\t\tPaint.DrawIcon( iconRect, Icon, 18, TextFlag.Center );\r\n\r\n\t\tvar textRect = new Rect( rect.Left \u002B 2, rect.Top \u002B rect.Height * 0.55f, rect.Width - 4, rect.Height * 0.45f );\r\n\t\tPaint.SetDefaultFont( 7 );\r\n\t\tPaint.DrawText( textRect, Text, TextFlag.Center | TextFlag.WordWrap );\r\n\t}\r\n}\r\n\r\n/// \u003Csummary\u003E\r\n/// A clickable box representing one terrain grid cell (or the \u0022All\u0022 box). Shows the tile\u0027s\r\n/// current category name and is highlighted when selected.\r\n/// \u003C/summary\u003E\r\npublic class TileGridBox : Widget\r\n{\r\n\tpublic int Index { get; }\r\n\tpublic string Text { get; set; }\r\n\tstring _subtitle;\r\n\tpublic bool IsSelected { get; set; }\r\n\tpublic Action OnClicked { get; set; }\r\n\r\n\tpublic TileGridBox( Widget parent, int index, string text, string subtitle = null ) : base( parent )\r\n\t{\r\n\t\tIndex = index;\r\n\t\tText = text;\r\n\t\t_subtitle = subtitle;\r\n\t\tCursor = CursorShape.Finger;\r\n\t\tToolTip = subtitle ?? text;\r\n\t\tMinimumSize = new Vector2( 44, 40 );\r\n\t\tMouseLeftPress = () =\u003E OnClicked?.Invoke();\r\n\t}\r\n\r\n\tprotected override Vector2 SizeHint()\r\n\t{\r\n\t\treturn new Vector2( 48, 44 );\r\n\t}\r\n\r\n\tprotected override void OnPaint()\r\n\t{\r\n\t\tbase.OnPaint();\r\n\r\n\t\tPaint.Antialiasing = true;\r\n\t\tPaint.ClearPen();\r\n\r\n\t\tvar rect = LocalRect;\r\n\t\tvar bg = IsSelected ? Theme.Primary.WithAlpha( 0.3f ) : Theme.ControlBackground.WithAlpha( 0.6f );\r\n\t\tif ( Paint.HasMouseOver ) bg = bg.Lighten( 0.1f );\r\n\t\tPaint.SetBrush( bg );\r\n\t\tPaint.DrawRect( rect, Theme.ControlRadius );\r\n\r\n\t\tif ( IsSelected )\r\n\t\t{\r\n\t\t\tPaint.SetPen( Theme.Primary, 2 );\r\n\t\t\tPaint.DrawRect( rect, Theme.ControlRadius );\r\n\t\t}\r\n\r\n\t\tvar textRect = new Rect( rect.Left \u002B 3, rect.Top \u002B 2, rect.Width - 6, rect.Height - 4 );\r\n\r\n\t\tPaint.SetDefaultFont( 7 );\r\n\t\tPaint.SetPen( IsSelected ? Theme.Primary : Theme.Text.WithAlpha( 0.9f ) );\r\n\t\tPaint.DrawText( textRect, Text ?? \u0022?\u0022, TextFlag.Center | TextFlag.WordWrap );\r\n\t}\r\n}\r\n\r\n/// \u003Csummary\u003E\r\n/// A compact dropdown-style picker used in the tile grid. Shows a button with the current value\r\n/// and opens a popup listing the available options.\r\n/// \u003C/summary\u003E\r\npublic class TileDropdownPicker : Widget\r\n{\r\n\tstring[] _options;\r\n\tButton _button;\r\n\tstring _label;\r\n\r\n\tpublic string Selected { get; private set; }\r\n\tpublic Action\u003Cstring\u003E OnPicked { get; set; }\r\n\r\n\tpublic TileDropdownPicker( Widget parent, string label, string[] options ) : base( parent )\r\n\t{\r\n\t\t_label = label;\r\n\t\t_options = options ?? Array.Empty\u003Cstring\u003E();\r\n\r\n\t\tLayout = Layout.Column();\r\n\t\tLayout.Spacing = 2;\r\n\r\n\t\tvar labelWidget = new Label( label );\r\n\t\tlabelWidget.SetStyles( \u0022font-size: 9px; color: #999;\u0022 );\r\n\t\tLayout.Add( labelWidget );\r\n\r\n\t\t_button = new Button( Selected ?? \u0022None\u0022, this );\r\n\t\t_button.FixedHeight = Theme.RowHeight;\r\n\t\t_button.Clicked \u002B= OpenMenu;\r\n\t\tLayout.Add( _button );\r\n\t}\r\n\r\n\tpublic void SetOptions( string[] options )\r\n\t{\r\n\t\t_options = options ?? Array.Empty\u003Cstring\u003E();\r\n\t}\r\n\r\n\tpublic void SetSelected( string value )\r\n\t{\r\n\t\tSelected = value;\r\n\t\tif ( _button != null \u0026\u0026 _button.IsValid() )\r\n\t\t\t_button.Text = value ?? \u0022None\u0022;\r\n\t}\r\n\r\n\tvoid OpenMenu()\r\n\t{\r\n\t\tvar popup = new PopupWidget( null );\r\n\t\tpopup.Layout = Layout.Column();\r\n\t\tpopup.Layout.Margin = 4;\r\n\t\tpopup.Width = Math.Max( 180, _button.ScreenRect.Width );\r\n\r\n\t\tvar scroller = popup.Layout.Add( new ScrollArea( this ), 1 );\r\n\t\tscroller.Canvas = new Widget( scroller )\r\n\t\t{\r\n\t\t\tLayout = Layout.Column(),\r\n\t\t\tVerticalSizeMode = SizeMode.CanGrow | SizeMode.Expand\r\n\t\t};\r\n\r\n\t\tforeach ( var option in _options )\r\n\t\t{\r\n\t\t\tvar item = scroller.Canvas.Layout.Add( new Button( option ) );\r\n\t\t\titem.MouseLeftPress = () =\u003E\r\n\t\t\t{\r\n\t\t\t\tSetSelected( option );\r\n\t\t\t\tOnPicked?.Invoke( option );\r\n\t\t\t\tpopup.Close();\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tpopup.Position = _button.ScreenRect.BottomLeft;\r\n\t\tpopup.Visible = true;\r\n\t\tpopup.AdjustSize();\r\n\t\tpopup.ConstrainToScreen();\r\n\t}\r\n\r\n\tprotected override void OnPaint()\r\n\t{\r\n\t\tPaint.ClearPen();\r\n\t\tPaint.SetBrush( Theme.ControlBackground );\r\n\t\tPaint.DrawRect( LocalRect, Theme.ControlRadius );\r\n\t\tbase.OnPaint();\r\n\t}\r\n}\r\n\r\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/TerrainShapes/Realistic.cs","FileName":"Realistic.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using Editor;\nusing Sandbox;\nusing System;\n\nnamespace Sturnus.TerrainGenerationTool;\npublic static class Realistic\n{\n\tpublic static float Default(\n\tint x,\n\tint y,\n\tint width,\n\tint height,\n\tlong seed,\n\tfloat minHeight,\n\tbool domainWarping,\n\tfloat domainWarpingSize,\n\tfloat domainWarpingStrength)\n\t{\n\t\t// Normalize coordinates to [-1, 1]\n\t\tfloat nx = (x / (float)width) * 2 - 1;\n\t\tfloat ny = (y / (float)height) * 2 - 1;\n\n\t\t// Apply domain warping for natural distortion\n\t\tif ( domainWarping )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed, nx * domainWarpingSize, ny * domainWarpingSize ) * domainWarpingStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 1, nx * domainWarpingSize, ny * domainWarpingSize ) * domainWarpingStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\tfloat baseTerrain = OpenSimplex2S.Noise2( seed, nx, ny );\n\n\t\t// Create hill/valley transitions with non-linear blending\n\t\tfloat hillFactor = MathF.Pow( baseTerrain, 6 ); // Emphasize hills\n\t\tfloat valleyFactor = 1.0f - MathF.Pow( 1.0f - baseTerrain, 1 ); // Emphasize valleys\n\n\t\t// Use smoothstep-like function for non-linear blending\n\t\tfloat smoothTransition = SmoothStep( 0.75f, 1.25f, baseTerrain );\n\t\tfloat terrainShape = MathX.Lerp( valleyFactor, hillFactor, smoothTransition );\n\n\t\t// Add finer details\n\t\tfloat fineNoise = OpenSimplex2S.Noise2( seed \u002B 2, nx * 8.0f, ny * 8.0f ) * 0.1f;\n\n\t\t// Combine base terrain with fine details\n\t\tfloat heightValue = terrainShape \u002B fineNoise;\n\n\t\t// Add a baseline value to ensure no flat zero areas\n\t\tfloat baseline = minHeight; // Minimum height\n\t\theightValue = MathF.Max( heightValue, baseline );\n\n\t\t// Normalize height to [0, 1]\n\t\treturn Math.Clamp( heightValue, 0.0f, 1.0f );\n\t}\n\n\tpublic static float Hills( int x, int y, int width, int height, long seed, float minHeight, bool warp, float warpSize = 0.1f, float warpStrength = 0.5f )\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\n\t\tfloat hillHeight = 0.6f;   // Maximum height of the hills\n\t\tfloat hillFrequency = 3f; // Frequency of the hills\n\t\tfloat noiseStrength = 0f; // Strength of noise for natural detail\n\n\t\t// Apply domain warping for irregularity\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Base overlapping hills\n\t\tfloat hillBase1 = MathF.Sin( nx * hillFrequency * MathF.PI ) \u002B MathF.Cos( ny * hillFrequency * MathF.PI );\n\t\tfloat hillBase2 = MathF.Sin( ny * (hillFrequency * 0.75f) * MathF.PI ) \u002B MathF.Cos( nx * (hillFrequency * 0.75f) * MathF.PI );\n\t\tfloat combinedHills = (hillBase1 \u002B hillBase2) / 5f; // Blend two hill patterns\n\t\tcombinedHills = MathF.Abs( combinedHills ); // Ensure positive-only values\n\n\t\t// Add noise for natural detail\n\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed, nx * 6.0f, ny * 6.0f ) * noiseStrength;\n\t\tfloat fineNoise = OpenSimplex2S.Noise2( seed \u002B 1, nx * 12.0f, ny * 12.0f ) * (noiseStrength / 2);\n\n\t\t// Combine hills and noise\n\t\tfloat heightValue = (combinedHills * hillHeight) \u002B minHeight \u002B fineNoise;\n\t\t// Ensure minimum base height\n\t\n\t\t// Clamp the final height value\n\t\treturn Math.Clamp( heightValue, 0, 1 );\n\t}\n\n\tpublic static float Plateau(\n\t\tint x,\n\t\tint y,\n\t\tint width,\n\t\tint height,\n\t\tlong seed,\n\t\tfloat minHeight,\n\t\tbool warp,\n\t\tfloat warpSize,\n\t\tfloat warpStrength\n\t\n\t)\n\t{\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\t\tfloat nx = (x / (float)width) * 2 - 1; // Normalize x to range [-1, 1]\n\t\tfloat ny = (y / (float)height) * 2 - 1; // Normalize y to range [-1, 1]\n\n\t\tfloat plateauHeight = 0.8f;   // Maximum height of the plateau\n\t\tfloat widthRatio = 0.75f;     // Width of the side that does not extend to the edge\n\t\tfloat slopeWidthRatio = 0.02f; // Width of the slope transition\n\t\tfloat slopeNoiseStrength = 0.1f; // Strength of additional noise on slopes\n\t\tfloat baseHeight = 0.1f;     // Minimum terrain height\n\t\tfloat noiseStrength = 0f;  // Strength of noise for natural variation\n\t\tfloat cutInOutStrength = 2f; // Strength of the cut-ins and jut-outs\n\t\tfloat topNoiseStrength = 0f; // Reduced noise strength for the plateau to\n\n\t\t// Apply domain warping for irregularity\n\t\tif ( warp )\n\t\t{\n\t\t\tfloat warpX = OpenSimplex2S.Noise2( seed \u002B 10, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tfloat warpY = OpenSimplex2S.Noise2( seed \u002B 11, nx * warpSize, ny * warpSize ) * warpStrength;\n\t\t\tnx \u002B= warpX;\n\t\t\tny \u002B= warpY;\n\t\t}\n\n\t\t// Define the plateau boundaries\n\t\tfloat plateauStartX = -1f; // Left edge\n\t\tfloat plateauEndX = widthRatio * 2 - 1; // End of the one side\n\t\tfloat plateauStartY = -1f; // Bottom edge\n\t\tfloat plateauEndY = 1f; // Top edge (extends to the edge)\n\n\t\t// Check if the current point is within the flat plateau region\n\t\tbool isFlatPlateau = nx \u003E= plateauStartX \u0026\u0026 nx \u003C= plateauEndX \u0026\u0026 ny \u003E= plateauStartY \u0026\u0026 ny \u003C= plateauEndY;\n\n\t\t// Flat plateau height\n\t\tfloat heightValue = isFlatPlateau ? plateauHeight : 0f;\n\n\t\t// Add slopes with jut-outs and cut-ins for smooth, natural drop-offs\n\t\tif ( !isFlatPlateau )\n\t\t{\n\t\t\t// Add noise-based cut-ins and jut-outs\n\t\t\tfloat noiseCut = OpenSimplex2S.Noise2( seed \u002B 20, nx * 10.0f, ny * 10.0f ) * cutInOutStrength;\n\t\t\t// Generate additional noise for slopes\n\t\t\tfloat slopeNoise = OpenSimplex2S.Noise2( seed \u002B 30, nx * 15.0f, ny * 15.0f ) * slopeNoiseStrength;\n\n\t\t\t// Left slope\n\t\t\tif ( nx \u003C plateauStartX )\n\t\t\t{\n\t\t\t\tfloat slope = Math.Clamp( 1f - MathF.Abs( (nx - plateauStartX) / slopeWidthRatio ) \u002B noiseCut  \u002B slopeNoise, 0f, 1f );\n\t\t\t\theightValue = Math.Max( heightValue, slope * plateauHeight );\n\t\t\t}\n\t\t\t// Right slope (for the single side ending early)\n\t\t\telse if ( nx \u003E plateauEndX )\n\t\t\t{\n\t\t\t\tfloat slope = Math.Clamp( 1f - MathF.Abs( (nx - plateauEndX) / slopeWidthRatio ) \u002B noiseCut \u002B slopeNoise, 0f, 1f );\n\t\t\t\theightValue = Math.Max( heightValue, slope * plateauHeight );\n\t\t\t}\n\t\t\t// Bottom slope\n\t\t\tif ( ny \u003C plateauStartY )\n\t\t\t{\n\t\t\t\tfloat slope = Math.Clamp( 1f - MathF.Abs( (ny - plateauStartY) / slopeWidthRatio ) \u002B noiseCut \u002B slopeNoise, 0f, 1f );\n\t\t\t\theightValue = Math.Max( heightValue, slope * plateauHeight );\n\t\t\t}\n\t\t\t// Top slope\n\t\t\tif ( ny \u003E plateauEndY )\n\t\t\t{\n\t\t\t\tfloat slope = Math.Clamp( 1f - MathF.Abs( (ny - plateauEndY) / slopeWidthRatio ) \u002B noiseCut \u002B slopeNoise, 0f, 1f );\n\t\t\t\theightValue = Math.Max( heightValue, slope * plateauHeight );\n\t\t\t}\n\t\t}\n\n\t\t// Add noise for terrain variation\n\t\tfloat baseNoise = OpenSimplex2S.Noise2( seed \u002B 100, nx * 8.0f, ny * 8.0f ) * topNoiseStrength;\n\n\t\tfloat fineNoise = OpenSimplex2S.Noise2( seed \u002B 1, nx * 16.0f, ny * 16.0f ) * (noiseStrength / 2);\n\n\t\t// Add the noise and base height to the terrain\n\t\theightValue \u002B= baseNoise \u002B fineNoise \u002B baseHeight;\n\n\t\t// Ensure the base terrain height does not fall below baseHeight\n\t\theightValue = Math.Max( heightValue, baseHeight );\n\n\t\tvar heightValueBase = Math.Max( heightValue, minHeight );\n\n\t\t// Clamp the final height\n\t\treturn Math.Clamp( heightValueBase, 0, 1 );\n\t}\n\n\tprivate static float SmoothStep( float edge0, float edge1, float x )\n\t{\n\t\tx = Math.Clamp( (x - edge0) / (edge1 - edge0), 0.0f, 1.0f ); // Normalize to [0, 1]\n\t\treturn x * x * (3 - 2 * x); // Smoothstep formula\n\t}\n\n}\n"},{"Ident":"sturnus.terraingenerationtool","Path":"Editor/Features/RiverStream.cs","FileName":"RiverStream.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":339832,"Code":"using Editor;\nusing Sandbox;\nusing System;\n\nnamespace Sturnus.TerrainGenerationTool.RiverStream;\npublic static class RiverStream\n{\n\tpublic static float[,] AddRiversAndStreams(\n\tfloat[,] heightmap,\n\tint frequency, // Number of rivers/streams\n\tfloat widthScale, // Relative width of rivers/streams\n\tlong seed\n)\n\t{\n\t\tint width = heightmap.GetLength( 0 );\n\t\tint height = heightmap.GetLength( 1 );\n\t\tfloat[,] modifiedHeightmap = (float[,])heightmap.Clone();\n\t\tRandom random = new Random( (int)(seed \u0026 0xFFFFFFFF) );\n\n\t\t// Generate river starting points based on frequency\n\t\tfor ( int i = 0; i \u003C frequency; i\u002B\u002B )\n\t\t{\n\t\t\tint startX = random.Next( 0, width );\n\t\t\tint startY = random.Next( 0, height );\n\n\t\t\t// Ensure the river starts at a relatively high elevation\n\t\t\twhile ( modifiedHeightmap[startX, startY] \u003C 0.5f )\n\t\t\t{\n\t\t\t\tstartX = random.Next( 0, width );\n\t\t\t\tstartY = random.Next( 0, height );\n\t\t\t}\n\n\t\t\t// Trace the river path\n\t\t\tAddRiverPath( modifiedHeightmap, startX, startY, width, height, widthScale, random );\n\t\t}\n\n\t\treturn modifiedHeightmap;\n\t}\n\n\tprivate static void AddRiverPath(\n\t\tfloat[,] heightmap,\n\t\tint startX,\n\t\tint startY,\n\t\tint width,\n\t\tint height,\n\t\tfloat widthScale,\n\t\tRandom random\n\t)\n\t{\n\t\tint currentX = startX;\n\t\tint currentY = startY;\n\n\t\t// Determine the river width based on widthScale\n\t\tint riverWidth = Math.Max( 1, (int)(widthScale * width) );\n\n\t\tfor ( int steps = 0; steps \u003C width * 2; steps\u002B\u002B ) // Ensure rivers stretch long distances\n\t\t{\n\t\t\t// Lower the terrain at the current position to form a river bed\n\t\t\tCarveRiverAtPosition( heightmap, currentX, currentY, riverWidth, width, height );\n\n\t\t\t// Find the next position by prioritizing downhill movement\n\t\t\t(int nextX, int nextY) = FindNextRiverPosition( heightmap, currentX, currentY, width, height, random );\n\n\t\t\t// Stop if the river can no longer flow\n\t\t\tif ( nextX == currentX \u0026\u0026 nextY == currentY )\n\t\t\t\tbreak;\n\n\t\t\tcurrentX = nextX;\n\t\t\tcurrentY = nextY;\n\t\t}\n\t}\n\n\tprivate static (int, int) FindNextRiverPosition(\n\t\tfloat[,] heightmap,\n\t\tint x,\n\t\tint y,\n\t\tint width,\n\t\tint height,\n\t\tRandom random\n\t)\n\t{\n\t\tfloat currentHeight = heightmap[x, y];\n\t\tint nextX = x;\n\t\tint nextY = y;\n\t\tfloat lowestHeight = currentHeight;\n\n\t\t// Check all 8 neighbors to find the steepest downhill path\n\t\tfor ( int offsetY = -1; offsetY \u003C= 1; offsetY\u002B\u002B )\n\t\t{\n\t\t\tfor ( int offsetX = -1; offsetX \u003C= 1; offsetX\u002B\u002B )\n\t\t\t{\n\t\t\t\tint nx = x \u002B offsetX;\n\t\t\t\tint ny = y \u002B offsetY;\n\n\t\t\t\t// Skip out-of-bounds and current position\n\t\t\t\tif ( nx \u003C 0 || nx \u003E= width || ny \u003C 0 || ny \u003E= height || (nx == x \u0026\u0026 ny == y) )\n\t\t\t\t\tcontinue;\n\n\t\t\t\tfloat neighborHeight = heightmap[nx, ny];\n\t\t\t\tif ( neighborHeight \u003C lowestHeight )\n\t\t\t\t{\n\t\t\t\t\tlowestHeight = neighborHeight;\n\t\t\t\t\tnextX = nx;\n\t\t\t\t\tnextY = ny;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add slight randomness to avoid perfectly straight rivers\n\t\tif ( random.NextDouble() \u003C 0.3 ) // 30% chance to adjust path\n\t\t{\n\t\t\tnextX = Math.Clamp( nextX \u002B random.Next( -1, 2 ), 0, width - 1 );\n\t\t\tnextY = Math.Clamp( nextY \u002B random.Next( -1, 2 ), 0, height - 1 );\n\t\t}\n\n\t\treturn (nextX, nextY);\n\t}\n\n\tprivate static void CarveRiverAtPosition(\n\t\tfloat[,] heightmap,\n\t\tint x,\n\t\tint y,\n\t\tint riverWidth,\n\t\tint width,\n\t\tint height\n\t)\n\t{\n\t\tfor ( int offsetY = -riverWidth / 2; offsetY \u003C= riverWidth / 2; offsetY\u002B\u002B )\n\t\t{\n\t\t\tfor ( int offsetX = -riverWidth / 2; offsetX \u003C= riverWidth / 2; offsetX\u002B\u002B )\n\t\t\t{\n\t\t\t\tint nx = x \u002B offsetX;\n\t\t\t\tint ny = y \u002B offsetY;\n\n\t\t\t\t// Ensure we\u0027re within bounds\n\t\t\t\tif ( nx \u003E= 0 \u0026\u0026 nx \u003C width \u0026\u0026 ny \u003E= 0 \u0026\u0026 ny \u003C height )\n\t\t\t\t{\n\t\t\t\t\t// Lower the terrain for the river bed\n\t\t\t\t\tfloat distance = MathF.Sqrt( offsetX * offsetX \u002B offsetY * offsetY );\n\t\t\t\t\tfloat factor = Math.Clamp( 1.0f - (distance / (riverWidth / 2.0f)), 0.0f, 1.0f );\n\t\t\t\t\theightmap[nx, ny] -= factor * 0.03f; // Adjust depth for river carving\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\n\n\n}\n"}]}