#!/usr/bin/env python
""" Examples of using qhull via scipy to generate 3D plots in visvis.
Requires numpy ver 1.5, scipy ver 0.9 and qhull from
http://www.qhull.org/ (on Windows this comes with Scipy).

plot3D meshes and plots random convex transformable data in both cartesian
and spherical coordinates

Play around with the many input parameters to plot3D to make interesting plots.

Keith Smith, 4 March 2011

"""

import numpy as np
from scipy.spatial import Delaunay
import visvis as vv


def plot3D( vuvi,
            coordSys='Cartesian',
            raised = True,
            depRange=[-40,0],
            ambient = 0.9,
            diffuse = 0.4,
            colormap = vv.CM_JET,
            faceShading='smooth',
            edgeColor = (0.5,0.5,0.5,1),
            edgeShading = 'smooth',
            faceColor = (1,1,1,1),
            shininess = 50,
            specular = 0.35,
            emission = 0.45 ):
    """ plot3D(vxyz,
            coordSys=['Cartesian', 'Spherical'],
            raised = True,
            depRange=[-40,0], #Note: second range limit not currently used
            rangeR=[-40,0],
            ambient = 0.9,
            diffuse = 0.4,
            colormap = vv.CM_JET,
            faceShading='smooth',
            edgeColor = (0.5,0.5,0.5,1),
            edgeShading = 'smooth',
            faceColor = (1,1,1,1),
            shininess = 50,
            specular = 0.35,
            emission = 0.45 ))
    """
  
    if coordSys == 'Spherical':
        thetaPhiR = vuvi  # data cols are theta, phi, radius
        vxyz = np.zeros(vuvi.shape)
        
        # Now find xyz data points on unit sphere (for meshing)
        vxyz[:,0] = np.sin(thetaPhiR[:,0])*np.cos(thetaPhiR[:,1])
        vxyz[:,1] = np.sin(thetaPhiR[:,0])*np.sin(thetaPhiR[:,1])
        vxyz[:,2] = np.cos(thetaPhiR[:,0])
        
        #normalize and scale dependent values
        thetaPhiR[thetaPhiR[:,2] < depRange[0], 2] = depRange[0]
        depVal = thetaPhiR[:,2] - np.min(thetaPhiR[:,2])
    else:
        vxyz = vuvi
        vxyz[vxyz[:,2] < depRange[0], 2] = depRange[0]
        numOfPts = np.shape(vxyz)[0]
        depVal = vxyz[:,2]
        # set to convex surface for meshing
        # find center of data
        center = np.average(vxyz, 0)
        #center data
        vxyz = vxyz - center
        # find x-y plane distance to each point
        radials = np.sqrt(vxyz[:,0]**2 + vxyz[:,1]**2)
        # get max and adjust so that arctan ranges between +-45 deg
        maxRadial = np.max(radials)/0.7
        #get angle on sphere
        xi =  np.arctan2(radials / maxRadial, 1)
        #force z axis data to sphere
        vxyz[:,2] = maxRadial * np.cos(xi)
        vxyz = np.append(vxyz, [[0.7, 0.7, -0.7],[-0.7, 0.7, -0.7],
                                [0.7, -0.7, -0.7],[-0.7, -0.7, -0.7]], axis=0)
       
    
    
    # Send data to convex_hull program qhull
    dly = Delaunay(vxyz)
    meshIndx = dly.convex_hull
    
    # Check each triangle facet and flip if
    # vertex order puts back side out
    for index, (I1, I2, I3) in enumerate(meshIndx):
        a = vxyz[I1,:] - vxyz[I2,:]
        b = vxyz[I2,:] - vxyz[I3,:]
        c = np.cross(a, b)
        if np.dot(c, vxyz[I2,:]) > 0:
            meshIndx[index] = (I1, I3, I2)

    
    
    # if 3D surface adjust dependent coordinates
    if raised:
        if coordSys == 'Spherical':
            vxyz[:,0] = depVal*np.sin(thetaPhiR[:,0])*np.cos(thetaPhiR[:,1])
            vxyz[:,1] = depVal*np.sin(thetaPhiR[:,0])*np.sin(thetaPhiR[:,1])
            vxyz[:,2] = depVal*np.cos(thetaPhiR[:,0])
        else:
            vxyz = vxyz + center
            vxyz[:numOfPts,2] = depVal
    else:
        
        if coordSys == 'Spherical':
            depRange[0] = 1.0
        else:
            # Since qhull encloses the data with Delaunay triangles there will be
            # a set of facets which cover the bottom of the data.  For flat
            # contours, the bottom facets need to be separated a fraction from
            # the top facets else you don't see colormap colors
            depValRange = np.max(vxyz[:numOfPts,2]) - np.min(vxyz[:numOfPts,2])
            vxyz[:numOfPts,2] = vxyz[:numOfPts,2] /   (10 * depValRange )
            
    #normalize depVal for color mapping
    dataRange = np.max(depVal) - np.min(depVal)
    depVal = (depVal- np.min(depVal)) /  dataRange

    # Get axes
    ax = vv.gca()
    
    ms = vv.Mesh(ax, vxyz, faces=meshIndx, normals=vxyz)
    ms.SetValues(np.reshape(depVal,np.size(depVal)))
    ms.ambient = ambient
    ms.diffuse = diffuse
    ms.colormap = colormap
    ms.faceShading = faceShading
    ms.edgeColor = edgeColor
    ms.edgeShading = edgeShading
    ms.faceColor = faceColor
    ms.shininess = shininess
    ms.specular = specular
    ms.emission = emission
    ax.SetLimits(rangeX=[-depRange[0],depRange[0]],
                 rangeY=[-depRange[0],depRange[0]],
                 rangeZ=[-depRange[0], depRange[0]])



# Start of test code.
if __name__ == '__main__':
    
    # Create figure
    fig = vv.figure()
    fig.position.w = 600
    
    
    # Cartesian plot
    numOfPts = 2000
    scale = 1
    
    # Create random points
    xyz = 2 * scale * (np.random.rand(numOfPts,3) - 0.5)
    
    # 2D sync function
    xyz[:,2] =  np.sinc(5*(np.sqrt(xyz[:,0]**2 + xyz[:,1]**2)))
    #xyz[:,2] =  scale - ( xyz[:,0]**2 + xyz[:,1]**2)
    
    # Plot
    vv.subplot(121)
    vv.title('Cartesian coordinates')
    plot3D(xyz, depRange=[-1,0])
    #plot3D(xyz, depRange=[-1,0], raised=False)
    
    
    # Sperical plot
    numOfPts = 1000
    
    # Create random points
    ThetaPhiR = np.zeros((numOfPts,3))
    ThetaPhiR[:,0] = np.pi * np.random.rand(numOfPts) # theta is 0 to 180 deg
    ThetaPhiR[:,1] = 2 * np.pi * np.random.rand(numOfPts) # phi is 0 to 360 deg
    ThetaPhiR[:,2] = 10 * np.log10((np.sin(ThetaPhiR[:,0])**4) * (np.cos(ThetaPhiR[:,1])**2))
    
    # Plot
    vv.subplot(122)
    vv.title('Sperical coordinates')
    plot3D(ThetaPhiR, coordSys='Spherical')
    #plot3D(ThetaPhiR, coordSys='Spherical', raised=False)
    
    
    # Run main loop
    app = vv.use()
    app.Run()
