Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / OpenGL

A basic X3DOM editor based on OpenGL running on ReactOS (and consequently on Windows XP and newer versions)

5.00/5 (4 votes)
16 May 2021CPOL12 min read 7K   327  
Creation of a basic X3DOM editor based on OpenGL with as little code as possible, that is running on ReactOS and Windows, to check out the capabilities of X3DOM.
This project was started to gather and share experience with the not yet standardized format X3DOM for embedding declarative 3D content (originally in HTML5 but used here in programming applications on ReactOS and Windows). It integrates many small things I've done already - like choosing the right IDE, preparing for source code documentation and developing user controls.

Contents

Introduction

X3DOM is a new attempt (as of April 2021 - not yet standardized) to establish an open-source framework and runtime for 3D graphics on the Web as well as to show how an integration of HTML5 and (which is my focus in this article) declarative 3D content could look like.

This simple ReactOS X3DOM Editor, i want to introduce here, shall be able to parse XML (which conforms to the HTML and X3DOM specification) and display it using OpenGL. In the longer term, it should also be possible to edit the declarative 3D elements and save them as XML (which conforms to the HTML and X3DOM specification).

Since I want to promote ReactOS on the side, the source code is provided as an Visual Studio project for Windows (Win32) as well as a collection of Code::Blocks projects for ReactOS (Win32).

ReactOS is an open source alternative to the Windows operation system. Even if the first version of ReactOS dates back to 1998, there is still no 'stable' version of ReactOS. Maybe, the most important reason is a lack of attention.

Driven by the tips, Geometric Primitives for OpenGL and Light Source Features and Related Material Properties in OpenGL as well as the articles Introduction to OpenGL with C/C++ on ReactOS and A Basic Icon Editor Running on ReactOS (and Consequently on Windows XP and Newer Versions), I wanted to dive deeper into the topic of declarative 3D content and - in the longer term - create an application intuitive enough to be used without long study of documentation or years of experience with 3D modeling.

In this article, I base on some findings from other articles, e.g.:

Since my simple ReactOS X3DOM Editor shall run on ReactOS and on Windows XP and newer versions, I need to use plain Win32 API or a C++ class library, that is supported on ReactOS.

Although Code::Blocks comes with the certainly excellent C++ class library wxWidgets, my many years of experience with the MFC make me shy away from familiarizing myself once again with such a complex class library, where a bug fix is only possible by deep diving into the source code and in the end a workaround for the problem has to be found on the Win32 API level anyway. That's why I decided to use the OGWW library from article A Basic Icon Editor Running on ReactOS (and Consequently on Windows XP and Newer Versions), even though I found out in the meantime that the impressive library Win32++ by David Nash invented almost exactly this wheel before I did. Currently, OGWW relies more on the STL than Win32++ does in some comparable aspects and provides additional window controls I need for this application. For example, the simple ReactOS X3DOM Editor requires a TreeView and a PropertyGrid, which are available from version 0.7 of OGWW on.

And also all icons used in this project have been created with the Icon Editor from article A Basic Icon Editor Running on ReactOS (and Consequently on Windows XP and Newer Versions).

Background

The Application

The source code of my simple ReactOS X3DOM Editor combines the essential global settings for displaying the 3D content in a World class (not known to X3DOM). The 3D content itself resides in the Scene class, which implements the Scene Node of the X3DOM Scene Author API. All child nodes of the Scene are as well more or less complete implementations of the nodes defined in the X3DOM Scene Author API.

The UI of my simple ReactOS X3DOM Editor makes World, Scene and all child nodes accessible through a TreeView control, which is displayed on the left side of the application window. The properties of a selected node can be accessed through a PropertyGrid control, which is displayed on the right side of the application window.

Image 1

The source code of the ReactOS X3DOM Editor is based on the OpenGL Windows Wrapper (Ogww) DLL, introduced by the tip Introduction to OpenGL with C/C++ on ReactOS. Meanwhile, the DLL has evolved to meet significantly more requirements on a professional UI, but the DLL is still far away from a release state. However, it is still designed to support application development with C/C++ and C#.

In the case, the inclined reader will ask: Why Ogww - yet another wrapper around the Win32 API? To answer this question, I refer to the The Application chapter in the A Basic Icon Editor Running on ReactOS (and Consequently on Windows XP and Newer Versions) article.

Resources

Since gcc/g++ don't offer a resource compiler I changed the whole resource handling so that no resource compiler is needed:

  • Icons: All icons are either natively compiled and linked from CPP files (my Basic Icon Editor supports CPP file export since version 0.7) or loaded dynamically directly from ICO files. The OGWW DLL provides a lot of functionality to deal with icons in a comfortable way and to compensate for the disadvantage of missing resoure files.
  • I10n/L18n: All UI texts are predefined in the source code and can be localized and internationalized with a functionality largely compatible to gettext. The POT and PO files are also syntax compatible to gettext. Largely compatible means: While on the one hand the alias _(...) is supported as in most gettext implementations, on the other hand wchar_t* is used instead of char* and the localized / internationalized texts can only be read from the PO files - MO files are currently not supported. The OGWW DLL offers this functionality ready to use - finally a much smarter localized and internationalized is possible this way, because the application does not have to be recompiled for it.

