/* -*- mode: C; c-basic-offset: 2 -*- */
/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2021)
  
	Adresse mèl :
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Damien
	CALISTE, laboratoire L_Sim, (2021)

	E-mail address:
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include <math.h>
#include <epoxy/gl.h>

#include "toolShape.h"

/**
 * SECTION:toolShape
 * @short_description: Provide method to generate vertices for various shapes.
 *
 * <para>This tool is providing methods to generate vertices for various shapes.
 * The layout is always %VISU_GL_XYZ_NRM. For most of the shapes, the vertices
 * are packed to form %VISU_GL_TRIANGLES.</para>
 */

static void _polar(gfloat v[6], const float xyz[3], gfloat radius, gfloat theta, gfloat phi)
{
  v[3] = sin(theta) * cos(phi);
  v[4] = sin(theta) * sin(phi);
  v[5] = cos(theta);
  v[0] = xyz[0] + radius * v[3];
  v[1] = xyz[1] + radius * v[4];
  v[2] = xyz[2] + radius * v[5];
}

/**
 * tool_drawSphere:
 * @vertices: (element-type float): a buffer.
 * @xyz: coordinates of the centre.
 * @radius: radius of the sphere.
 * @nlat: discretisation value.
 *
 * Add vertices in @vertices array, using a %VISU_GL_XYZ_NRM layout. Vertices are
 * describing %VISU_GL_TRIANGLES. The sphere is approximated by a discretisation of
 * @nlat elements along the theta and phi angles (in polar coordinates).
 *
 * See tool_drawIcosahedron() for another discretisation using triangles.
 *
 * Since: 3.9
 */
void tool_drawSphere(GArray *vertices, const float xyz[3], float radius, guint nlat)
{
  guint i, j;
  gfloat fact, fact2;

  fact = 2.f * G_PI / (gfloat)nlat;
  fact2 = G_PI / (gfloat)(nlat / 2);
  for (i = 0; i < nlat / 2; i++)
    {
      gfloat theta0, theta1;
      theta0 = (gfloat)i * fact2;
      theta1 = theta0 + fact2;
      for (j = 0; j < nlat; j++)
        {
          gfloat phi0, phi1;
          GLfloat v[6][6];

          phi0 = (gfloat)j * fact;
          phi1 = phi0 + fact;

          _polar(v[0], xyz, radius, theta0, phi0);
          _polar(v[1], xyz, radius, theta1, phi0);
          _polar(v[2], xyz, radius, theta1, phi1);
          memcpy(v[3], v[2], 6 * sizeof(GLfloat));
          _polar(v[4], xyz, radius, theta0, phi1);
          memcpy(v[5], v[0], 6 * sizeof(GLfloat));

          g_array_append_vals(vertices, v, 36);
        }
    }
}

