Experiment with Wt, Cinema4D Melange and WebGL

Yesterday I decided to have a play with Wt. Wt (or Witty) is a C++ Web Toolkit which lets you write a webserver entirely in C++ without having to know anything about HTML, Javascript, HTTP or TCP/IP.

I was also interested to see that they have basic support for WebGL in there and decided to give it a test drive. Not being one to make things simple I thought I would also throw Cinema4D’s Melange library into the mix to allow the export of some interesting scenes from Cinema4D and try to view them in a web browser.

Getting started with Wt

Getting up and running with Wt is not simple. First you need to download Wt and unzip it somewhere. Then also get CMake and the latest boost libraries.  You can choose to get the prebuilt boost libraries from BoostPro, but if like me you don’t have a lot of bandwidth then you can compile the boost libraries without too much of a problem.

After following through some of the instructions here for unix or here for windows on using CMake, setting some paths to boost, postgresql (if you have it) then you should successfully be able to generate the Visual Studio Solution files from the CMake program.

Now you can load up the Wt.sln file into Visual Studio and finally build Wt.

After building the WebGL project (part of the Wt.Sln) you might want to test it out to make sure your browser can support it. To do this open the Properties for that Project and set the debugging command arguments to “–http-address=0.0.0.0 –http-port=8080 –deploy-path=/hello –docroot=.”.   

Now you can run that project, open up a browser (I am using Firefox 4) and go to “http://127.0.0.1:8080/hello” and you will see…  nothing.

If you see nothing like me then you need to edit the “teapot.C” file and set the path to the teapot.obj file for it to load, in my case it was the following…

int main(int argc, char **argv)
{
readObj("C:\\Projects\\other\\Wt\\wt-3.1.9\\examples\\webgl\\teapot.obj", data);
return WRun(argc, argv, &createApplication);
}

Now when you run the project and try that link again in your browser you should see a teapot!

Add in Melange

Get Melange from the plugin cafe at Maxon.Net

Next rip apart the WebGL demo project and throw in some code to load in Cinema4D (C4D) files.   

You will also need to add the melange include and lib directories to your visual studio project.

The code below loads in polygon objects and seperates them into VertexBufferObjects that only have a maximum of 65000 vertices (a current WebGL limitation). I have also not worried about calculating vertex normals and just use face normals.

So… what is the result of all this messing around… well here it is!
Good ol’ primitives.
In  the end there were 5 files. These files contain parts of the Wt example files and so any use of this code must also abide by the same licensing terms as outlined on the Wt website.

c4dobjects.h

#ifndef C4D_OBJECTS_H
#define C4D_OBJECTS_H

#include <vector>
#include <string>
#include <boost/tuple/tuple.hpp>

#include <Wt/WGLWidget>
#include <Wt/WMatrix4x4>

#include "c4d_memory.h"
#include "c4d_file.h"

using namespace _melange_;

class PaintWidget;

typedef std::vector PointList;

class C4DWebObjPart
{
public:
C4DWebObjPart();
~C4DWebObjPart();

void SetupBuffers(PaintWidget *pWidget);
void PaintGL(PaintWidget *pWidget);
void AddPoint(const Vector &point);
void AddNormal(const Vector &normal);

const PointList &GetPoints() const;
const PointList &GetNormals() const;

Wt::WGLWidget::Buffer GetPointBuffer() const;
Wt::WGLWidget::Buffer GetNormalBuffer() const;

void SetPointBuffer(Wt::WGLWidget::Buffer buffer);
void SetNormalBuffer(Wt::WGLWidget::Buffer buffer);

private:
PointList m_points;
PointList m_normals;
Wt::WGLWidget::Buffer m_pointsBuffer;
Wt::WGLWidget::Buffer m_normalBuffer;
};

typedef std::vector Parts;

class C4DWebObj
{
public:
C4DWebObj();
~C4DWebObj();
void SetupBuffers(PaintWidget *pWidget);
void PaintGL(PaintWidget *pWidget);
void SetMatrix(const Wt::WMatrix4x4 &mat);
void AddPart(C4DWebObjPart *pPart);

Parts &GetParts();
const Wt::WMatrix4x4 &GetMatrix() const;

private:
Parts m_parts;
Wt::WMatrix4x4 m_modelMatrix;
};

typedef std::vector WebObjectsList;

