# White_dune developer documentation

- white_dune program overview
- white_dune GUI overview
- VRML/X3D implementation overview
- VRML97/X3D parser
- Dangerous constructs
- "Adding a new node to white_dune"-cookbook
- Open Wonderland export
- Localisation: translating the program to foreign languages
- Localisation from a programmers point of view
- class overview

## "white_dune", what's that ?

"white_dune" is a continuation of the "dune" project by Stephan F. White.
"dune" is a graphical VRML97/X3D Editor, simple NURBS modeller and animation tool with some OpenGL scene rendering capabilities.

## white_dune program overview

In princple, the structure of the white_dune program can be written as:

1. evaluate commandline parameters
2. enter GUI (Grapical User Interface) mainloop

For details, see the file main.cpp

## white_dune GUI overview

The GUI of white_dune consists of 2 parts:

1. 2D GUI
   This handles two dimensional operations like opening windows, display icons, menus, buttons etc.
   2D GUI operations are seperated in a extra programming level, called swt (Stephan White Toolkit).
   Currently, there are two different implementations of swt written in C, one for Motif/Lesstif (Unix/Linux) and one for Win32 (M$Windows).
   For details, see the directory swt. swt use M$Windows rc files. For details about the implementation on M$Windows machines see the directory swt/rc.
2. 3D GUI
   This handles three dimensional operations. This is displaying 3D data (rendering, implemented in OpenGL) and manipulating 3D Objects.
   Some 3D operations, like mouseinput and reaction to desktop events (e.g. resize of Windows) are handled in connection with the 2D GUI (see the file Scene3DView.cpp for details).
   Additional, there are other sources of information for manipulating 3D Objects (also handled in Scene3DView.cpp): input from devices like joystick, dialbox or spaceball. Code for input from this devices is located in the file InputDevice.cpp.

The 2D GUI mainloop of white_dune is event driven. Typical events are mouse-movement, mouse-click, resize of window and so on.
Additionally the mainloop can produce timer events.
When a event occures, the matching callback function is started. The callbacks work for every subwindow of white_dune.
The following image shows the subwindows (red text) of the mainwindow.

Some 2D GUI events are distributed to the different subwindows. The distribution of this events is based on inheritance.
The class SceneView define inheritable 2D GUI callbacks like OnMouseMove and is parent class to the classes ChannelView, FieldView, Scene3DView, SceneGraphView, SceneTreeView, StatusBar, ToolbarWindow and PanedWindow. PanedWindow is parent class to the class MainWindow.

A additional callback OnUpdate is used to distribute messages like UPDATE_FIELD or UPDATE_ADD_NODE to the child classes of SceneView. OnUpdate is started by the function UpdateViews of class Scene.

Some operations require additional input of data. Then a "Dialog" is opened, that block the data input to all other windows.

*Dialog classes are all inheritable from the class Dialog, which implements functions like SaveData() and Validate().
The layout of Dialogs (as well as the layout of the menues) is defined in the dune*.rc files.

## VRML/X3D implementation overview

The class Scene (Scene.h/cpp) can be identified with one VRML/X3D file. For example Scene.write() writes the VRML/X3D file to disk.

The global variable TheApp of class DuneApp (DuneApp.h/cpp) can be identified with things that are global to all VRML/X3D files.

#### Node overfield

The internals of each VRML/X3D Node are implemented in the files named NodeNodeName (for example NodeTransform (NodeTransform.h/cpp), NodeShape (NodeShape.h/cpp) or NodeBox (NodeBox.h/cpp)).