/* The icosahedron drawing. */
#define X .525731112119133606 
#define Z .850650808352039932
static GLfloat vdata[12][3] = {    
   {X, 0.0, -Z}, {-X, 0.0, -Z}, {X, 0.0, Z}, {-X, 0.0, Z},    
   {0.0, -Z, -X}, {0.0, -Z, X}, {0.0, Z, -X}, {0.0, Z, X},    
   {-Z, -X, 0.0}, {Z, -X, 0.0}, {-Z, X, 0.0}, {Z, X, 0.0} 
};
static GLuint tindices[20][3] = { 
   {0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},    
   {8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},    
   {7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6}, 
   {6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };
static void normalize(float v[3]) {    
   GLfloat d = 1.f / sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); 

   g_return_if_fail(d > 0.);
   v[0] *= d; v[1] *= d; v[2] *= d; 
}
static void subdivide(GArray *arr, const float xyz[3], float radius,
                      float *v1, float *v2, float *v3, int depth) 
{ 
  GLfloat v12[3], v23[3], v31[3];    
  GLint i;

  if (depth == 0)
    {
      GLfloat v[3];
      v[0] = xyz[0] + v1[0] * radius;
      v[1] = xyz[1] + v1[1] * radius;
      v[2] = xyz[2] + v1[2] * radius;
      g_array_append_vals(arr, v, 3);
      g_array_append_vals(arr, v1, 3);
      v[0] = xyz[0] + v2[0] * radius;
      v[1] = xyz[1] + v2[1] * radius;
      v[2] = xyz[2] + v2[2] * radius;
      g_array_append_vals(arr, v, 3);
      g_array_append_vals(arr, v2, 3);
      v[0] = xyz[0] + v3[0] * radius;
      v[1] = xyz[1] + v3[1] * radius;
      v[2] = xyz[2] + v3[2] * radius;
      g_array_append_vals(arr, v, 3);
      g_array_append_vals(arr, v3, 3);
      return;
    }
  for (i = 0; i < 3; i++)
    { 
      v12[i] = v1[i]+v2[i]; 
      v23[i] = v2[i]+v3[i];     
      v31[i] = v3[i]+v1[i];    
    } 
  normalize(v12);    
  normalize(v23); 
  normalize(v31); 
  subdivide(arr, xyz, radius, v1, v12, v31, depth - 1);    
  subdivide(arr, xyz, radius, v2, v23, v12, depth - 1);    
  subdivide(arr, xyz, radius, v3, v31, v23, depth - 1);    
  subdivide(arr, xyz, radius, v12, v23, v31, depth - 1); 
}
/**
 * tool_drawIcosahedron:
 * @vertices: (element-type float): a buffer.
 * @xyz: coordinates of the centre.
 * @radius: radius of the sphere.
 * @nlat: discretisation value.
 *
 * Add vertices in @vertices array, using a %VISU_GL_XYZ_NRM layout. Vertices are
 * describing %VISU_GL_TRIANGLES. The sphere is approximated by a discretisation of
 * @nlat elements along an icosahedron.
 *
 * Since: 3.9
 */
void tool_drawIcosahedron(GArray* vertices, const float xyz[3], gfloat radius, guint nlat)
{
  int nfac, i;

  nfac = (int)(log((float)(nlat + 2) / 4.f) / log(2.f));
  for (i = 0; i < 20; i++)
    subdivide(vertices, xyz, radius,
              &vdata[tindices[i][0]][0],       
              &vdata[tindices[i][1]][0],       
              &vdata[tindices[i][2]][0], nfac);
}

static void _rotatePhi(gfloat xyz[3], gfloat phi)
{
  gfloat u, v;
  u = xyz[0];
  v = xyz[1];
  xyz[0] = cos(phi) * u - sin(phi) * v;
  xyz[1] = cos(phi) * v + sin(phi) * u;
}

static void _rotateTheta(gfloat xyz[3], gfloat theta)
{
  gfloat u, v;
  u = xyz[0];
  v = xyz[2];
  xyz[0] = cos(theta) * u - sin(theta) * v;
  xyz[2] = cos(theta) * v + sin(theta) * u;
}

static void _rotatePolar(gfloat xyzNrm[6], gfloat theta, gfloat phi)
{
  _rotateTheta(xyzNrm, theta);
  _rotateTheta(xyzNrm + 3, theta);
  _rotatePhi(xyzNrm, phi);
  _rotatePhi(xyzNrm + 3, phi);
}

static void _generalizedPolar(gfloat xyzNrm[6], const gfloat xyz[3], gfloat radius,
                              gfloat theta0, gfloat phi0,
                              gfloat ratio, gfloat theta, gfloat phi)
{
  const gfloat orig[3] = {0.f, 0.f, 0.f};
  _polar(xyzNrm, orig, radius, theta0, phi0);
  xyzNrm[2] *= ratio;
  _rotatePolar(xyzNrm, theta, phi);
  xyzNrm[0] += xyz[0];
  xyzNrm[1] += xyz[1];
  xyzNrm[2] += xyz[2];
}

/**
 * tool_drawEllipsoid:
 * @vertices: (element-type float): a buffer.
 * @xyz: coordinates of the centre.
 * @theta: alignment of the main axis along the theta angle in polar coordinates.
 * @phi: alignment of the main axis along the phi angle in polar coordinates.
 * @radius: main axis half length.
 * @ratio: ratio between the main and minor axis.
 * @nlat: discretisation value.
 *
 * Generate vertices in %VISU_GL_XYZ_NRM layout describing an ellipsoid shape.
 * The discretisation of the volume is the same than the one used by tool_drawSphere()
 * but with non homogeneous axis.
 *
 * Since: 3.9
 */
