 # Copyright: GEATEC engineering, 2021 and up
# License: Apache 2

'''
Requirements
------------
Generate a somewhat irregular 3D 'mountainscape' with a lake at the lowest point
Start on an arbitrary location
Go downhill in the fasted way possible
Try not to get stuck in a local minimum
The mountainscape and the "skier" has to be visible in a 3D plot

Testspecs
---------
For at least 3 times:
-   Generate an arbitrary mountainscape
-   Start at an arbitrary x, y (so z) location
-   Move downhill according to the steepest descent
-   Check that you don't get stuck
-   Check that you reach the lake eventually

Design
------
The generated landscape is a linear combination of a parabola, sin's and cos's
The gradient is approximated numerically by making small 'test'moves in e.g. 16 directions
If there turns out to be no progress possible while the lake isn't yet reached, try a bigger random jump
'''
# Import standard modules

import random as rd
import copy as cp
import math as mt

# Import 3rd party modules

import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as pp
import mpl_toolkits.mplot3d as p3

# Define constants

nrOfSteps = 2 ** 10
halfNrOfSteps = nrOfSteps // 2
nrOfPeriods = 5.5

class Plot:
    def __init__ (self):
        pp.ion ()
        self.figure = pp.figure ()
        self.mountainPlot = self.figure.add_subplot (1, 1, 1, projection = '3d')

    def getZ (self, x, y):
        return ((1.5 * x) ** 2 + (1.5 * y) ** 2) *  ((3 - mt.cos (x)) + (3 - mt.cos (y))) ** 2

    def draw (self):
        self.xVec = np.array ([2 * mt.pi * nrOfPeriods * (stepIndex/nrOfSteps) for stepIndex in range (-halfNrOfSteps, halfNrOfSteps)])
        self.yVec = cp.copy (self.xVec)
        self.xGrid, self.yGrid = np.meshgrid (self.xVec, self.yVec)
        self.zVec = np.array ([[self.getZ (x, y) for y in self.yVec] for x in self.xVec])
        self.mountainPlot.plot_surface (self.xGrid, self.yGrid, self.zVec, cmap = cm.coolwarm)
        self.figure.canvas.draw ()
        input ()

# Define global code

plot = Plot ()
plot.draw ()