class C4DWebObjects
{
public:
C4DWebObjects();
~C4DWebObjects();
void SetupBuffers(PaintWidget *pWidget);
void PaintGL(PaintWidget *pWidget);
void LoadFile(const char *pFilename);
void AddObjectsRec(BaseObject *pObj);

WebObjectsList &GetObjects();

private:
WebObjectsList m_objects;
};

#endif


c4dobjects.cpp

#include "c4dobjects.h"
#include "paintwidget.h"
#include "default_alien_overloads.h"

using namespace _melange_;

// overload this function
void GetWriterInfo(LONG &id, String &appname )
{
// register your own pluginid once for your exporter and enter it here under id
// this id must be used for your own unique ids
// Bool AddUniqueID(LONG appid, const CHAR *const mem, LONG bytes);
// Bool FindUniqueID(LONG appid, const CHAR *&mem, LONG &bytes) const;
// Bool GetUniqueIDIndex(LONG idx, LONG &id, const CHAR *&mem, LONG &bytes) const;
// LONG GetUniqueIDCount() const;
id = 0;
appname = "C4D File Viewer";
}

//=======================================================
C4DWebObjPart::C4DWebObjPart()
{
}

C4DWebObjPart::~C4DWebObjPart()
{
}

void C4DWebObjPart::AddPoint(const Vector &point)
{
m_points.push_back(point.x * 0.01);
m_points.push_back(point.y * 0.01);
m_points.push_back(point.z * 0.01);
}

void C4DWebObjPart::AddNormal(const Vector &normal)
{
m_normals.push_back(normal.x);
m_normals.push_back(normal.y);
m_normals.push_back(normal.z);
}

const PointList &C4DWebObjPart::GetPoints() const
{
return m_points;
}

const PointList &C4DWebObjPart::GetNormals() const
{
       return m_normals;
}

Wt::WGLWidget::Buffer C4DWebObjPart::GetPointBuffer() const
{
return m_pointsBuffer;
}

Wt::WGLWidget::Buffer C4DWebObjPart::GetNormalBuffer() const
{
return m_normalBuffer;
}

void C4DWebObjPart::SetPointBuffer(Wt::WGLWidget::Buffer buffer)
{
m_pointsBuffer = buffer;
}

void C4DWebObjPart::SetNormalBuffer(Wt::WGLWidget::Buffer buffer)
{
m_normalBuffer = buffer;
}



//=======================================================
C4DWebObj::C4DWebObj()
{
}

C4DWebObj::~C4DWebObj()
{
for(unsigned int i=0;iGetFirstObject();
if(pObj)
{
AddObjectsRec(pObj);
}
}
}

void C4DWebObjects::AddObjectsRec(BaseObject *pObj)
{
if(pObj)
{
C4DWebObj *pWebObject = new C4DWebObj();
if(pObj->GetType() == Opolygon)
{
C4DWebObjPart *pWebObjectPart = new C4DWebObjPart();
pWebObject->AddPart(pWebObjectPart);

PolygonObject *pPolyObj = (PolygonObject*)pObj;
const CPolygon* pPolys = pPolyObj->GetPolygonR();
const Vector *pPoints = pPolyObj->GetPointR();
LONG polyCount = pPolyObj->GetPolygonCount();
LONG pointCount = pPolyObj->GetPointCount();

Matrix mat = pPolyObj->GetMg();

                       pWebObject->SetMatrix(Wt::WMatrix4x4(
                               mat.v1.x,mat.v1.y,mat.v1.z,mat.off.x * 0.01,
mat.v2.x,mat.v2.y,mat.v2.z,mat.off.y * 0.01,
mat.v3.x,mat.v3.y,mat.v3.z,mat.off.z * 0.01,
0,0,0,1));

LONG a;
for(a=0;aAddPoint(pointA);
pWebObjectPart->AddPoint(pointB);
pWebObjectPart->AddPoint(pointC);

pWebObjectPart->AddNormal(cross);
pWebObjectPart->AddNormal(cross);
pWebObjectPart->AddNormal(cross);

if(poly.c != poly.d)
{
pWebObjectPart->AddPoint(pointA);
pWebObjectPart->AddPoint(pointC);
pWebObjectPart->AddPoint(pointD);

pWebObjectPart->AddNormal(cross);
pWebObjectPart->AddNormal(cross);
pWebObjectPart->AddNormal(cross);
}

if(pWebObjectPart->GetPoints().size() > 65000)
{
pWebObjectPart = new C4DWebObjPart();
pWebObject->AddPart(pWebObjectPart);
}
}
}
m_objects.push_back(pWebObject);

AddObjectsRec(pObj->GetDown());
AddObjectsRec(pObj->GetNext());
}
}