void tool_drawEllipsoid(GArray *vertices, const float xyz[3], gfloat theta, gfloat phi,
                        float radius, gfloat ratio, guint nlat)
{
  guint i, j, nlati;
  gfloat fact, facti, rad;

  rad = radius / ratio;
  nlati = nlat * ratio;
  fact = 2.f * G_PI / (gfloat)nlat;
  facti = G_PI / (gfloat)(nlati / 2);
  for (i = 0; i < nlati / 2; i++)
    {
      gfloat theta0, theta1;
      theta0 = (gfloat)i * facti;
      theta1 = theta0 + facti;
      for (j = 0; j < nlat; j++)
        {
          gfloat phi0, phi1;
          GLfloat v[6][6];

          phi0 = (gfloat)j * fact;
          phi1 = phi0 + fact;

          _generalizedPolar(v[0], xyz, rad, theta0, phi0, ratio, theta, phi);
          _generalizedPolar(v[1], xyz, rad, theta1, phi0, ratio, theta, phi);
          _generalizedPolar(v[2], xyz, rad, theta1, phi1, ratio, theta, phi);
          memcpy(v[3], v[2], 6 * sizeof(GLfloat));
          _generalizedPolar(v[4], xyz, rad, theta0, phi1, ratio, theta, phi);
          memcpy(v[5], v[0], 6 * sizeof(GLfloat));

          g_array_append_vals(vertices, v, 36);
        }
    }
}

/**
 * tool_drawTorus:
 * @vertices: (element-type float): a buffer.
 * @xyz: coordinates of the centre.
 * @theta: alignment of the axis along the theta angle in polar coordinates.
 * @phi: alignment of the axis along the phi angle in polar coordinates.
 * @radius: external radius of the torus.
 * @ratio: ratio between the main and minor axis.
 * @nlat: discretisation value.
 *
 * Generate vertices in %VISU_GL_XYZ_NRM layout describing a torus shape.
 * The vertices are packed using %VISU_GL_TRIANGLES.
 *
 * Since: 3.9
 */
void tool_drawTorus(GArray *vertices, const gfloat xyz[3], gfloat theta, gfloat phi,
                    gfloat radius, gfloat ratio, guint nlat)
{
  guint i, j;
  gfloat tp, tc, dtA, dtB, xp1, yp1, xp2, yp2, xc1, yc1, xc2, yc2, alpha, nrm, beta, f;
  gfloat a[6][6];

  dtA = 2.f * G_PI / (gfloat)nlat;
  dtB = 2.f * G_PI / (gfloat)nlat;
  alpha = 0.5f / ratio;
  beta = radius * (1.f - alpha);
  f = 1.f / (1.f - alpha);

  /* loop on the position of the centre of the circle
     which stands vertical in the plane, and which
     is rotated to create the torus */
  for (i = 0; i < nlat; i++)
    {
      tp = (gfloat)i * dtA;
      xp1 = beta * cos(tp);
      yp1 = beta * sin(tp);
      xp2 = beta * cos(tp+dtA);
      yp2 = beta * sin(tp+dtA);

      /* loop around the circle */
      for (j = 0; j < nlat; j++)
	{
	  tc = (gfloat)j * dtB;
	  xc1 = alpha * cos(tc);
	  yc1 = alpha * sin(tc);
	  xc2 = alpha * cos(tc+dtB);
	  yc2 = alpha * sin(tc+dtB);

          /* Vertices */
	  a[0][0] = xp1 * (1.f + xc1 * f);
	  a[0][1] = yp1 * (1.f + xc1 * f);
	  a[0][2] = yc1 * radius;
          nrm = 1.f / sqrt(f * f * xp1 * xc1 * xp1 * xc1 + f * f * yp1 * xc1 * yp1 * xc1 + yc1 * radius * yc1 * radius);
	  a[0][3] = xp1 * xc1 * nrm;
	  a[0][4] = yp1 * xc1 * nrm;
	  a[0][5] = yc1 * radius * nrm;
          _rotatePolar(a[0], theta, phi);
          a[0][0] += xyz[0];
          a[0][1] += xyz[1];
          a[0][2] += xyz[2];

	  a[1][0] = xp2 * (1.f + xc1 * f);
	  a[1][1] = yp2 * (1.f + xc1 * f);
	  a[1][2] = yc1 * radius;
          nrm = 1.f / sqrt(f * f * xp2 * xc1 * xp2 * xc1 + f * f * yp2 * xc1 * yp2 * xc1 + yc1 * radius * yc1 * radius);
	  a[1][3] = xp2 * xc1 * nrm;
	  a[1][4] = yp2 * xc1 * nrm;
	  a[1][5] = yc1 * radius * nrm;
          _rotatePolar(a[1], theta, phi);
          a[1][0] += xyz[0];
          a[1][1] += xyz[1];
          a[1][2] += xyz[2];

	  a[4][0] = xp1 * (1.f + xc2 * f);
	  a[4][1] = yp1 * (1.f + xc2 * f);
	  a[4][2] = yc2 * radius;
          nrm = 1.f / sqrt(f * f * xp1 * xc2 * xp1 * xc2 + f * f * yp1 * xc2 * yp1 * xc2 + yc2 * radius * yc2 * radius);
	  a[4][3] = xp1 * xc2 * nrm;
	  a[4][4] = yp1 * xc2 * nrm;
	  a[4][5] = yc2 * radius * nrm;
          _rotatePolar(a[4], theta, phi);
          a[4][0] += xyz[0];
          a[4][1] += xyz[1];
          a[4][2] += xyz[2];

	  a[2][0] = xp2 * (1.f + xc2 * f);
	  a[2][1] = yp2 * (1.f + xc2 * f);
	  a[2][2] = yc2 * radius;
          nrm = 1.f / sqrt(f * f * xp2 * xc2 * xp2 * xc2 + f * f * yp2 * xc2 * yp2 * xc2 + yc2 * radius * yc2 * radius);
	  a[2][3] = xp2 * xc2 * nrm;
	  a[2][4] = yp2 * xc2 * nrm;
	  a[2][5] = yc2 * radius * nrm;
          _rotatePolar(a[2], theta, phi);
          a[2][0] += xyz[0];
          a[2][1] += xyz[1];
          a[2][2] += xyz[2];

          memcpy(a[3], a[2], 6 * sizeof(GLfloat));
          memcpy(a[5], a[0], 6 * sizeof(GLfloat));

          g_array_append_vals(vertices, a, 36);
	}	  
    }
}

