FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Property handling (ctprops)
Goto page 1, 2  Next
 
Post new topic   Reply to topic     Forum Index -> OpenMeshD
View previous topic :: View next topic  
Author Message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Mon Sep 24, 2007 2:37 pm    Post subject: Property handling (ctprops) Reply with quote

In response to Jascha's ctprops code posted on the D newsgroup.
This version allows for making predefined groups of properties, and also for associating a default type alias with each known property. For instance if there's a "vposition" compile-time property, then there will always be a VPosition type alias that refers to the type of the vposition property elements.

Code:

module ctprops2;

/// used like a Trait for the Mesh type
struct Prop(T, string N)
{
    alias T Type;
    const string Name = N;
}


struct PropSet(T...)
{
    alias T properties;
}

template DEFAULT_PROPERTIES(float_t=float) {
    alias PropSet!(
        Prop!(float_t[3], "vposition"),
        Prop!(float_t[3], "vnormal"),
        Prop!(float_t[3], "vcolor"))
 
       DEFAULT_PROPERTIES;
}

/// runtime property handle
struct PropHandle(T)
{
    alias T Type;
    size_t  index;
}

/// compile-time property handle
struct PropHandleCT(T, size_t I)
{
    alias T Type;
    const size_t index = I;
   
    static PropHandle!(T) rt_handle()
    {
        return PropHandle!(T)(I);
    }
}

// properties are just like before
abstract class BaseProperty
{
    this(string name)
    { this.name = name; }

    void resize(size_t n);
   
    string name;
}

class Property(T) : BaseProperty
{
    this(string name)
    { super(name); }

    override void resize(size_t n)
    { data.length = n; }
   
    T[] data;
}

/// here we just add a few compile-time variants of some functions
class PropertyContainer
{
    // compile-time compatible add_property
    PropHandle!(T) add_property(T, string name)()
    {
        size_t index = properties.length;
        properties ~= new Property!(T)(name);
        return PropHandle!(T)(index);
    }

    // retrieve a property using a compile-time handle
    Property!(PH.Type) property(alias PH)()
    {
        assert(PH.index < properties.length);
        return cast(Property!(PH.Type))properties[PH.index];
    }

    // retrieve a property using a runtime-time handle
    Property!(T) property(T)(PropHandle!(T) ph)
    {
        assert(ph.index < properties.length);
        return cast(Property!(T))properties[ph.index];
    }

    // some operation for testing
    void resize(size_t n)
    {
        foreach ( p; properties )
            p.resize(n);
    }
   
    BaseProperty[]  properties;
}

static const char[][] STANDARD_PROPERTIES = [
    "vposition",   "VPosition",
    "vnormal",     "VNormal",
    "vcolor",      "VColor",
    "vtexcoord1d", "VTexCoord1D",
    "vtexcoord2d", "VTexCoord2D",
    "vtexcoord3d", "VTexCoord3D"

    "fposition",   "FPosition",
    "fnormal",     "FNormal",
    "fcolor",      "FColor",
    "ftexcoord1d", "FTexCoord1D",
    "ftexcoord2d", "FTexCoord2D",
    "ftexcoord3d", "FTexCoord3D"
];

private string lookup_standard_property_type(string key)
{
    size_t N = STANDARD_PROPERTIES.length;
    for(size_t i=0; i<N; i+=2) {
        if (key==STANDARD_PROPERTIES[i]) {
            return STANDARD_PROPERTIES[i+1];
        }
    }
    return "";
}

private string standard_property_type_alias(string typename)
{
    if (typename=="") { return ""; }
    return "alias P.Type " ~ typename ~ ";";
}

template Tuple(T...) {
    alias T Tuple;
}

string fmtnum(uint x) {
    char[] ret;
    static const char[] dig = "0123456789";
    if (x==0) return "0";
    while (x > 0) {
        ret = dig[ x%10 ] ~ ret;
        x/=10;
    }
    return ret;
}

class Mesh(P_=DEFAULT_PROPERTIES!(), T_...) : PropertyContainer
{
    // add handles for all compile-time properties
    template AddPropHandles(size_t I, alias P, T...)
    {
        static if(is(P.properties)) {
            mixin AddPropHandles!(I, P.properties);
            static if ( T.length > 0 )
                mixin AddPropHandles!(I+P.properties.length, T);
        }
        else {
            mixin("alias PropHandleCT!(P.Type, I) "~P.Name~";");
            mixin(standard_property_type_alias(lookup_standard_property_type(P.Name)));
            static if ( T.length > 0 )
                mixin AddPropHandles!(I+1, T);
        }
    }
    mixin AddPropHandles!(0, Tuple!(P_,T_));

    private void _add_property(P)() {
        static if(is(P.properties)) {
            foreach( PP; P.properties ) {
                _add_property!(PP);
            }
        }
        else {
            add_property!(P.Type, P.Name);
        }
    }

    // constructor initializes all compile-time properties
    this()
    {
        // make sure the properties get added in the same order
        // as their indeces are declared by AddPropHandles
        foreach ( P; Tuple!(P_,T_) ) {
            _add_property!(P);
        }
    }
}


//-----------------------------------------------------------------------------
import std.stdio;