Every NodeNodeName.h file contain 2 classes: the class NodeNodeName which contain functionality like draw() for 3D rendering of shapes and the class ProtoNodeName which are used to build the definitions of the VRML97 standard.
For example, the definiton of the Transform Node in the ISO/IEC 14772 standard
is implemented in the constructor of the class ProtoTransform (in file NodeTransform.cpp):

    ProtoTransform::ProtoTransform(Scene *scene)
      : Proto(scene, "Transform")
    {
        addEventIn(MFNODE, "addChildren");
        addEventIn(MFNODE, "removeChildren");
        center.set(addExposedField(SFVEC3F, "center", new SFVec3f(0.0f, 0.0f, 0.0f)));
        children.set (addExposedField(MFNODE, "children", new MFNode(), CHILD_NODE));
        rotation.set(addExposedField(SFROTATION, "rotation", new SFRotation(0.0f, 0.0f, 1.0f, 0.0f)));
        scale.set(addExposedField(SFVEC3F, "scale", new SFVec3f(1.0f, 1.0f, 1.0f), new SFFloat(0.0f)));
        scaleOrientation.set(addExposedField(SFROTATION, "scaleOrientation",new SFRotation(0.0f, 0.0f, 1.0f, 0.0f)));
        translation.set(addExposedField(SFVEC3F, "translation", new SFVec3f(0.0f, 0.0f, 0.0f)));
        bboxCenter.set(addField(SFVEC3F, "bboxCenter", new SFVec3f(0, 0, 0)));
        bboxSize.set(addField(SFVEC3F, "bboxSize", new SFVec3f(-1, -1, -1), new SFFloat(-1.0f)));
    }

Different fields are internally handled as integer values.
The variables center, children, rotation, scale, scaleOrientation, translation, bboxCenter and bboxSize are of type "fieldIndex", something like a readonly integer, that can be only set once.
There are NodeTransform memberfunctions to get and set field values. For example, the memberfunctions for the field "center" of class "NodeTransform" are:

SFVec3f * | center (void)
void | center (SFVec3f *value)
void | center (SFVec3f &value)
int | center_Field (void)

The memberfunctions "something_Field()" deliver the integer number of a field that is needed for example for a MoveCommand to move nodes in the scenegraph.