/**
 * tool_drawCube:
 * @vertices: (element-type float): a buffer.
 * @xyz: coordinates of the centre.
 * @size: size of the cube.
 *
 * Generate vertices in %VISU_GL_XYZ_NRM layout describing a cube.
 * The vertices are packed using %VISU_GL_TRIANGLES.
 *
 * Since: 3.9
 */
void tool_drawCube(GArray *vertices, const float xyz[3], float size)
{
  GLfloat v[6];
  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  v[3] = 0.f;
  v[4] = 0.f;
  v[5] = -1.f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[1] = xyz[1] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);

  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  v[3] = 0.f;
  v[4] = 0.f;
  v[5] = +1.f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[1] = xyz[1] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);

  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  v[3] = 0.f;
  v[4] = -1.f;
  v[5] = 0.f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);

  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] + size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  v[3] = 0.f;
  v[4] = +1.f;
  v[5] = 0.f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] + size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[0] = xyz[0] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);

  v[0] = xyz[0] - size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  v[3] = -1.f;
  v[4] = 0.f;
  v[5] = 0.f;
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] + size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] + size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);

  v[0] = xyz[0] + size * 0.5f;
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  v[3] = +1.f;
  v[4] = 0.f;
  v[5] = 0.f;
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] + size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] + size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] + size * 0.5f;
  g_array_append_vals(vertices, v, 6);
  v[1] = xyz[1] - size * 0.5f;
  v[2] = xyz[2] - size * 0.5f;
  g_array_append_vals(vertices, v, 6);
}

static void _basis(float u[3], float v[3], float w[3],
                   const float xyz1[3], const float xyz2[3], float radius)
{
  float nrm;

  w[0] = xyz2[0] - xyz1[0];
  w[1] = xyz2[1] - xyz1[1];
  w[2] = xyz2[2] - xyz1[2];
  if (w[0] || w[1])
    {
      u[0] = w[1];
      u[1] = -w[0];
      u[2] = 0.f;
    }
  else
    {
      u[0] = 1.f;
      u[1] = 0.f;
      u[2] = 0.f;
    }
  nrm = radius / sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
  u[0] *= nrm;
  u[1] *= nrm;
  u[2] *= nrm;
  v[0] = u[1] * w[2] - u[2] * w[1];
  v[1] = u[2] * w[0] - u[0] * w[2];
  v[2] = u[0] * w[1] - u[1] * w[0];
  nrm = radius / sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  v[0] *= nrm;
  v[1] *= nrm;
  v[2] *= nrm;
}