XML Parser

The XML parser is based on the brilliant work of in his article Streaming XML parser in C++. The only drawbacks are:

  • The stream API is based on char* instead of wchar_t*.
  • DTD (the <!DOCTYPE doc...> node) is not supported.
  • The creation of an automatic variable  Xml::Inspector<Xml::Encoding::...>(char*) produces ugly crashes, if the file doesn't exist. If a dynamic variable is created instead new Xml::Inspector<Xml::Encoding::...>(char*), the problem can be caught beforehand if (File::Exists(strFullPath)).

For parsing the XML file, however, a variety of data types and encodings are supported. The library is well structured, easy to understand and excellent documented.

Using the code

The project

The initial workspace structure for Code::Blocks looks like this (the solution structure for Visual Studio looks very similar):

Image 2

The X3DomLight project is based on the OpenGL Windows Wrapper (Ogww) DLL. The compilation result of this project, the DLL, is copied to the solution target via an pre build step.

The object-oriented facade, used to address the OpenGL Windows Wrapper (Ogww) DLL, is located in the project folder OGWW_Wrapper. This project part is compiled completely into the solution target.

The application itself is located in the project Folder X3DomLight. There are two sub folders:

  • X3D contains the X3DOM classes.
  • XML contains the XML parser.

This project part is compiled completely into the solution target.

In addition, there are two more folders in the project folder Others:

  • Images contains the icon of the application and the bitmap of the texture.
  • Resources contains the HTML file, that defines the 3D scene.

This project part is copied to the solution target via an pre build step.

The pre build steps can be configured via the Project/targets options dialog, acessable via the Project | Properties... menu. The Project/targets options dialog provides the button Project's build options..., which can be used to open the Project build options dialog. In the Project build options dialog the tab Pre/post build steps allows the configuration of the required pre build steps.

Image 3

The pre build steps I use for the debug version are:

BAT
cmd /c copy "$(PROJECT_DIR)\..\OGWW\bin\Debug\ogww32.dll" "$(PROJECT_DIR)\bin\Debug" /Y
cmd /c if not exist "$(PROJECT_DIR)\bin\Debug\Images" mkdir "$(PROJECT_DIR)\bin\Debug\Images"
cmd /c copy "$(PROJECT_DIR)\Images\ReactOS_X3D_Explorer.ico" "$(PROJECT_DIR)\bin\Debug\Images" /Y
cmd /c copy "$(PROJECT_DIR)\Images\WoodParquet_24bpp.bmp" "$(PROJECT_DIR)\bin\Debug\Images" /Y
cmd /c if not exist "$(PROJECT_DIR)\bin\Debug\Resources" mkdir "$(PROJECT_DIR)\bin\Debug\Resources"
cmd /c copy "$(PROJECT_DIR)\Resources\sample_01.htm" "$(PROJECT_DIR)\bin\Debug\Resources" /Y

The pre build steps I use for the release version are:

BAT
cmd /c copy "$(PROJECT_DIR)\..\OGWW\bin\Release\ogww32.dll" "$(PROJECT_DIR)\bin\Release" /Y
cmd /c if not exist "$(PROJECT_DIR)\bin\Release\Images" mkdir "$(PROJECT_DIR)\bin\Release\Images"
cmd /c copy "$(PROJECT_DIR)\Images\ReactOS_X3D_Explorer.ico" "$(PROJECT_DIR)\bin\Release\Images" /Y
cmd /c copy "$(PROJECT_DIR)\Images\WoodParquet_24bpp.bmp" "$(PROJECT_DIR)\bin\Release\Images" /Y
cmd /c if not exist "$(PROJECT_DIR)\bin\Release\Resources" mkdir "$(PROJECT_DIR)\bin\Release\Resources"
cmd /c copy "$(PROJECT_DIR)\Resources\sample_01.htm" "$(PROJECT_DIR)\bin\Release\Resources" /Y

If I would use the project dependency feature of Code::Blocks, the first line in each pre build steps would be unnecessary - I decided against it, but everybody is free to use the project dependency feature.

X3DOM Scene Author API