void main()
{
    // add two properties at compile-time
    /*
    auto mesh = new Mesh!( Prop!(float[3],"vnormal"),
                           Prop!(float[3],"vposition"),
                           Prop!(float[3],"vcolor"),
                           Prop!(float,"prop1"),
                           Prop!(float,"prop2") );
    */
    auto mesh = new Mesh!( DEFAULT_PROPERTIES!(float), Prop!(float,"prop1"), Prop!(float,"prop2") );
    //auto mesh = new Mesh!();

    // the compile-time check for a property can use the property's name and type
    static assert ( is(mesh.vposition.Type == float[3]), "this code needs a mesh with vposition of type float[3]" );

    // add a property at runtime
    auto ph3 = mesh.add_property!(float[3], "prop3");

    mesh.resize(100);

    // accessing properties using compile-time handles
    auto prop1 = mesh.property!(mesh.vposition).data;
    // ... or runtime handles
    auto prop3 = mesh.property(ph3).data;
    // or get a runtime handle for a compile-time property
    auto rtph2 = mesh.vnormal.rt_handle;
    auto prop2 = mesh.property(rtph2).data;
   
    writefln("%s %s", typeid(typeof(prop1)), prop1.length);
    writefln("%s %s", typeid(typeof(prop2)), prop2.length);
    writefln("%s %s", typeid(typeof(prop3)), prop3.length);

    writefln("VPosition %s", typeid(mesh.VPosition));
    writefln("VNormal %s", typeid(mesh.VNormal));
    writefln("VColor %s", typeid(mesh.VColor));
   
}
Back to top
View user's profile Send private message
jascha



Joined: 17 Jun 2007
Posts: 13
Location: Aachen, Germany

PostPosted: Tue Sep 25, 2007 6:07 am    Post subject: Reply with quote

the property sets are handy. also, having an alias for the property types is useful, so one doesn't have to type "mesh.propName.Type".
but how about we just use the property name with a suffix "_t" as the type's name? that way we don't have to list all the standard type names. and non-standard properties will have type aliases, too:

Code:

    template AddPropHandles(size_t I, alias P, T...)
    {
        static if(is(P.properties)) {
            mixin AddPropHandles!(I, P.properties);
            static if ( T.length > 0 )
                mixin AddPropHandles!(I+P.properties.length, T);
        }
        else {
            mixin("alias PropHandleCT!(P.Type, I) "~P.Name~";");
            mixin("alias P.Type "~P.Name~"_t;");
            static if ( T.length > 0 )
                mixin AddPropHandles!(I+1, T);
        }
    }


About default property sets: how about naming them with acronyms for the contained properties:

Code:

template DEFAULT_PROPS_PNC(float_t=float) {
    alias PropSet!(
        Prop!(float_t[3], "vposition"),
        Prop!(float_t[3], "vnormal"),
        Prop!(float_t[3], "vcolor"))
 
       DEFAULT_PROPS_PNC;
}

// then add shortcuts
template MeshPNCf(T...) { alias Mesh!(DEFAULT_PROPS_PNC!(float), T) MeshPNCf; }
template MeshPNCd(T...) { alias Mesh!(DEFAULT_PROPS_PNC!(double), T) MeshPNCf; }
template MeshPNCr(T...) { alias Mesh!(DEFAULT_PROPS_PNC!(real), T) MeshPNCf; }


... and the same for MeshPNf, etc.

Here is another design proposal that gets enabled by the new property design:

Properties have to be saved by vertex, by halfedge, by face or by mesh. Abstracting that leads us to "property streams". A mesh is a set of property streams. Each stream has an arbitrary number of properties with a shared number of elements (e.g. all vertex properties have to have the same number of elements, which is the number of vertices).

As of now, a mesh always has a vertex stream, a face stream and either a simple halfedge stream or one with prev-handle. Abstracting to property streams makes the latter option less of a special case. Some meshes just have a VertexStream, FaceStream and a HalfedgeStream others have a HalfedgePrevStream instead.

But we can even change the way topology is represented by the mesh. We can define meshes that only have a vertex and a triangle stream. Or let's say we define stream types TriangleStrip, TriangleFan, etc. OpenMeshD would therefore become more useful for applications like rendering.

We use the same techniques that we use for compile-time properties, to check for the availability of certain streams at compile-time. Especially Circulators would use that check.

Property streams would be compile-time only and have a hard coded set of must-have properties. The HalfedgeVertex stream has at least a halfedge handle. The Halfedge stream has a vertex-, next-halfedge- and face-handle, etc.

From a runtime perspective, a mesh would be a class that has one BaseProperty[] member for each stream. Algorithms working on meshes do compile-time checks for the right stream types and properties in those streams. We should only allow predefined combinations of streams that make sense. Then the check, that algorithms need to add, would simplify to a single static assert.

If you think this is interesting, i'll build another prototype.
Back to top
View user's profile Send private message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Tue Sep 25, 2007 8:08 pm    Post subject: Reply with quote

jascha wrote:
how about we just use the property name with a suffix "_t" as the type's name? that way we don't have to list all the standard type names. and non-standard properties will have type aliases, too:

The code for that is certainly simpler and less magical. I'm just not wild about having to type vposition_t everywhere as the type of vertex positions. But it's certainly more logical and consistent than the current code which has the type of vertex positions being "Point", and normals being "Normal". Maybe we could just shorten the default property names down to "vpos", "vnorm" and "vcolor". "vpos_t" wouldn't be too bad. What I would like to avoid is having aliases so yucky that I end up aliasing them to something else in my code anyway. If I'm going to feel compelled to alias them, then they might as well stay as mesh.propertyname.type.

jascha wrote:

About default property sets: how about naming them with acronyms for the contained properties:

Code:

template DEFAULT_PROPS_PNC(float_t=float) {
    alias PropSet!(
        Prop!(float_t[3], "vposition"),
        Prop!(float_t[3], "vnormal"),
        Prop!(float_t[3], "vcolor"))
 
       DEFAULT_PROPS_PNC;
}

// then add shortcuts
template MeshPNCf(T...) { alias Mesh!(DEFAULT_PROPS_PNC!(float), T) MeshPNCf; }
template MeshPNCd(T...) { alias Mesh!(DEFAULT_PROPS_PNC!(double), T) MeshPNCf; }
template MeshPNCr(T...) { alias Mesh!(DEFAULT_PROPS_PNC!(real), T) MeshPNCf; }