/**
 * tool_drawCylinder:
 * @vertices: (element-type float): a buffer.
 * @xyz1: coordinates of one end.
 * @xyz2: coordinates of the other end.
 * @radius: radius of the cylinder.
 * @nlat: discretisation value.
 * @filled: a boolean.
 *
 * Generate vertices in %VISU_GL_XYZ_NRM layout describing a cylinder.
 * The vertices are packed using %VISU_GL_TRIANGLES. If @filled, a disk is
 * drawn on @xyz1 to close the cylinder.
 *
 * Since: 3.9
 */
void tool_drawCylinder(GArray *vertices, const float xyz1[3], const float xyz2[3],
                       float radius, guint nlat, gboolean filled)
{
  GLfloat vert[6], ref[6], disk[6];
  float u[3], v[3], w[3];
  float da, c, s;
  guint i;

  _basis(u, v, w, xyz1, xyz2, radius);
  disk[3] = -w[0];
  disk[4] = -w[1];
  disk[5] = -w[2];
  da = 2.f * G_PI / (float)nlat;
  c = 1.f;
  s = 0.f;
  for (i = 0; i < nlat; i++)
    {
      gfloat c0 = c, s0 = s;
      vert[3] = ref[3] = u[0] * c0 + v[0] * s0;
      vert[4] = ref[4] = u[1] * c0 + v[1] * s0;
      vert[5] = ref[5] = u[2] * c0 + v[2] * s0;
      vert[0] = xyz1[0] + vert[3];
      vert[1] = xyz1[1] + vert[4];
      vert[2] = xyz1[2] + vert[5];
      g_array_append_vals(vertices, vert, 6);
      vert[0] = ref[0] = xyz2[0] + vert[3];
      vert[1] = ref[1] = xyz2[1] + vert[4];
      vert[2] = ref[2] = xyz2[2] + vert[5];
      g_array_append_vals(vertices, vert, 6);
      c = cos(da * (i + 1));
      s = sin(da * (i + 1));
      vert[3] = u[0] * c + v[0] * s;
      vert[4] = u[1] * c + v[1] * s;
      vert[5] = u[2] * c + v[2] * s;
      vert[0] = xyz1[0] + vert[3];
      vert[1] = xyz1[1] + vert[4];
      vert[2] = xyz1[2] + vert[5];
      g_array_append_vals(vertices, vert, 6);
      g_array_append_vals(vertices, vert, 6);
      g_array_append_vals(vertices, ref, 6);
      vert[0] = xyz2[0] + vert[3];
      vert[1] = xyz2[1] + vert[4];
      vert[2] = xyz2[2] + vert[5];
      g_array_append_vals(vertices, vert, 6);
    }
  c = 1.f;
  s = 0.f;
  if (filled)
    for (i = 0; i < nlat; i++)
      {
        gfloat c0 = c, s0 = s;
        disk[0] = xyz1[0];
        disk[1] = xyz1[1];
        disk[2] = xyz1[2];
        g_array_append_vals(vertices, disk, 6);
        disk[0] = xyz1[0] + 1.03f * (u[0] * c0 + v[0] * s0);
        disk[1] = xyz1[1] + 1.03f * (u[1] * c0 + v[1] * s0);
        disk[2] = xyz1[2] + 1.03f * (u[2] * c0 + v[2] * s0);
        g_array_append_vals(vertices, disk, 6);
        c = cos(da * (i + 1));
        s = sin(da * (i + 1));
        disk[0] = xyz1[0] + 1.03f * (u[0] * c + v[0] * s);
        disk[1] = xyz1[1] + 1.03f * (u[1] * c + v[1] * s);
        disk[2] = xyz1[2] + 1.03f * (u[2] * c + v[2] * s);
        g_array_append_vals(vertices, disk, 6);
      }
}

