blob: c0537b028628c41f9b3540378db524df80047573 [file] [log] [blame]
#ifndef RTTL_ISG_HXX
#define RTTL_ISG_HXX
/*! \file ISG.hxx descrbes the _I_nternal _S_cene _G_raph */
#include "RTTL/common/RTInclude.hxx"
#include "RTTL/common/RTVec.hxx"
#include "RTTL/common/RTMatrix.hxx"
#include "RTTL/common/RTBox.hxx"
#include "RTTL/common/Timer.hxx"
#include <cstring>
#include <vector>
#include <map>
namespace ISG
{
extern int current_timestamp; /*!< to track whether a node is dirty ... */
using RTTL::Matrix4x4;
typedef RTTL::RTVec3i vec3i;
typedef RTTL::RTVec3f vec3f;
typedef RTTL::RTVec4i vec4i;
typedef RTTL::RTVec4f vec4f;
/*! base class for all nodes in the scene graph */
class Node
{
int last_modified;
public:
Node() {};
Node(Node *parent) {
addParent(parent);
};
virtual ~Node()
{};
template<class nodetype>
static void removeParent(nodetype *&from, Node *what)
{
if (from == NULL)
return;
from->removeParent(what);
if (from->parent.size() == 0) {
delete from;
from = NULL;
};
};
void addParent(Node *node)
{
if (find(parent.begin(),parent.end(),node) == parent.end())
{
parent.push_back(node);
}
}
void removeParent(Node *node)
{
vector<Node*>::iterator where = find(parent.begin(),parent.end(),node);
assert(where != parent.end()); /* otherwise the node was not a parent! */
parent.erase(where);
}
/*! notification (usually by one of the children) that one of the
children (or one of the children thereof) was changed. might
eventually have to propagate more information on what was
actually changed (transform, connectivity, vertex poss,
creation/deletion, etc), but for now it's a single bool, and
the renderer has to figure out what exactly was changed, and
how to react to it... */
virtual void childWasChanged(Node *value)
{
markAsDirty();
};
/*! check whether node is (already) marked as dirty. */
bool markedAsDirty()
{
return last_modified >= current_timestamp;
};
/*! mark node as 'dirty' (i.e, 'something' has changed that
requires update by the renderer), and notify all parents of
the change, too */
void markAsDirty()
{
if (!markedAsDirty()) {
// not yet dirty, so mark it and notify all parents...
last_modified = current_timestamp;
for (int i=0; i<parent.size();i++)
parent[i]->childWasChanged(this);
}
}
protected:
vector<Node *> parent; /*!< list of nodes referencing this node. node
gets automatically deleted if all parents
got deleted (if you want to avoid that,
get another node to reference this one.
\note yes, this consumes addtl memory,
but if we want to propagate changes (e.g., in
bbox size) up to the root, we probably need
it
*/
};
/*! transformation that may be attached to _any_ node. might
eventually make special 'XfmNode' class, but right now we decided
to be able to attach xfms to _any_ node
\warning matrix _must_ be invertible (but not checking that)
*/
struct TransformNode : public Node
{
Matrix4x4 matrix;
bool is_visible; /*!< for hiding the subtree ... */
bool has_transform; /*!< if transform is _not_ the identity */
TransformNode(Node *t)
{
target = t;
t->addParent(this);
};
void setTransform(Matrix4x4 &m)
{
has_transform = true; /*! actually, might set has_transform = m.isIdentity() */
matrix = m;
}
void clearTransform()
{
has_transform = false;
#ifdef DEBUG
matrix.setIdentity();
#endif
}
Node *target;
};
struct GroupNode : public Node
{
public:
void addChild(Node *node)
{
children.push_back(node);
node->addParent(this);
}
/*! remove a child from children list */
void removeChild(Node *node)
{
vector<Node*>::iterator where = find(children.begin(),children.end(),node);
assert(where != children.end()); /* otherwise the node was not a parent! */
children.erase(where);
removeParent(node,this);
}
/*! find given node in child list, 'attach' a transform node to that
child (i.e., create a new transform node for that child, and
replace direct child ref with ref to transform */
TransformNode *attachTransformTo(Node *child)
{
// find child reference
vector<Node*>::iterator where = find(children.begin(),children.end(),child);
assert(where != children.end()); /* otherwise the node was not a parent! */
// create new xfm node
TransformNode *xfm = new TransformNode(child);
// replace direct child ref w/ ref to transform node
removeChild(child);
addChild(xfm);
return xfm;
}
inline int getNumChildren() { return children.size(); };
inline Node *getChild(int i)
{
assert(i >= 0 && i < children.size());
return children[i];
}
protected:
vector<Node *> children;
};
struct World;
/*! node that does not need a parent to live. each root node has a
"visible" flag that defines whether the node contains some
actually visible geometry, or whether it is a "dummy" node for
storing stuff that will only be referenced from other
subtrees. (i.e., if you want top have 10 skinned instances of a
given base mesh, it would be natural to have a "dummy" (i.e.,
invisible) subtree that defines that base mesh, and reference that
from 10 properly parameterized and transformed skinning shaders
somewhere else in other, visible subtrees
*/
struct RootNode : public GroupNode
{
/*! constructor, will eventually demand reference to a world it lives in */
RootNode(/* World *world */) {};
};
/*! something that specifies a material _ID_ (or material
pointer). The _core_ does not even have the concept of a
(recurisve?) shader or material, or what the difference between
shaders and materials are, not what the app should support
etcpp. All the _core_ supports is the concept that there is
something that is responsible for the appearance of an object, and
that this is a unique thing. */
struct Material: public Node
{
union {
int materialID;
void *materialPtr;
};
Material(int materialID=0)
: materialID(materialID)
{
};
};
// can specialize certain types yourself if you like to ....
template<class target_t, class source_t>
_INLINE void single_convert(target_t &target, source_t &source)
{ FATAL("this kind of conversion ont implemented ... "); };
template<>
_INLINE void single_convert(vec4f &target, vec3f &source)
{ (vec3f&)target = source; }
template<>
_INLINE void single_convert(vec3f &target, vec4f &source)
{ target = (vec3f&)source; }
template<class target_t, class source_t>
_INLINE void array_convert(target_t *target, source_t *source, int count)
{
for (int i=0;i<count;i++)
single_convert(target[i],source[i]);
};
/*! the main context for the entire world/scene graph. just to keep things together
a world can have multiple
*/
struct World
{
vector<RootNode *> rootNode;
static World *getDefaultWorld()
{ if (m_default == NULL) { World *w = new World; w->makeDefault(); }; return m_default; };
virtual void makeDefault() { m_default = this; };
private:
static World *m_default;
};
/*! format of a mesh */
typedef enum {
RT_TRIANGLE_MESH,
RT_QUAD_MESH
} MeshType;
/*! if you add formats here, be sure to add a size entry into RTDataSizeOf ! */
typedef enum {
RT_ANY_FORMAT = 0/*! format not (yet) specified, accept all format */,
RT_UCHAR,
RT_BYTE,
RT_INT,
RT_INT2,
RT_INT3,
RT_INT4,
RT_FLOAT,
RT_FLOAT2,
RT_FLOAT3,
RT_FLOAT4 /*!< has to be 16-byte aligned */
} DataFormat;
/*! \warning ordering of entires has to correspond to enum DataFormat! */
static const int RTDataSizeOf[] =
{
1,
1,
1,
1*sizeof(int),
2*sizeof(int),
3*sizeof(int),
4*sizeof(int),
1*sizeof(float),
2*sizeof(float),
3*sizeof(float),
4*sizeof(float)
};
typedef enum {
RT_RAW_DATA /*!< raw, unformatted data ... */,
RT_COORDINATE /*!< a point in space, transfomed as a point */,
RT_COORDS=RT_COORDINATE,
RT_VECTOR,
RT_NORMAL /*!< a normal, transfomred with the inverse of the transposed transform */,
RT_TEXCOORD,
RT_INDEX,
RT_INDICES=RT_INDEX
} SemanticType;
/*! wrapper class for all "bulky" items of data that the ray tracer
might need. the data array will store data items of a given
semantic type (coord, normal, ...) in a specified format
(RT_FLOAT3, RT_INT4, ...). <ul><li>Data arrays can be created
and written to via the API, including automatic format
conversions, and, eventually, automatic double-buffering and
(asynchronous) LRB uploads. <li>Data arrays can be shared among
different scene graph entities, giving an additional layer of
instantiation. Writing to a shared data array will trigger
(intentional!) side effects<li> data arrays will track whether
they get modified, and will propagate that information upwards
to their parents</ul>
\note The data array's own format is called the "internal
format" as it is what the ray tracer will operate on. Writing
data in a different format will automatically trigger a format
conversion (ie, rtCoord3f writing to RT_FLOAT4 will
automatically convert 3-floats to 4-floats. Not all conversions
will be supported).
\note In a certain sense, the data array is the central data
container in ISG, and as many objects as possible should make
use of it. Then, once we plug onto LRB, we'll have to implement
the double-buffering etc _only_ for the data array, not for 20
different types...
\warning Note that the ray tracer's performance may (will!)
depend on whether it "likes" the specified data format. Right
now, however, it is too early to say which internal formats are
better than others.
*/
struct DataArray : public Node
{
typedef enum {
APP_IS_OWNER,
I_AM_OWNER,
NO_DATA_TO_OWN
} OwnerType;
SemanticType type;
DataFormat format;
int units; // size in _items_, not in bytes
int size_in_bytes;
void *m_ptr; // actual type depends on 'format' and 'type'
OwnerType owner; /*! if true, we own the data, and will take it with us (to the grave...) when we die */
DataArray(Node *parent, SemanticType type, DataFormat format)
: Node(parent),
type(type),
format(format),
units(0),
m_ptr(NULL),
owner(NO_DATA_TO_OWN)
{};
~DataArray() { clear(); };
void clear()
{
if (owner == I_AM_OWNER
&&
m_ptr != NULL)
free(m_ptr);
m_ptr = NULL;
units = 0;
size_in_bytes = 0;
owner = NO_DATA_TO_OWN;
};
// template<class T>
// void allocate(T *t, int newUnits)
// {
// clear();
// units = newUnits;
// m_ptr = malloc(units * sizeof(T));
// owner = I_AM_OWNER;
// };
// template<class T>
void set(SemanticType type, DataFormat sourceFormat, void *ptr, int units, OwnerType owner)
{
if (I_AM_OWNER)
{
DataFormat &internalFormat = this->format;
if (this->type != type) {
if (this->type == RT_RAW_DATA)
this->type = type;
else
FATAL("writing data of incompatible format to a data array");
}
if (internalFormat == RT_ANY_FORMAT)
// if we didn't care so far, let's just use what's being
// presented (not sure this can ever happen, though ...
internalFormat = sourceFormat;
long new_size_in_bytes = units * RTDataSizeOf[internalFormat];
m_ptr = realloc(m_ptr,new_size_in_bytes);
size_in_bytes = new_size_in_bytes;
this->units = units;
if (internalFormat == sourceFormat)
memcpy(m_ptr,ptr,new_size_in_bytes);
else
switch (internalFormat) {
case RT_FLOAT4:
switch (sourceFormat) {
case RT_FLOAT3:
array_convert((vec4f*)m_ptr,(vec3f*)ptr,units);
break;
default:
FATAL("data conversion from this source format to FLOAT4 not implemented, yet");
}
break;
default:
FATAL("data conversion to this internal format not implemented, yet");
}
}
else
{
/* APP_IS_OWNER, and tells us to use this format no matter
what -- ignore all type checks, just use the data (note:
might actually change semantics here, and do a
copy-n-format-conversoin if types do not match */
clear();
// app owns it (and has promised it's of the correct type), so let's just use it
m_ptr = ptr;
size_in_bytes = 0; /*! dunno about what the app alloced, actually ... */
this->type = type;
this->format = format;
this->units = units;
this->owner = owner;
}
};
};
// struct IndexArray : public DataArray
// {
// IndexArray(Node *parent) : DataArray(parent) {};
// };
// struct CoordArray : public DataArray
// {
// CoordArray(Node *parent) : DataArray(parent) {};
// };
/*! for everything that's based on cages/meshes of (indexed) vertices:
note: could eventually template by coord type, normal type, color
type, etc.
Note that this is a placeholder for various _different_ kinds of
meshes (triangle mesh, polygon mesh, quad mesh, subdiv cage, etc),
and that it's up to the _backend_ to decide which actual type to
generate. this is not particular "nice" from an implementation
standpoint, but it gives lots of flexibility to programmer to
change various things on the fly, and to use a consistent way of
allocating a "mesh" (whatever the app thinks that means) without
having to differentiate from calls like
rtNewTriangleMeshSameShader(),
rtNewTriangleMeshWithOneShaderPerTri(),
rtNewQuadMeshWith.... etcpp. Might still change that, though, I
don't have a strong preference, here... (might at least have to
specify the type during creation like
rtNewMesh(RT_TRIANGLEMESH,RT_SHADER_PER_TRIANGLE) or something
like that -- feel free to change that if you want)
*/
struct BaseMesh : public Node
{
// struct Triangle3 {
// int a;
// int b;
// int c;
// };
// struct Triangle4 {
// int a;
// int b;
// int c;
// int shaderID;
// };
// typedef enum {
// RT_TRIANGLE3,
// RT_TRIANGLE4,
// RT_QUAD4,
// RT_QUAD5,
// RT_POLYGON
// } PrimitiveType;
// PrimitiveType type;
// CoordArray *vtxCoord;
// NormalArray *vtxNormal;
// ColorArray *vtxColor;
// TexCoordArray *texCoord;
/*! connectivity is shared even for different types. the base
primitive type (triangle, quad, polygon, ...) is specified by
the _type_ of the _mesh_ itself (i.e., whether it is a
TriangleMesh, PolygonMesh, etc). The actual data layout of the
connectivity array then defines what actual format the
specific primitives have. e.g.:
for a triangle mesh:
- int3 : Mesh of Triangle3's, shader ID in separate array (or constnat for mesh)
- int4 : each tri has vertex IDs and shader ID
- none : "fat" triangles --> every three vertices are one triangle
quad mesh: int4 and int4, as above
polygonmesh: only int-array allowed (right now, that is), with each poly terminated by '-1'
of course, all 'int's could also appear in 'byte' or 'short' form ....
not all derived meshes will support all connectivity formats
(e.g., int2 does not make sense for TriangleMesh, but also
short3 might not be implemented (implementation specific)
*/
DataArray *index;
/*! vertex coordinates. also see comments on indices field about what those mean
data array for coordinates. note that we do not perform any
type checking whether the actual derived mesh (trianglemesh vs
quadmesh vs subdivmesh vs ...) supports that actual format
(e.g., might not support halfs or uchars for coords, etc) */
DataArray *coord;
/*! most derived mesh implementations will likely interpre this as
a per-primitive shader ID (though some primitives like
Triannlge4i might use their own format for the shader
ID). most "should" assume that if this list has length 1, the
value contained in that list applies to _every_ primitive. */
DataArray *shaderList;
void setCoords(DataArray *c)
{
removeParent(coord,this);
coord = c;
coord->addParent(this);
// NO type checking here, as connectivity could still change afterwards...
}
void setIndices(DataArray *i)
{
removeParent(index,this);
index = i;
index->addParent(this);
// NO type checking here, as connectivity could still change afterwards...
}
MeshType type;
BaseMesh(Node *parent, MeshType type )
: Node(parent), type(type), index(NULL),coord(NULL),shaderList(NULL)
{};
};
// struct GeometryShader : public Node
// {
// };
// struct MeshInterpolator: public GeometryShader
// {
// Node *mesh_t0; /*!< mesh at time t=0 */
// Node *mesh_t1; /*!< mesh at time t=1 */
// float t; /*! time for interpolatoin. has to be 0<=t<=1 */
// };
} // namespace
#endif