The following video shows how to configure a Qt project to create a static library to use in your own projects, the main code for this can be found on github here
The main things you will need to do are in the .pro file as follows
TEMPLATE = lib
CONFIG+=staticlib
If you omit the staticlib it will create a dynamic library and the runtime linker will need to be told where to find your lib.
In the project you intend to use the library in you need to set the LIBS Qt variable passing in the -L[path to lib] and -l lib(s) to link
I have been using the ngl:: library for many years as part of teaching various graphic programming courses. I decided recently it would be interesting to port the core library and many of the demos to work interactively on the web using WebGL so started investigating a number of ways to do this. The main library is written in C++ and uses either Qt or SDL to create the OpenGL context.
I had a number of choices as to which approach to take, I could learn Java Script and three.js however this would mean porting all my codebase to Java Script which seemed like too much work.
In the end I came across the emscripten system which is a LLVM-to-JavaScript Compiler which can convert my C++ code into LLVM and then into JavaScript and then into asm.js the process was quite a steep learning curve, however I manage to get quite a lot of demos ported very quickly which can be seen here the rest of the blog will outline the process and how the webngl system was developed.
Installing Emscripten
My main development environment is a mac, however I have also tested the files under linux and it also works well. To get started I followed the tutorial here and all worked first time. The next stage was to try a simple WebGL demo that is provided with the examples. There are several WebGL demos using different libraries for OpenGL context creation however as I'm most familiar with SDL I chose to use this as the basis of the framework.
A Simple SDL demo program
The following code is a simple SDL program (very similar to a normal SDL program) the only difference is the call to emscripten_set_main_loop.
#include "SDL.h"
#include <GLES2/gl2.h>
#define GL_GLEXT_PROTOTYPES 1
#include <GLES2/gl2ext.h>
#include <emscripten.h>
#include <iostream>
#include <cstdlib>
void process()
{
// as we don't have a timer we need to do something here
// using a static to update at an interval
static int t=0;
if(++t > 100)
{
float r=(double)rand() / ((double)RAND_MAX + 1);
float g=(double)rand() / ((double)RAND_MAX + 1);
float b=(double)rand() / ((double)RAND_MAX + 1);
glClearColor(r,g,b,1);
t=0;
}
glClear(GL_COLOR_BUFFER_BIT);
// this is where we draw
SDL_GL_SwapBuffers();
}
int main(int argc, char *argv[])
{
SDL_Surface *screen;
// Init SDL
if ( SDL_Init(SDL_INIT_VIDEO) != 0 )
{
std::cerr<<"Unable to initialize SDL: "<<SDL_GetError();
return EXIT_FAILURE;
}
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
screen = SDL_SetVideoMode( 720, 576, 16, SDL_OPENGL | SDL_RESIZABLE);
if ( !screen )
{
std::cerr<<"Unable to set video mode: "<<SDL_GetError();
return EXIT_FAILURE;
}
glEnable(GL_DEPTH_TEST);
// let emscripten process something then
// give control back to the browser
emscripten_set_main_loop (process, 0, true);
SDL_Quit();
return EXIT_SUCCESS;
}
The emscripten_set_main_loop function is explained very well here basically we create a function that is called asynchronously to allow the browser to regain control after every iteration of the function. It is important that this function does exit else the browser will hang up and I have had several times when I get a complete lockup of the system.
Compiling the program
To compile the program we use the following command line (in this case I'm using c++ )
em++ -s FULL_ES2=1 SDL1.cpp -o SDL1.html
The flag -s FULL_ES2 tells emscripten to use full OpenGL ES 2 specification when compiling the code, the -o SDL1.html will generate an html file as well as the javascript file for the canvas to use.
The html file can now be opened in the browser an in this case you will see a screen that changes colour every 100 cycles of the main loop.
In the next post I will begin to discuss how I ported the rest of ngl to use emscripten.
This is the first guest post on my Blog, this one comes from a PhD student (and previously an MSc at Bournemouth) Mathieu Sanchez.
This post is specifically aimed at the design of complex software systems and is in general feedback on how to so the initial design required for some of our assignments. I was really impressed with this email and decided to share it with everyone.
Design often takes experience, which is why it is so difficult to teach, but there are some "half rules".
Write down your concepts, keywords and actions on a piece of paper, there is no need to draw anything. Verbs often translate to a relation and/or a class method (important). Nouns often refer to a class. ( see this and this)
Classes are most likely a singular noun. If it is not then maybe you are doing something wrong. In this case, check your multiplicity, and try to find a better name. It can happen that you have "container" classes. Don't name them by what they contain. An example would be a container for several wolves. If you name it wolves, it is unclear, and will lead to confusion. Do you mean a "pack of wolves"? Naming is very important, and will help you get a clearer vision, and help the markers (which is always good).
Classes ending in -ER are a warning flag. It can happen, but if it does, you need to be sure of yourself. Once again, naming might be the issue, not the actual design. (see herehere and here)
Don't build crazy associations everywhere. Data can travel along an association and should have a strong meaning such as "owns" "directs" etc...
Triangle relations are nasty. If you have class A, B and C, there should not be a link between each of them with each other.
If you find that there are many ways to achieve the same task, then there might be some inheritance in there. For instance, shadows in rendering can be done through shadow mapping or shadow feelers. This is crying for inheritance. Make sure you have a look at Liskov substitution if you have inheritance.
Many of the common problems are solved with (famous) patterns. For instance, if there are two ways of doing one operations, but can also be combined together to achieve a better result, then there is a pattern for that.
When I read a diagram, I first look for a point of entry. And so should you. A point of entry for me is where everything starts, the class that controls the entire system. If it is unclear to you where it starts, then you might have missed something.
Think at a higher level at first, don't go straight for details, in fact implementation details such as acceleration structures only come last. The first diagram gives an overview of the system. If the interfaces are correctly made, adding spatial data structures is a piece of cake.
Think about extensibility. What if someone else wants to extend your library/system? If I have to jump into your code to add some if/else and edit your interface, then it is just plain wrong. Hence inheritance and factories.
Finally, once you have a draft, you need to review your diagram. The only thing I do when I try to help you is ask you my famous "what is its responsibility, in one and only sentence?".
Do it yourself. Once the diagram is under your eyes, you can see if one of the classes overlaps with another, or worse, there is no class for a particular class. Then, double check your multiplicity. Read it out loud if needed. You should have two sentences to read. Let s say we have [ A ] 1 ------- * [ B ], then it would read "A has many B, and B belongs to one and only one A". Multiplicity can be: 0..1 (at most one), 1 (one and only one), 0..* (any number), 1..* (one or more). Your inheritance should be solid if it respects the Liskov substitution principle.
Don't leave your diagrams without explanation, and stand your ground. Design is all about making decisions and trade-offs. I want to know those, because this is what lets me know if you actually worked on this, and are an able analyst, or if you are just fighting to just get a working system.
So it's the first week of term, and many new students have arrived to start their lab sessions only to find that we don't use windows!
There are many reasons for this, however the main one is the our main output industry is VFX and the majority of (large) VFX houses use linux / unix. This is due to a number of reasons, however mainly stability, scalability and ease of customisation.
Learning to use the linux command line and linux in general is quite a daunting task for a new user, and many people often wonder why they should bother when there is a perfectly good GUI. However as I shall explain in the following videos it will make you a more efficient / better operator if you can master some of the basic shell commands.
Also it has the added benefit that once you have learnt them, most of them will also be available in both shell scripts, python programs and C / C++ programs so you are learning valuable transferable skills all in one go.
Whilst these videos will concentrate on the university lab setup they will also be relevant to other linux distributions and linux in general and are mainly a repetition of what we do in the introduction lab sessions.
There is a link to a workbook and other resources here
I've just started looking at the Alembic interchange format which looks like a really promising system for passing baked animation data plus a whole lot more around. With big players like ILM, Sony Image Works, The Foundry etc all using it I thought it would be good to get started with implementing my own wrapper for the ngl:: library and just have a general look at what it can do so I can do a lecture or two on it.
Installation is fairly straight forward, you need boost++, HDF5 and cmake and the OpenEXR framework libraries ( ILMBase which has IMath etc). On the mac the latest version get placed in /usr/local/alembic-1.0.3 and this will have the static libs, headers and maya plugins needed to get started.
I'm not going to dwell too long on the structures etc of Alembic at present and just have a look at getting started on some very basic loading of meshes etc. A good overview can be found here.
Getting some geometry
To get some sample geometry to test I decided to generate a very simple scene of primitives in maya
I have named each of the primitives to I can see how they fit into the alembic structure, also as transforms are present within the alembic structure, I decided to duplicate and transform some of the elements to see how this worked.
To export the data we can use the maya exported which comes with Alembic. This is called AbcExport and can be run from the Mel script window. I've not really managed to figure out how selection / grouping works with the exporter as yet, and it seems best to make each individual element in the scene a root and export in turn. This can be done with the following command.
You will see that the -j option is basically a command string that specifies the job, in this case we are exporting world space root nodes (-ws) where -root indicates each of the objects to store.
Finally I'm writing to the file MayaTest.abc with an absolute directory.
Basic Qt Project
To get started I've created a basic Qt project based on the installation directory /usr/local/alembic-1.0.3 to build and link agains the libs using a qt program can be done using the following
You will notice that we need to include both the hdf5 and ILM libs as well as the static alembic libraries.
Basic File read / echo
The first program I wrote was a basic traversal of the structure looking for nodes / meshes. From initial reading of the docs It seems that the internal structure of an alembic file closely resembles as unix file system. With a root node / then nodes following down a tree structure. Each of these nodes is a distinct object that we can access and gather attribute values from.
In most of the sample code / demos a recursive visitor pattern seems to have been used for initial test I wanted a simple iterative solution for quick testing and debugging so I decided to use static loops instead.
The following code is the basic opening of a Alembic archive.
One of the key design philosophies behind Alembic is that the API is split into In and Out classes similar to iostream. This is summed up well by the following quote in the documents
"Because Alembic is intended as a caching system, and not a live scenegraph, the API is split into two roughly symmetrical halves: one for writing, and one for reading. At the Abc and AbcGeom layers, classes that start with ‘O’ are for writing (or “output”), and classes that start with ‘I’ are for reading (or “input”). This is analogous to the C++ iostreams conceptual separation of istreams and ostreams."
So in the above we are opening an input (I) archive.
The next section of code will get the top of the archive and see how many children there are
std::cout<<"traversing archive for elements\n";
IObject obj=archive.getTop();
unsigned int numChildren=obj.getNumChildren();
std::cout<< "found "<<numChildren<<" children in file\n";
Once we have the number of children we can iterate for each child in the node and traverse the tree (it must be noted in this example I know the tree is of a set depth, in reality we would need to traverse using recursion / visitor pattern to cope with more complex scene / geometry data however for a proof of concept this is fine)
The code above grabs the child header of the current object and prints out the name. We then create a new IObject from the current object branch (getChildHeader(i).getName() ), this is the next object in the tree and we can then traverse this.
To access all of the data we can grab the meta data and call the serialize method, followed by traversing the data to see if we have a mesh and seeing what the name is. The output on the scene above looks like the following ( a partial listing )
traversing archive for elements found 18 children in file
/Plane Children 1 schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform Found a mesh plane /Platonic Children 1 schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform Found a mesh icosa /Solid Children 1 schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform Found a mesh buckyball /Sphere Children 1 schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform Found a mesh sphere /Torus Children 1 schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform Found a mesh torus
Getting to the Points
Once I have the ability to grab the mesh in the file structure I can now look at accessing the point data and rendering it. This is a two stage process with the maya output as we have a top level transform node and then the point data. We need to get the transform matrix, then we need to multiply the points by it to get them in the correct world space.
Using the previous code frame work, we can grab the transform using the following
You will see that this returns a M44d which is from the IMath library and is a 4x4 Matrix. We can also access the XformSample elements in a number of ways such as getTranslation, get[X/Y/Z]Rotation, getScale which will actually fit in well with the ngl::Transform class once I get to that stage. Initially I'm just going to use the raw matrix and use IMath to do the multiplication with the point.
We now do the check to see if we have a mesh as outlined above, If we do we can built a MeshObject and access the data as show below
// we have a mesh so build a mesh object
IPolyMesh mesh(child,child2.getName());
// grab the schema for the object
IPolyMeshSchema schema=mesh.getSchema();
// now grab the time sample data for the object
IPolyMeshSchema::Sample mesh_samp;
schema.get( mesh_samp );
// get how many points (positions) there are
uint32_t size=mesh_samp.getPositions()->size();
Another important concept of Alembic is the idea of sampling, again for the documentation
"Because Alembic is a sampling system, and does not natively store things like animation curves or natively provide interpolated values, there is a rich system for recording and recovering the time information associated with the data stored in it. In the classes AbcA::TimeSamplingType and AbcA::TimeSampling, you will find that interface."
We access the mesh sample at the current time value (0 as we've not set any frame data yet) and we can get the positions (and size) at this temporal sample.
The following code will now loop for each of the positions in the sample and multiply them by the current transform. I then save each in my own format Vec3
for(uint32_t m=0; m<size; ++m)
{
// get the current point
V3f p= mesh_samp.getPositions()->get()[m];
// multiply by transform
p=p*mat;
// store for later use
data.push_back(ngl::Vec3(p.x,p.y,p.z));
}
In the case of this demo I just create a point cloud into a Vertex Array object and draw using my ngl:: framework. This can be seen in the following video
A lot of this post is supposition / basic rantings about what I've done so far, as this is a very new API and there is very little solid documentation at the moment, I think this post may well be updated / superseded very soon. It is my intention to fully integrate the mesh and possibly lights / cameras as much as I can into ngl, and we also intend to use this as a core data exchange format at the NCCA, so I hope to have much more detail about all this very soon.