The X3DOM Scene Author API currently defines more than 240 node types (classes). My simple ReactOS X3DOM Editor currently implements 25 of then - and not all of them complete:

  • X3DNodePropertyNames to provide the property names of X3D node properties
  • CSSColors to provide the named CSS colors (currently 147)
  • X3DNode as the abstract base type for all nodes in the X3D system
  • X3DChildNode as the abstract base type for all nodes that may be used in children, addChildren, and removeChildren fields
  • X3DBoundedObject as the abstract base type for all nodes that have bounds specified as part of the definition
  • X3DGroupingNode as the abstract base type for all nodes that contain children nodes and are the basis for all aggregation
  • X3DTransformNode as the abstract base type for all nodes that group and transform their children
  • X3DAppearanceNode as the abstract base type for all appearance nodes in X3D
  • X3DGeometryNode as the abstract base type for all geometry nodes in X3D
  • X3DSpatialGeometryNode as the abstract base type for all spatial geometry nodes in X3D
  • X3DShapeNode as the abstract base type for all shape nodes in X3D
  • X3DAppearanceChildNode as the abstract base type for all child nodes of the X3DShapeNode
  • X3DTextureNode as the abstract base type for all nodes which specify sources for texture images
  • X3DMaterialNode as the abstract base type for all material nodes in X3D
  • Scene* to represent a scene, composed of shapes and their appearance
  • Appearance* to represent a shape's material and texture
  • Material* to represent a shape's behavior in relation to light
  • Texture to represent a shape's surface consistency
  • ImageTexture* to represent a shape's surface consistency by an image
  • Transform* to represent translation, rotation and scale of a shape
  • Shape* to represent a 2D or 3D object within the scene
  • Box* to represent a 3D cuboid
  • Cylinder* to represent a 3D cylinder
  • Cone* to represent a 3D cone
  • Sphere* to represent a 3D sphere

Where * indicates that the XML parser reads the corresponding node types from the HTML file.

XML Parser

As mentioned before - the XML parser is based on the brilliant work of in his article Streaming XML parser in C++. To turn this into a DOM parser that can read the X3D scene from an HTML file, it is enough to create one very simple document class XmlDocument and four helper classes XmlAttribute, XmlAttributeCollection, XmlNodeList and XmlNode. All are well documented and are located in the files XmlDocument.hpp and XmlDocument.cpp.

Another class, the Parser class, is responsible for the transformation of XML nodes and attributes into X3D objects. Also this class is well documented and is located in the files X3DParser.hpp and X3DParser.cpp.

Limitations of the MinGW tool chain

Limitations of the GCC/MinGW tool chain affect

  • a few additional (new) C runtime functions provided by Microsoft with Windows, e.g.:
C++
#if defined(__GNUC__) || defined(__MINGW32__)
    wcscat(buf, L" ");
#else
    wcscat_s(buf, L" ");
#endif
  • a few additional (new) Windows API functions not available in the Windows headers, e.g.:
C++
#if !defined(__GNUC__) && !defined(__MINGW32__)
    /// <summary>
    /// Checks whether the indicated major version is equal to current OS major version.
    /// </summary>
    /// <param name="dwMajorVersion">The major version to check for equality.</param>
    /// <returns>Returns <c>TRUE</c> on equality, or <c>FALSE</c> otherwise.</returns>
    /// <returns>Starting with Windows 8.1 (version 6.3) the manifested version,
    /// not the runtime version, is tested.</returns>
    BOOL EqualsMajorVersion(DWORD dwMajorVersion)
    {
        OSVERSIONINFOEX osVersionInfo;
        ::ZeroMemory(&osVersionInfo, sizeof(OSVERSIONINFOEX));
        osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
        osVersionInfo.dwMajorVersion = dwMajorVersion;
        ULONGLONG maskCondition = ::VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);

        return ::VerifyVersionInfo(&osVersionInfo, VER_MAJORVERSION, maskCondition);
    }
#endif
  • and indirect restrictions in ReactOS (which essentially corresponds to the feature level of Windows XP and and is relevant for the GCC/MinGW tool chain because GCC/MinGW is used to compile on ReactOS only) e.g.:
C++
#if defined(__GNUC__) || defined(__MINGW32__)
    bAvoidDebuggerInterference = ::IsDebuggerPresent();
#endif

    if (bAvoidDebuggerInterference)
    {
        Console::WriteError(L"OpenGL enabling failed!\n");
        result = MainFrame::Run((IDLEPROCCB)NULL);
    }
    else
    {
        ...
    }

In all cases, the limitations are compiled conditionally using either
#if defined(__GNUC__) || defined(__MINGW32__) or
#if !defined(__GNUC__) && !defined(__MINGW32__).

Interaction with the PropertyGrid

Properties of the X3DOM Scene Author API node types

Out of the 25 X3DOM Scene Author API node types I have implemented so far, 11 node types and the World node type can be visible in the application's TreeView. Each of these node types can be selected there and its properties are then displayed in the PropertyGrid. To realize this the properties of the selected node, that should be displayed in the PropertyGrid, have to be passed to the PropertyGrid. Some node types have very few properties, other node types have much more properties.