... and the same for MeshPNf, etc.


I like the idea of predefining a few common mesh types, but the storage locations have to go in there somewhere too, whether per-vertex, or per-face, etc.

Mesh_vPNCf - vertex pos,norm,color
Mesh_vPfNC - vertex pos, face norms and colors

Another design axis of the current code is how data is physically stored, though the only current implementation is an ArrayKernel. But if you look at CGAL they have other Kernel implementations. There's also a TriMesh vs PolyMesh distinction.

So it's questionable whether "Mesh" should be pre-defined to mean a paticular Kernel. At some point there are so many choices encoded in the predefined names that it's just easier to just write out the one-line definition of the type explicitly than trying to remember the predef names.

jascha wrote:

Here is another design proposal that gets enabled by the new property design:

Properties have to be saved by vertex, by halfedge, by face or by mesh. Abstracting that leads us to "property streams". A mesh is a set of property streams. Each stream has an arbitrary number of properties with a shared number of elements (e.g. all vertex properties have to have the same number of elements, which is the number of vertices).

As of now, a mesh always has a vertex stream, a face stream and either a simple halfedge stream or one with prev-handle. Abstracting to property streams makes the latter option less of a special case. Some meshes just have a VertexStream, FaceStream and a HalfedgeStream others have a HalfedgePrevStream instead.

If you think this is interesting, i'll build another prototype.


It's interesting but it seems like a bigger change. I'd rather see a prototype of how to handle the different per-part storage locations first. Perhaps creating distinct VProp, FProp structs will work there? Then static ifs could pick out which you're trying to add and add the property to the correct property container.

--bb
Back to top
View user's profile Send private message
jascha



Joined: 17 Jun 2007
Posts: 13
Location: Aachen, Germany

PostPosted: Wed Sep 26, 2007 1:39 pm    Post subject: Reply with quote

i agree, the "_t" suffix isn't the nicest thing, but i think the generality is more important.

Quote:

So it's questionable whether "Mesh" should be pre-defined to mean a paticular Kernel. At some point there are so many choices encoded in the predefined names that it's just easier to just write out the one-line definition of the type explicitly than trying to remember the predef names.


right, let's see what get's used in practice. maybe we can provide a couple of commonly used aliases then.

Quote:

It's interesting but it seems like a bigger change. I'd rather see a prototype of how to handle the different per-part storage locations first. Perhaps creating distinct VProp, FProp structs will work there? Then static ifs could pick out which you're trying to add and add the property to the correct property container.


i thought it would be a lot of work, too. but when i wrote this prototype for channeling the properties into the right PropertyContainer, i found myself basically writing the generalized version. the 3 dispatcher methods in Mesh would actually get shorter in the general version, since we get rid of the static ifs.

here we go:

Code:

module ctprops3;

enum PropStream
{
    vertex,
    halfedge,
    face
}

/// used like a Trait for the Mesh type
struct Prop(PropStream S, T, string N)
{
    const PropStream stream = S;
    alias T Type;
    const string name = N;
}

struct PropSet(T...)
{
    alias T properties;
}

template DEFAULT_PROPERTIES(float_t=float) {
    alias PropSet!(
        Prop!(PropStream.vertex, float_t[3], "vpos"),
        Prop!(PropStream.vertex, float_t[3], "vnorm"),
        Prop!(PropStream.vertex, float_t[3], "vcol"))
 
       DEFAULT_PROPERTIES;
}

/// runtime property handle
struct PropHandle(T)
{
    alias T     Type;
    PropStream  stream;
    size_t      index;
}

/// compile-time property handle
struct PropHandleCT(T, PropStream S, size_t I)
{
    alias T             Type;
    const PropStream    stream = S;
    const size_t        index = I;
   
    static PropHandle!(T) rt_handle()
    { return PropHandle!(T)(S, I); }
}

// properties are just like before
abstract class BaseProperty
{
    this(string name)
    { this.name = name; }

    void resize(size_t n);
   
    string name;
}

class Property(T) : BaseProperty
{
    this(string name)
    { super(name); }

    override void resize(size_t n)
    { data.length = n; }
   
    T[] data;
}

/// here we just add a few compile-time variants of some functions
class PropertyContainer
{
    // compile-time compatible add_property
    PropHandle!(T) add_property(T, PropStream S, string N)()
    {
        size_t index = properties.length;
        properties ~= new Property!(T)(N);
        return PropHandle!(T)(S, index);
    }

    // retrieve a property using a compile-time handle
    Property!(PH.Type) property(alias PH)()
    {
        assert(PH.index < properties.length);
        return cast(Property!(PH.Type))properties[PH.index];
    }

    // retrieve a property using a runtime-time handle
    Property!(T) property(T)(PropHandle!(T) ph)
    {
        assert(ph.index < properties.length);
        return cast(Property!(T))properties[ph.index];
    }

    // some operation for testing
    void resize(size_t n)
    {
        foreach ( p; properties )
            p.resize(n);
    }
   
    BaseProperty[]  properties;
}

template Tuple(T...) {
    alias T Tuple;
}

// used to keep track of multiple index-counts at runtime
template IndexTuple(size_t count, T...)
{
    static if ( count > 0 )
        mixin IndexTuple!(count-1, Tuple!(0, T));
    else
        alias T indeces;
}

template IndexTupleInc(T...)
{
    alias T indeces;
}

// work around DMD bug evaluating "IT.indeces[cast(uint)P.stream-1]+1" at compile-time
template Inc(uint i)
{
    const uint Inc = i+1;
}

