GSNLib  Tutorial


Tutorial Overview

The Tutorial is designed as a set of exercises for the programmer to either work through or browse.  Once they have completed this tutorial, they should understand the basics.  This tutorial assumes a basic knowledge of the C++ language and some understanding of NURBS curves and surfaces.  All of the source code in this tutorial is working code which can be found in the "prog_test\GSNLib_tutorial.cpp" file.  

Linear Algebra Tutorial

This section of the tutorial gives you some of the basics of how to utilize the linear algebra objects.  The linear algebra classes can be thought of as the basic building blocks of GSNLib.  We have attempted to make them as efficient as possible both in terms of memory usage and performance.  The linear algebra classes are top level classes (i.e. they do not inherit from any other objects).  In general, these classes are either declared on the stack or as part of a more complex object.  Although it is possible, it is recommended that they not be created on heap (i.e. via new).  They have no special memory management system and will directly invoke the default system new and delete to handle their memory.  Much of the implementation of the methods of the linear algebra classes is done using inline methods to enable maximum performance.  

Points and Vectors

There are two actual classes (IwVector2d), IwVector3d), and two symbolic classes (IwPoint2d), IwPoint3d).  In other words, a IwPoint2d does not actually exist; it is just another name for a IwVector2d.  The point classes are just #define equivalents to the vector classes.  The symbolic point classes exist primarily to clarify documentation.  Nearly all of the vector methods are implemented as inline methods The vector classes also provide a reasonable set of math operator overloading to help simplify and clarify the vector operations.  2D vectors and points are often used in conjunction with surface parameter space, where the "X" component of the vector maps to the "U" parameter of the surface and "Y" maps to the "V" parameter. 

See the Source code for iwtutorial_IwVector3d()


There are four extent classes: IwExtent1d, IwExtent2d, IwExtent3d, and IwExtentNd.  Extent classes define a domain of the corresponding dimension by specifying the minimum and maximum values for the range of the domain.  IwExtent1d defines a one-dimensional domain on the real line.  IwExtent2d defines a two-dimensional domain in the real plane.  IwExtent3d defines a three-dimensional domain in three space.  IwExtentNd defines an N-dimensional domain in N-Space.  IwExtent1d is often used to define the parameter interval of a curve.  IwExtent2d is often used to define the domain of the parameters of a surface.  IwExtent3d is often used to define a three-dimensional axis-aligned bounding box.  IwExtentNd is used primarily as the domain representation to limit parameters for solving N-dimensional equations.  

See the Source code for iwtutorial_IwExtent3d()


A Pseudo-Box, IwPseudoBox, is a non-axis aligned bounding volume.  In many cases it can provide a much tighter bound on curves and surfaces than the IwExtent3d object. 

          See the Source code for iwtutorial_IwPseudoBox()

Axis Placement

The Axis Placement, IwAxis2Placement, defines a local coordinate system.  It contains a point representing the origin of the new coordinate system and two vectors representing the new X and Y axes of the new coordinate system.  It is derived from the STEP representation of the equivalent name with the exception that the X and Y axes must be orthogonal.  The IwAxis2Placement is used primarily to represent transformations from one coordinate system to another.  

            See the Source code for iwtutorial_IwAxis2Placement()

Container Tutorial

The container classes are utilized to represent collections.  GSNLib has a single map class (IwMapPtrToPtr - maps a pointer to another pointer) and a single templated array classes, IwTArray.  All of the container classes are subclasses of IwObject and inherit the memory management, and type methods from IwObject.  This section of the tutorial discusses the IwTArray.  There are several things which should be kept in mind while dealing with arrays: arrays have a dynamically allocated data areas which will grow automatically; arrays may utilize the overloaded [] operator only for up to the current size (see GetSize/SetSize methods); arrays may be allocated and constructed in a variety of ways.
           See the Source code for iwtutorial_Container()

Curve Tutorial

