Using cryph with OpenGL

Prerequisites

  1. Points & Vectors in Affine and Projective Spaces
  2. Dot and Cross Product Review

Reference for cryph Utilities

See this cryph documentation.

Using cryph

  1. Preliminary concepts and interfaces
    1. TheBasics.c++ (Instantiating points and vectors; overloaded arithmetic operators; using some basic methods)
    2. Some useful public instance methods in class AffVector
      void arbitraryNormal(AffVector& normal) const; // to "this"
      AffVector cross(const AffVector& rhs) const; // return this x rhs
      void decompose(const AffVector& arbitraryVector, // with respect to "this"
                     AffVector& parallel, AffVector& perpendicular) const;
      double dot(const AffVector& rhs) const; // return this . rhs
      double length() const; // of "this"
      double lengthSquared() const; // of "this"
      // In the following two "normalize" methods, the return value is the length
      // of "this" vector before normalization. For example, for |v| > eps:
      //      double L = v.normalize();
      // is equivalent to:
      //      double L = v.length();
      //      v = v / L;
      double normalize(); // "this" (Unlike all the other methods in 1.b, "this" gets modified)
      double normalizeToCopy(AffVector& normalizedCopy) const;
    3. Some useful public class methods in class AffVector:

      In the first two:

      • U (or V) ← component of U (or V) perpendicular to W;
      • if the resulting U (or V) is the zero vector then U (or V) ← arbitrary normal to W
      • VW x U (or UV x W)
      • All three are normalized and returned
      void coordinateSystemFromUW(AffVector& U, AffVector& V, AffVector& W);
      void coordinateSystemFromVW(AffVector& U, AffVector& V, AffVector& W);
      AffVector cross(const AffVector& v1, const AffVector& v2);
      double dot(const AffVector& v1, const AffVector& v2);
    4. Public class variables in class AffVector (these can be used, but not modified):
      const AffVector xu; // (1, 0, 0) - in your code, you reference this as: "cryph::AffVector::xu"
      const AffVector yu; // (0, 1, 0)
      const AffVector zu; // (0, 0, 1)
      const AffVector zeroVector;
    5. Some useful public instance methods in class AffPoint
      double distanceSquaredTo(const AffPoint& P) const; // distance squared from "this" to "P"
      double distanceTo(const AffPoint& P) const; // distance from "this" to "P"
      void toCylindrical(double& r, double& theta, double& z) const;
      void toSpherical(double& rho, double& theta, double& phi) const;
    6. Some useful public class methods in class AffPoint:
      AffPoint fromCylindrical(double r, double theta, double z);
      AffPoint fromSpherical(double rho, double theta, double phi);
    7. Public class variables in class AffPoint (these can be used, but not modified):
      const AffPoint origin; // e.g., in your code, you reference this as: "cryph::AffPoint::origin"
      const AffPoint xAxisPoint; // (1, 0, 0)
      const AffPoint yAxisPoint; // (0, 1, 0)
      const AffPoint zAxisPoint; // (0, 0, 1)
    8. Intermediate complexity: Rendering and Querying a Parabola
  2. Designing 3D geometry "in place"
    1. pwlApproxCircle.c++: PWL approximation of an arbitrary circle in space
    2. pwlApproxCylinder.c++: Extending the circle example to create a PWL approximation of an arbitrary cylinder in space
    3. Exercise: Create a model consisting of a series of spokes like those on a bicycle wheel, ferris wheel, etc.
    4. Exercise: Generalize Block from SampleProgramSet3/MandM/Block.c++:
      Suppose the constructor is: Block(cryph::AffPoint C, cryph::AffVector edge1, cryph::AffVector edge2, cryph::AffVector edge3). The bulk of the class remains unchanged. The only changes required are:
      • How the VBO containing the 8 vertices is generated in Block::defineBlock
      • How the MC bounding box limits are determined (This can be done in Block::defineBlock as well.)
      • How the normal vectors are determined. (Also best computed in Block::defineBlock and stored in instance variables.)

      Try starting from here: Block.h; Block.c++

  3. Transformation matrices: construction and use

    Transformation matrices are used to define view transformations for OpenGL (e.g., MC→EC; EC→LDS), but they can also be used to transform model geometry (e.g., MC→MC). We have already seen the cryph::Matrix4x4 class methods for creating view transformation matrices. Here we focus on methods that create other general modeling transformations.

    For example, instead of creating our general Block as in 2.d, we may define it in canonical position and orientation (e.g., almost exactly as we first saw it in MandM), then create translation and/or rotation and/or scale transformations to place it where we want it in our MC space. Doing so typically requires that we transform both points and (normal) vectors. We will generally create 4x4 matrices for these transformations. Be sure you carefully declare normal vectors as cryph::AffVector and points as cryph::AffPoint because the overloaded matrix operators behave differently (as they must) when applied to points versus vectors. See, for example, 3.b.

    1. Primary modeling transformation interfaces

      Like the view transformation matrices we saw earlier (lookAt, perspective, et al.), all of the following are class method prototypes in class Matrix4x4. They are all "factory methods" that return matrices built as specified.

      • Matrix4x4 xRotationDegrees(double angle);
      • Matrix4x4 yRotationDegrees(double angle);
      • Matrix4x4 zRotationDegrees(double angle);
      • Matrix4x4 xRotationRadians(double angle);
      • Matrix4x4 yRotationRadians(double angle);
      • Matrix4x4 zRotationRadians(double angle);
      • Matrix4x4 generalRotationDegrees(const AffPoint& B, const AffVector& axis, double angle);
      • Matrix4x4 generalRotationRadians(const AffPoint& B, const AffVector& axis, double angle);
      • Matrix4x4 mirror(const AffPoint& B, const AffVector& n);
      • Matrix4x4 translation(const AffVector& trans);
      • Matrix4x4 translation(double dx, double dy, double dz);
      • Matrix4x4 scale(double sx, double sy, double sz);
    2. On the use of 4x4 matrices
      • All the arithmetic operators are overloaded so that operations between various combinations of matrices, points, and vectors work as expected. Specifically, you can do:
        • M1 * M2
        • M1 * aPoint
        • M1 * aVector
      • Do not mistakenly define a vector as a point and then transform it:
        cryph::Matrix4x4 M = …;
        cryph::AffPoint myVector = …;
        … M * myVector …; // You will get unexpected results!!!
        

        Instead, be sure you do:

        cryph::Matrix4x4 M = …;
        cryph::AffVector myVector = …;
        … M * myVector …; // OK now!
        
      • Suppose a 4x4 Matrix, M, is used to transform geometry, and suppose M3x3 is the upper left 3x3 sub-matrix of M. Recall that normal vectors (i.e., vectors defined as being perpendicular to some surface in your model) must be transformed using (M3x3-1)T.
    3. Exercise: Add these declarations and code stubs to class Block and then complete the implementations as indicated: Block_canonical.h; Block_canonical.c++.
    4. Exercise: Show how to create an instance of this Block with sizes 2x3x5, rotated 45 degrees about the y-axis, then 45 degrees about the z-axis and placed with its corner at (10, -3, 40).
  4. Interfacing with OpenGL
    1. If you have created a single point or an individual vector and you need to send it to OpenGL, that is very straightforward as we saw above. For example: glVertexAttrib3f(shaderIF->pvaLoc("mcNormal"), n1.dx, n1.dy, n1.dz);
    2. Oftentimes we generate arrays of points and/or arrays of vectors that we want to send to VBOs. To facilitate that, use the various cryph::AffPoint::aCoords or cryph::AffVector::vComponents methods which allow you to directly copy into vec3 or simple float arrays. For example, sending as a simple float array:
      void sendToGPU_AsFloatArray(cryph::AffPoint* points, int numPoints)
      {
          float* ptsAsFloat = new float[3*numPoints];
      
          for (int i=0 ; i<numPoints ; i++)
              points[i].aCoords(ptsAsFloat, 3*i); // NOTE: "3*i" when passing as float*
      
          int numBytes = 3 * numPoints * sizeof(float);
          glBufferData(GL_ARRAY_BUFFER, numBytes, ptsAsFloat, GL_STATIC_DRAW);
          delete [] ptsAsFloat;
      }
              

      Equivalently, you can send as a vec3 array:

      void sendToGPU_AsVec3Array(cryph::AffPoint* points, int numPoints)
      {
          typedef float vec3[3];
          vec3* ptsAsVec3 = new vec3[numPoints];
      
          for (int i=0 ; i<numPoints ; i++)
              points[i].aCoords(ptsAsVec3, i); // NOTE: "i" when passing as vec3*
      
          int numBytes = numPoints * sizeof(vec3);
          glBufferData(GL_ARRAY_BUFFER, numBytes, ptsAsVec3, GL_STATIC_DRAW);
          delete [] ptsAsVec3;
      }
              

      Regardless of how you send them, don't forget you also need to establish glVertexAttribPointer and glEnableVertexAttribArray properly.

    3. When passing matrices to OpenGL as uniform variables, OpenGL needs to know whether we are passing them in row-major or column-major order. It expects column-major, so the third parameter to the glUniformMatrix* family of routines is called "transpose" and is set to false if you are supplying the matrix in column-major order. The cryph::Matrix4x4 class has instance methods extractColMajor and extractRowMajor. Depending on which you use, you will set the "transpose" parameter accordingly. For example:
      cryph::Matrix4x4 mc_ec, ec_lds;
      ModelView::getMatrices(mc_ec, ec_lds);
      float m[16];
      glUniformMatrix4fv(shaderIF->ppuLoc("mc_ec"), 1, false, mc_ec.extractColMajor(m));
      glUniformMatrix4fv(shaderIF->ppuLoc("ec_lds"), 1, false, ec_lds.extractColMajor(m));