class Mesh(P_=DEFAULT_PROPERTIES!(), T_...)
{
    // add handles for all compile-time properties
    template AddPropHandles(alias IT, alias P, T...)
    {
        mixin("alias PropHandleCT!(P.Type, P.stream, IT.indeces[cast(uint)P.stream]) "~P.name~";");
        mixin("alias P.Type "~P.name~"_t;");
        static if ( T.length > 0 )
        {
            mixin AddPropHandles!(
                IndexTupleInc!(Tuple!(
                    IT.indeces[0 .. P.stream],
                    Inc!(IT.indeces[cast(uint)P.stream]),
                    IT.indeces[P.stream+1 .. $]
                )),
                T
            );
        }
    }
    static if ( is(P_.properties) )
        mixin AddPropHandles!(IndexTuple!(PropStream.max+1), Tuple!(P_.properties,T_));
    else
        mixin AddPropHandles!(IndexTuple!(PropStream.max+1), Tuple!(P_,T_));

    // dispatch property method calls to right container
    PropHandle!(T) add_property(T, PropStream S, string N)()
    {
        PropHandle!(T) ph;
        static if ( S == PropStream.vertex )
            ph = vprops.add_property!(T, S, N);
        else static if ( S == PropStream.halfedge )
            ph = heprops.add_property!(T, S, N);
        else static if ( S == PropStream.face )
            ph = fprops.add_property!(T, S, N);
        ph.stream = S;
        return ph;
    }
   
    Property!(PH.Type) property(alias PH)()
    {
        static if ( PH.stream == PropStream.vertex )
            return vprops.property!(PH);
        else static if ( PH.stream == PropStream.halfedge )
            return heprops.property!(PH);
        else static if ( PH.stream == PropStream.face )
            return fprops.property!(PH);
    }
   
    Property!(T) property(T)(PropHandle!(T) ph)
    {
        if ( ph.stream == PropStream.vertex )
            return vprops.property(ph);
        else if ( ph.stream == PropStream.halfedge )
            return heprops.property(ph);
        else if ( ph.stream == PropStream.face )
            return fprops.property(ph);
    }
   
    private void add_property(P)()
    {
        static if(is(P.properties))
        {
            foreach( PP; P.properties )
                add_property!(PP);
        }
        else
            add_property!(P.Type, P.stream, P.name);
    }

    // constructor initializes all compile-time properties
    this()
    {
        vprops = new PropertyContainer;
        heprops = new PropertyContainer;
        fprops = new PropertyContainer;

        // make sure the properties get added in the same order
        // as their indeces are declared by AddPropHandles
        foreach ( P; Tuple!(P_,T_) )
            add_property!(P);
    }

    PropertyContainer   vprops,
                        heprops,
                        fprops;
}

//-----------------------------------------------------------------------------
import std.stdio;

void main()
{
    // add two properties at compile-time
    auto mesh = new Mesh!(
        DEFAULT_PROPERTIES!(float),
        Prop!(PropStream.vertex, float, "vweight"),
        Prop!(PropStream.face, float[3], "fnorm")
    );
    //auto mesh = new Mesh!();

    // the compile-time check for a property can use the property's name and type
    static assert ( is(mesh.vpos.Type == float[3]), "this code needs a mesh with vposition of type float[3]" );

    // add a property at runtime
    auto rtph = mesh.add_property!(float, PropStream.halfedge, "prop");

    mesh.vprops.resize(100);
    mesh.heprops.resize(600);
    mesh.fprops.resize(200);

    // accessing properties using compile-time handles
    auto vpos = mesh.property!(mesh.vpos).data;
    // ... or runtime handles
    auto prop = mesh.property(rtph).data;
    // or get a runtime handle for a compile-time property
    auto rtph2 = mesh.fnorm.rt_handle;
    auto fnorm = mesh.property(rtph2).data;
   
    writefln("%s %s", typeid(typeof(vpos)), vpos.length);
    writefln("%s %s", typeid(typeof(prop)), prop.length);
    writefln("%s %s", typeid(typeof(fnorm)), fnorm.length);

    writefln("vpos_t %s %s %s", typeid(mesh.vpos_t), mesh.vpos.stream, mesh.vpos.index);
    writefln("vnorm_t %s %s %s", typeid(mesh.vnorm_t), mesh.vnorm.stream, mesh.vnorm.index);
    writefln("vcol_t %s %s %s", typeid(mesh.vcol_t), mesh.vcol.stream, mesh.vcol.index);

    writefln("vweight %s %s %s", typeid(mesh.vweight_t), mesh.vweight.stream, mesh.vweight.index);
    writefln("fnorm %s %s %s", typeid(mesh.fnorm_t), mesh.fnorm.stream, mesh.fnorm.index);

    writefln("vprops %d", mesh.vprops.properties.length);
    writefln("heprops %d", mesh.heprops.properties.length);
    writefln("fprops %d", mesh.fprops.properties.length);
}


Last edited by jascha on Wed Sep 26, 2007 3:28 pm; edited 1 time in total
Back to top
View user's profile Send private message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Wed Sep 26, 2007 3:27 pm    Post subject: Reply with quote

Just a quick reply for now.
I see now better what you meant by the "Streams". I think I was getting confused with Streams as in "stream computing" and "streaming to disk".

I think maybe a better name would be "Affinity".
I think I've heard that sort of terminology somewhere -- "this attribute has Vertex affinity". Maybe NVIDIA uses that?

--bb
Back to top
View user's profile Send private message
jascha



Joined: 17 Jun 2007
Posts: 13
Location: Aachen, Germany

PostPosted: Wed Sep 26, 2007 3:47 pm    Post subject: Reply with quote

