// Copyright 2021 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
package org.terasology.engine.rendering;

import org.joml.Vector2f;
import org.joml.Vector3fc;
import org.terasology.engine.rendering.dag.nodes.RefractiveReflectiveBlocksNodeProxy;

public final class RenderHelper {

    // Parameters which are also defined on shader side
    private static final int OCEAN_OCTAVES = 16;
    private static final Vector2f[] OCEAN_WAVE_DIRECTIONS = {
            new Vector2f(-0.613392f, 0.617481f),
            new Vector2f(0.170019f, -0.040254f),
            new Vector2f(-0.299417f, 0.791925f),
            new Vector2f(0.645680f, 0.493210f),
            new Vector2f(-0.651784f, 0.717887f),
            new Vector2f(0.421003f, 0.027070f),
            new Vector2f(-0.817194f, -0.271096f),
            new Vector2f(-0.705374f, -0.668203f),
            new Vector2f(0.977050f, -0.108615f),
            new Vector2f(0.063326f, 0.142369f),
            new Vector2f(0.203528f, 0.214331f),
            new Vector2f(-0.667531f, 0.326090f),
            new Vector2f(-0.098422f, -0.295755f),
            new Vector2f(-0.885922f, 0.215369f),
            new Vector2f(0.566637f, 0.605213f),
            new Vector2f(0.039766f, -0.396100f)
    };

    private RenderHelper() {
    }

    // Various functions that are also available on the shader side but need to be
    // evaluated on the CPU
    public static float smoothCurve(float x) {
        return x * x * (3.f - 2.0f * x);
    }

    public static float triangleWave(float x) {
        float normX = x + 0.5f;
        float fract = normX - (float) Math.floor(normX);
        return Math.abs(fract * 2.0f - 1.0f);
    }

    public static float smoothTriangleWave(float x) {
        return smoothCurve(triangleWave(x)) * 2.0f - 1.0f;
    }

    public static float timeToTick(float time, float speed) {
        return time * 4000.0f * speed;
    }

    public static float evaluateOceanHeightAtPosition(Vector3fc position, float days) {
        float height = 0.0f;

        float waveSize = RefractiveReflectiveBlocksNodeProxy.waveSize;
        float waveIntensity = RefractiveReflectiveBlocksNodeProxy.waveIntensity;
        float timeFactor = RefractiveReflectiveBlocksNodeProxy.waveSpeed;

        for (int i = 0; i < OCEAN_OCTAVES; ++i) {
            height += (float) (smoothTriangleWave(timeToTick(days, timeFactor)
                    + position.x() * OCEAN_WAVE_DIRECTIONS[i].x * waveSize
                    + position.z() * OCEAN_WAVE_DIRECTIONS[i].y * waveSize) * 2.0 - 1.0) * waveIntensity;

            waveSize *= RefractiveReflectiveBlocksNodeProxy.waveSizeFalloff;
            waveIntensity *= RefractiveReflectiveBlocksNodeProxy.waveIntensityFalloff;
            timeFactor *= RefractiveReflectiveBlocksNodeProxy.waveSpeedFalloff;
        }

        height /= OCEAN_OCTAVES;

        return height + RefractiveReflectiveBlocksNodeProxy.waterOffsetY;
    }

}