paintwidget.h

/*
* Copyright (C) 2010 Emweb bvba, Heverlee, Belgium.
*
* See the LICENSE file for terms of use.
*/

#ifndef PAINTWIDGET_H_
#define PAINTWIDGET_H_

#include <Wt/WGLWidget>
#include "c4dobjects.h"

// You must inherit Wt::WGLWidget to draw a 3D scene
class PaintWidget: public Wt::WGLWidget
{
public:
 PaintWidget(Wt::WContainerWidget *root);
 
 virtual void initializeGL();  // Specialization of WGLWidgeT::intializeGL()
 virtual void paintGL();  // Specialization of WGLWidgeT::paintGL()
 virtual void resizeGL(int width, int height);  // Specialization of WGLWidgeT::resizeGL()

 // Sets the shader source. Must be set before the widget is first rendered.
 void SetShaders(const std::string &vertexShader, const std::string &fragmentShader);
 void LoadFile(const char *fileName);

private:
 // The shaders, in plain text format
 std::string m_vertexShader;
 std::string m_fragmentShader;

 // Program and related variables
 Program m_shaderProgram;
 AttribLocation m_vertexPositionAttribute;
 AttribLocation m_vertexNormalAttribute;
 UniformLocation m_pMatrixUniform;
 UniformLocation m_cMatrixUniform;
 UniformLocation m_mvMatrixUniform;
 UniformLocation m_nMatrixUniform;

 // A client-side JavaScript matrix variable
 JavaScriptMatrix4x4 m_jsMatrix;

 C4DWebObjects m_objects;
};

#endif


paintwidget.cpp

#include "PaintWidget.h"
#include <Wt/WGLWidget>
#include <Wt/WMatrix4x4>

using namespace Wt;

PaintWidget::PaintWidget(WContainerWidget *root): WGLWidget(root)
{
}

// The initializeGL() captures all JS commands that are to be executed
// before the scene is rendered for the first time. It is executed only
// once. It is re-executed when the WebGL context is restored after it
// was lost.
// In general, it should be used to set up shaders, create VBOs, initialize
// matrices, ...
void PaintWidget::initializeGL()
{
 // In order to know where to look at, calculate the centerpoint of the scene
 double cx, cy, cz;
 cx=cy=cz=0;

 // Transform the world so that we look at the centerpoint of the scene
 WMatrix4x4 worldTransform;
 worldTransform.lookAt(
     cx, cy, cz + 10, // camera position
     cx, cy, cz,      // looking at
     0, 1, 0);        // 'up' vector

 // We want to be able to change the camera position client-side. In
 // order to do so, the world transformation matrix must be stored in
 // a matrix that can be manipulated from JavaScript.
 m_jsMatrix = createJavaScriptMatrix4();
 setJavaScriptMatrix4(m_jsMatrix, worldTransform);

 // This installs a client-side mouse handler that modifies the
 // world transformation matrix. Like WMatrix4x4::lookAt, this works
 // by specifying a center point and an up direction; mouse movements
 // will allow the camera to be moved around the center point.
 setClientSideLookAtHandler(m_jsMatrix, // the name of the JS matrix
     cx, cy, cz,                       // the center point
     0, 1, 0,                          // the up direction
     0.005, 0.005);                    // 'speed' factors
 // Alternative: this installs a client-side mouse handler that allows
 // to 'walk' around: go forward, backward, turn left, turn right, ...
 //setClientSideWalkHandler(jsMatrix_, 0.05, 0.005);

 // First, load a simple shader
 Shader fragmentShader = createShader(FRAGMENT_SHADER);
 shaderSource(fragmentShader, m_fragmentShader);
 compileShader(fragmentShader);

 Shader vertexShader = createShader(VERTEX_SHADER);
 shaderSource(vertexShader, m_vertexShader);
 compileShader(vertexShader);

 m_shaderProgram = createProgram();
 attachShader(m_shaderProgram, vertexShader);
 attachShader(m_shaderProgram, fragmentShader);
 linkProgram(m_shaderProgram);
 useProgram(m_shaderProgram);

 // Extract the references to the attributes from the shader.
 m_vertexNormalAttribute = getAttribLocation(m_shaderProgram, "aVertexNormal");
 m_vertexPositionAttribute = getAttribLocation(m_shaderProgram, "aVertexPosition");
 enableVertexAttribArray(m_vertexPositionAttribute);
 enableVertexAttribArray(m_vertexNormalAttribute);

 // Extract the references the uniforms from the shader
 m_pMatrixUniform  = getUniformLocation(m_shaderProgram, "uPMatrix");
 m_cMatrixUniform  = getUniformLocation(m_shaderProgram, "uCMatrix");
 m_mvMatrixUniform = getUniformLocation(m_shaderProgram, "uMVMatrix");
 m_nMatrixUniform  = getUniformLocation(m_shaderProgram, "uNMatrix");

 // Create a Vertex Buffer Object (VBO) and load all polygon's data
 // (points, normals) into it. In this case we use one VBO that contains
 // all data (6 per point: vx, vy, vz, nx, ny, nz); alternatively you
 // can use multiple VBO's (e.g. one VBO for normals, one for points,
 // one for texture coordinates).
 // Note that if you use indexed buffers, you cannot have indexes
 // larger than 65K, due to the limitations of WebGL.
 WebObjectsList &objects = m_objects.GetObjects();
 for(unsigned int i=0;iGetParts();
for(unsigned int j=0;jSetPointBuffer(createBuffer());
bindBuffer(ARRAY_BUFFER, pPart->GetPointBuffer());
const PointList &points = pPart->GetPoints();
bufferDatafv(ARRAY_BUFFER, points.begin(), points.end(), STATIC_DRAW);

pPart->SetNormalBuffer(createBuffer());
bindBuffer(ARRAY_BUFFER, pPart->GetNormalBuffer());

const PointList &normals = pPart->GetNormals();
bufferDatafv(ARRAY_BUFFER, normals.begin(), normals.end(), STATIC_DRAW);
}
 }
 
 // Set the clear color to a transparant background
 clearColor(0, 0, 0, 0);

 // Reset Z-buffer, enable Z-buffering
 clearDepth(1);
 enable(DEPTH_TEST);
 depthFunc(LEQUAL);
}