true, "stream" isn't a good term. i haven't heard "affinity" in that context before. would you still call it "affinity" if we'd change the number and type of those entities in a mesh class? that is, does "this mesh has vertex and triangle affinities as opposed to that other mesh that has vertex, halfedge and face affinities" make any sense?
Back to top
View user's profile Send private message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Wed Sep 26, 2007 7:08 pm    Post subject: Reply with quote

jascha wrote:
true, "stream" isn't a good term. i haven't heard "affinity" in that context before.


It just means "is associated with" in this case. So "the attribute has vertex affinity" means the attribute is associated with vertices, a per-vertex attribute.

jascha wrote:
would you still call it "affinity" if we'd change the number and type of those entities in a mesh class? that is, does "this mesh has vertex and triangle affinities as opposed to that other mesh that has vertex, halfedge and face affinities" make any sense?


I might not say it that way, but I think it makes sense to say "the mesh has this and that affinities", sure.

Another word could be "association", but I could swear I've heard affinity used somewhere before in this context, though googling turns up nothing. I'd rather not invent a new term if there's one in use already. Theres also "binding", but that's also an overloaded term.


It's not clear to me how generalized storage affinities would play out in practice. TriStrips or other groupings of elements would be useful, but where are they stored? How do you store the group membership?

Using generalized code to implement the 4 fixed property affinities (face,edge,halfedge,vertex) seems like good idea, though.

I do wish there were some way to load up a standard 3D model file without losing all the shared material info. For that I think you need to have some kind of notion of groups and per-group attributes. But there are too many ways to store groups with different performance trade-offs. Do you need fast membership queries, fast enumeration, least memory usage?

Probably there's some solution in the form of yet more generic programming (make different GroupKernels implemented as AA's,LinkedLists,SequentialArray etc). It gets less and less understandable for us mere mortals, though. Smile
Back to top
View user's profile Send private message
jascha



Joined: 17 Jun 2007
Posts: 13
Location: Aachen, Germany

PostPosted: Thu Sep 27, 2007 5:01 am    Post subject: Reply with quote

Quote:
I might not say it that way, but I think it makes sense to say "the mesh has this and that affinities", sure.


ok then. the literal meaning of a property having vertex affinity worked for me, but a mesh having vertex affinity sounds odd. but since it's not overloaded, it can be as odd as it wants as long as we define it that way Smile

there is a 1 to 1 correspondence of affinities to PropertyContainers. generalizing affinities therefore means adding a customizable number of PropertyContainers to the mesh. what changes is that you don't know what affinities there are until compile-time. code working on meshes has to check for them.
we could generate a mesh structure that has the 3 or 4 PCs that we have now and that will be a halfedge structure. but we could also just add 2 PCs and it would be a shared vertex structure. the code that works on these meshes differs greatly now. checking for the right affinities therefore becomes a fundamental categorization of the code.

to implement grouping for shared materials, i used to group multiple meshes into a Model. a Model consists of a set of meshes, each mesh shares a set of attributes. it is useful for rendering, but it might be too restrictive for other uses.
loaders for files that can store mesh groups therefore should not be loaders for Meshes but for Models.
Back to top
View user's profile Send private message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Thu Sep 27, 2007 5:27 pm    Post subject: Where affinity came from Reply with quote

I figured out where I heard the term "affinity". In multi-CPU systems there's a "thread affinity" which means a physical processor (or processors) that the thread is set to run on. I think they also talk about "data affinity" in some cases too, where particular data is set to go in a particular processors local scratch memory.

So maybe nobody has used it to describe mesh properties before. But I still think it fits pretty well.
Back to top
View user's profile Send private message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Mon Oct 01, 2007 9:03 pm    Post subject: Reply with quote

A good friend of mine who works on GPUs, and whose opinion I respect says "Affinity" is no good. They already use it for other things regarding multithreading in the GPU.

So he suggests taking a cue from the "uniform" vs "varying" parameter types in shading languages. So he suggests calling it "PropertyVariability".

Here are a few more I just thought of
"PropertyLocality".
"PropertyResidency"
"PropertyDomain"

Hmm I like property domain. Using the mathy meaning of 'domain', of course. The domain of a function f(x) being the set of x's over which the function is defined. That's exactly what we have here. A property is a function that maps some kind of handle to some kind of value. The set of handles a property is defined for is the domain of that function.

PropertyDomain. Perfect. Any arguments to the contrary?


Terminology aside, I still owe you some comments on your second prototype. It's definitely on my todo list. Today I found out that copying was really broken (I knew it didn't work right but I thought it would just take a little touch-up to get the C++ code working). So in the process of fixing copying I had to look again at the bunches of duplicated property handling code in OpenMeshD. I'd really like to find a better way, and I think you're on the right track.
Back to top
View user's profile Send private message
jascha



Joined: 17 Jun 2007
Posts: 13
Location: Aachen, Germany

PostPosted: Tue Oct 02, 2007 5:09 am    Post subject: Reply with quote

A PropertyDomain is exactly what it is, no objections!
Stream was bad, because usually streams are not indexed. Plus, it includes the properties themselves. But what we're really talking about is just the index range and therefore the domain.
The other candidates (including affinity) are more like nouns derived from adjectives that describe some trait a property has, instead of naming that which binds them together.

I'll be needing OpenMeshD in 2-3 weeks. Then i will revisit my prototype for the compile-time dynamic property domains and try to actually use it as a mesh structure (integrating existing OMD code when necessary).
Back to top
View user's profile Send private message
jascha



Joined: 17 Jun 2007
Posts: 13
Location: Aachen, Germany

PostPosted: Wed Oct 03, 2007 2:54 pm    Post subject: Reply with quote

hm, well, it was more like 1 day...

i collect the required domains from the compile-time properties. that is, a domain always has to have at least one compile-time property in order to exist. domains cannot be added at runtime.