The two primary curve classes of interest to the users are IwBSplineCurve and its abstract superclass IwCurve.  The other curve classes (IwSurfaceCurve, IwCompositeCurve, IwHermiteCurve, IwIsoCurve) are used for internal computation and are not currently part of the public interface of GSNLib. The wBSplineCurve is the class which represents a NURBS curve. It provides a method interface for creation, query and editing of the NURBS representation. The IwBSplineCurve class also contains implementations for abstract IwCurve methods. The IwCurve class contains the method interface for most of the numerical algorithms.

         See the Source code for iwtutorial_Curve()

Surface Tutorial

The two primary surface classes of interest to the users are IwBSplineSurface and its abstract superclass IwSurface   The other surface class (IwCurveBoundedSurface) is used for internal computation and is not currently part of the public interface of GSNLib. The >IwBSplineSurface is the class which represents a NURBS surface. It provides a method interface for creation, query and editing of the NURBS representation. The IwBSplineSurface class also contains implementations for abstract IwSurface methods. The IwSurface class contains the method interface for most of the numerical algorithms

See the Source code for iwtutorial_Surface()

Local Solver Tutorial

This portion of the tutorial is for advanced users who wish to programatically extend the Local Solver functionality of GSNLib to suit their particular application. GSNLib provides a class interface for determining solutions to one-dimensional functions (IwLocalSolve1d) and N-dimensional functions (IwLocalSolveNd). A majority of the local solving is done utilizing Newton-based itteration. All of the "Local" methods on curves and surfaces invoke one of these solvers. One can create new solvers by providing new function evaluators. This can be done by subclassing the corresponding evaluator object (IwEvalFunctionObject, IwEvalNFunctionsObject). The evaluators must be able to evaluate the function/functions and supply its derivative/Jacobian. If you are unable to compute the Jacobian matrix for the function you are solving, there are publicly available methods for computing solutions to equations which compute their own Jacobian (see Numerical Recipes in C). The basic procedure for creating a new local N-dimensional solver is as follows:

  1. Create a subclass or IwEvalNFunctionsObject. IwFindCCNormENFO is used in our example.
  2. Other than the domains and parameters which are passed into the evaluation method, which fields will be needed to evaluate the function and its Jacobian at a given point? In our example, pointers to the two curves are needed. You may also want to include data which will help you determine when an adequate solution is found. The tolerance is used to do this in our example.
  3. Write a constructor which loads the extra fields and a corresponding destructor.
  4. Implement the virtual Evaluate method.
    • Evaluate the functions (rF) at the given input parameters (rX).
    • If requested, evaluate the Jacobian matrix (pOptJacobian).
    • Test to determine if the current parameters provide an adequate solution. Typically, GSNLib forces total convergence. This step is not really necessary because the solver which calls the evaluator will be able to determine when it converges based on the parameters.
  5. Invoke the new local solver.
    • Load the intervals for each parameter into an IwExtentNd object of appropriate size. Note that it is possible to solve without specifying intervals.
    • Load the periodicity flags into an IwTArray<ULONG> of appropriate size. If the local solver is intended for use in a global solver which breaks down the curve into smaller segments, you may improve performance by always making periodicity FALSE.
    • Load the initial guess parameters. If intervals are used then these parameters must not be outside of the interval domains. If it is possible to get more than one answer out of an interval, you may wish to choose several guess points and check to make sure they all converge to the same solution.
    • Create the customized evaluator object using the constructor which initializes any additional fields. This is usually done on the stack.
    • Create a local solver object (IwLocalSolveNd) using the evaluator object, the intervals and periodicities.
    • Optionally set up the boundary handler and various tolerances.
    • Invoke the SolveIt method on the local solver with the guess parameters and acceptable tolerance.
    • SolveIt returns a flag indicating if a solution was found and a solution vector (IwTArray<double>).
    • If a solution is found, the solution vector will contain the corresponding parameters.

The following example demonstrates the creation of a local solver which normalizes two curves: 

#include <iwos_extern.h>
#include <iwgfx_extern.h>
#include <IwExtent1d.h>
#include <IwBSplineCurve.h>
#include <IwTArray.h>
#include <IwSolutionArray.h>
#include <IwLocalSolveNd.h>

// The following is the evaluator object for an N-dimensional local
// solver for normalizing two curves. It is a simplified version
// of the one used by IwCurve::LocalCurveSolve.

