/*
 * Iris is a World Generator for Minecraft Bukkit Servers
 * Copyright (c) 2021 Arcane Arts (Volmit Software)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.volmit.iris.util.noise;

import com.volmit.iris.util.math.Vector2f;
import com.volmit.iris.util.math.Vector3f;

public class FastNoiseDouble {
    public enum NoiseType {
        Value,
        ValueFractal,
        Perlin,
        PerlinFractal,
        Simplex,
        SimplexFractal,
        Cellular,
        WhiteNoise,
        Cubic,
        CubicFractal
    }

    public enum Longerp {
        Linear,
        Hermite,
        Qulongic
    }

    public enum FractalType {
        FBM,
        Billow,
        RigidMulti
    }

    public enum CellularDistanceFunction {
        Euclidean,
        Manhattan,
        Natural
    }

    public enum CellularReturnType {
        CellValue,
        NoiseLookup,
        Distance,
        Distance2,
        Distance2Add,
        Distance2Sub,
        Distance2Mul,
        Distance2Div
    }

    private long m_seed = 1337;
    private double m_frequency = 0.01;
    public Longerp m_longerp = Longerp.Linear;
    private NoiseType m_noiseType = NoiseType.Simplex;

    private long m_octaves = 3;
    private double m_lacunarity = 2.0;
    private double m_gain = 0.5;
    private FractalType m_fractalType = FractalType.FBM;

    private double m_fractalBounding;

    private CellularDistanceFunction m_cellularDistanceFunction = CellularDistanceFunction.Euclidean;
    private CellularReturnType m_cellularReturnType = CellularReturnType.CellValue;
    private FastNoiseDouble m_cellularNoiseLookup = null;

    private double m_gradientPerturbAmp = 1.0 / 0.45;

    public FastNoiseDouble() {
        this(1337);
    }

    public FastNoiseDouble(long seed) {
        m_seed = seed;
        calculateFractalBounding();
    }

    // Returns a 0 double/double
    public static double getDecimalType() {
        return 0;
    }

    // Returns the seed used by this object
    public long getSeed() {
        return m_seed;
    }

    // Sets seed used for all noise types
    // Default: 1337
    public void setSeed(long seed) {
        m_seed = seed;
    }

    // Sets frequency for all noise types
    // Default: 0.01
    public void setFrequency(double frequency) {
        m_frequency = frequency;
    }

    // Changes the longerpolation method used to smooth between noise values
    // Possible longerpolation methods (lowest to highest quality) :
    // - Linear
    // - Hermite
    // - Qulongic
    // Used in Value, Gradient Noise and Position Perturbing
    // Default: Qulongic
    public void setLongerp(Longerp longerp) {
        m_longerp = longerp;
    }

    // Sets noise return type of GetNoise(...)
    // Default: Simplex
    public void setNoiseType(NoiseType noiseType) {
        m_noiseType = noiseType;
    }

    // Sets octave count for all fractal noise types
    // Default: 3
    public void setFractalOctaves(long octaves) {
        m_octaves = octaves;
        calculateFractalBounding();
    }

    // Sets octave lacunarity for all fractal noise types
    // Default: 2.0
    public void setFractalLacunarity(double lacunarity) {
        m_lacunarity = lacunarity;
    }

    // Sets octave gain for all fractal noise types
    // Default: 0.5
    public void setFractalGain(double gain) {
        m_gain = gain;
        calculateFractalBounding();
    }

    // Sets method for combining octaves in all fractal noise types
    // Default: FBM
    public void setFractalType(FractalType fractalType) {
        m_fractalType = fractalType;
    }

    // Sets return type from cellular noise calculations
    // Note: NoiseLookup requires another FastNoise object be set with
    // SetCellularNoiseLookup() to function
    // Default: CellValue
    public void setCellularDistanceFunction(CellularDistanceFunction cellularDistanceFunction) {
        m_cellularDistanceFunction = cellularDistanceFunction;
    }

    // Sets distance function used in cellular noise calculations
    // Default: Euclidean
    public void setCellularReturnType(CellularReturnType cellularReturnType) {
        m_cellularReturnType = cellularReturnType;
    }

    // Noise used to calculate a cell value if cellular return type is NoiseLookup
    // The lookup value is acquired through GetNoise() so ensure you SetNoiseType()
    // on the noise lookup, value, gradient or simplex is recommended
    public void setCellularNoiseLookup(FastNoiseDouble noise) {
        m_cellularNoiseLookup = noise;
    }

    // Sets the maximum perturb distance from original location when using
    // GradientPerturb{Fractal}(...)
    // Default: 1.0
    public void setGradientPerturbAmp(double gradientPerturbAmp) {
        m_gradientPerturbAmp = gradientPerturbAmp / 0.45;
    }

    @SuppressWarnings("ClassCanBeRecord")
    private static class Double2 {
        public final double x, y;

        public Double2(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }

    @SuppressWarnings("ClassCanBeRecord")
    private static class Double3 {
        public final double x, y, z;

        public Double3(double x, double y, double z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    private static final Double2[] GRAD_2D = {new Double2(-1, -1), new Double2(1, -1), new Double2(-1, 1), new Double2(1, 1), new Double2(0, -1), new Double2(-1, 0), new Double2(0, 1), new Double2(1, 0),
    };

    private static final Double3[] GRAD_3D = {new Double3(1, 1, 0), new Double3(-1, 1, 0), new Double3(1, -1, 0), new Double3(-1, -1, 0), new Double3(1, 0, 1), new Double3(-1, 0, 1), new Double3(1, 0, -1), new Double3(-1, 0, -1), new Double3(0, 1, 1), new Double3(0, -1, 1), new Double3(0, 1, -1), new Double3(0, -1, -1), new Double3(1, 1, 0), new Double3(0, -1, 1), new Double3(-1, 1, 0), new Double3(0, -1, -1),
    };

    private static final Double2[] CELL_2D = {new Double2(-0.4313539279f, 0.1281943404f), new Double2(-0.1733316799f, 0.415278375f), new Double2(-0.2821957395f, -0.3505218461f), new Double2(-0.2806473808f, 0.3517627718f), new Double2(0.3125508975f, -0.3237467165f), new Double2(0.3383018443f, -0.2967353402f), new Double2(-0.4393982022f, -0.09710417025f), new Double2(-0.4460443703f, -0.05953502905f), new Double2(-0.302223039f, 0.3334085102f), new Double2(-0.212681052f, -0.3965687458f), new Double2(-0.2991156529f, 0.3361990872f), new Double2(0.2293323691f, 0.3871778202f), new Double2(0.4475439151f, -0.04695150755f), new Double2(0.1777518f, 0.41340573f), new Double2(0.1688522499f, -0.4171197882f), new Double2(-0.0976597166f, 0.4392750616f), new Double2(0.08450188373f, 0.4419948321f), new Double2(-0.4098760448f, -0.1857461384f), new Double2(0.3476585782f, -0.2857157906f), new Double2(-0.3350670039f, -0.30038326f), new Double2(0.2298190031f, -0.3868891648f), new Double2(-0.01069924099f, 0.449872789f), new Double2(-0.4460141246f, -0.05976119672f), new Double2(0.3650293864f, 0.2631606867f), new Double2(-0.349479423f, 0.2834856838f), new Double2(-0.4122720642f, 0.1803655873f), new Double2(-0.267327811f, 0.3619887311f), new Double2(0.322124041f, -0.3142230135f), new Double2(0.2880445931f, -0.3457315612f), new Double2(0.3892170926f, -0.2258540565f), new Double2(0.4492085018f, -0.02667811596f), new Double2(-0.4497724772f, 0.01430799601f), new Double2(0.1278175387f, -0.4314657307f), new Double2(-0.03572100503f, 0.4485799926f), new Double2(-0.4297407068f, -0.1335025276f), new Double2(-0.3217817723f, 0.3145735065f), new Double2(-0.3057158873f, 0.3302087162f), new Double2(-0.414503978f, 0.1751754899f), new Double2(-0.3738139881f, 0.2505256519f), new Double2(0.2236891408f, -0.3904653228f), new Double2(0.002967775577f, -0.4499902136f), new Double2(0.1747128327f, -0.4146991995f), new Double2(-0.4423772489f, -0.08247647938f), new Double2(-0.2763960987f, -0.355112935f), new Double2(-0.4019385906f, -0.2023496216f), new Double2(0.3871414161f, -0.2293938184f), new Double2(-0.430008727f, 0.1326367019f), new Double2(-0.03037574274f, -0.4489736231f), new Double2(-0.3486181573f, 0.2845441624f), new Double2(0.04553517144f, -0.4476902368f), new Double2(-0.0375802926f, 0.4484280562f), new Double2(0.3266408905f, 0.3095250049f), new Double2(0.06540017593f, -0.4452222108f), new Double2(0.03409025829f, 0.448706869f), new Double2(-0.4449193635f, 0.06742966669f), new Double2(-0.4255936157f, -0.1461850686f), new Double2(0.449917292f, 0.008627302568f), new Double2(0.05242606404f, 0.4469356864f), new Double2(-0.4495305179f, -0.02055026661f), new Double2(-0.1204775703f, 0.4335725488f), new Double2(-0.341986385f, -0.2924813028f), new Double2(0.3865320182f, 0.2304191809f), new Double2(0.04506097811f, -0.447738214f), new Double2(-0.06283465979f, 0.4455915232f), new Double2(0.3932600341f, -0.2187385324f), new Double2(0.4472261803f, -0.04988730975f), new Double2(0.3753571011f, -0.2482076684f), new Double2(-0.273662295f, 0.357223947f), new Double2(0.1700461538f, 0.4166344988f), new Double2(0.4102692229f, 0.1848760794f), new Double2(0.323227187f, -0.3130881435f), new Double2(-0.2882310238f, -0.3455761521f), new Double2(0.2050972664f, 0.4005435199f), new Double2(0.4414085979f, -0.08751256895f), new Double2(-0.1684700334f, 0.4172743077f), new Double2(-0.003978032396f, 0.4499824166f), new Double2(-0.2055133639f, 0.4003301853f), new Double2(-0.006095674897f, -0.4499587123f), new Double2(-0.1196228124f, -0.4338091548f), new Double2(0.3901528491f, -0.2242337048f), new Double2(0.01723531752f, 0.4496698165f), new Double2(-0.3015070339f, 0.3340561458f), new Double2(-0.01514262423f, -0.4497451511f), new Double2(-0.4142574071f, -0.1757577897f), new Double2(-0.1916377265f, -0.4071547394f), new Double2(0.3749248747f, 0.2488600778f), new Double2(-0.2237774255f, 0.3904147331f), new Double2(-0.4166343106f, -0.1700466149f), new Double2(0.3619171625f, 0.267424695f), new Double2(0.1891126846f, -0.4083336779f), new Double2(-0.3127425077f, 0.323561623f), new Double2(-0.3281807787f, 0.307891826f), new Double2(-0.2294806661f, 0.3870899429f), new Double2(-0.3445266136f, 0.2894847362f), new Double2(-0.4167095422f, -0.1698621719f), new Double2(-0.257890321f, -0.3687717212f), new Double2(-0.3612037825f, 0.2683874578f), new Double2(0.2267996491f, 0.3886668486f), new Double2(0.207157062f, 0.3994821043f), new Double2(0.08355176718f, -0.4421754202f), new Double2(-0.4312233307f, 0.1286329626f), new Double2(0.3257055497f, 0.3105090899f), new Double2(0.177701095f, -0.4134275279f), new Double2(-0.445182522f, 0.06566979625f), new Double2(0.3955143435f, 0.2146355146f), new Double2(-0.4264613988f, 0.1436338239f), new Double2(-0.3793799665f, -0.2420141339f), new Double2(0.04617599081f, -0.4476245948f), new Double2(-0.371405428f, -0.2540826796f), new Double2(0.2563570295f, -0.3698392535f), new Double2(0.03476646309f, 0.4486549822f), new Double2(-0.3065454405f, 0.3294387544f), new Double2(-0.2256979823f, 0.3893076172f), new Double2(0.4116448463f, -0.1817925206f), new Double2(-0.2907745828f, -0.3434387019f), new Double2(0.2842278468f, -0.348876097f), new Double2(0.3114589359f, -0.3247973695f), new Double2(0.4464155859f, -0.0566844308f), new Double2(-0.3037334033f, -0.3320331606f), new Double2(0.4079607166f, 0.1899159123f), new Double2(-0.3486948919f, -0.2844501228f), new Double2(0.3264821436f, 0.3096924441f), new Double2(0.3211142406f, 0.3152548881f), new Double2(0.01183382662f, 0.4498443737f), new Double2(0.4333844092f, 0.1211526057f), new Double2(0.3118668416f, 0.324405723f), new Double2(-0.272753471f, 0.3579183483f), new Double2(-0.422228622f, -0.1556373694f), new Double2(-0.1009700099f, -0.4385260051f), new Double2(-0.2741171231f, -0.3568750521f), new Double2(-0.1465125133f, 0.4254810025f), new Double2(0.2302279044f, -0.3866459777f), new Double2(-0.3699435608f, 0.2562064828f), new Double2(0.105700352f, -0.4374099171f), new Double2(-0.2646713633f, 0.3639355292f), new Double2(0.3521828122f, 0.2801200935f), new Double2(-0.1864187807f, -0.4095705534f), new Double2(0.1994492955f, -0.4033856449f), new Double2(0.3937065066f, 0.2179339044f), new Double2(-0.3226158377f, 0.3137180602f), new Double2(0.3796235338f, 0.2416318948f), new Double2(0.1482921929f, 0.4248640083f), new Double2(-0.407400394f, 0.1911149365f), new Double2(0.4212853031f, 0.1581729856f), new Double2(-0.2621297173f, 0.3657704353f), new Double2(-0.2536986953f, -0.3716678248f), new Double2(-0.2100236383f, 0.3979825013f), new Double2(0.3624152444f, 0.2667493029f), new Double2(-0.3645038479f, -0.2638881295f), new Double2(0.2318486784f, 0.3856762766f), new Double2(-0.3260457004f, 0.3101519002f), new Double2(-0.2130045332f, -0.3963950918f), new Double2(0.3814998766f, -0.2386584257f), new Double2(-0.342977305f, 0.2913186713f), new Double2(-0.4355865605f, 0.1129794154f), new Double2(-0.2104679605f, 0.3977477059f), new Double2(0.3348364681f, -0.3006402163f), new Double2(0.3430468811f, 0.2912367377f), new Double2(-0.2291836801f, -0.3872658529f), new Double2(0.2547707298f, -0.3709337882f), new Double2(0.4236174945f, -0.151816397f), new Double2(-0.15387742f, 0.4228731957f), new Double2(-0.4407449312f, 0.09079595574f), new Double2(-0.06805276192f, -0.444824484f), new Double2(0.4453517192f, -0.06451237284f), new Double2(0.2562464609f, -0.3699158705f), new Double2(0.3278198355f, -0.3082761026f), new Double2(-0.4122774207f, -0.1803533432f), new Double2(0.3354090914f, -0.3000012356f), new Double2(0.446632869f, -0.05494615882f), new Double2(-0.1608953296f, 0.4202531296f), new Double2(-0.09463954939f, 0.4399356268f), new Double2(-0.02637688324f, -0.4492262904f), new Double2(0.447102804f, -0.05098119915f), new Double2(-0.4365670908f, 0.1091291678f), new Double2(-0.3959858651f, 0.2137643437f), new Double2(-0.4240048207f, -0.1507312575f), new Double2(-0.3882794568f, 0.2274622243f), new Double2(-0.4283652566f, -0.1378521198f), new Double2(0.3303888091f, 0.305521251f), new Double2(0.3321434919f, -0.3036127481f), new Double2(-0.413021046f, -0.1786438231f), new Double2(0.08403060337f, -0.4420846725f), new Double2(-0.3822882919f, 0.2373934748f), new Double2(-0.3712395594f, -0.2543249683f), new Double2(0.4472363971f, -0.04979563372f), new Double2(-0.4466591209f, 0.05473234629f), new Double2(0.0486272539f, -0.4473649407f), new Double2(-0.4203101295f, -0.1607463688f), new Double2(0.2205360833f, 0.39225481f), new Double2(-0.3624900666f, 0.2666476169f), new Double2(-0.4036086833f, -0.1989975647f), new Double2(0.2152727807f, 0.3951678503f), new Double2(-0.4359392962f, -0.1116106179f), new Double2(0.4178354266f, 0.1670735057f), new Double2(0.2007630161f, 0.4027334247f), new Double2(-0.07278067175f, -0.4440754146f), new Double2(0.3644748615f, -0.2639281632f), new Double2(-0.4317451775f, 0.126870413f), new Double2(-0.297436456f, 0.3376855855f), new Double2(-0.2998672222f, 0.3355289094f), new Double2(-0.2673674124f, 0.3619594822f), new Double2(0.2808423357f, 0.3516071423f), new Double2(0.3498946567f, 0.2829730186f), new Double2(-0.2229685561f, 0.390877248f), new Double2(0.3305823267f, 0.3053118493f), new Double2(-0.2436681211f, -0.3783197679f), new Double2(-0.03402776529f, 0.4487116125f), new Double2(-0.319358823f, 0.3170330301f), new Double2(0.4454633477f, -0.06373700535f), new Double2(0.4483504221f, 0.03849544189f), new Double2(-0.4427358436f, -0.08052932871f), new Double2(0.05452298565f, 0.4466847255f), new Double2(-0.2812560807f, 0.3512762688f), new Double2(0.1266696921f, 0.4318041097f), new Double2(-0.3735981243f, 0.2508474468f), new Double2(0.2959708351f, -0.3389708908f), new Double2(-0.3714377181f, 0.254035473f), new Double2(-0.404467102f, -0.1972469604f), new Double2(0.1636165687f, -0.419201167f), new Double2(0.3289185495f, -0.3071035458f), new Double2(-0.2494824991f, -0.3745109914f), new Double2(0.03283133272f, 0.4488007393f), new Double2(-0.166306057f, -0.4181414777f), new Double2(-0.106833179f, 0.4371346153f), new Double2(0.06440260376f, -0.4453676062f), new Double2(-0.4483230967f, 0.03881238203f), new Double2(-0.421377757f, -0.1579265206f), new Double2(0.05097920662f, -0.4471030312f), new Double2(0.2050584153f, -0.4005634111f), new Double2(0.4178098529f, -0.167137449f), new Double2(-0.3565189504f, -0.2745801121f), new Double2(0.4478398129f, 0.04403977727f), new Double2(-0.3399999602f, -0.2947881053f), new Double2(0.3767121994f, 0.2461461331f), new Double2(-0.3138934434f, 0.3224451987f), new Double2(-0.1462001792f, -0.4255884251f), new Double2(0.3970290489f, -0.2118205239f), new Double2(0.4459149305f, -0.06049689889f), new Double2(-0.4104889426f, -0.1843877112f), new Double2(0.1475103971f, -0.4251360756f), new Double2(0.09258030352f, 0.4403735771f), new Double2(-0.1589664637f, -0.4209865359f), new Double2(0.2482445008f, 0.3753327428f), new Double2(0.4383624232f, -0.1016778537f), new Double2(0.06242802956f, 0.4456486745f), new Double2(0.2846591015f, -0.3485243118f), new Double2(-0.344202744f, -0.2898697484f), new Double2(0.1198188883f, -0.4337550392f), new Double2(-0.243590703f, 0.3783696201f), new Double2(0.2958191174f, -0.3391033025f), new Double2(-0.1164007991f, 0.4346847754f), new Double2(0.1274037151f, -0.4315881062f), new Double2(0.368047306f, 0.2589231171f), new Double2(0.2451436949f, 0.3773652989f), new Double2(-0.4314509715f, 0.12786735f),
    };

    private static final Double3[] CELL_3D = {new Double3(0.1453787434f, -0.4149781685f, -0.0956981749f), new Double3(-0.01242829687f, -0.1457918398f, -0.4255470325f), new Double3(0.2877979582f, -0.02606483451f, -0.3449535616f), new Double3(-0.07732986802f, 0.2377094325f, 0.3741848704f), new Double3(0.1107205875f, -0.3552302079f, -0.2530858567f), new Double3(0.2755209141f, 0.2640521179f, -0.238463215f), new Double3(0.294168941f, 0.1526064594f, 0.3044271714f), new Double3(0.4000921098f, -0.2034056362f, 0.03244149937f), new Double3(-0.1697304074f, 0.3970864695f, -0.1265461359f), new Double3(-0.1483224484f, -0.3859694688f, 0.1775613147f), new Double3(0.2623596946f, -0.2354852944f, 0.2796677792f), new Double3(-0.2709003183f, 0.3505271138f, -0.07901746678f), new Double3(-0.03516550699f, 0.3885234328f, 0.2243054374f), new Double3(-0.1267712655f, 0.1920044036f, 0.3867342179f), new Double3(0.02952021915f, 0.4409685861f, 0.08470692262f), new Double3(-0.2806854217f, -0.266996757f, 0.2289725438f), new Double3(-0.171159547f, 0.2141185563f, 0.3568720405f), new Double3(0.2113227183f, 0.3902405947f, -0.07453178509f), new Double3(-0.1024352839f, 0.2128044156f, -0.3830421561f), new Double3(-0.3304249877f, -0.1566986703f, 0.2622305365f), new Double3(0.2091111325f, 0.3133278055f, -0.2461670583f), new Double3(0.344678154f, -0.1944240454f, -0.2142341261f), new Double3(0.1984478035f, -0.3214342325f, -0.2445373252f), new Double3(-0.2929008603f, 0.2262915116f, 0.2559320961f), new Double3(-0.1617332831f, 0.006314769776f, -0.4198838754f), new Double3(-0.3582060271f, -0.148303178f, -0.2284613961f), new Double3(-0.1852067326f, -0.3454119342f, -0.2211087107f), new Double3(0.3046301062f, 0.1026310383f, 0.314908508f), new Double3(-0.03816768434f, -0.2551766358f, -0.3686842991f), new Double3(-0.4084952196f, 0.1805950793f, 0.05492788837f), new Double3(-0.02687443361f, -0.2749741471f, 0.3551999201f), new Double3(-0.03801098351f, 0.3277859044f, 0.3059600725f), new Double3(0.2371120802f, 0.2900386767f, -0.2493099024f), new Double3(0.4447660503f, 0.03946930643f, 0.05590469027f), new Double3(0.01985147278f, -0.01503183293f, -0.4493105419f), new Double3(0.4274339143f, 0.03345994256f, -0.1366772882f), new Double3(-0.2072988631f, 0.2871414597f, -0.2776273824f), new Double3(-0.3791240978f, 0.1281177671f, 0.2057929936f), new Double3(-0.2098721267f, -0.1007087278f, -0.3851122467f), new Double3(0.01582798878f, 0.4263894424f, 0.1429738373f), new Double3(-0.1888129464f, -0.3160996813f, -0.2587096108f), new Double3(0.1612988974f, -0.1974805082f, -0.3707885038f), new Double3(-0.08974491322f, 0.229148752f, -0.3767448739f), new Double3(0.07041229526f, 0.4150230285f, -0.1590534329f), new Double3(-0.1082925611f, -0.1586061639f, 0.4069604477f), new Double3(0.2474100658f, -0.3309414609f, 0.1782302128f), new Double3(-0.1068836661f, -0.2701644537f, -0.3436379634f), new Double3(0.2396452163f, 0.06803600538f, -0.3747549496f), new Double3(-0.3063886072f, 0.2597428179f, 0.2028785103f), new Double3(0.1593342891f, -0.3114350249f, -0.2830561951f), new Double3(0.2709690528f, 0.1412648683f, -0.3303331794f), new Double3(-0.1519780427f, 0.3623355133f, 0.2193527988f), new Double3(0.1699773681f, 0.3456012883f, 0.2327390037f), new Double3(-0.1986155616f, 0.3836276443f, -0.1260225743f), new Double3(-0.1887482106f, -0.2050154888f, -0.353330953f), new Double3(0.2659103394f, 0.3015631259f, -0.2021172246f), new Double3(-0.08838976154f, -0.4288819642f, -0.1036702021f), new Double3(-0.04201869311f, 0.3099592485f, 0.3235115047f), new Double3(-0.3230334656f, 0.201549922f, -0.2398478873f), new Double3(0.2612720941f, 0.2759854499f, -0.2409749453f), new Double3(0.385713046f, 0.2193460345f, 0.07491837764f), new Double3(0.07654967953f, 0.3721732183f, 0.241095919f), new Double3(0.4317038818f, -0.02577753072f, 0.1243675091f), new Double3(-0.2890436293f, -0.3418179959f, -0.04598084447f), new Double3(-0.2201947582f, 0.383023377f, -0.08548310451f), new Double3(0.4161322773f, -0.1669634289f, -0.03817251927f), new Double3(0.2204718095f, 0.02654238946f, -0.391391981f), new Double3(-0.1040307469f, 0.3890079625f, -0.2008741118f), new Double3(-0.1432122615f, 0.371614387f, -0.2095065525f), new Double3(0.3978380468f, -0.06206669342f, 0.2009293758f), new Double3(-0.2599274663f, 0.2616724959f, -0.2578084893f), new Double3(0.4032618332f, -0.1124593585f, 0.1650235939f), new Double3(-0.08953470255f, -0.3048244735f, 0.3186935478f), new Double3(0.118937202f, -0.2875221847f, 0.325092195f), new Double3(0.02167047076f, -0.03284630549f, -0.4482761547f), new Double3(-0.3411343612f, 0.2500031105f, 0.1537068389f), new Double3(0.3162964612f, 0.3082064153f, -0.08640228117f), new Double3(0.2355138889f, -0.3439334267f, -0.1695376245f), new Double3(-0.02874541518f, -0.3955933019f, 0.2125550295f), new Double3(-0.2461455173f, 0.02020282325f, -0.3761704803f), new Double3(0.04208029445f, -0.4470439576f, 0.02968078139f), new Double3(0.2727458746f, 0.2288471896f, -0.2752065618f), new Double3(-0.1347522818f, -0.02720848277f, -0.4284874806f), new Double3(0.3829624424f, 0.1231931484f, -0.2016512234f), new Double3(-0.3547613644f, 0.1271702173f, 0.2459107769f), new Double3(0.2305790207f, 0.3063895591f, 0.2354968222f), new Double3(-0.08323845599f, -0.1922245118f, 0.3982726409f), new Double3(0.2993663085f, -0.2619918095f, -0.2103333191f), new Double3(-0.2154865723f, 0.2706747713f, 0.287751117f), new Double3(0.01683355354f, -0.2680655787f, -0.3610505186f), new Double3(0.05240429123f, 0.4335128183f, -0.1087217856f), new Double3(0.00940104872f, -0.4472890582f, 0.04841609928f), new Double3(0.3465688735f, 0.01141914583f, -0.2868093776f), new Double3(-0.3706867948f, -0.2551104378f, 0.003156692623f), new Double3(0.2741169781f, 0.2139972417f, -0.2855959784f), new Double3(0.06413433865f, 0.1708718512f, 0.4113266307f), new Double3(-0.388187972f, -0.03973280434f, -0.2241236325f), new Double3(0.06419469312f, -0.2803682491f, 0.3460819069f), new Double3(-0.1986120739f, -0.3391173584f, 0.2192091725f), new Double3(-0.203203009f, -0.3871641506f, 0.1063600375f), new Double3(-0.1389736354f, -0.2775901578f, -0.3257760473f), new Double3(-0.06555641638f, 0.342253257f, -0.2847192729f), new Double3(-0.2529246486f, -0.2904227915f, 0.2327739768f), new Double3(0.1444476522f, 0.1069184044f, 0.4125570634f), new Double3(-0.3643780054f, -0.2447099973f, -0.09922543227f), new Double3(0.4286142488f, -0.1358496089f, -0.01829506817f), new Double3(0.165872923f, -0.3136808464f, -0.2767498872f), new Double3(0.2219610524f, -0.3658139958f, 0.1393320198f), new Double3(0.04322940318f, -0.3832730794f, 0.2318037215f), new Double3(-0.08481269795f, -0.4404869674f, -0.03574965489f), new Double3(0.1822082075f, -0.3953259299f, 0.1140946023f), new Double3(-0.3269323334f, 0.3036542563f, 0.05838957105f), new Double3(-0.4080485344f, 0.04227858267f, -0.184956522f), new Double3(0.2676025294f, -0.01299671652f, 0.36155217f), new Double3(0.3024892441f, -0.1009990293f, -0.3174892964f), new Double3(0.1448494052f, 0.425921681f, -0.0104580805f), new Double3(0.4198402157f, 0.08062320474f, 0.1404780841f), new Double3(-0.3008872161f, -0.333040905f, -0.03241355801f), new Double3(0.3639310428f, -0.1291284382f, -0.2310412139f), new Double3(0.3295806598f, 0.0184175994f, -0.3058388149f), new Double3(0.2776259487f, -0.2974929052f, -0.1921504723f), new Double3(0.4149000507f, -0.144793182f, -0.09691688386f), new Double3(0.145016715f, -0.0398992945f, 0.4241205002f), new Double3(0.09299023471f, -0.299732164f, -0.3225111565f), new Double3(0.1028907093f, -0.361266869f, 0.247789732f), new Double3(0.2683057049f, -0.07076041213f, -0.3542668666f), new Double3(-0.4227307273f, -0.07933161816f, -0.1323073187f), new Double3(-0.1781224702f, 0.1806857196f, -0.3716517945f), new Double3(0.4390788626f, -0.02841848598f, -0.09435116353f), new Double3(0.2972583585f, 0.2382799621f, -0.2394997452f), new Double3(-0.1707002821f, 0.2215845691f, 0.3525077196f), new Double3(0.3806686614f, 0.1471852559f, -0.1895464869f), new Double3(-0.1751445661f, -0.274887877f, 0.3102596268f), new Double3(-0.2227237566f, -0.2316778837f, 0.3149912482f), new Double3(0.1369633021f, 0.1341343041f, -0.4071228836f), new Double3(-0.3529503428f, -0.2472893463f, -0.129514612f), new Double3(-0.2590744185f, -0.2985577559f, -0.2150435121f), new Double3(-0.3784019401f, 0.2199816631f, -0.1044989934f), new Double3(-0.05635805671f, 0.1485737441f, 0.4210102279f), new Double3(0.3251428613f, 0.09666046873f, -0.2957006485f), new Double3(-0.4190995804f, 0.1406751354f, -0.08405978803f), new Double3(-0.3253150961f, -0.3080335042f, -0.04225456877f), new Double3(0.2857945863f, -0.05796152095f, 0.3427271751f), new Double3(-0.2733604046f, 0.1973770973f, -0.2980207554f), new Double3(0.219003657f, 0.2410037886f, -0.3105713639f), new Double3(0.3182767252f, -0.271342949f, 0.1660509868f), new Double3(-0.03222023115f, -0.3331161506f, -0.300824678f), new Double3(-0.3087780231f, 0.1992794134f, -0.2596995338f), new Double3(-0.06487611647f, -0.4311322747f, 0.1114273361f), new Double3(0.3921171432f, -0.06294284106f, -0.2116183942f), new Double3(-0.1606404506f, -0.358928121f, -0.2187812825f), new Double3(-0.03767771199f, -0.2290351443f, 0.3855169162f), new Double3(0.1394866832f, -0.3602213994f, 0.2308332918f), new Double3(-0.4345093872f, 0.005751117145f, 0.1169124335f), new Double3(-0.1044637494f, 0.4168128432f, -0.1336202785f), new Double3(0.2658727501f, 0.2551943237f, 0.2582393035f), new Double3(0.2051461999f, 0.1975390727f, 0.3484154868f), new Double3(-0.266085566f, 0.23483312f, 0.2766800993f), new Double3(0.07849405464f, -0.3300346342f, -0.2956616708f), new Double3(-0.2160686338f, 0.05376451292f, -0.3910546287f), new Double3(-0.185779186f, 0.2148499206f, 0.3490352499f), new Double3(0.02492421743f, -0.3229954284f, -0.3123343347f), new Double3(-0.120167831f, 0.4017266681f, 0.1633259825f), new Double3(-0.02160084693f, -0.06885389554f, 0.4441762538f), new Double3(0.2597670064f, 0.3096300784f, 0.1978643903f), new Double3(-0.1611553854f, -0.09823036005f, 0.4085091653f), new Double3(-0.3278896792f, 0.1461670309f, 0.2713366126f), new Double3(0.2822734956f, 0.03754421121f, -0.3484423997f), new Double3(0.03169341113f, 0.347405252f, -0.2842624114f), new Double3(0.2202613604f, -0.3460788041f, -0.1849713341f), new Double3(0.2933396046f, 0.3031973659f, 0.1565989581f), new Double3(-0.3194922995f, 0.2453752201f, -0.200538455f), new Double3(-0.3441586045f, -0.1698856132f, -0.2349334659f), new Double3(0.2703645948f, -0.3574277231f, 0.04060059933f), new Double3(0.2298568861f, 0.3744156221f, 0.0973588921f), new Double3(0.09326603877f, -0.3170108894f, 0.3054595587f), new Double3(-0.1116165319f, -0.2985018719f, 0.3177080142f), new Double3(0.2172907365f, -0.3460005203f, -0.1885958001f), new Double3(0.1991339479f, 0.3820341668f, -0.1299829458f), new Double3(-0.0541918155f, -0.2103145071f, 0.39412061f), new Double3(0.08871336998f, 0.2012117383f, 0.3926114802f), new Double3(0.2787673278f, 0.3505404674f, 0.04370535101f), new Double3(-0.322166438f, 0.3067213525f, 0.06804996813f), new Double3(-0.4277366384f, 0.132066775f, 0.04582286686f), new Double3(0.240131882f, -0.1612516055f, 0.344723946f), new Double3(0.1448607981f, -0.2387819045f, 0.3528435224f), new Double3(-0.3837065682f, -0.2206398454f, 0.08116235683f), new Double3(-0.4382627882f, -0.09082753406f, -0.04664855374f), new Double3(-0.37728353f, 0.05445141085f, 0.2391488697f), new Double3(0.1259579313f, 0.348394558f, 0.2554522098f), new Double3(-0.1406285511f, -0.270877371f, -0.3306796947f), new Double3(-0.1580694418f, 0.4162931958f, -0.06491553533f), new Double3(0.2477612106f, -0.2927867412f, -0.2353514536f), new Double3(0.2916132853f, 0.3312535401f, 0.08793624968f), new Double3(0.07365265219f, -0.1666159848f, 0.411478311f), new Double3(-0.26126526f, -0.2422237692f, 0.2748965434f), new Double3(-0.3721862032f, 0.252790166f, 0.008634938242f), new Double3(-0.3691191571f, -0.255281188f, 0.03290232422f), new Double3(0.2278441737f, -0.3358364886f, 0.1944244981f), new Double3(0.363398169f, -0.2310190248f, 0.1306597909f), new Double3(-0.304231482f, -0.2698452035f, 0.1926830856f), new Double3(-0.3199312232f, 0.316332536f, -0.008816977938f), new Double3(0.2874852279f, 0.1642275508f, -0.304764754f), new Double3(-0.1451096801f, 0.3277541114f, -0.2720669462f), new Double3(0.3220090754f, 0.0511344108f, 0.3101538769f), new Double3(-0.1247400865f, -0.04333605335f, -0.4301882115f), new Double3(-0.2829555867f, -0.3056190617f, -0.1703910946f), new Double3(0.1069384374f, 0.3491024667f, -0.2630430352f), new Double3(-0.1420661144f, -0.3055376754f, -0.2982682484f), new Double3(-0.250548338f, 0.3156466809f, -0.2002316239f), new Double3(0.3265787872f, 0.1871229129f, 0.2466400438f), new Double3(0.07646097258f, -0.3026690852f, 0.324106687f), new Double3(0.3451771584f, 0.2757120714f, -0.0856480183f), new Double3(0.298137964f, 0.2852657134f, 0.179547284f), new Double3(0.2812250376f, 0.3466716415f, 0.05684409612f), new Double3(0.4390345476f, -0.09790429955f, -0.01278335452f), new Double3(0.2148373234f, 0.1850172527f, 0.3494474791f), new Double3(0.2595421179f, -0.07946825393f, 0.3589187731f), new Double3(0.3182823114f, -0.307355516f, -0.08203022006f), new Double3(-0.4089859285f, -0.04647718411f, 0.1818526372f), new Double3(-0.2826749061f, 0.07417482322f, 0.3421885344f), new Double3(0.3483864637f, 0.225442246f, -0.1740766085f), new Double3(-0.3226415069f, -0.1420585388f, -0.2796816575f), new Double3(0.4330734858f, -0.118868561f, -0.02859407492f), new Double3(-0.08717822568f, -0.3909896417f, -0.2050050172f), new Double3(-0.2149678299f, 0.3939973956f, -0.03247898316f), new Double3(-0.2687330705f, 0.322686276f, -0.1617284888f), new Double3(0.2105665099f, -0.1961317136f, -0.3459683451f), new Double3(0.4361845915f, -0.1105517485f, 0.004616608544f), new Double3(0.05333333359f, -0.313639498f, -0.3182543336f), new Double3(-0.05986216652f, 0.1361029153f, -0.4247264031f), new Double3(0.3664988455f, 0.2550543014f, -0.05590974511f), new Double3(-0.2341015558f, -0.182405731f, 0.3382670703f), new Double3(-0.04730947785f, -0.4222150243f, -0.1483114513f), new Double3(-0.2391566239f, -0.2577696514f, -0.2808182972f), new Double3(-0.1242081035f, 0.4256953395f, -0.07652336246f), new Double3(0.2614832715f, -0.3650179274f, 0.02980623099f), new Double3(-0.2728794681f, -0.3499628774f, 0.07458404908f), new Double3(0.007892900508f, -0.1672771315f, 0.4176793787f), new Double3(-0.01730330376f, 0.2978486637f, -0.3368779738f), new Double3(0.2054835762f, -0.3252600376f, -0.2334146693f), new Double3(-0.3231994983f, 0.1564282844f, -0.2712420987f), new Double3(-0.2669545963f, 0.2599343665f, -0.2523278991f), new Double3(-0.05554372779f, 0.3170813944f, -0.3144428146f), new Double3(-0.2083935713f, -0.310922837f, -0.2497981362f), new Double3(0.06989323478f, -0.3156141536f, 0.3130537363f), new Double3(0.3847566193f, -0.1605309138f, -0.1693876312f), new Double3(-0.3026215288f, -0.3001537679f, -0.1443188342f), new Double3(0.3450735512f, 0.08611519592f, 0.2756962409f), new Double3(0.1814473292f, -0.2788782453f, -0.3029914042f), new Double3(-0.03855010448f, 0.09795110726f, 0.4375151083f), new Double3(0.3533670318f, 0.2665752752f, 0.08105160988f), new Double3(-0.007945601311f, 0.140359426f, -0.4274764309f), new Double3(0.4063099273f, -0.1491768253f, -0.1231199324f), new Double3(-0.2016773589f, 0.008816271194f, -0.4021797064f), new Double3(-0.07527055435f, -0.425643481f, -0.1251477955f),
    };

    private static long fastFloor(double f) {
        return (f >= 0 ? (long) f : (long) f - 1);
    }

    private static long fastRound(double f) {
        return (f >= 0) ? (long) (f + 0.5) : (long) (f - 0.5);
    }

    private static double lerp(double a, double b, double t) {
        return a + t * (b - a);
    }

    private static double longerpHermiteFunc(double t) {
        return t * t * (3 - 2 * t);
    }

    private static double longerpQulongicFunc(double t) {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    private static double cubicLerp(double a, double b, double c, double d, double t) {
        double p = (d - c) - (a - b);
        return t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b;
    }

    private void calculateFractalBounding() {
        double amp = m_gain;
        double ampFractal = 1;
        for (long i = 1; i < m_octaves; i++) {
            ampFractal += amp;
            amp *= m_gain;
        }
        m_fractalBounding = 1 / ampFractal;
    }

    // Hashing
    private final static long X_PRIME = 1619;
    private final static long Y_PRIME = 31337;
    private final static long Z_PRIME = 6971;
    private final static long W_PRIME = 1013;

    private static long hash2D(long seed, long x, long y) {
        long hash = seed;
        hash ^= X_PRIME * x;
        hash ^= Y_PRIME * y;

        hash = hash * hash * hash * 60493;
        hash = (hash >> 13) ^ hash;

        return hash;
    }

    private static long hash3D(long seed, long x, long y, long z) {
        long hash = seed;
        hash ^= X_PRIME * x;
        hash ^= Y_PRIME * y;
        hash ^= Z_PRIME * z;

        hash = hash * hash * hash * 60493;
        hash = (hash >> 13) ^ hash;

        return hash;
    }

    public static long hash4D(long seed, long x, long y, long z, long w) {
        long hash = seed;
        hash ^= X_PRIME * x;
        hash ^= Y_PRIME * y;
        hash ^= Z_PRIME * z;
        hash ^= W_PRIME * w;

        hash = hash * hash * hash * 60493;
        hash = (hash >> 13) ^ hash;

        return hash;
    }

    private static double valCoord2D(long seed, long x, long y) {
        long n = seed;
        n ^= X_PRIME * x;
        n ^= Y_PRIME * y;

        return ((n * n * n * 60493L) / (double) Long.MAX_VALUE);
    }

    private static double valCoord3D(long seed, long x, long y, long z) {
        long n = seed;
        n ^= X_PRIME * x;
        n ^= Y_PRIME * y;
        n ^= Z_PRIME * z;

        return ((n * n * n * 60493L) / (double) Long.MAX_VALUE);
    }

    private static double valCoord4D(long seed, long x, long y, long z, long w) {
        long n = seed;
        n ^= X_PRIME * x;
        n ^= Y_PRIME * y;
        n ^= Z_PRIME * z;
        n ^= W_PRIME * w;

        return ((n * n * n * 60493L) / (double) Long.MAX_VALUE);
    }

    private static double GradCoord2D(long seed, long x, long y, double xd, double yd) {
        long hash = seed;
        hash ^= X_PRIME * x;
        hash ^= Y_PRIME * y;

        hash = hash * hash * hash * 60493;
        hash = (hash >> 13) ^ hash;

        Double2 g = GRAD_2D[(int) hash & 7];

        return xd * g.x + yd * g.y;
    }

    private static double GradCoord3D(long seed, long x, long y, long z, double xd, double yd, double zd) {
        long hash = seed;
        hash ^= X_PRIME * x;
        hash ^= Y_PRIME * y;
        hash ^= Z_PRIME * z;

        hash = hash * hash * hash * 60493;
        hash = (hash >> 13) ^ hash;

        Double3 g = GRAD_3D[(int) (hash & 15)];

        return xd * g.x + yd * g.y + zd * g.z;
    }

    private static double GradCoord4D(long seed, long x, long y, long z, long w, double xd, double yd, double zd, double wd) {
        long hash = seed;
        hash ^= X_PRIME * x;
        hash ^= Y_PRIME * y;
        hash ^= Z_PRIME * z;
        hash ^= W_PRIME * w;

        hash = hash * hash * hash * 60493;
        hash = (hash >> 13) ^ hash;

        hash &= 31;
        double a = yd, b = zd, c = wd; // X,Y,Z
        switch ((int) hash >> 3) { // OR, DEPENDING ON HIGH ORDER 2 BITS:
            case 1 -> {
                a = wd;
                b = xd;
                c = yd;
            } // W,X,Y
            case 2 -> {
                a = zd;
                b = wd;
                c = xd;
            } // Z,W,X
            case 3 -> {
                a = yd;
                b = zd;
                c = wd;
            } // Y,Z,W
        }
        return ((hash & 4) == 0 ? -a : a) + ((hash & 2) == 0 ? -b : b) + ((hash & 1) == 0 ? -c : c);
    }

    public double GetNoise(double x, double y, double z) {
        x *= m_frequency;
        y *= m_frequency;
        z *= m_frequency;

        switch (m_noiseType) {
            case Value:
                return SingleValue(m_seed, x, y, z);
            case ValueFractal:
                return switch (m_fractalType) {
                    case FBM -> SingleValueFractalFBM(x, y, z);
                    case Billow -> SingleValueFractalBillow(x, y, z);
                    case RigidMulti -> SingleValueFractalRigidMulti(x, y, z);
                };
            case Perlin:
                return SinglePerlin(m_seed, x, y, z);
            case PerlinFractal:
                return switch (m_fractalType) {
                    case FBM -> SinglePerlinFractalFBM(x, y, z);
                    case Billow -> SinglePerlinFractalBillow(x, y, z);
                    case RigidMulti -> SinglePerlinFractalRigidMulti(x, y, z);
                };
            case Simplex:
                return SingleSimplex(m_seed, x, y, z);
            case SimplexFractal:
                return switch (m_fractalType) {
                    case FBM -> SingleSimplexFractalFBM(x, y, z);
                    case Billow -> SingleSimplexFractalBillow(x, y, z);
                    case RigidMulti -> SingleSimplexFractalRigidMulti(x, y, z);
                };
            case Cellular:
                return switch (m_cellularReturnType) {
                    case CellValue, NoiseLookup, Distance -> SingleCellular(x, y, z);
                    default -> SingleCellular2Edge(x, y, z);
                };
            case WhiteNoise:
                return GetWhiteNoise(x, y, z);
            case Cubic:
                return SingleCubic(m_seed, x, y, z);
            case CubicFractal:
                return switch (m_fractalType) {
                    case FBM -> SingleCubicFractalFBM(x, y, z);
                    case Billow -> SingleCubicFractalBillow(x, y, z);
                    case RigidMulti -> SingleCubicFractalRigidMulti(x, y, z);
                };
            default:
                return 0;
        }
    }

    public double GetNoise(double x, double y) {
        x *= m_frequency;
        y *= m_frequency;

        switch (m_noiseType) {
            case Value:
                return SingleValue(m_seed, x, y);
            case ValueFractal:
                return switch (m_fractalType) {
                    case FBM -> SingleValueFractalFBM(x, y);
                    case Billow -> SingleValueFractalBillow(x, y);
                    case RigidMulti -> SingleValueFractalRigidMulti(x, y);
                };
            case Perlin:
                return SinglePerlin(m_seed, x, y);
            case PerlinFractal:
                return switch (m_fractalType) {
                    case FBM -> SinglePerlinFractalFBM(x, y);
                    case Billow -> SinglePerlinFractalBillow(x, y);
                    case RigidMulti -> SinglePerlinFractalRigidMulti(x, y);
                };
            case Simplex:
                return SingleSimplex(m_seed, x, y);
            case SimplexFractal:
                return switch (m_fractalType) {
                    case FBM -> SingleSimplexFractalFBM(x, y);
                    case Billow -> SingleSimplexFractalBillow(x, y);
                    case RigidMulti -> SingleSimplexFractalRigidMulti(x, y);
                };
            case Cellular:
                return switch (m_cellularReturnType) {
                    case CellValue, NoiseLookup, Distance -> SingleCellular(x, y);
                    default -> SingleCellular2Edge(x, y);
                };
            case WhiteNoise:
                return GetWhiteNoise(x, y);
            case Cubic:
                return SingleCubic(m_seed, x, y);
            case CubicFractal:
                return switch (m_fractalType) {
                    case FBM -> SingleCubicFractalFBM(x, y);
                    case Billow -> SingleCubicFractalBillow(x, y);
                    case RigidMulti -> SingleCubicFractalRigidMulti(x, y);
                };
            default:
                return 0;
        }
    }

    // White Noise

    private long DoubleCast2Long(double f) {
        long i = Double.doubleToRawLongBits(f);

        return i ^ (i >> 16);
    }

    public double GetWhiteNoise(double x, double y, double z, double w) {
        long xi = DoubleCast2Long(x);
        long yi = DoubleCast2Long(y);
        long zi = DoubleCast2Long(z);
        long wi = DoubleCast2Long(w);

        return valCoord4D(m_seed, xi, yi, zi, wi);
    }

    public double GetWhiteNoise(double x, double y, double z) {
        long xi = DoubleCast2Long(x);
        long yi = DoubleCast2Long(y);
        long zi = DoubleCast2Long(z);

        return valCoord3D(m_seed, xi, yi, zi);
    }

    public double GetWhiteNoise(double x, double y) {
        long xi = DoubleCast2Long(x);
        long yi = DoubleCast2Long(y);

        return valCoord2D(m_seed, xi, yi);
    }

    public double GetWhiteNoiseLong(long x, long y, long z, long w) {
        return valCoord4D(m_seed, x, y, z, w);
    }

    public double GetWhiteNoiseLong(long x, long y, long z) {
        return valCoord3D(m_seed, x, y, z);
    }

    public double GetWhiteNoiseLong(long x, long y) {
        return valCoord2D(m_seed, x, y);
    }

    // Value Noise
    public double GetValueFractal(double x, double y, double z) {
        x *= m_frequency;
        y *= m_frequency;
        z *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SingleValueFractalFBM(x, y, z);
            case Billow -> SingleValueFractalBillow(x, y, z);
            case RigidMulti -> SingleValueFractalRigidMulti(x, y, z);
        };
    }

    private double SingleValueFractalFBM(double x, double y, double z) {
        long seed = m_seed;
        double sum = SingleValue(seed, x, y, z);
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += SingleValue(++seed, x, y, z) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleValueFractalBillow(double x, double y, double z) {
        long seed = m_seed;
        double sum = Math.abs(SingleValue(seed, x, y, z)) * 2 - 1;
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += (Math.abs(SingleValue(++seed, x, y, z)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleValueFractalRigidMulti(double x, double y, double z) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SingleValue(seed, x, y, z));
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SingleValue(++seed, x, y, z))) * amp;
        }

        return sum;
    }

    public double GetValue(double x, double y, double z) {
        return SingleValue(m_seed, x * m_frequency, y * m_frequency, z * m_frequency);
    }

    private double SingleValue(long seed, double x, double y, double z) {
        long x0 = fastFloor(x);
        long y0 = fastFloor(y);
        long z0 = fastFloor(z);
        long x1 = x0 + 1;
        long y1 = y0 + 1;
        long z1 = z0 + 1;

        double xs = 0, ys = 0, zs = 0;
        switch (m_longerp) {
            case Linear -> {
                xs = x - x0;
                ys = y - y0;
                zs = z - z0;
            }
            case Hermite -> {
                xs = longerpHermiteFunc(x - x0);
                ys = longerpHermiteFunc(y - y0);
                zs = longerpHermiteFunc(z - z0);
            }
            case Qulongic -> {
                xs = longerpQulongicFunc(x - x0);
                ys = longerpQulongicFunc(y - y0);
                zs = longerpQulongicFunc(z - z0);
            }
        }

        double xf00 = lerp(valCoord3D(seed, x0, y0, z0), valCoord3D(seed, x1, y0, z0), xs);
        double xf10 = lerp(valCoord3D(seed, x0, y1, z0), valCoord3D(seed, x1, y1, z0), xs);
        double xf01 = lerp(valCoord3D(seed, x0, y0, z1), valCoord3D(seed, x1, y0, z1), xs);
        double xf11 = lerp(valCoord3D(seed, x0, y1, z1), valCoord3D(seed, x1, y1, z1), xs);

        double yf0 = lerp(xf00, xf10, ys);
        double yf1 = lerp(xf01, xf11, ys);

        return lerp(yf0, yf1, zs);
    }

    public double GetValueFractal(double x, double y) {
        x *= m_frequency;
        y *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SingleValueFractalFBM(x, y);
            case Billow -> SingleValueFractalBillow(x, y);
            case RigidMulti -> SingleValueFractalRigidMulti(x, y);
        };
    }

    private double SingleValueFractalFBM(double x, double y) {
        long seed = m_seed;
        double sum = SingleValue(seed, x, y);
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum += SingleValue(++seed, x, y) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleValueFractalBillow(double x, double y) {
        long seed = m_seed;
        double sum = Math.abs(SingleValue(seed, x, y)) * 2 - 1;
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            amp *= m_gain;
            sum += (Math.abs(SingleValue(++seed, x, y)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleValueFractalRigidMulti(double x, double y) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SingleValue(seed, x, y));
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SingleValue(++seed, x, y))) * amp;
        }

        return sum;
    }

    public double GetValue(double x, double y) {
        return SingleValue(m_seed, x * m_frequency, y * m_frequency);
    }

    private double SingleValue(long seed, double x, double y) {
        long x0 = fastFloor(x);
        long y0 = fastFloor(y);
        long x1 = x0 + 1;
        long y1 = y0 + 1;

        double xs = 0, ys = 0;
        switch (m_longerp) {
            case Linear -> {
                xs = x - x0;
                ys = y - y0;
            }
            case Hermite -> {
                xs = longerpHermiteFunc(x - x0);
                ys = longerpHermiteFunc(y - y0);
            }
            case Qulongic -> {
                xs = longerpQulongicFunc(x - x0);
                ys = longerpQulongicFunc(y - y0);
            }
        }

        double xf0 = lerp(valCoord2D(seed, x0, y0), valCoord2D(seed, x1, y0), xs);
        double xf1 = lerp(valCoord2D(seed, x0, y1), valCoord2D(seed, x1, y1), xs);

        return lerp(xf0, xf1, ys);
    }

    // Gradient Noise
    public double GetPerlinFractal(double x, double y, double z) {
        x *= m_frequency;
        y *= m_frequency;
        z *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SinglePerlinFractalFBM(x, y, z);
            case Billow -> SinglePerlinFractalBillow(x, y, z);
            case RigidMulti -> SinglePerlinFractalRigidMulti(x, y, z);
        };
    }

    private double SinglePerlinFractalFBM(double x, double y, double z) {
        long seed = m_seed;
        double sum = SinglePerlin(seed, x, y, z);
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += SinglePerlin(++seed, x, y, z) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SinglePerlinFractalBillow(double x, double y, double z) {
        long seed = m_seed;
        double sum = Math.abs(SinglePerlin(seed, x, y, z)) * 2 - 1;
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += (Math.abs(SinglePerlin(++seed, x, y, z)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SinglePerlinFractalRigidMulti(double x, double y, double z) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SinglePerlin(seed, x, y, z));
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SinglePerlin(++seed, x, y, z))) * amp;
        }

        return sum;
    }

    public double GetPerlin(double x, double y, double z) {
        return SinglePerlin(m_seed, x * m_frequency, y * m_frequency, z * m_frequency);
    }

    private double SinglePerlin(long seed, double x, double y, double z) {
        long x0 = fastFloor(x);
        long y0 = fastFloor(y);
        long z0 = fastFloor(z);
        long x1 = x0 + 1;
        long y1 = y0 + 1;
        long z1 = z0 + 1;

        double xs = 0, ys = 0, zs = 0;
        switch (m_longerp) {
            case Linear -> {
                xs = x - x0;
                ys = y - y0;
                zs = z - z0;
            }
            case Hermite -> {
                xs = longerpHermiteFunc(x - x0);
                ys = longerpHermiteFunc(y - y0);
                zs = longerpHermiteFunc(z - z0);
            }
            case Qulongic -> {
                xs = longerpQulongicFunc(x - x0);
                ys = longerpQulongicFunc(y - y0);
                zs = longerpQulongicFunc(z - z0);
            }
        }

        double xd0 = x - x0;
        double yd0 = y - y0;
        double zd0 = z - z0;
        double xd1 = xd0 - 1;
        double yd1 = yd0 - 1;
        double zd1 = zd0 - 1;

        double xf00 = lerp(GradCoord3D(seed, x0, y0, z0, xd0, yd0, zd0), GradCoord3D(seed, x1, y0, z0, xd1, yd0, zd0), xs);
        double xf10 = lerp(GradCoord3D(seed, x0, y1, z0, xd0, yd1, zd0), GradCoord3D(seed, x1, y1, z0, xd1, yd1, zd0), xs);
        double xf01 = lerp(GradCoord3D(seed, x0, y0, z1, xd0, yd0, zd1), GradCoord3D(seed, x1, y0, z1, xd1, yd0, zd1), xs);
        double xf11 = lerp(GradCoord3D(seed, x0, y1, z1, xd0, yd1, zd1), GradCoord3D(seed, x1, y1, z1, xd1, yd1, zd1), xs);

        double yf0 = lerp(xf00, xf10, ys);
        double yf1 = lerp(xf01, xf11, ys);

        return lerp(yf0, yf1, zs);
    }

    public double GetPerlinFractal(double x, double y) {
        x *= m_frequency;
        y *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SinglePerlinFractalFBM(x, y);
            case Billow -> SinglePerlinFractalBillow(x, y);
            case RigidMulti -> SinglePerlinFractalRigidMulti(x, y);
        };
    }

    private double SinglePerlinFractalFBM(double x, double y) {
        long seed = m_seed;
        double sum = SinglePerlin(seed, x, y);
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum += SinglePerlin(++seed, x, y) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SinglePerlinFractalBillow(double x, double y) {
        long seed = m_seed;
        double sum = Math.abs(SinglePerlin(seed, x, y)) * 2 - 1;
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum += (Math.abs(SinglePerlin(++seed, x, y)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SinglePerlinFractalRigidMulti(double x, double y) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SinglePerlin(seed, x, y));
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SinglePerlin(++seed, x, y))) * amp;
        }

        return sum;
    }

    public double GetPerlin(double x, double y) {
        return SinglePerlin(m_seed, x * m_frequency, y * m_frequency);
    }

    private double SinglePerlin(long seed, double x, double y) {
        long x0 = fastFloor(x);
        long y0 = fastFloor(y);
        long x1 = x0 + 1;
        long y1 = y0 + 1;

        double xs = 0, ys = 0;
        switch (m_longerp) {
            case Linear -> {
                xs = x - x0;
                ys = y - y0;
            }
            case Hermite -> {
                xs = longerpHermiteFunc(x - x0);
                ys = longerpHermiteFunc(y - y0);
            }
            case Qulongic -> {
                xs = longerpQulongicFunc(x - x0);
                ys = longerpQulongicFunc(y - y0);
            }
        }

        double xd0 = x - x0;
        double yd0 = y - y0;
        double xd1 = xd0 - 1;
        double yd1 = yd0 - 1;

        double xf0 = lerp(GradCoord2D(seed, x0, y0, xd0, yd0), GradCoord2D(seed, x1, y0, xd1, yd0), xs);
        double xf1 = lerp(GradCoord2D(seed, x0, y1, xd0, yd1), GradCoord2D(seed, x1, y1, xd1, yd1), xs);

        return lerp(xf0, xf1, ys);
    }

    // Simplex Noise
    public double GetSimplexFractal(double x, double y, double z) {
        x *= m_frequency;
        y *= m_frequency;
        z *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SingleSimplexFractalFBM(x, y, z);
            case Billow -> SingleSimplexFractalBillow(x, y, z);
            case RigidMulti -> SingleSimplexFractalRigidMulti(x, y, z);
        };
    }

    private double SingleSimplexFractalFBM(double x, double y, double z) {
        long seed = m_seed;
        double sum = SingleSimplex(seed, x, y, z);
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += SingleSimplex(++seed, x, y, z) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleSimplexFractalBillow(double x, double y, double z) {
        long seed = m_seed;
        double sum = Math.abs(SingleSimplex(seed, x, y, z)) * 2 - 1;
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += (Math.abs(SingleSimplex(++seed, x, y, z)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleSimplexFractalRigidMulti(double x, double y, double z) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SingleSimplex(seed, x, y, z));
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SingleSimplex(++seed, x, y, z))) * amp;
        }

        return sum;
    }

    public double GetSimplex(double x, double y, double z) {
        return SingleSimplex(m_seed, x * m_frequency, y * m_frequency, z * m_frequency);
    }

    private final static double F3 = 1.0 / 3.0;
    private final static double G3 = 1.0 / 6.0;
    private final static double G33 = G3 * 3 - 1;

    private double SingleSimplex(long seed, double x, double y, double z) {
        double t = (x + y + z) * F3;
        long i = fastFloor(x + t);
        long j = fastFloor(y + t);
        long k = fastFloor(z + t);

        t = (i + j + k) * G3;
        double x0 = x - (i - t);
        double y0 = y - (j - t);
        double z0 = z - (k - t);

        long i1, j1, k1;
        long i2, j2, k2;

        if (x0 >= y0) {
            if (y0 >= z0) {
                i1 = 1;
                j1 = 0;
                k1 = 0;
                i2 = 1;
                j2 = 1;
                k2 = 0;
            } else if (x0 >= z0) {
                i1 = 1;
                j1 = 0;
                k1 = 0;
                i2 = 1;
                j2 = 0;
                k2 = 1;
            } else // x0 < z0
            {
                i1 = 0;
                j1 = 0;
                k1 = 1;
                i2 = 1;
                j2 = 0;
                k2 = 1;
            }
        } else // x0 < y0
        {
            if (y0 < z0) {
                i1 = 0;
                j1 = 0;
                k1 = 1;
                i2 = 0;
                j2 = 1;
                k2 = 1;
            } else if (x0 < z0) {
                i1 = 0;
                j1 = 1;
                k1 = 0;
                i2 = 0;
                j2 = 1;
                k2 = 1;
            } else // x0 >= z0
            {
                i1 = 0;
                j1 = 1;
                k1 = 0;
                i2 = 1;
                j2 = 1;
                k2 = 0;
            }
        }

        double x1 = x0 - i1 + G3;
        double y1 = y0 - j1 + G3;
        double z1 = z0 - k1 + G3;
        double x2 = x0 - i2 + F3;
        double y2 = y0 - j2 + F3;
        double z2 = z0 - k2 + F3;
        double x3 = x0 + G33;
        double y3 = y0 + G33;
        double z3 = z0 + G33;

        double n0, n1, n2, n3;

        t = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
        if (t < 0)
            n0 = 0;
        else {
            t *= t;
            n0 = t * t * GradCoord3D(seed, i, j, k, x0, y0, z0);
        }

        t = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
        if (t < 0)
            n1 = 0;
        else {
            t *= t;
            n1 = t * t * GradCoord3D(seed, i + i1, j + j1, k + k1, x1, y1, z1);
        }

        t = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
        if (t < 0)
            n2 = 0;
        else {
            t *= t;
            n2 = t * t * GradCoord3D(seed, i + i2, j + j2, k + k2, x2, y2, z2);
        }

        t = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
        if (t < 0)
            n3 = 0;
        else {
            t *= t;
            n3 = t * t * GradCoord3D(seed, i + 1, j + 1, k + 1, x3, y3, z3);
        }

        return 32 * (n0 + n1 + n2 + n3);
    }

    public double GetSimplexFractal(double x, double y) {
        x *= m_frequency;
        y *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SingleSimplexFractalFBM(x, y);
            case Billow -> SingleSimplexFractalBillow(x, y);
            case RigidMulti -> SingleSimplexFractalRigidMulti(x, y);
        };
    }

    private double SingleSimplexFractalFBM(double x, double y) {
        long seed = m_seed;
        double sum = SingleSimplex(seed, x, y);
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum += SingleSimplex(++seed, x, y) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleSimplexFractalBillow(double x, double y) {
        long seed = m_seed;
        double sum = Math.abs(SingleSimplex(seed, x, y)) * 2 - 1;
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum += (Math.abs(SingleSimplex(++seed, x, y)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleSimplexFractalRigidMulti(double x, double y) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SingleSimplex(seed, x, y));
        double amp = 1;

        for (long i = 1; i < m_octaves; i++) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SingleSimplex(++seed, x, y))) * amp;
        }

        return sum;
    }

    public double GetSimplex(double x, double y) {
        return SingleSimplex(m_seed, x * m_frequency, y * m_frequency);
    }

    private final static double F2 = 1.0 / 2.0;
    private final static double G2 = 1.0 / 4.0;

    private double SingleSimplex(long seed, double x, double y) {
        double t = (x + y) * F2;
        long i = fastFloor(x + t);
        long j = fastFloor(y + t);

        t = (i + j) * G2;
        double X0 = i - t;
        double Y0 = j - t;

        double x0 = x - X0;
        double y0 = y - Y0;

        long i1, j1;
        if (x0 > y0) {
            i1 = 1;
            j1 = 0;
        } else {
            i1 = 0;
            j1 = 1;
        }

        double x1 = x0 - i1 + G2;
        double y1 = y0 - j1 + G2;
        double x2 = x0 - 1 + F2;
        double y2 = y0 - 1 + F2;

        double n0, n1, n2;

        t = 0.5 - x0 * x0 - y0 * y0;
        if (t < 0)
            n0 = 0;
        else {
            t *= t;
            n0 = t * t * GradCoord2D(seed, i, j, x0, y0);
        }

        t = 0.5 - x1 * x1 - y1 * y1;
        if (t < 0)
            n1 = 0;
        else {
            t *= t;
            n1 = t * t * GradCoord2D(seed, i + i1, j + j1, x1, y1);
        }

        t = 0.5 - x2 * x2 - y2 * y2;
        if (t < 0)
            n2 = 0;
        else {
            t *= t;
            n2 = t * t * GradCoord2D(seed, i + 1, j + 1, x2, y2);
        }

        return 50 * (n0 + n1 + n2);
    }

    public double GetSimplex(double x, double y, double z, double w) {
        return SingleSimplex(m_seed, x * m_frequency, y * m_frequency, z * m_frequency, w * m_frequency);
    }

    private static final byte[] SIMPLEX_4D = {0, 1, 2, 3, 0, 1, 3, 2, 0, 0, 0, 0, 0, 2, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 3, 1, 2, 0, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 0, 1, 3, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 1, 2, 3, 1, 0, 1, 0, 2, 3, 1, 0, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 3, 1, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 2, 3, 0, 2, 1, 0, 0, 0, 0, 3, 1, 2, 0, 2, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 2, 0, 0, 0, 0, 3, 2, 0, 1, 3, 2, 1, 0
    };

    private final static double F4 = (2.23606797 - 1.0) / 4.0;
    private final static double G4 = (5.0 - 2.23606797) / 20.0;

    private double SingleSimplex(long seed, double x, double y, double z, double w) {
        double n0, n1, n2, n3, n4;
        double t = (x + y + z + w) * F4;
        long i = fastFloor(x + t);
        long j = fastFloor(y + t);
        long k = fastFloor(z + t);
        long l = fastFloor(w + t);
        t = (i + j + k + l) * G4;
        double X0 = i - t;
        double Y0 = j - t;
        double Z0 = k - t;
        double W0 = l - t;
        double x0 = x - X0;
        double y0 = y - Y0;
        double z0 = z - Z0;
        double w0 = w - W0;

        int c = (x0 > y0) ? 32 : 0;
        c += (x0 > z0) ? 16 : 0;
        c += (y0 > z0) ? 8 : 0;
        c += (x0 > w0) ? 4 : 0;
        c += (y0 > w0) ? 2 : 0;
        c += (z0 > w0) ? 1 : 0;
        c <<= 2;

        long i1 = SIMPLEX_4D[c] >= 3 ? 1 : 0;
        long i2 = SIMPLEX_4D[c] >= 2 ? 1 : 0;
        long i3 = SIMPLEX_4D[c++] >= 1 ? 1 : 0;
        long j1 = SIMPLEX_4D[c] >= 3 ? 1 : 0;
        long j2 = SIMPLEX_4D[c] >= 2 ? 1 : 0;
        long j3 = SIMPLEX_4D[c++] >= 1 ? 1 : 0;
        long k1 = SIMPLEX_4D[c] >= 3 ? 1 : 0;
        long k2 = SIMPLEX_4D[c] >= 2 ? 1 : 0;
        long k3 = SIMPLEX_4D[c++] >= 1 ? 1 : 0;
        long l1 = SIMPLEX_4D[c] >= 3 ? 1 : 0;
        long l2 = SIMPLEX_4D[c] >= 2 ? 1 : 0;
        long l3 = SIMPLEX_4D[c] >= 1 ? 1 : 0;

        double x1 = x0 - i1 + G4;
        double y1 = y0 - j1 + G4;
        double z1 = z0 - k1 + G4;
        double w1 = w0 - l1 + G4;
        double x2 = x0 - i2 + 2 * G4;
        double y2 = y0 - j2 + 2 * G4;
        double z2 = z0 - k2 + 2 * G4;
        double w2 = w0 - l2 + 2 * G4;
        double x3 = x0 - i3 + 3 * G4;
        double y3 = y0 - j3 + 3 * G4;
        double z3 = z0 - k3 + 3 * G4;
        double w3 = w0 - l3 + 3 * G4;
        double x4 = x0 - 1 + 4 * G4;
        double y4 = y0 - 1 + 4 * G4;
        double z4 = z0 - 1 + 4 * G4;
        double w4 = w0 - 1 + 4 * G4;

        t = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
        if (t < 0)
            n0 = 0;
        else {
            t *= t;
            n0 = t * t * GradCoord4D(seed, i, j, k, l, x0, y0, z0, w0);
        }
        t = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
        if (t < 0)
            n1 = 0;
        else {
            t *= t;
            n1 = t * t * GradCoord4D(seed, i + i1, j + j1, k + k1, l + l1, x1, y1, z1, w1);
        }
        t = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
        if (t < 0)
            n2 = 0;
        else {
            t *= t;
            n2 = t * t * GradCoord4D(seed, i + i2, j + j2, k + k2, l + l2, x2, y2, z2, w2);
        }
        t = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
        if (t < 0)
            n3 = 0;
        else {
            t *= t;
            n3 = t * t * GradCoord4D(seed, i + i3, j + j3, k + k3, l + l3, x3, y3, z3, w3);
        }
        t = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
        if (t < 0)
            n4 = 0;
        else {
            t *= t;
            n4 = t * t * GradCoord4D(seed, i + 1, j + 1, k + 1, l + 1, x4, y4, z4, w4);
        }

        return 27 * (n0 + n1 + n2 + n3 + n4);
    }

    // Cubic Noise
    public double GetCubicFractal(double x, double y, double z) {
        x *= m_frequency;
        y *= m_frequency;
        z *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SingleCubicFractalFBM(x, y, z);
            case Billow -> SingleCubicFractalBillow(x, y, z);
            case RigidMulti -> SingleCubicFractalRigidMulti(x, y, z);
        };
    }

    private double SingleCubicFractalFBM(double x, double y, double z) {
        long seed = m_seed;
        double sum = SingleCubic(seed, x, y, z);
        double amp = 1;
        long i = 0;

        while (++i < m_octaves) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += SingleCubic(++seed, x, y, z) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleCubicFractalBillow(double x, double y, double z) {
        long seed = m_seed;
        double sum = Math.abs(SingleCubic(seed, x, y, z)) * 2 - 1;
        double amp = 1;
        long i = 0;

        while (++i < m_octaves) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum += (Math.abs(SingleCubic(++seed, x, y, z)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleCubicFractalRigidMulti(double x, double y, double z) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SingleCubic(seed, x, y, z));
        double amp = 1;
        long i = 0;

        while (++i < m_octaves) {
            x *= m_lacunarity;
            y *= m_lacunarity;
            z *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SingleCubic(++seed, x, y, z))) * amp;
        }

        return sum;
    }

    public double GetCubic(double x, double y, double z) {
        return SingleCubic(m_seed, x * m_frequency, y * m_frequency, z * m_frequency);
    }

    private final static double CUBIC_3D_BOUNDING = 1 / (1.5 * 1.5 * 1.5);

    private double SingleCubic(long seed, double x, double y, double z) {
        long x1 = fastFloor(x);
        long y1 = fastFloor(y);
        long z1 = fastFloor(z);

        long x0 = x1 - 1;
        long y0 = y1 - 1;
        long z0 = z1 - 1;
        long x2 = x1 + 1;
        long y2 = y1 + 1;
        long z2 = z1 + 1;
        long x3 = x1 + 2;
        long y3 = y1 + 2;
        long z3 = z1 + 2;

        double xs = x - (double) x1;
        double ys = y - (double) y1;
        double zs = z - (double) z1;

        return cubicLerp(cubicLerp(cubicLerp(valCoord3D(seed, x0, y0, z0), valCoord3D(seed, x1, y0, z0), valCoord3D(seed, x2, y0, z0), valCoord3D(seed, x3, y0, z0), xs), cubicLerp(valCoord3D(seed, x0, y1, z0), valCoord3D(seed, x1, y1, z0), valCoord3D(seed, x2, y1, z0), valCoord3D(seed, x3, y1, z0), xs), cubicLerp(valCoord3D(seed, x0, y2, z0), valCoord3D(seed, x1, y2, z0), valCoord3D(seed, x2, y2, z0), valCoord3D(seed, x3, y2, z0), xs), cubicLerp(valCoord3D(seed, x0, y3, z0), valCoord3D(seed, x1, y3, z0), valCoord3D(seed, x2, y3, z0), valCoord3D(seed, x3, y3, z0), xs), ys), cubicLerp(cubicLerp(valCoord3D(seed, x0, y0, z1), valCoord3D(seed, x1, y0, z1), valCoord3D(seed, x2, y0, z1), valCoord3D(seed, x3, y0, z1), xs), cubicLerp(valCoord3D(seed, x0, y1, z1), valCoord3D(seed, x1, y1, z1), valCoord3D(seed, x2, y1, z1), valCoord3D(seed, x3, y1, z1), xs), cubicLerp(valCoord3D(seed, x0, y2, z1), valCoord3D(seed, x1, y2, z1), valCoord3D(seed, x2, y2, z1), valCoord3D(seed, x3, y2, z1), xs), cubicLerp(valCoord3D(seed, x0, y3, z1), valCoord3D(seed, x1, y3, z1), valCoord3D(seed, x2, y3, z1), valCoord3D(seed, x3, y3, z1), xs), ys), cubicLerp(cubicLerp(valCoord3D(seed, x0, y0, z2), valCoord3D(seed, x1, y0, z2), valCoord3D(seed, x2, y0, z2), valCoord3D(seed, x3, y0, z2), xs), cubicLerp(valCoord3D(seed, x0, y1, z2), valCoord3D(seed, x1, y1, z2), valCoord3D(seed, x2, y1, z2), valCoord3D(seed, x3, y1, z2), xs), cubicLerp(valCoord3D(seed, x0, y2, z2), valCoord3D(seed, x1, y2, z2), valCoord3D(seed, x2, y2, z2), valCoord3D(seed, x3, y2, z2), xs), cubicLerp(valCoord3D(seed, x0, y3, z2), valCoord3D(seed, x1, y3, z2), valCoord3D(seed, x2, y3, z2), valCoord3D(seed, x3, y3, z2), xs), ys), cubicLerp(cubicLerp(valCoord3D(seed, x0, y0, z3), valCoord3D(seed, x1, y0, z3), valCoord3D(seed, x2, y0, z3), valCoord3D(seed, x3, y0, z3), xs), cubicLerp(valCoord3D(seed, x0, y1, z3), valCoord3D(seed, x1, y1, z3), valCoord3D(seed, x2, y1, z3), valCoord3D(seed, x3, y1, z3), xs), cubicLerp(valCoord3D(seed, x0, y2, z3), valCoord3D(seed, x1, y2, z3), valCoord3D(seed, x2, y2, z3), valCoord3D(seed, x3, y2, z3), xs), cubicLerp(valCoord3D(seed, x0, y3, z3), valCoord3D(seed, x1, y3, z3), valCoord3D(seed, x2, y3, z3), valCoord3D(seed, x3, y3, z3), xs), ys), zs) * CUBIC_3D_BOUNDING;
    }

    public double GetCubicFractal(double x, double y) {
        x *= m_frequency;
        y *= m_frequency;

        return switch (m_fractalType) {
            case FBM -> SingleCubicFractalFBM(x, y);
            case Billow -> SingleCubicFractalBillow(x, y);
            case RigidMulti -> SingleCubicFractalRigidMulti(x, y);
        };
    }

    private double SingleCubicFractalFBM(double x, double y) {
        long seed = m_seed;
        double sum = SingleCubic(seed, x, y);
        double amp = 1;
        long i = 0;

        while (++i < m_octaves) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum += SingleCubic(++seed, x, y) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleCubicFractalBillow(double x, double y) {
        long seed = m_seed;
        double sum = Math.abs(SingleCubic(seed, x, y)) * 2 - 1;
        double amp = 1;
        long i = 0;

        while (++i < m_octaves) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum += (Math.abs(SingleCubic(++seed, x, y)) * 2 - 1) * amp;
        }

        return sum * m_fractalBounding;
    }

    private double SingleCubicFractalRigidMulti(double x, double y) {
        long seed = m_seed;
        double sum = 1 - Math.abs(SingleCubic(seed, x, y));
        double amp = 1;
        long i = 0;

        while (++i < m_octaves) {
            x *= m_lacunarity;
            y *= m_lacunarity;

            amp *= m_gain;
            sum -= (1 - Math.abs(SingleCubic(++seed, x, y))) * amp;
        }

        return sum;
    }

    public double GetCubic(double x, double y) {
        x *= m_frequency;
        y *= m_frequency;

        return SingleCubic(0, x, y);
    }

    private final static double CUBIC_2D_BOUNDING = 1 / (1.5 * 1.5);

    private double SingleCubic(long seed, double x, double y) {
        long x1 = fastFloor(x);
        long y1 = fastFloor(y);

        long x0 = x1 - 1;
        long y0 = y1 - 1;
        long x2 = x1 + 1;
        long y2 = y1 + 1;
        long x3 = x1 + 2;
        long y3 = y1 + 2;

        double xs = x - (double) x1;
        double ys = y - (double) y1;

        return cubicLerp(cubicLerp(valCoord2D(seed, x0, y0), valCoord2D(seed, x1, y0), valCoord2D(seed, x2, y0), valCoord2D(seed, x3, y0), xs), cubicLerp(valCoord2D(seed, x0, y1), valCoord2D(seed, x1, y1), valCoord2D(seed, x2, y1), valCoord2D(seed, x3, y1), xs), cubicLerp(valCoord2D(seed, x0, y2), valCoord2D(seed, x1, y2), valCoord2D(seed, x2, y2), valCoord2D(seed, x3, y2), xs), cubicLerp(valCoord2D(seed, x0, y3), valCoord2D(seed, x1, y3), valCoord2D(seed, x2, y3), valCoord2D(seed, x3, y3), xs), ys) * CUBIC_2D_BOUNDING;
    }

    // Cellular Noise
    public double GetCellular(double x, double y, double z) {
        x *= m_frequency;
        y *= m_frequency;
        z *= m_frequency;

        return switch (m_cellularReturnType) {
            case CellValue, NoiseLookup, Distance -> SingleCellular(x, y, z);
            default -> SingleCellular2Edge(x, y, z);
        };
    }

    private double SingleCellular(double x, double y, double z) {
        long xr = fastRound(x);
        long yr = fastRound(y);
        long zr = fastRound(z);

        double distance = 999999;
        long xc = 0, yc = 0, zc = 0;

        switch (m_cellularDistanceFunction) {
            case Euclidean:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        for (long zi = zr - 1; zi <= zr + 1; zi++) {
                            Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255];

                            double vecX = xi - x + vec.x;
                            double vecY = yi - y + vec.y;
                            double vecZ = zi - z + vec.z;

                            double newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ;

                            if (newDistance < distance) {
                                distance = newDistance;
                                xc = xi;
                                yc = yi;
                                zc = zi;
                            }
                        }
                    }
                }
                break;
            case Manhattan:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        for (long zi = zr - 1; zi <= zr + 1; zi++) {
                            Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255];

                            double vecX = xi - x + vec.x;
                            double vecY = yi - y + vec.y;
                            double vecZ = zi - z + vec.z;

                            double newDistance = Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ);

                            if (newDistance < distance) {
                                distance = newDistance;
                                xc = xi;
                                yc = yi;
                                zc = zi;
                            }
                        }
                    }
                }
                break;
            case Natural:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        for (long zi = zr - 1; zi <= zr + 1; zi++) {
                            Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255];

                            double vecX = xi - x + vec.x;
                            double vecY = yi - y + vec.y;
                            double vecZ = zi - z + vec.z;

                            double newDistance = (Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ);

                            if (newDistance < distance) {
                                distance = newDistance;
                                xc = xi;
                                yc = yi;
                                zc = zi;
                            }
                        }
                    }
                }
                break;
        }

        switch (m_cellularReturnType) {
            case CellValue:
                return valCoord3D(0, xc, yc, zc);

            case NoiseLookup:
                Double3 vec = CELL_3D[(int) hash3D(m_seed, xc, yc, zc) & 255];
                return m_cellularNoiseLookup.GetNoise(xc + vec.x, yc + vec.y, zc + vec.z);

            case Distance:
                return distance - 1;
            default:
                return 0;
        }
    }

    private double SingleCellular2Edge(double x, double y, double z) {
        long xr = fastRound(x);
        long yr = fastRound(y);
        long zr = fastRound(z);

        double distance = 999999;
        double distance2 = 999999;

        switch (m_cellularDistanceFunction) {
            case Euclidean:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        for (long zi = zr - 1; zi <= zr + 1; zi++) {
                            Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255];

                            double vecX = xi - x + vec.x;
                            double vecY = yi - y + vec.y;
                            double vecZ = zi - z + vec.z;

                            double newDistance = vecX * vecX + vecY * vecY + vecZ * vecZ;

                            distance2 = Math.max(Math.min(distance2, newDistance), distance);
                            distance = Math.min(distance, newDistance);
                        }
                    }
                }
                break;
            case Manhattan:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        for (long zi = zr - 1; zi <= zr + 1; zi++) {
                            Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255];

                            double vecX = xi - x + vec.x;
                            double vecY = yi - y + vec.y;
                            double vecZ = zi - z + vec.z;

                            double newDistance = Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ);

                            distance2 = Math.max(Math.min(distance2, newDistance), distance);
                            distance = Math.min(distance, newDistance);
                        }
                    }
                }
                break;
            case Natural:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        for (long zi = zr - 1; zi <= zr + 1; zi++) {
                            Double3 vec = CELL_3D[(int) hash3D(m_seed, xi, yi, zi) & 255];

                            double vecX = xi - x + vec.x;
                            double vecY = yi - y + vec.y;
                            double vecZ = zi - z + vec.z;

                            double newDistance = (Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ);

                            distance2 = Math.max(Math.min(distance2, newDistance), distance);
                            distance = Math.min(distance, newDistance);
                        }
                    }
                }
                break;
            default:
                break;
        }

        return switch (m_cellularReturnType) {
            case Distance2 -> distance2 - 1;
            case Distance2Add -> distance2 + distance - 1;
            case Distance2Sub -> distance2 - distance - 1;
            case Distance2Mul -> distance2 * distance - 1;
            case Distance2Div -> distance / distance2 - 1;
            default -> 0;
        };
    }

    public double GetCellular(double x, double y) {
        x *= m_frequency;
        y *= m_frequency;

        return switch (m_cellularReturnType) {
            case CellValue, NoiseLookup, Distance -> SingleCellular(x, y);
            default -> SingleCellular2Edge(x, y);
        };
    }

    private double SingleCellular(double x, double y) {
        long xr = fastRound(x);
        long yr = fastRound(y);

        double distance = 999999;
        long xc = 0, yc = 0;

        switch (m_cellularDistanceFunction) {
            default:
            case Euclidean:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255];

                        double vecX = xi - x + vec.x;
                        double vecY = yi - y + vec.y;

                        double newDistance = vecX * vecX + vecY * vecY;

                        if (newDistance < distance) {
                            distance = newDistance;
                            xc = xi;
                            yc = yi;
                        }
                    }
                }
                break;
            case Manhattan:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255];

                        double vecX = xi - x + vec.x;
                        double vecY = yi - y + vec.y;

                        double newDistance = (Math.abs(vecX) + Math.abs(vecY));

                        if (newDistance < distance) {
                            distance = newDistance;
                            xc = xi;
                            yc = yi;
                        }
                    }
                }
                break;
            case Natural:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255];

                        double vecX = xi - x + vec.x;
                        double vecY = yi - y + vec.y;

                        double newDistance = (Math.abs(vecX) + Math.abs(vecY)) + (vecX * vecX + vecY * vecY);

                        if (newDistance < distance) {
                            distance = newDistance;
                            xc = xi;
                            yc = yi;
                        }
                    }
                }
                break;
        }

        switch (m_cellularReturnType) {
            case CellValue:
                return valCoord2D(0, xc, yc);

            case NoiseLookup:
                Double2 vec = CELL_2D[(int) hash2D(m_seed, xc, yc) & 255];
                return m_cellularNoiseLookup.GetNoise(xc + vec.x, yc + vec.y);

            case Distance:
                return distance - 1;
            default:
                return 0;
        }
    }

    private double SingleCellular2Edge(double x, double y) {
        long xr = fastRound(x);
        long yr = fastRound(y);

        double distance = 999999;
        double distance2 = 999999;

        switch (m_cellularDistanceFunction) {
            default:
            case Euclidean:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255];

                        double vecX = xi - x + vec.x;
                        double vecY = yi - y + vec.y;

                        double newDistance = vecX * vecX + vecY * vecY;

                        distance2 = Math.max(Math.min(distance2, newDistance), distance);
                        distance = Math.min(distance, newDistance);
                    }
                }
                break;
            case Manhattan:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255];

                        double vecX = xi - x + vec.x;
                        double vecY = yi - y + vec.y;

                        double newDistance = Math.abs(vecX) + Math.abs(vecY);

                        distance2 = Math.max(Math.min(distance2, newDistance), distance);
                        distance = Math.min(distance, newDistance);
                    }
                }
                break;
            case Natural:
                for (long xi = xr - 1; xi <= xr + 1; xi++) {
                    for (long yi = yr - 1; yi <= yr + 1; yi++) {
                        Double2 vec = CELL_2D[(int) hash2D(m_seed, xi, yi) & 255];

                        double vecX = xi - x + vec.x;
                        double vecY = yi - y + vec.y;

                        double newDistance = (Math.abs(vecX) + Math.abs(vecY)) + (vecX * vecX + vecY * vecY);

                        distance2 = Math.max(Math.min(distance2, newDistance), distance);
                        distance = Math.min(distance, newDistance);
                    }
                }
                break;
        }

        return switch (m_cellularReturnType) {
            case Distance2 -> distance2 - 1;
            case Distance2Add -> distance2 + distance - 1;
            case Distance2Sub -> distance2 - distance - 1;
            case Distance2Mul -> distance2 * distance - 1;
            case Distance2Div -> distance / distance2 - 1;
            default -> 0;
        };
    }

    public void GradientPerturb(Vector3f v3) {
        SingleGradientPerturb(m_seed, m_gradientPerturbAmp, m_frequency, v3);
    }

    public void GradientPerturbFractal(Vector3f v3) {
        long seed = m_seed;
        double amp = m_gradientPerturbAmp * m_fractalBounding;
        double freq = m_frequency;

        SingleGradientPerturb(seed, amp, m_frequency, v3);

        for (long i = 1; i < m_octaves; i++) {
            freq *= m_lacunarity;
            amp *= m_gain;
            SingleGradientPerturb(++seed, amp, freq, v3);
        }
    }

    private void SingleGradientPerturb(long seed, double perturbAmp, double frequency, Vector3f v3) {
        double xf = v3.x * frequency;
        double yf = v3.y * frequency;
        double zf = v3.z * frequency;

        long x0 = fastFloor(xf);
        long y0 = fastFloor(yf);
        long z0 = fastFloor(zf);
        long x1 = x0 + 1;
        long y1 = y0 + 1;
        long z1 = z0 + 1;

        double xs = 0, ys = 0, zs = 0;
        switch (m_longerp) {
            case Linear -> {
                xs = xf - x0;
                ys = yf - y0;
                zs = zf - z0;
            }
            case Hermite -> {
                xs = longerpHermiteFunc(xf - x0);
                ys = longerpHermiteFunc(yf - y0);
                zs = longerpHermiteFunc(zf - z0);
            }
            case Qulongic -> {
                xs = longerpQulongicFunc(xf - x0);
                ys = longerpQulongicFunc(yf - y0);
                zs = longerpQulongicFunc(zf - z0);
            }
        }

        Double3 vec0 = CELL_3D[(int) hash3D(seed, x0, y0, z0) & 255];
        Double3 vec1 = CELL_3D[(int) hash3D(seed, x1, y0, z0) & 255];

        double lx0x = lerp(vec0.x, vec1.x, xs);
        double ly0x = lerp(vec0.y, vec1.y, xs);
        double lz0x = lerp(vec0.z, vec1.z, xs);

        vec0 = CELL_3D[(int) hash3D(seed, x0, y1, z0) & 255];
        vec1 = CELL_3D[(int) hash3D(seed, x1, y1, z0) & 255];

        double lx1x = lerp(vec0.x, vec1.x, xs);
        double ly1x = lerp(vec0.y, vec1.y, xs);
        double lz1x = lerp(vec0.z, vec1.z, xs);

        double lx0y = lerp(lx0x, lx1x, ys);
        double ly0y = lerp(ly0x, ly1x, ys);
        double lz0y = lerp(lz0x, lz1x, ys);

        vec0 = CELL_3D[(int) hash3D(seed, x0, y0, z1) & 255];
        vec1 = CELL_3D[(int) hash3D(seed, x1, y0, z1) & 255];

        lx0x = lerp(vec0.x, vec1.x, xs);
        ly0x = lerp(vec0.y, vec1.y, xs);
        lz0x = lerp(vec0.z, vec1.z, xs);

        vec0 = CELL_3D[(int) hash3D(seed, x0, y1, z1) & 255];
        vec1 = CELL_3D[(int) hash3D(seed, x1, y1, z1) & 255];

        lx1x = lerp(vec0.x, vec1.x, xs);
        ly1x = lerp(vec0.y, vec1.y, xs);
        lz1x = lerp(vec0.z, vec1.z, xs);

        v3.x += lerp(lx0y, lerp(lx0x, lx1x, ys), zs) * perturbAmp;
        v3.y += lerp(ly0y, lerp(ly0x, ly1x, ys), zs) * perturbAmp;
        v3.z += lerp(lz0y, lerp(lz0x, lz1x, ys), zs) * perturbAmp;
    }

    public void GradientPerturb(Vector2f v2) {
        SingleGradientPerturb(m_seed, m_gradientPerturbAmp, m_frequency, v2);
    }

    public void GradientPerturbFractal(Vector2f v2) {
        long seed = m_seed;
        double amp = m_gradientPerturbAmp * m_fractalBounding;
        double freq = m_frequency;

        SingleGradientPerturb(seed, amp, m_frequency, v2);

        for (long i = 1; i < m_octaves; i++) {
            freq *= m_lacunarity;
            amp *= m_gain;
            SingleGradientPerturb(++seed, amp, freq, v2);
        }
    }

    private void SingleGradientPerturb(long seed, double perturbAmp, double frequency, Vector2f v2) {
        double xf = v2.x * frequency;
        double yf = v2.y * frequency;

        long x0 = fastFloor(xf);
        long y0 = fastFloor(yf);
        long x1 = x0 + 1;
        long y1 = y0 + 1;

        double xs = 0, ys = 0;
        switch (m_longerp) {
            case Linear -> {
                xs = xf - x0;
                ys = yf - y0;
            }
            case Hermite -> {
                xs = longerpHermiteFunc(xf - x0);
                ys = longerpHermiteFunc(yf - y0);
            }
            case Qulongic -> {
                xs = longerpQulongicFunc(xf - x0);
                ys = longerpQulongicFunc(yf - y0);
            }
        }

        Double2 vec0 = CELL_2D[(int) hash2D(seed, x0, y0) & 255];
        Double2 vec1 = CELL_2D[(int) hash2D(seed, x1, y0) & 255];

        double lx0x = lerp(vec0.x, vec1.x, xs);
        double ly0x = lerp(vec0.y, vec1.y, xs);

        vec0 = CELL_2D[(int) hash2D(seed, x0, y1) & 255];
        vec1 = CELL_2D[(int) hash2D(seed, x1, y1) & 255];

        double lx1x = lerp(vec0.x, vec1.x, xs);
        double ly1x = lerp(vec0.y, vec1.y, xs);

        v2.x += lerp(lx0x, lx1x, ys) * perturbAmp;
        v2.y += lerp(ly0x, ly1x, ys) * perturbAmp;
    }

}