i couldn't come up with a use-case where one might want to create a domain with runtime properties only. but i still have my first, slightly longer version that also allows explicit domains to be added that can be unsed until runtime.

if we assume that you always have at least one compile-time property, specifying the domains explicitly is redundant.

Code:

module ctprops4;

typedef uint PropDomain;

struct Prop(string D, T, string N)
{
    const string domain = D;
    alias T Type;
    const string name = N;
}

struct PropSet(T...)
{
    alias T properties;
}

// default properties for a halfedge structure
template HALFEDGE_PROPERTIES(float_t=float)
{
    alias PropSet!(
        Prop!("vertex", size_t, "vhedge"),
        Prop!("vertex", float_t[3], "vpos"),     // todo declare them in propertycontainer to avoid name collisions?

        Prop!("halfedge", size_t, "heopp"),
        Prop!("halfedge", size_t, "henext"),
        Prop!("halfedge", size_t, "heface"),
        Prop!("halfedge", size_t, "hevertex"),

        Prop!("face", size_t, "fhedge")
    )
       HALFEDGE_PROPERTIES;
}

template ASSERT_HALFEDGE(float_t, alias m, string msg)
{
    static assert(
        is(typeof(m.vertex_domain) == PropertyContainer) &&
        is(typeof(m.halfedge_domain) == PropertyContainer) &&
        is(typeof(m.face_domain) == PropertyContainer) &&
        is(m.vhedge_t == size_t) &&
        is(m.vpos_t == float_t[3]) &&
        is(m.heopp_t == size_t) &&
        is(m.henext_t == size_t) &&
        is(m.heface_t == size_t) &&
        is(m.hevertex_t == size_t) &&
        is(m.fhedge_t == size_t),
        msg
    );
}

// default properties for a shared vertex triangle structure
template SHARED_VERTEX_TRIMESH_PROPERTIES(float_t=float)
{
    alias PropSet!(
        Prop!("vertex", float_t[3], "vpos"),
        Prop!("triangle", size_t[3], "vertices")
    )
       SHARED_VERTEX_TRIMESH_PROPERTIES;
}

template ASSERT_SHARED_VERTEX_TRIMESH(float_t, alias m, string msg)
{
    static assert(
        is(typeof(m.vertex_domain) == PropertyContainer) &&
        is(typeof(m.triangle_domain) == PropertyContainer) &&
        is(m.vpos_t == float_t[3]) &&
        is(m.vertices_t == size_t[3]),
        msg
    );
}

/// runtime property handle
struct PropHandle(T)
{
    alias T     Type;
    PropDomain  domain;
    size_t      index;
}

/// compile-time property handle
struct PropHandleCT(T, PropDomain D, size_t I)
{
    alias T             Type;
    const PropDomain    domain = D;
    const size_t        index = I;
   
    static PropHandle!(T) rt_handle()
    { return PropHandle!(T)(D, I); }
}

// properties are just like before
abstract class BaseProperty
{
    this(string name)
    { this.name = name; }

    void resize(size_t n);
   
    string name;
}

class Property(T) : BaseProperty
{
    this(string name)
    { super(name); }

    override void resize(size_t n)
    { data.length = n; }
   
    T[] data;
}

/// here we just add a few compile-time variants of some functions
class PropertyContainer
{
    // compile-time compatible add_property
    PropHandle!(T) add_property(T, PropDomain D, string N)()
    {
        size_t index = properties.length;
        properties ~= new Property!(T)(N);
        return PropHandle!(T)(D, index);
    }

    // retrieve a property using a compile-time handle
    Property!(PH.Type) property(alias PH)()
    {
        assert(PH.index < properties.length);
        return cast(Property!(PH.Type))properties[PH.index];
    }

    // retrieve a property using a runtime-time handle
    Property!(T) property(T)(PropHandle!(T) ph)
    {
        assert(ph.index < properties.length);
        return cast(Property!(T))properties[ph.index];
    }

    // some operation for testing
    void resize(size_t n)
    {
        foreach ( p; properties )
            p.resize(n);
    }
   
    BaseProperty[]  properties;
}

template Tuple(T...) {
    alias T Tuple;
}