class IwFindCCNormENFO : public IwEvalNFunctionsObject
const IwCurve & m_crCurve1;
const IwCurve & m_crCurve2;
double m_dSquaredTolerance;
IwFindCCNormENFO(const IwCurve & crCurve1, const IwCurve & crCurve2,
double dSquaredTolerance)
: m_crCurve1(crCurve1), m_crCurve2(crCurve2),
m_dSquaredTolerance(dSquaredTolerance) {}
~IwFindCCNormENFO() {}
virtual IwStatus Evaluate(const IwTArray<double> & crX,
IwTArray<double> & rF,
IwMatrix * pOptJacobian,
IwBoolean & rbFoundAnswer);

IwStatus IwFindCCNormENFO::Evaluate(const IwTArray<double> & crX,
IwTArray<double> & rF,
IwMatrix * pOptJacobian,
IwBoolean & rbFoundAnswer)
// Check to make sure all sizes are correct
IW_ASSERT(crX.GetSize() == 2);
IW_ASSERT(rF.GetSize() == 2);
if (pOptJacobian) {
IW_ASSERT(pOptJacobian->GetNumRows() == 2);
IW_ASSERT(pOptJacobian->GetNumColumns() == 2);

rbFoundAnswer = FALSE;
IwPoint3d sPVV1[3];
SER(m_crCurve1.Evaluate(crX[0], 2, TRUE, sPVV1));

double sTanLenSq1 = sPVV1[1].LengthSquared();
// if we have a zero length tangent then we must fail
if (sTanLenSq1 < IW_EFF_ZERO_SQ) {
return IW_ERR;

IwPoint3d sPVV2[3];
SER(m_crCurve2.Evaluate(crX[1], 2, TRUE, sPVV2));

double sTanLenSq2 = sPVV2[1].LengthSquared();
// if we have a zero length tangent then we must fail
if (sTanLenSq2 < IW_EFF_ZERO_SQ) {
return IW_ERR;

IwVector3d sVecFMinusG = sPVV1[0] - sPVV2[0];

// Curves are Normalzed when the projection of
// the tangents of the curve to the vector between
// the points on the curve is zero (they are perpendicular)
// Set up Jacobian matrix for the following two functions
// fun[0] = f'(f-g)
// fun[1] = g'(f-g)
// |f''(f-g) + f'f' -f'g' |
// |g'f' g''(f-g)-g'g' |
// Where f is sPVV1 and g is sPVV2

if (pOptJacobian) {
(*pOptJacobian)[0][0] = sPVV1[2].Dot(sVecFMinusG) + sPVV1[1].Dot(sPVV1[1]);
(*pOptJacobian)[0][1] = - sPVV1[1].Dot(sPVV2[1]);
(*pOptJacobian)[1][0] = - (*pOptJacobian)[0][1];
(*pOptJacobian)[1][1] = sPVV2[2].Dot(sVecFMinusG) - sPVV2[1].Dot(sPVV2[1]);

// Compute function values
// For this one we take dot product of tangent and vector to point on
// other curve and try to minimize that.
// fun[0] = f'(f-g)
// fun[1] = g'(f-g)
rF[0] = sPVV1[1].Dot(sVecFMinusG);
rF[1] = sPVV2[1].Dot(sVecFMinusG);

if (sVecFMinusG.LengthSquared() < m_dSquaredTolerance) {
rbFoundAnswer = TRUE;
return IW_SUCCESS;
double dProjDistSquared1 = (rF[0]*rF[0]) / (sVecFMinusG.LengthSquared());
double dProjDistSquared2 = (rF[1]*rF[1]) / (sVecFMinusG.LengthSquared());

if (dProjDistSquared1 < m_dSquaredTolerance &&
dProjDistSquared2 < m_dSquaredTolerance) {
rbFoundAnswer = TRUE;
return IW_SUCCESS;

return IW_SUCCESS;
Then look at the Source Code in iwtutorial_LocalSolve ()

Global Solver Tutorial

This portion of the tutorial is for advanced users who wish to programatically extend the global solver functionality of GSNLib to suit their particular application. The Global Solver is a customizable search engine. It searches one or more binary trees (IwTree) with bounding boxes on the nodes. There are several search criterion which are already implemented, minimization, maximization, intersection, and at distance. By default the global solver (IwGlobalSolver) will return pointers to the nodes which satisfy the search criterion. Typically you will want to subclass IwGlobalSolver to specialize one or more of the following:

  • Removal of branches from the search (BranchMayContainAnswers).
    • This is done automatically for minimize, maximize, at distance, and intersect.
  • Determination of when to invoke a local solver (AreReadyForLocalSolve).
    • This is done automatically when leaf nodes of all trees are found.
  • How to subdivide (Subdivide).
    • By default it only traverses the existing tree.
    • It will optimize minimization and maximization by trying to take the best branch first.
  • Invocation of a method for determining a local solution and if additional subdivision is required (LocalSolve).
    • Without customization this returns only pointers to nodes.
    • Usually this method will be overridden to call an appropriate local solver.
  • When solutions are identical (IdenticalSolutions).
    • By default solutions are identical when the solution value and all of its parameters are within effective zero of each other.

Depending upon what you want to do and how, the details of how to implement these methods can vary greatly. We suggest that you attempt to familiarize yourself with the global solver implementations done in IwCurve.cpp and IwSurface.cpp prior to attempting an implementation.

User Defined Curve/Surface Tutorial

This portion of the tutorial is for advanced users who wish to programmatically extend the geometry classes by adding a user defined curve or surface. There are two levels of functionality that may be achieved when adding a new curve or surface; Local Solver support and Global Solver support. To achieve support sufficient to execute all of the Local Solver operations it is sufficient to implement only three methods other then the constructor and destructor. These methods are as follows:

  • GetNaturalInterval/GetNaturalUVDomain - returns the parametric domain of the curve/surface.
  • Evaluate - evaluates the point and requested derivatives of the curve/surface.
  • EvaluatePoint - evaluates just the point of the curve/surface.

The following example shows what is needed to define a new curve type:

IwIsoCurve : public IwCurve
    const IwSurface & m_crSurface;  // Base surface
    IwSurfParamType   m_eSurfParam; // Is it a constant U or V
    double m_dIsoParameter;  // Constant U or V value
    BOOL  m_bFromLeft; // Evaluate surface from left or right side
                          // of the parametric boundary.
    IwIsoCurve(const IwSurface & crSurface,
               IwSurfParamType eSurfParam,
               double dIsoParameter,
               BOOL bFromLeft);
    ~IwIsoCurve() {}

    virtual IwExtent1d GetNaturalInterval() const;

    virtual IwStatus Evaluate(double dParameter,
                              ULONG lNumDerivatives,
                              BOOL bFromLeft,
                              IwVector3d aPointAndDerivatives[]) const;
    virtual IwStatus EvaluatePoint(double dParameter,   IwPoint3d rPoint) const;

In order to extend the curve/surface to support the Global Solver algorithms, they need to support the curve/surface cache creation. This could be accomplished by implementing a number of the methods and rewriting the existing tessellation routines. However, this would be quite difficult. The best way is to create a corresponding NURBS representation and create the cache using the IwBSplineCurve/IwBSplineSurface objects. Ideally you should create an exact NURBS representation of the curve/surface. If this is not possible, you can create an approximation. If you create an approximation, make sure that the approximation tolerance is sufficiently tight and then refine the resulting answers using one of the local solver algorithms and the original geometry. Note that the approximation tolerance should be at least 10 times smaller than the 3D tolerance utilized by the global solver operations.

Legal Stuff

All of the software and documentation received with this release is copyrighted by Solid Modeling Solutions, Inc. You may not distribute source code or documentation for this software outside of the company and the site which owns the license. The standard license agreement allows you to freely distribute object code in any application which does not contain a programmatic interface. All software and documentation is considered proprietary information and the intellectual property of Solid Modeling Solutions, Inc. This document contains trade secret information which is deemed proprietary.

Copyright 1998-2010 Solid Modeling Solutions All rights reserved.
Information in this document is subject to change without notice.