void PaintWidget::resizeGL(int width, int height)
{
 // Set the viewport size.
 viewport(0, 0, width, height);

 // Set projection matrix to some fixed values
 WMatrix4x4 proj;
 proj.perspective(45, ((double)width)/height, 1, 40);
 uniformMatrix4(m_pMatrixUniform, proj);
}

// The paintGL function is executed every time the canvas is to be
// repainted. For example: when the camera location is modified,
// an animated object is changed, ...
void PaintWidget::paintGL()
{
 // Clear color an depth buffers
 clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT);

 // Configure the shader: set the uniforms
 // Uniforms are 'configurable constants' in a shader: they are
 // identical for every point that has to be drawn.
 // Set the camera transformation to the value of a client-side JS matrix
 uniformMatrix4(m_cMatrixUniform, m_jsMatrix);


 WebObjectsList &objs = m_objects.GetObjects();
 for(unsigned int i=0;iGetMatrix();
 uniformMatrix4(m_mvMatrixUniform, modelMatrix);

 // The next one is a bit complicated. In desktop OpenGL, a shader
 // has the gl_NormalMatrix matrix available in the shader language,
 // a matrix that is used to transform normals to e.g. implement proper
 // Phong shading (google will help you to find a detailed explanation
 // of why you need it). It is the transposed inverse of the model view
 // matrix. Unfortunately, this matrix is not available in WebGL, so if
 // you want to do phong shading, you must calculate it yourself.
 // Wt provides methods to calculate the transposed inverse of a matrix,
 // when client-side JS matrices are involved. Here, we inverse-transpose
 // the product of the client-side camera matrix and the model matrix.
 uniformMatrix4(m_nMatrixUniform, (m_jsMatrix * modelMatrix).inverted().transposed());


Parts &parts = pObj->GetParts();
for(unsigned int j=0;jGetPointBuffer());

// Configure the vertex attributes:
vertexAttribPointer(m_vertexPositionAttribute,
 3,     // size: Every vertex has an X, Y anc Z component
 FLOAT, // type: They are floats
 false, // normalized: Please, do NOT normalize the vertices
 3*4, // stride: The first byte of the next vertex is located this
 //         amount of bytes further. The format of the VBO is
 //         vx, vy, vz, and every element is a
 //         Float32, hence 4 bytes large
 0);    // offset: The byte position of the first vertex in the buffer
//         is 0.

bindBuffer(ARRAY_BUFFER, pPart->GetNormalBuffer());
vertexAttribPointer(m_vertexNormalAttribute,
 3,
 FLOAT,
 false,
 3*4, // stride: see above. We jump from normal to normal now
 0);  // offset:

// Now draw all the triangles.
drawArrays(TRIANGLES, 0, pPart->GetPoints().size()/3);
}
 }
}