class Mesh(P_=HALFEDGE_PROPERTIES!(), T_...)
{
    // add handles for all compile-time properties
    template AddPropHandles(alias IT, alias P, T...)
    {
        // work around DMD bug evaluating "IT.indeces[cast(uint)P.domain-1]+1" at compile-time
        template Inc(uint i)
        {
            const uint Inc = i+1;
        }

        template IndexTupleInc(T...)
        {
            alias T indeces;
        }

        mixin("alias PropHandleCT!(P.Type, DomainIndex."~P.domain~", IT.indeces[cast(uint)DomainIndex."~P.domain~"]) "~P.name~";");
        mixin("alias P.Type "~P.name~"_t;");
        static if ( T.length > 0 )
        {
            mixin("
                mixin AddPropHandles!(
                    IndexTupleInc!(Tuple!(
                        IT.indeces[0 .. DomainIndex."~P.domain~"],
                        Inc!(IT.indeces[cast(uint)DomainIndex."~P.domain~"]),
                        IT.indeces[DomainIndex."~P.domain~"+1 .. $]
                    )),
                    T
                );
            ");
        }
    }

    // collect domains from properties if not specified explicitly
    template CollectDomains(Ps, Ds...)
    {
        template AddDomain(uint i, string D, Ds...)
        {
            static if ( i >= Ds.length )
                alias Tuple!(Ds,D) AddDomain;
            else static if ( is(typeof(Ds[i]) == string) && Ds[i] == D )
                alias Ds AddDomain;
            else
                alias AddDomain!(i+1, D, Ds) AddDomain;
        }

        static if ( Ps.properties.length > 0 )
            mixin CollectDomains!(PropSet!(Ps.properties[1 .. $]), AddDomain!(0, Ps.properties[0].domain, Ds));
        else
            mixin DeclareDomains!(Ds);
    }

    // declare domains
    template DeclareDomains(Ds...)
    {
        // builds string of declarations for domains
        template AddDomains(Ds...)
        {
            static if ( Ds.length > 0 )
                const string AddDomains = "PropertyContainer "~Ds[0]~"_domain; "~ AddDomains!(Ds[1 .. $]);
            else
                const string AddDomains = "";
        }
        // builds string of enum element declarations
        template AddDomainIndeces(Ds...)
        {
            static if ( Ds.length > 1 )
                const string AddDomainIndeces = Ds[0] ~", "~ AddDomainIndeces!(Ds[1 .. $]);
            else static if ( Ds.length > 0 )
                const string AddDomainIndeces = Ds[0];
            else
                static assert(0, "One or more domains required");
        }

        // declare enum for named domain indeces
        mixin("enum DomainIndex { "~AddDomainIndeces!(Ds)~" }");
        // declare PropertyContainers such that we can reference them by name or index
        union
        {
            PropertyContainer[DomainIndex.max+1] domains;
            mixin("struct { "~AddDomains!(Ds)~" }");
        }
    }
   
    // used to keep track of multiple index-counts at runtime
    template IndexTuple(size_t count, T...)
    {
        static if ( count > 0 )
            mixin IndexTuple!(count-1, Tuple!(0, T));
        else
            alias T indeces;
    }

    // declare domains and compile-time property handlers
    static if ( is(P_.properties) ) {
        mixin CollectDomains!(PropSet!(P_.properties,T_));
        mixin AddPropHandles!(IndexTuple!(DomainIndex.max+1), Tuple!(P_.properties,T_));
    }
    else {
        mixin CollectDomains!(PropSet!(P_,T_));
        mixin AddPropHandles!(IndexTuple!(DomainIndex.max+1), Tuple!(P_,T_));
    }


    //-------------------------------------------------------------------------
    // methods
   
    Property!(PH.Type) property(alias PH)()
    {
        assert(domains.length > PH.domain);
        return domains[PH.domain].property!(PH);
    }
   
    Property!(T) property(T)(PropHandle!(T) ph)
    {
        assert(domains.length > ph.domain);
        return domains[ph.domain].property(ph);
    }

    PropHandle!(T) add_property(T, PropDomain D, string N)()
    {
        assert(domains.length > D);
        return domains[D].add_property!(T, D, N);
    }

    PropHandle!(T) add_property(T, string D, string N)()
    {
        mixin("
            assert(domains.length>DomainIndex."~D~");
            return domains[DomainIndex."~D~"].add_property!(T, DomainIndex."~D~", N);
        ");
    }

    private void add_property(P)()
    {
        static if( is(P.properties) )
        {
            foreach( PP; P.properties )
                add_property!(PP);
        }
        else
            add_property!(P.Type, P.domain, P.name);
    }

    // constructor initializes all compile-time properties
    this()
    {
        foreach ( i, ref d; domains )
            d = new PropertyContainer;

        // make sure the properties get added in the same order
        // as their indeces are declared by AddPropHandles
        foreach ( P; Tuple!(P_,T_) )
            add_property!(P);
    }
}

//-----------------------------------------------------------------------------
import std.stdio;

void main()
{
    auto mesh = new Mesh!(
        HALFEDGE_PROPERTIES!(),
        Prop!("vertex", float, "vweight"),
        Prop!("face", float[3], "fnorm")
    );
    writefln("domain indeces: %s %s %s", mesh.DomainIndex.vertex, mesh.DomainIndex.halfedge, mesh.DomainIndex.face);
    writefln("#domains: %s", mesh.domains.length);
    writefln("typeof(mesh.domains): %s", typeid(typeof(mesh.domains)));
    writefln("typeof(mesh.vertex_domain): %s", typeid(typeof(mesh.vertex_domain)));
    writefln("typeof(mesh.halfedge_domain): %s", typeid(typeof(mesh.halfedge_domain)));
    writefln("typeof(mesh.face_domain): %s", typeid(typeof(mesh.face_domain)));
    writefln("#vertex properties: %s", mesh.vertex_domain.properties.length);
    writefln("#halfedge properties: %s", mesh.halfedge_domain.properties.length);
    writefln("#face properties: %s", mesh.face_domain.properties.length);
    writefln("sizeof mesh structure = %s", mesh.classinfo.init.length);
    writefln;

    auto mesh2 = new Mesh!(SHARED_VERTEX_TRIMESH_PROPERTIES!());
    writefln("domain indeces: %s %s", mesh2.DomainIndex.vertex, mesh2.DomainIndex.triangle);
    writefln("#domains: %s", mesh2.domains.length);
    writefln("#vertex properties: %s", mesh2.vertex_domain.properties.length);
    writefln("#triangle properties: %s", mesh2.triangle_domain.properties.length);
    writefln("sizeof mesh2 structure = %s", mesh2.classinfo.init.length);
    writefln;

    // the compile-time check for a property can use the property's name and type
    mixin ASSERT_HALFEDGE!(float, mesh, "Need halfedge structure");
    mixin ASSERT_SHARED_VERTEX_TRIMESH!(float, mesh2, "Need shared vertex structure");
    static assert ( is(mesh.vpos.Type == float[3]), "this code needs a mesh with vposition of type float[3]" );

    // add a property at runtime
    auto rtph = mesh.add_property!(float, "halfedge", "prop");

    mesh.vertex_domain.resize(100);
    mesh.halfedge_domain.resize(600);
    mesh.face_domain.resize(200);

    // accessing properties using compile-time handles
    auto vpos = mesh.property!(mesh.vpos).data;
    // ... or runtime handles
    auto prop = mesh.property(rtph).data;
    // or get a runtime handle for a compile-time property
    auto rtph2 = mesh.fnorm.rt_handle;
    auto fnorm = mesh.property(rtph2).data;
   
    writefln("%s %s", typeid(typeof(vpos)), vpos.length);
    writefln("%s %s", typeid(typeof(prop)), prop.length);
    writefln("%s %s", typeid(typeof(fnorm)), fnorm.length);
    writefln;

    writefln("name: type, domain index, property index");
    writefln("vhedge_t: %s %s %s", typeid(mesh.vhedge_t), mesh.vhedge.domain, mesh.vhedge.index);
    writefln("vpos_t: %s %s %s", typeid(mesh.vpos_t), mesh.vpos.domain, mesh.vpos.index);
    writefln("heface_t: %s %s %s", typeid(mesh.heface_t), mesh.heface.domain, mesh.heface.index);
    writefln("vweight: %s %s %s", typeid(mesh.vweight_t), mesh.vweight.domain, mesh.vweight.index);
    writefln("fnorm: %s %s %s", typeid(mesh.fnorm_t), mesh.fnorm.domain, mesh.fnorm.index);
}
Back to top
View user's profile Send private message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Wed Oct 03, 2007 4:53 pm    Post subject: Reply with quote

Looks pretty sweet!
What's the strategy for convenience functions?

Like when you want to do something like:
Point p = mesh.point(vert_handle)
Back to top
View user's profile Send private message
jascha



Joined: 17 Jun 2007
Posts: 13
Location: Aachen, Germany

PostPosted: Thu Oct 04, 2007 3:37 am    Post subject: Reply with quote

here is a value handle:
Code:
struct ValueHandle(PropDomain D)
{
    const PropDomain    domain = D;
    size_t index;
}

this is how we get values from ValueHandles:
Code:
PH.Type value(PH)(ValueHandle!(PH.domain) value_handle)
{
    return property!(PH)().data[value_handle.index];
}

if we give the compile-time property handles a suffix, we can use their plain names as aliases to that function:
Code:
mixin("alias value!("~P.name~"_ph) "~P.name~";");

then we have:
Code:
auto point = mesh.vpos(vert_handle);

So now, for each property, we add 3 identifiers to the mesh:
Code:
mesh.vpos_ph - the PropertyHandleCT
mesh.vpos_t - it's value type
mesh.vpos - the indexed accessor function

(all implemented in ctprops5, which i don't post, since it has only the changes above)
Back to top
View user's profile Send private message
baxissimo



Joined: 23 Oct 2006
Posts: 241
Location: Tokyo, Japan

PostPosted: Fri Oct 05, 2007 7:24 pm    Post subject: Reply with quote

jascha wrote:
here is a value handle:
if we give the compile-time property handles a suffix, we can use their plain names as aliases to that function:
Code:
mixin("alias value!("~P.name~"_ph) "~P.name~";");



How did you get around the fact that you can't return float_t[3] from the value method?

I think it's worth supporting that case. So I got around it with some static if's and std.traits.isStaticArray. (On the other hand, having a property that doesn't act like a value type for core things like vpos may make writing generic mesh algorithms difficult. If I can't just say vpos_t v = mesh.vpos(), then life will be kinda sucky. Besides, supporting a vpos_t without assuming overloads for +/- etc, will also be painful. -- But anyway, if a user wants to go with float[3] for a custom property, then that should be do-able.)

I also expanded the list of standard accessor functions:
{value}(handle) -- returns prop by value; not implemented for static array types
{value}_ptr(handle) -- returns a pointer to the value
get_{value}(handle, ref val_out) -- returns using ref param
set_{value}(handle, val) -- sets the value to val

Added {domain}_handle as an alias for ValueHandle!(domain).

Added a new Domain!(string Dname) template similar to Prop, but it just adds a new Domain without any associated compile-time properties.

Changed some names to be more in line with the current OpenMesh (e.g. "Type" to "value_type"). Maybe we should change that again, since for props I think it makes more sense to use the briefer vpos_t, vnorm_t, etc. Having to use "vpos_type" everywhere would bite. As it is the type names will become slightly shorter than they were. As well as more consistent.

Added some documentation.

Changed "indeces" to the proper spelling Smile

One reason for the "Domain!()" thing is that I wanted to be able to add an "edge" domain since that can be useful for something like silhouette processing (tag each edge as silouhette or not). But I think there are some thorny issues there. "edge" has tight coupling with "halfedge" that's difficult to express using this totally generic approach. Resizing edges needs to be in sync with the resizing of halfedges, and you might want to add pointers between edges and their associated halfedges. I guess what needs to happen is that the core halfedgemesh sees the domain name "edge" then it will do the right thing.

I guess that's not so bad. The thing called Mesh in the currently code is more like OM's BaseKernel, ArrayKernel and AttribKernel rolled together. The "what's 'edge' mean?" level of stuff comes from PolyConnectivity. And that's also where we implement whether it's a simple tri-list mesh or a halfedge mesh or whatever.

So in the end we should be able to make MyMesh be something like
Code:

alias HalfEdgeConnectivity(ArrayKernel!(STD_HALFEDGE_PROPERITES!())) MyHEMesh;
alias FaceListConnectivity(ArrayKernel!(STD_FACELIST_PROPS!())) MyFLMesh;


The code is getting a little long, so I put the version with my changes up at http://www.billbaxter.com/tmp/ctprops6.d
[edit -- and now checked into svn too in the trunk/experimental directory]


Last edited by baxissimo on Thu Oct 11, 2007 9:19 pm; edited 1 time in total
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic     Forum Index -> OpenMeshD All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group