All properties are accessible via specific Set...(...) and Get...() methods as well as via generic HasProperty(wszName) and GetProperty(wszName) methods. Here is an example to illustrate the point using the X3DBoundedObject node type:

C++
void SetBBoxCenter(Vector3f vec3dCenter) noexcept
{ ... }

Vector3f GetBBoxCenter() noexcept
{ ... }

versus (where all property names wszName are taken from string constants, like X3DNodePropertyNames::BBoxCenterN)

C++
virtual bool HasProperty(LPCWSTR wszName) noexcept
{ ... }

inline LPPROPERTYDATA GetProperty(LPCWSTR wszName) noexcept
{ ... }

In other words, this means that the properties can also be addressed by their names using generic functions. his works well for read access so far and the next step will be to provide write access as well.

Disclosure of the properties of the node types

To automate the transfer of the properties to the PropertyGrid, I introduced a helper structure DISCLOSEDPROPERTY. Every property, that shold be displayed in the PropertyGrid - which is usually a selection from the complete set of properties of a class - is represented by a DISCLOSEDPROPERTY entry.

I will demonstrate the creation of a disclosed property using the X3DBoundedObject node type as an example:

C++
/// <summary>Initializes the <c>X3DBoundedObject</c> object's data holder.</summary>
/// <remarks>Provides the possibility to initialize members outside the constructor.</remarks>
void InitInstance()
{
    X3DChildNode::InitInstance();

    AddBooleanProperty(X3DNodePropertyNames::RenderN(), true);
    AddDisclosedProperty(X3DNode::DISCLOSEDPROPERTY{ X3DNodePropertyNames::RenderN(),
                         PropertyDataType::Boolean, X3DNode::PropertyValueLimit::None,
                         X3DNode::PropertyValueLimit::None, false });

    AddVec3dProperty(X3DNodePropertyNames::BBoxCenterN(), 0.0F, 0.0F, 0.0F);
    AddDisclosedProperty(X3DNode::DISCLOSEDPROPERTY{ X3DNodePropertyNames::BBoxCenterN(),
                         PropertyDataType::Vec3f, X3DNode::PropertyValueLimit::FloatMin,
                         X3DNode::PropertyValueLimit::FloatMax, false });

    AddVec3dProperty(X3DNodePropertyNames::BBoxSizeN(), -1.0F, -1.0F, -1.0F);
    AddDisclosedProperty(X3DNode::DISCLOSEDPROPERTY{ X3DNodePropertyNames::BBoxSizeN(),
                         PropertyDataType::Vec3f, X3DNode::PropertyValueLimit::FloatMin,
                         X3DNode::PropertyValueLimit::FloatMax, false });
}

While an Add...Property(...) call registers the property, an AddDisclosedProperty(...) call registers an associated DISCLOSEDPROPERTY entry. In the end, each node type has N properties and a collection of M DISCLOSEDPROPERTY entries with N >= M.

After the DISCLOSEDPROPERTY structure contains the respective name of the property, the property can be accessed via the generic functions with knowledge of the DISCLOSEDPROPERTY structure.

Now it is possible to iterate through the DISCLOSEDPROPERTY entries and in this way the disclosed properties can be handled automatically (and thus be passed from the node they belong to to the PropertyGrid).

C++
auto it = pNode->GetDisclosedPropertyBeginIterator();
for (; it != pNode->GetDisclosedPropertyEndIterator(); it++)
{
    X3DNode::DISCLOSEDPROPERTY    propertyData = *it;
    const X3DNode::LPPROPERTYDATA pData        = pNode->GetProperty(propertyData.Name);

...

}

Since the properties are to be edited in the PropertyGrid, it is sometimes necessary to set value limits for editing. These value limits are also part of the DISCLOSEDPROPERTY structure. The complete declaration of the DISCLOSEDPROPERTY structure looks like this:

C++
/// <summary>This structure is designed to provide information about a disclosed 
///          property (that can be bound to a property grid dynamically).</summary>
typedef struct tagDISCLOSEDPROPERTY
{
    /// <summary>The name of the property, that is disclosed.</summary>
    LPCWSTR             Name;

    /// <summary>The data type of the property.</summary>
    PropertyDataType    DataType;

    /// <summary>The lower limit of the value of the property.</summary>
    PropertyValueLimit  LowerLimitAlias;

    /// <summary>The upper limit of the value of the property.</summary>
    PropertyValueLimit  UpperLimitAlias;

    /// <summary>The flag indicating whether the property is read-only.</summary>
    bool                ReadOnly;
} DISCLOSEDPROPERTY;

Points of Interest

The first version of my simple ReactOS X3DOM Editor puts together all the necessary pieces of the puzzle: UI with TreeView and PropertyGrid, X3DOM parser, OpenGL and an automated filling of the PropertyGrid.

History

  • 16th May, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)