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





5.00/5 (4 votes)
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.
- Download Code::Blocks Project V.0.1
- Download VisualStudio Project V.0.1
- Download Documentation V.0.1
- Download Debug compilation for Windows
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.:
- The OpenGL support on ReactOS, selection of the development environment for ReaktOS and adding a documentation generator in Introduction to OpenGL with C/C++ on ReactOS.
- The OGWW C++ class library and the icon creation in A Basic Icon Editor Running on ReactOS (and Consequently on Windows XP and Newer Versions).
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.
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 supportsCPP
file export since version 0.7) or loaded dynamically directly fromICO
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
andPO
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 handwchar_t*
is used instead ofchar*
and the localized / internationalized texts can only be read from thePO
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 Przemek Mazurkiewicz in his article Streaming XML parser in C++. The only drawbacks are:
- The stream API is based on
char*
instead ofwchar_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 insteadnew Xml::Inspector<Xml::Encoding::...>(char*)
, the problem can be caught beforehandif (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):
![]() | 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:
This project part is compiled completely into the solution target. In addition, there are two more folders in the project folder Others:
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.
The pre build steps I use for the debug version are:
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:
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 Przemek Mazurkiewicz 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.:
#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.:
#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.:
#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:
void SetBBoxCenter(Vector3f vec3dCenter) noexcept
{ ... }
Vector3f GetBBoxCenter() noexcept
{ ... }
versus (where all property names wszName
are taken from string constants, like X3DNodePropertyNames::BBoxCenterN
)
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:
/// <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
).
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:
/// <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