/**
 * tool_drawCone:
 * @vertices: (element-type float): a buffer.
 * @xyz1: coordinates of the bottom end.
 * @xyz2: coordinates of the pointy end.
 * @radius: radius at the bottom.
 * @nlat: discretisation value.
 * @filled: a boolean.
 *
 * Generate vertices in %VISU_GL_XYZ_NRM layout describing a cone.
 * The vertices are packed using %VISU_GL_TRIANGLES. If @filled, a disk is
 * drawn on @xyz1 to close the cone.
 *
 * Since: 3.9
 */
void tool_drawCone(GArray *vertices, const float xyz1[3], const float xyz2[3],
                   float radius, guint nlat, gboolean filled)
{
  GLfloat vert[6], disk[6];
  float u[3], v[3], w[3], z[3], nrm;
  float da, c, s;
  guint i;

  _basis(u, v, w, xyz1, xyz2, radius);
  nrm = radius * radius / (w[0] * w[0] + w[1] * w[1] + w[2] * w[2]);
  z[0] = w[0] * nrm;
  z[1] = w[1] * nrm;
  z[2] = w[2] * nrm;
  disk[3] = -w[0];
  disk[4] = -w[1];
  disk[5] = -w[2];
  da = 2.f * G_PI / (float)nlat;
  c = 1.f;
  s = 0.f;
  for (i = 0; i < nlat; i++)
    {
      gfloat n[3] = {u[0] * c + v[0] * s,
                     u[1] * c + v[1] * s,
                     u[2] * c + v[2] * s};
      vert[0] = n[0] + xyz1[0];
      vert[1] = n[1] + xyz1[1];
      vert[2] = n[2] + xyz1[2];
      vert[3] = n[0] + z[0];
      vert[4] = n[1] + z[1];
      vert[5] = n[2] + z[2];
      g_array_append_vals(vertices, vert, 6);
      vert[0] = xyz2[0];
      vert[1] = xyz2[1];
      vert[2] = xyz2[2];
      vert[3] = w[0];
      vert[4] = w[1];
      vert[5] = w[2];
      g_array_append_vals(vertices, vert, 6);
      c = cos(da * (i + 1));
      s = sin(da * (i + 1));
      n[0] = u[0] * c + v[0] * s;
      n[1] = u[1] * c + v[1] * s;
      n[2] = u[2] * c + v[2] * s;
      vert[0] = n[0] + xyz1[0];
      vert[1] = n[1] + xyz1[1];
      vert[2] = n[2] + xyz1[2];
      vert[3] = n[0] + z[0];
      vert[4] = n[1] + z[1];
      vert[5] = n[2] + z[2];
      g_array_append_vals(vertices, vert, 6);
    }
  c = 1.f;
  s = 0.f;
  if (filled)
    for (i = 0; i < nlat; i++)
      {
        gfloat c0 = c, s0 = s;
        disk[0] = xyz1[0];
        disk[1] = xyz1[1];
        disk[2] = xyz1[2];
        g_array_append_vals(vertices, disk, 6);
        disk[0] = xyz1[0] + 1.03f * (u[0] * c0 + v[0] * s0);
        disk[1] = xyz1[1] + 1.03f * (u[1] * c0 + v[1] * s0);
        disk[2] = xyz1[2] + 1.03f * (u[2] * c0 + v[2] * s0);
        g_array_append_vals(vertices, disk, 6);
        c = cos(da * (i + 1));
        s = sin(da * (i + 1));
        disk[0] = xyz1[0] + 1.03f * (u[0] * c + v[0] * s);
        disk[1] = xyz1[1] + 1.03f * (u[1] * c + v[1] * s);
        disk[2] = xyz1[2] + 1.03f * (u[2] * c + v[2] * s);
        g_array_append_vals(vertices, disk, 6);
      }
}

/**
 * tool_drawArrow:
 * @vertices: (element-type float): a buffer.
 * @xyz1: coordinates of the bottom end.
 * @xyz2: coordinates of the pointy end.
 * @tailRadius: radius of the cylinder.
 * @hatRadius: radius of the cone.
 * @ratio: ratio between the cylinder and the cone.
 * @nlat: discretisation value.
 *
 * Generate vertices in %VISU_GL_XYZ_NRM layout describing an arrow made
 * of a cylinder and a cone. The vertices are packed using %VISU_GL_TRIANGLES.
 *
 * Since: 3.9
 */