void PaintWidget::SetShaders(const std::string &vertexShader, const std::string &fragmentShader)
{
 m_vertexShader = vertexShader;
 m_fragmentShader = fragmentShader;
}

void PaintWidget::LoadFile(const char *fileName)
{
m_objects.LoadFile(fileName);
}

main.cpp

#include <Wt/WApplication>
#include <Wt/WBreak>
#include <Wt/WContainerWidget>
#include <Wt/WImage>
#include <Wt/WPushButton>
#include <Wt/WTabWidget>
#include <Wt/WText>
#include <Wt/WTextArea>
#include <Wt/WFitLayout>
#include <Wt/WBorderLayout>
#include <Wt/WGridLayout>

#include "paintwidget.h"

using namespace Wt;

const char *fragmentShaderSrc =
"#ifdef GL_ES\n"
"precision highp float;\n"
"#endif\n"
"\n"
"varying vec3 vLightWeighting;\n"
"\n"
"void main(void) {\n"
"  vec4 matColor = vec4(0.278, 0.768, 0.353, 1.0);\n"
"  gl_FragColor = vec4(matColor.rgb * vLightWeighting, matColor.a);\n"
"}\n";

const char *vertexShaderSrc =
"attribute vec3 aVertexPosition;\n"
"attribute vec3 aVertexNormal;\n"
"\n"
"uniform mat4 uMVMatrix; // [M]odel[V]iew matrix\n"
"uniform mat4 uCMatrix;  // Client-side manipulated [C]amera matrix\n"
"uniform mat4 uPMatrix;  // Perspective [P]rojection matrix\n"
"uniform mat4 uNMatrix;  // [N]ormal transformation\n"
"// uNMatrix is the transpose of the inverse of uCMatrix * uMVMatrix\n"
"\n"
"varying vec3 vLightWeighting;\n"
"\n"
"void main(void) {\n"
"  // Calculate the position of this vertex\n"
"  gl_Position = uPMatrix * uCMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\n"
"\n"
"  // Phong shading\n"
"  vec3 transformedNormal = normalize((uNMatrix * vec4(normalize(aVertexNormal), 0)).xyz);\n"
"  vec3 lightingDirection = normalize(vec3(1, 1, 1));\n"
"  float directionalLightWeighting = max(dot(transformedNormal, lightingDirection), 0.0);\n"
"  vec3 uAmbientLightColor = vec3(0.2, 0.2, 0.2);\n"
"  vec3 uDirectionalColor = vec3(0.8, 0.8, 0.8);\n"
"  vLightWeighting = uAmbientLightColor + uDirectionalColor * directionalLightWeighting;\n"
"}\n";

class C4DWeb : public WApplication
{
public:
 C4DWeb(const WEnvironment& env);
 bool Init();
};

C4DWeb::C4DWeb(const WEnvironment& env) : WApplication(env)
{
}

bool C4DWeb::Init()
{
setTitle("C4D File Viewer");
WContainerWidget *pGLContainer = new WContainerWidget(root());
pGLContainer->resize(512, 512);
pGLContainer->setInline(false);
PaintWidget *pPaintWidget = new PaintWidget(pGLContainer);
pPaintWidget->resize(512, 512);
pPaintWidget->SetShaders(vertexShaderSrc, fragmentShaderSrc);
pPaintWidget->LoadFile("C:\\temp\\test.c4d");

return true;
}


WApplication *createApplication(const WEnvironment& env)
{
C4DWeb *pData = new C4DWeb(env);
if(pData)
{
if(pData->Init()) return pData;
}
delete pData;
return NULL;
}

int main(int argc, char **argv)
{
return WRun(argc, argv, &createApplication);
}



Comments

Popular posts from this blog

Creating a renderer using a video post plugin

Selecting a Game Engine

C++ Code Generation using T4 Templates