Functionality common to all Nodes (like writing a Node to a file (Node.write()) is in the class Node (file Node.h/cpp). All NodeNodeName classes are subclasses of the class Node.
Some of the memberfunctions of the class Node are virtual and can be overwritten by the NodeNodeName classes (for example, the class NodeScript need a special version of Node.write()).
Here is a list of important virtual Node memberfunctions:

- getType()
  deliver a value needed to identify different nodes. For example, the transform node deliver VRML_TRANSFORM.
  The result of getType() is either beginning
  - "VRML_" for nodes which exist in VRML97
  - "X3D_" for nodes which exist in X3D
  - "COVER_" for nodes which are extensions in the cover VRML browser
  - "KAMBI_" for nodes which are extensions in the kambi VRML gameengine
  - "DUNE_" for nodes which only exist in white_dune (scripted PROTOs and export data container nodes)
- write
  write the node to a VRML/X3D file.
- update
  update a node after a field change.
- setHandle
  change a node field from handles (e.g. white boxes in the Scene3DView, that can be moved around with the mouse).
- getHandle
  get the value of a handle.
- drawHandles
  draw the handle in the Scene3DView.
- preDraw
  do operations that prepare the drawing of nodes in the Scene3DView.
- draw
  drawing the nodes in the Scene3DView (and internally to find mouse hits).

Some geometry nodes (like ElevationGrid) are implemented as a subclass of MeshBasedNode. The MeshBasedNode class is used for geometry that can implemented as a IndexedFaceSet type of mesh. The MeshBasedNode class provides important virtual Node memberfunctions like draw. The creation of the mesh in a MeshBasedNode is done via the virtual function createMesh.
Some geometry nodes (like NodeSuperShape) are implemented as a subclass of MeshMorphingNode which is a subclass of MeshBasedNode by itself. A MeshMorphingNode can write a morphing animation of a IndexedFaceSet, when "File->Export As...->Export as &Pure VRML97..." or "File->Export As...->Export as VRML level &X3DV..." is used.
For templates of a MeshBased/MeshMorphing geometry node see NodeGeometryExample.cpp/ NodeGeometryExample.h.
For templates for the rest of nodes see NodeExample.cpp/ NodeExample.h.

#### FieldValue overfield

The class "FieldValue" is used to set the data of the nodes. The wellknown VRML/X3D types like SFFloat, MFVec3f, SFString etc. are subclasses of the "FieldValue" class.
When setting data of the nodes, it is important that the old data is always deallocated using the "delete" command.
The constructor of "MF-type" data (like MFVec3f, MFInt32, MFString etc.) never copy the data array, it reuses the data via a pointer.
Therefore it is not possible to use "automatic" variables (like "float data[30]") to initalise MF-type data directly. When the automatic variable is removed from the memory, the data inside the node is also lost and it is likely, that a crash would occure.
Wrong example:

       {
           float data[20];
           ...
           MFFloat data4node = new MFFloat(data, 20);
           ...
           someNode->setField(fieldName_Field(), data4node);
           ...
       } // someNode.fieldName gets invalid cause memory is freed at this point

A other example of automatic variable usage uses a internal white_dune data type like "array", "list" etc.:

       {
           Array data;
           ...
           data.append(1.0f);
           ...
           data.append(42.0f);
           ...
           MFFloat data4node = new MFFloat(data.getData(), data.size());
           ...
           someNode->setField(fieldName_Field(), data4node);
           ...
       } // someNode.fieldName gets invalid cause memory is freed at this point

The correct way uses "new"

####

####

##

##

##

##

##

##

##

       {
           float* data = new float[20];
           ...
           MFFloat data4node = new MFFloat(data, 20);
           ...
           someNode->setField(fieldName_Field(), data4node);
           ...
       } // someNode.fieldName ok

    Path overview

    As Path is a class that holds the path to a node in the Scenegraph or
    in a PROTO. The path in the Scenegraph is mainly a integer array of
    the fields containing the SFNode or MFNode field of the next node
    (one integer) and the index of the next MFNode field (or 0 in case of a
    SNode field) (another integer).

    If path[0] is less than zero, the path is inside a PROTO. A path[0] == -1
    is inside the first PROTO, a path[0] == -2 is inside the second proto,
    a path[0] == -3 is inside the third PROTO etc.

    In case of a PROTO, path[1] contains the number of the root node of the PROTO:
    path[1] == 0 is the path beginning with the first root node, path[1] == 1 is
    the path beginning with the second root node, path[1] == 2 is the path
    beginning with the third root node.

    PROTO overview

    Proto is the PROTO interface (ProtoDeclare or ExternProtoDeclare) or
    Node interface. Node::getProto() gets the node interface.

    NodePROTO is a ProtoInstance or ExternProtoInstance.
    ExternProtoDeclare is a ProtoDeclare with variable "url" set.

    VRML97/X3D parser

    Dune need to read and parse ("understand") VRML97 or X3DV files.

    This is done with using the tools lex/yacc (it looks like, the advanced
    tools flex and bison from the GNU project are needed).

    The file lexer.l do the lexical analysis (detect things like "what is
    a string", "what is a number", "what is that VRML97 or X3D keyword").

    The file parser.y do the grammatical analysis (detect and prove, if the
    tokens found by the lexical analysis form valid VRML/X3DV contructions
    (like Node or ROUTE statements). If some tokens of the grammar are valid,
    the action part of the command in the parser.y file create the matching
    VRML Nodes, Numbers etc.

    Thanks to Doug Sanden, white_dune can also parse XML encoded X3D files.
    This is done via the expat library.

    Dangerous constructs

    The buildin stringtype ("MyString") can be misleading, it act very
    pointerlike. For example be carefull about constructs like the following:

      MyString str = node->getProto()->getName();
      str += "something";                         // also appended to the name...

    This do not only copy the name of a Proto of "node" to str and append
    "something" to it, it also appends "something" to the name of a Proto !

    In this situation, it is better to use something like

      MyString str = strdup(node->getProto()->getName());
      str += "something";

    or

      MyString str = "";
      str += node->getProto()->getName();
      str += "something";

    Another dangerous constructs are the constructors for MF* types with pointers
    to multiple data of the basic datatype. This is already described
    in a chapter above.

    "Adding a new node to white_dune"-cookbook

    Select a name of the new node (here "Something")

     add a ID_NEW_SOMETHING to resource.h.m4, usually via adding a new m4 macro
         M4_ID(SOMETHING)

     Add

      MENUITEM "Something",                  ID_NEW_SOMETHING

      in the matching submenu of the POPUP "&Create" menu and and

          ID_NEW_SOMETHING      "Create a new Something Scripted PROTO\nSomething Scripted"

      to all src/dune.*.rc files. See the chapter about
      localisation for more informations about handling the needed language
      translations.

      add a

        VRML_SOMETHING for VRML97 nodes
        X3D_SOMETHING for X3D nodes
        COVER_SOMETHING for extensions of the cover VRML browser
        KAMBI_SOMETHING for extensions of the kambi VRML gameengine
        DUNE_SOMETHING for scripted protos and all other extension nodes

      to the matching
      place (sorted by SOMETHING in the alphabet) to the matching enum in Node.h

     add a

       addOrReplace(protos, "Something", new ProtoSomething(scene), add);

      in a matching place (sorted by the alphabet of the SOMETHING in the list
      in Node.h) in the function SceneProtoMap::createProtoMap and add

      #include "NodeSomething.h"

      to SceneProtoMap.cpp

     copy a similar Node*.h/cpp to NodeSomething.h/cpp and change
      the fields/EventIns and draw, setHandle etc. functions.

      Do not forget to change the getType() function.

     in case the new node is always insertable (as a usual geometry node) add a

      { DUNE_SOMETHING,       ID_NEW_SOMETHING, true, true },

      (you may replace DUNE_SOMETHING with VRML_SOMETHING or X3D_SOMETHING)
      to the matching NodeButton arrays in MainWindow.cpp

      In case the new node is not always insertable (like a texture node, which
      requires a Appearance node) add something similar to

      { DUNE_SOMETHING,       ID_NEW_SOMETHING, false, true },

      to MainWindows.cpp and add something like

              case DUNE_SOMETHING:
                valid = node->findValidFieldType(VRML_REQUIRED_BY_SOMETHING) != -1;
                break;

      to the swich in MainWindow::UpdateToolbar(STOOLBAR toolbar, Node *node,
      int field, NodeButton *buttons, int count)

    Use gimp to include a new icon (16 pixels width) of your node
         (nodenames need to be sorted by the alphabet) into res/node_icons.bmp
         (or res_color/node_icons.bmp for UNIX type compilation with configure)

      Warning: DO NOT change the numbers of colors in this bitmap file !

      When you have gimp version 1.2 installed as "gimp", you can
      use the script "batch/inserticon.sh" under Linux/UNIX/MacOSX by typing

             sh batch/inserticon.sh DUNE_SOMETHING

      or

             sh batch/inserticon.sh X3D_SOMETHING

      gimp will then open and select the matching node icons that has to be
      moved 16 pixels to left. Use "edit->cut" and "edit->paste" then move
      the icons to the left border of the gimp window. Draw your new icon
      in the new 16 pixels wide white gap.

     add a

            case ID_NEW_SOMETHING:
              CreateNode("Something");
              break;

      to MainWindow::OnCommand(int id) in MainWindow.cpp

      You may need to replace CreateNode("Something"); by
      CreateGeometryNode("Something"); for a geometry node or you need to
      replace it with InsertNode(DUNE_SOMETHING, "Something"); for a node,
      that can only be inserted depending on the current node.

     In case of a new scripted node: it makes only sense with a VRML97 PROTO
         statement.
         Put your PROTO into the docs/scriptedNodes directory.

     add the name of NodeSomething.o file into the matching place
         in the src/Makefile.in file.
         Add NodeSomething.cpp to the M$Windows project or add it as a include
         into src/FilesMissingInWindowsProjects.cpp

     Do not forget the GPL license, which forces you to public somehow
         the complete sources, if you want to redistribute a modified version
         of white_dune...

    Open Wonderland export

    White_dune is able to write the VRML97/X3D scenegraph data in the programming
    languages C, C++ and java. The resulting data can be used in other programs,
    e.g. to render the data with a own OpenGL program. There are a lot of visable
    nodes in VRML97/X3D, so there is away to convert primitive (e.g. Box) or
    parametric (e.g. NurbsPatchSurface) shapes to mesh data. It is also possible
    to convert to triangulated mesh data. All visible shapes classes (except
    shape classes not rendered by white_dune yet and the class of the "Text" node)
    are children of the MeshBasedNode class, which handles the conversion.
    Animateable shapes (like Nurbs(Patch)Surface) are children of the
    MeshMorphingNode class, which is able to create morphing animation data
    for the resulting mesh data. The MeshMorphingNode class is also a child
    of the MeshBasedNode class.

    White_dune can also directly write the needed java sourcecode to render
    VRML97/X3D data in the Open Wonderland multiuser server.

    This includes features like animation, scripting and interaction.

    See the man page of white_dune
    for more information.

    Unfortunatly, there are a lot of nodes, which are not supported by
    the wonderland exporter. Each node can be written as a java class, but it
    makes a remarkable difference to the java compiler (with respect to
    memory usage and compile time) if the classes of the unsupported nodes
    are written or not. Therefore, the optimization option to avoid unsupported
    nodes is now per default in use. Unsupported nodes found in the X3D file
    are also written, but developers who wish to implement the support for
    such a node may need either always switch off the optimization or
    mark this node as supported by the Wonderland exporter in the white_dune
    sources.

    The interface of a node is defined in class named e.g. "ProtoSomeNodeName"
    in the file NodeSomeNodeName.cpp.

    The "ProtoSomeNodeName" class is a (direct or indirect) child of a class named
    "Proto". To mark a node as supported by the Wonderland exporter replace the
    "Proto" class name in the ProtoSomeNodeName definition

        class SomeNodeName : public Proto {

    with the class name "WonderlandExportProto"

        class SomeNodeName : public WonderlandExportProto {

    Localisation: translating the program to foreign
        languages

    It requires absolute no programming skills to translate the program to a
    foreign language, if the foreign languages uses ASCII characters:

    you only need to copy the file "src/dune.english.rc" into a similar file,
    use a texteditor with search and replace und at end translate the strings
    in this file to the foreign language.

    The problems when trying to translate the program to a foreign
    language with non ASCII characters (like chinease UTF8 characters) are
    untested/unknown.

    To complete the task to create a new runnable program a compatible compiler
    is needed. Of course, this task can be done (after publicing the main
    translation file src/dune.something.rc) by a programmer later, it do not need to
    be done by the translator.

    All "src/dune.*.rc" files are copied together into one src/dune.rc file during the
    run of "configure" and "make".

    This means, that for creating a new runnable program, you need either a
    UNIX like system (e.g. Linux or MacOSX) or a bunch of unixtools:

    m4, make, cat, grep and sh (e.g. bash or ksh).

    A complete free/opensource installable collection of unixtools for M$Windows
    is called cygwin and is available for free from the internet.

    For Translaton, first search the ISO 3166 two characters of your language
    (e.g. "en" for english, "de" for german, "it" for italian, etc...)
    and build the uppercase version of this characters (e.g. EN for english,
    DE for german, IT for italian, etc).

    Second, select a src/dune.something.rc file. In the simpler case, do not select
    "src/dune.english.rc" cause this requires more complicated search and replace
    commands.

      In case of translating "src/dune.english.rc", you have to replace the following
      characters in the whole file:

       IDR_ with IDR_DE_ for german, IDR_ with IDR_IT_ for italian etc
       IDD_ with IDD_DE_ for german, IDD_ with IDD_IT_ for italian etc
       IDS_ with IDS_DE_ for german, IDS_ with IDS_IT_ for italian etc

      Now search for the first occurance of STRINGTABLE DISCARDABLE in
      src/dune.english.rc and replace (starting at this point)

       ID_ with ID_DE_ for german, ID_ with ID_IT_ for italian etc

      In case of translating a other "src/dune.*.rc" than "src/dune.english.rc", you
      simply have to replace the term _??_ in the whole file, where ?? marks
      the uppercase ISO 3166 two characters. E.g. if you translate
      "src/dune.german.rc" to "src/dune.italian.rc", you have to replace _DE_ with
      _IT_ in the whole file, thats all.

    Third you need to translate all the strings in the new src/dune.something.rc file.
    A so called string is a term that starts with a " and ends with a "

    For example, the translation of the line from src/dune.english.rc

        POPUP "&File"

    to german language in src/dune.german.rc would result in

        POPUP "&Datei"

    where the german word Datei is the translation of the english word File.
    The "&" sign marks the keyboard shortcut. The next character after "&"
    should not be ambient in the same level of a menu.

    After the translation of the strings is ready, the translation step is
    ready. On a Linux/UNIX/MacOSX/cygwin system with the gawk (GNU awk) program
    you can use

    sh test/testmenus.sh

    one the commandline to use some consistency testing of the new rc file.

    The new file src/dune.something.rc can be now publiced (e.g. in a usenet
    group like comp.lang.vrml, or at
    the
    sourceforge site for the original dune project).

    For a recompilation, you first need to extend the list of lanugages,
    white_dune understands.

    Currently you need to extend the list of known languages in the file
    src/swt/include/languages.m4. E.g. to extend the list of known
    languages from english (empty default) and german (DE) in
    src/swt/include/languages.m4

    define(`M4_LANGUAGES_CONFIG',`
    DE,german')

    to english, italian (IT) and german, then change the list to

    define(`M4_LANGUAGES_CONFIG',`
    IT,italian,
    DE,german')

    or extent to english, italian and spanish (ES) and german, then change the list
    to

    define(`M4_LANGUAGES_CONFIG',`
    IT,italian,
    ES,spanish,
    DE,german')

    Take care that the last language line (here with "DE,german") do not end
    with a comma.

    It is also possible to change the yes/no strings in Linux/UNIX messageboxes.
    E.g. you extend the yes/no strings from english and german in
    src/swt/include/languages.m4

    define(M4_YES_english,"yes")
    define(M4_YES_german,"Ja")

    define(M4_NO_english,"no")
    define(M4_NO_german,"Nein")

    with the yes/no strings of italian, you have to change
    src/swt/include/languages.m4 to

    define(M4_YES_english,"yes")
    define(M4_YES_italian,"si")
    define(M4_YES_german,"Ja")

    define(M4_NO_english,"no")
    define(M4_NO_italian,"no")
    define(M4_NO_german,"Nein")

    The final step is rebuilding the needed files for compilation. On a
    Linux/UNIX/MacOSX/cygwin system, this is done automatically by using
    "sh build.sh"

    (or "configure && make").

    On other systems, at least a text editor and a port of the m4 program is
    required.

    With the texteditor, copy all dune.*.rc files together and store the
    result in the file "dune.rc"

    With m4 you need to execute the following commands in the src directory:

    m4 resource.h.m4 > resource.h

    m4 CommandlineLanguages.h.m4 > CommandlineLanguages.h

    After recompilation (see the file INSTALL for details), the new language can
    be used with a commandline parameter.
    The name of the new commandline parameter is the minus sign followed by
    the second word in the new line in M4_LANGUAGES_CONFIG.

    E.g. if you added "IT,italian," to M4_LANGUAGES_CONFIG, the new commandline
    parameter to switch to the italien language is -italien

    All that have to be done now is to add the new commandline parameter to the
    documentation in the manpage file man/dune.1

    Localisation from a programmers point
    of view

    When handling text messages from a programmers point of view, always try to
    use the function "swLoadString" to get a language related string (e.g. for a
    errormessage) from the "src/dune.rc" file. The first argument of the
    swLoadString file is a identifier, like IDS_INTERNAL_ERROR, which refers to
    the message in the default (english) language. To get the current language
    translation, add the term TheApp->getLang() to the idenifier, e.g . with
    swLoadString(IDS_INTERNAL_ERROR + TheApp->getLang(),...

    As a programmer, take care about the fact, that each program code
    modification (e.g. addition of new menu items) in one of the
    src/dune.something.rc
    files requires the change of a all other "src/dune.somelanguage.rc" files.

    Cause a programmer is usually not a multilingual genius, the addition of
    a new menuitem, icon tooltip or errormessage comes with a translation
    problem. There are two possible solutions: either use a dictionary/internet
    translation service like babelfish.altavista.com, or leave the english
    word in the src/dune.something.rc file and hope for modification by users.

    To leave the line blank cause of a unknown foreign word is not a option,
    cause this can result in a crash of the program when used in this foreign
    language.

    The addition of a new icon tooltip or errormessage also requires the
    inclusion of a new "#define" command which can be usually found in the
    resource.h file.

    Do not change the resource.h file directly. The file resource.h is a
    autogenerated file, that could be overwritten at each run of the "make"
    command or "configure" command.

    class overview

    The name of most sourcefiles in the "src" directory is identical
    to the name of the major contained class.

    The following class/filenames have special meanings:

         NodeSomeName:

            Implement details for the VRML Node "SomeName"

         SomeNameNode:

            Parentclass of other NodeSomeName classes

         SomethingDialog, SomethingWindow, SomethingView:

            Implement details for the graphical user interface (GUI)

         SomethingApp:

            Implement details for data global to the whole application

         SomethingCommand:

            Implement details for sending internal commands

         SFSomething/MFSomething:

            Implement details for VRML/X3D datatypes

    If doxygen
    is installed, a class hierarchy of white_dune can be produced by typing

             make documentation

    and can then be found here.