void tool_drawArrow(GArray *vertices, const float xyz1[3], const float xyz2[3],
                    float tailRadius, float hatRadius, float ratio, guint nlat)
{
  float xyz[3];

  xyz[0] = xyz1[0] + ratio * (xyz2[0] - xyz1[0]);
  xyz[1] = xyz1[1] + ratio * (xyz2[1] - xyz1[1]);
  xyz[2] = xyz1[2] + ratio * (xyz2[2] - xyz1[2]);
  tool_drawCylinder(vertices, xyz1, xyz, tailRadius + 0 * hatRadius, nlat, TRUE);
  tool_drawCone(vertices, xyz, xyz2, hatRadius, nlat, TRUE);
}

/**
 * tool_drawAngle:
 * @vertices: (element-type float): a buffer.
 * @xyzRef: coordinates of the angle reference.
 * @xyzRef2: coordinates of one branch.
 * @xyz: coordinates of the other branch.
 * @at: (out caller-allocates): a buffer for coordinates.
 *
 * Generate vertices in %VISU_GL_XYZ layout describing an angle.
 * The vertices are packed using %VISU_GL_TRIANGLE_FAN. @at contains on output
 * coordinates close to the middle of the angle shape.
 *
 * Since: 3.9
 *
 * Returns: the angle value in radians.
 */
gfloat tool_drawAngle(GArray *vertices, const float xyzRef[3], const float xyzRef2[3],
                      const float xyz[3], gfloat at[3])
{
  float dist, u[3], v[3], M[3], c, s, theta, thetaM, l;
  int i, iM;

  /* We calculate the new basis set. */
  u[0] = xyz[0] - xyzRef[0];
  u[1] = xyz[1] - xyzRef[1];
  u[2] = xyz[2] - xyzRef[2];
  l = sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
  dist = 1.f / l;
  u[0] *= dist;
  u[1] *= dist;
  u[2] *= dist;

  v[0] = xyzRef2[0] - xyzRef[0];
  v[1] = xyzRef2[1] - xyzRef[1];
  v[2] = xyzRef2[2] - xyzRef[2];
  dist = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  l = MIN(l, dist);
  dist = 1.f / dist;
  v[0] *= dist;
  v[1] *= dist;
  v[2] *= dist;
  l /= 4.f;

  dist = u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
  thetaM = acos(dist);
  iM     = MAX((int)(thetaM / G_PI * 20.f), 2);

  v[0] -= dist * u[0];
  v[1] -= dist * u[1];
  v[2] -= dist * u[2];
  dist = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
  if (dist < 1e-3)
    {
      v[0] = 1.f;
      v[1] = 1.f;
      v[2] = 1.f;
      i = 0;
      if (u[0] != 0.f)
	i = 0;
      else if (u[1] != 0.f)
	i = 1;
      else if (u[2] != 0.f)
	i = 2;
      else
	g_warning("Selected node and reference are the same.");
      v[i] = 1.f - (u[0] + u[1] + u[2]) / u[i];
      dist = 1.f / sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    }
  else
    dist = 1.f / dist;
  v[0] *= dist;
  v[1] *= dist;
  v[2] *= dist;

  g_array_append_vals(vertices, xyzRef, 3);
  for (i = 0; i < iM; i++)
    {
      theta = (float)i / ((float)iM - 1.f) * thetaM;
      c = cos(theta);
      s = sin(theta);
      M[0] = xyzRef[0] + (u[0] * c + v[0] * s) * l;
      M[1] = xyzRef[1] + (u[1] * c + v[1] * s) * l;
      M[2] = xyzRef[2] + (u[2] * c + v[2] * s) * l;
      g_array_append_vals(vertices, M, 3);
    }

  theta = 0.5f * thetaM;
  c = cos(theta);
  s = sin(theta);
  at[0] = xyzRef[0] + (u[0] * c + v[0] * s) * l * 1.33f;
  at[1] = xyzRef[1] + (u[1] * c + v[1] * s) * l * 1.33f;
  at[2] = xyzRef[2] + (u[2] * c + v[2] * s) * l * 1.33f;
  return thetaM;
}
