Forum: Poser Python Scripting


Subject: Moving morphs between different figures

Cage opened this issue on Dec 20, 2006 · 1232 posts


Spanki posted Mon, 08 January 2007 at 2:45 AM

Ok, thanks for the notes file - that helps a lot.  As I mentioned earlier, I hadn't really followed/disected what you were already doing very closely, so this gives me a good overview.

From my brief look so far, it appears that none of that (including your notes on proposed variations) really includes what I've been thinking about and trying to convey :).  I still need to dig up ockham's Rosie & Paris example (which script is that?), but I'm now at least familiar with his NoPoke code.

Anyway, at the risk of repeating myself (what's new? :)), let me try to outline the approach I've had in mind (or at lest the salient differences) in some way that makes sense (using your list of terminology, where possible).

There are some certain 'features' (properties?) of my method:

1. the resulting morph created in meshB, would only have delta values for the vertices within meshB that accomplished a "similar difference from the default meshB to the difference between the morphed and unmorphed meshA".  In other words, if the morph is a "longer nose tip" morph, then there would only be deltas for the vertices in meshB that made up the tip of the nose.

It might be helpful to look at how a morph is stored in a .cr2 file.  For example purposes, I recently made a "Chin Un-Cleft" morph for V4 and saved it out as a Pose Injection file...

<pre style="border-right:#000000 1px solid;padding-right:2mm;border-top:#000000 1px solid;padding-left:2mm;padding-bottom:2mm;margin:2mm;border-left:#000000 1px solid;padding-top:2mm;border-bottom:#000000 1px solid;background-color:#aefe50;">

{

version
        {
        number  4.01
        }

actor head
        {
        channels
                {
                targetGeom PBMCC_34
                        {
                        name    ChinUn-Cleft
                        hidden  0
                        keys
                                {
                                static  0
                                k       0       0
                                }
                        indexes 84
                        numbDeltas      15078
                        deltas
                                {
                                d 12496 0.00000 -0.00009 0.00000
                                d 12497 0.00000 -0.00007 0.00000
                                d 12499 0.00000 -0.00010 0.00000
                                d 12500 0.00000 -0.00025 0.00000
                                d 12501 0.00000 -0.00025 0.00000
                                d 12502 0.00000 -0.00013 0.00000
                                d 12503 0.00000 -0.00033 0.00000
                                d 12504 0.00000 -0.00034 0.00000
                                d 12505 0.00000 -0.00022 0.00000
                                d 12506 0.00000 -0.00030 0.00000
                                d 12507 0.00000 -0.00013 0.00000
                                d 12508 0.00000 -0.00011 0.00000
                                d 12774 0.00000 -0.00001 0.00000
                                d 12778 0.00000 -0.00004 0.00000
                                d 12863 0.00000 -0.00002 0.00000
                                d 12865 0.00000 -0.00007 0.00000
                                d 12866 0.00000 -0.00003 0.00000
                                d 12867 0.00000 -0.00004 0.00000
                                d 12868 0.00000 -0.00012 0.00000
                                d 12906 0.00000 -0.00003 0.00000
                                d 12907 0.00000 -0.00003 0.00000
                                d 12910 0.00000 -0.00003 0.00000
                                d 12933 0.00000 -0.00024 0.00000
                                d 12934 0.00000 -0.00020 0.00000
                                d 12935 0.00000 -0.00034 0.00000
                                d 12936 0.00000 -0.00030 0.00000
                                d 12937 0.00000 -0.00025 0.00000
                                d 12938 0.00000 -0.00035 0.00000
                                d 12939 0.00000 -0.00002 0.00000
                                d 12941 0.00000 -0.00005 0.00000
                                d 12942 0.00000 -0.00002 0.00000
                                d 12943 0.00000 -0.00007 0.00000
                                d 12944 0.00000 -0.00012 0.00000
                                d 12945 0.00000 -0.00014 0.00000
                                d 12946 0.00000 -0.00022 0.00000
                                d 12947 0.00000 -0.00005 0.00000
                                d 12948 0.00000 -0.00001 0.00000
                                d 12949 0.00000 -0.00014 0.00000
                                d 12950 0.00000 -0.00037 0.00000
                                d 12951 0.00000 -0.00038 0.00000
                                d 12952 0.00000 -0.00033 0.00000
                                d 12960 0.00000 -0.00025 0.00000
                                d 12961 0.00000 -0.00023 0.00000
                                d 12962 0.00000 -0.00016 0.00000
                                d 12963 0.00000 -0.00007 0.00000
                                d 12964 0.00000 -0.00001 0.00000
                                d 13640 0.00000 -0.00009 0.00000
                                d 13642 0.00000 -0.00007 0.00000
                                d 13643 0.00000 -0.00025 0.00000
                                d 13644 0.00000 -0.00013 0.00000
                                d 13645 0.00000 -0.00022 0.00000
                                d 13646 0.00000 -0.00011 0.00000
                                d 13647 0.00000 -0.00033 0.00000
                                d 13648 0.00000 -0.00030 0.00000
                                d 13910 0.00000 -0.00001 0.00000
                                d 13915 0.00000 -0.00004 0.00000
                                d 13999 0.00000 -0.00002 0.00000
                                d 14000 0.00000 -0.00004 0.00000
                                d 14001 0.00000 -0.00007 0.00000
                                d 14002 0.00000 -0.00012 0.00000
                                d 14004 0.00000 -0.00003 0.00000
                                d 14039 0.00000 -0.00003 0.00000
                                d 14041 0.00000 -0.00003 0.00000
                                d 14063 0.00000 -0.00024 0.00000
                                d 14064 0.00000 -0.00034 0.00000
                                d 14065 0.00000 -0.00020 0.00000
                                d 14066 0.00000 -0.00030 0.00000
                                d 14067 0.00000 -0.00002 0.00000
                                d 14068 0.00000 -0.00007 0.00000
                                d 14069 0.00000 -0.00005 0.00000
                                d 14070 0.00000 -0.00012 0.00000
                                d 14072 0.00000 -0.00002 0.00000
                                d 14073 0.00000 -0.00014 0.00000
                                d 14074 0.00000 -0.00022 0.00000
                                d 14075 0.00000 -0.00005 0.00000
                                d 14076 0.00000 -0.00014 0.00000
                                d 14077 0.00000 -0.00001 0.00000
                                d 14078 0.00000 -0.00037 0.00000
                                d 14079 0.00000 -0.00033 0.00000
                                d 14087 0.00000 -0.00025 0.00000
                                d 14088 0.00000 -0.00023 0.00000
                                d 14089 0.00000 -0.00016 0.00000
                                d 14090 0.00000 -0.00007 0.00000
                                d 14091 0.00000 -0.00001 0.00000
                                }
                        }
                }
        }
}

...(this is a .pz2 file, but the formatting is the same as a .cr2 file).

Here's the part we're interested in...

<pre style="border-right:#000000 1px solid;padding-right:2mm;border-top:#000000 1px solid;padding-left:2mm;padding-bottom:2mm;margin:2mm;border-left:#000000 1px solid;padding-top:2mm;border-bottom:#000000 1px solid;background-color:#feba50;">

                    <strong>indexes 84
                        numbDeltas      15078</strong>
                        deltas
                                {
                                d 12496 0.00000 -0.00009 0.00000
                                d 12497 0.00000 -0.00007 0.00000

...so in this case numbDeltas is the number of total vertices in the head mesh, BUT this morph is only made up of deltas for 84 of those (the 'indexes' value).  So the talble listed below there makes up a 'sparse' table that only lists delta values for 84 vertices...

<pre style="border-right:#000000 1px solid;padding-right:2mm;border-top:#000000 1px solid;padding-left:2mm;padding-bottom:2mm;margin:2mm;border-left:#000000 1px solid;padding-top:2mm;border-bottom:#000000 1px solid;background-color:#feba50;">

                    deltas
                                {
                                d <strong>12496</strong> 0.00000 -0.00009 0.00000
                                d <strong>12497</strong> 0.00000 -0.00007 0.00000
                                d <strong>12499</strong> 0.00000 -0.00010 0.00000
                                d <strong>12500</strong> 0.00000 -0.00025 0.00000
                                d <strong>12501</strong> 0.00000 -0.00025 0.00000
...etc.

...the numbers in bold are the vertex indices that are involved in this morph.

So, if we're trying to transfer that morph from meshA to meshB, we'd expect that the morph would look similar in the .cr2 file (the number of vertices would likely be different, but we're looking for a 'sparse' table - not the entire list of vertices for that actor).

So, in Python-ease... when we're looking at morphs, you might have something like the following code:

<pre style="border-right:#000000 1px solid;padding-right:2mm;border-top:#000000 1px solid;padding-left:2mm;padding-bottom:2mm;margin:2mm;border-left:#000000 1px solid;padding-top:2mm;border-bottom:#000000 1px solid;background-color:#aefe50;">

#----------------------------------------------------------------------------
# walk through current actor's parameters, looking for morphs
#----------------------------------------------------------------------------
for parm in actor.Parameters():
        if (parm.IsMorphTarget()):
                
                #-------------------------
                # ok, we found a morph
                #-------------------------
                try:
                        geom = actor.Geometry()
                except:
                        # do nothing
                        pass
                else:
                        if(not geom):  #-- is this redundant?
                                continue

                        verts = geom.Vertices()
                        numVerts = geom.NumVertices()

                        for i in range(numVerts):
                                (deltaX, deltaY, deltaZ) = parm.MorphTargetDelta(i)

                                #-------------------------
                                # deltaX, deltaY, deltaZ contain the morph
                                # delta values for this vertex... for example
                                # purposes, we'll 'bake' the morph into the
                                # default mesh...
                                #-------------------------
                                mvert = verts[i]
                                mvert.SetX(mvert.X() + morphval * deltaX)
                                mvert.SetY(mvert.Y() + morphval * deltaY)
                                mvert.SetZ(mvert.Z() + morphval * deltaZ)
#-- yada, yada, other code below here, etc.

...but, as we've seen above, not every vertex in the actor necessarily has any 'delta' for any particular morph, so let's add a simple test...

<pre style="border-right:#000000 1px solid;padding-right:2mm;border-top:#000000 1px solid;padding-left:2mm;padding-bottom:2mm;margin:2mm;border-left:#000000 1px solid;padding-top:2mm;border-bottom:#000000 1px solid;background-color:#feba50;">

#----------------------------------------------------------------------------
# walk through current actor's parameters, looking for morphs
#----------------------------------------------------------------------------
for parm in actor.Parameters():
        if (parm.IsMorphTarget()):
                
                #-------------------------
                # ok, we found a morph
                #-------------------------
                try:
                        geom = actor.Geometry()
                except:
                        # do nothing
                        pass
                else:
                        if(not geom):  #-- is this redundant?
                                continue

                        verts = geom.Vertices()
                        numVerts = geom.NumVertices()

                        for i in range(numVerts):
                                (deltaX, deltaY, deltaZ) = parm.MorphTargetDelta(i)

                                #-------------------------------------------------
                                # deltaX, deltaY, deltaZ contain the morph
                                # delta values for this vertex... for example
                                # purposes, we'll 'bake' the morph into the
                                # default mesh...
                                #-------------------------------------------------

                                <strong>#-------------------------------------------------
                                # But, Only some verts have morph deltas... so
                                # we can test for non-zero values before doing
                                # anything with this vertex...
                                #-------------------------------------------------
                                if( deltaX or deltaY or deltaZ ):</strong>
                                        mvert = verts[i]
                                        mvert.SetX(mvert.X() + morphval * deltaX)
                                        mvert.SetY(mvert.Y() + morphval * deltaY)
                                        mvert.SetZ(mvert.Z() + morphval * deltaZ)
#-- yada, yada, other code below here, etc.

So, what the script would be doing is walking through the meshA actor until it found the approriate morph (my sample code above looks at all morphs on that actor). When it finds the right morph, it then walks through the meshA vertex list, getting the morph delta values. it's at that spot in the script (however you get to that point) that you'd want to skip all non-involved vertices (skip any vertices that don't have deltas for this morph).

The morph deltas (of meshA) are driving the creation of the morph in meshB... each time you find a vertex that has morph deltas, you call some routine or do whatever code is needed to find the corresponding vertex (or vertices) in meshB and create/update the morph deltas for that (or those) vertex/vertices only.
**
2**. The "whatever code is needed to find the corresponding vertex (or vertices) in meshB" mentioned above is basically what I outlined earlier in the thread, but could be some other variation like closest vertex (Cage), weighted average of close vertices (svdl), etc.

My proposed method sounds similar to what svdl suggested recently, but I look for intersections of rays cast out from each vertex position (btw, not the center of any mesh)** **in meshB along each vertex normal, with polygons in meshA.

Once you determine which polygon the vertex intersects, you update the list of vertex weights, for the vertices that make up that polygon for this vertex (each vertex in meshB can/would be associated with multiple vertices in meshA).

You'd have to put some thought into how to best organize the data, but basically, we're going to use this data to match against, in the code mentioned in #1 above when you find a vertex that has morph deltas.  In fact, it probably makes sense not to create this table at all, but just compute the information on-the-fly at that point in the script (this let's us use some known screening information, described later...).

Just for completeness, the information we need is:

For each (morph-involved) vertex  in meshA, we need the list of vertices in meshB that will be affected and a weight value to affect each one by.  The weight value is computed... uhm, I forget :), but it's spelled out earlier in the thread and is based on 'where' that ray intersected with the polygon/triangle, relative to the distance to each vertex that makes up the triangle.

I hadn't really focused on it, but all of the above can be integrated with the octree code to reduce the number of polygons/vertices you search through, along with any other screening (normal direction tests, exclusion lists, etc).  But see further discussion on this below...

3. Sorry for the disjunction in numbering, since this part is actually part of #1, but I wanted to set it up first :).  Ok, so we now have:
**
meshA** - has morphs we want
meshB - will get morphs transfered to it
morphX - user-selected morph to transfer
vertWeightTable - the table computed in #2 above, containing the weighted vertex correlations

...so now we jump back into the code point mentioned in #1 above...

<pre style="border-right:#000000 1px solid;padding-right:2mm;border-top:#000000 1px solid;padding-left:2mm;padding-bottom:2mm;margin:2mm;border-left:#000000 1px solid;padding-top:2mm;border-bottom:#000000 1px solid;background-color:#aefe50;">

                    verts = meshAgeom.Vertices()
                        numVerts = meshAgeom.NumVertices()

                        for i in range(numVerts):
                                (deltaX, deltaY, deltaZ) = parm.MorphTargetDelta(i)

                                #-------------------------------------------------
                                # deltaX, deltaY, deltaZ contain the morph
                                # delta values for this vertex... 
                                # But, Only some verts have morph deltas... so
                                # we can test for non-zero values before doing
                                # anything with this vertex...
                                #-------------------------------------------------
                                if( deltaX or deltaY or deltaZ ):

We're looping through the vertices of meshA, and looking at the morph deltas to see if this vertex has some (non-zero) delta value.  If it does, then we loop through our vertWeightTable, looking for this vertex index.  If there are any vertices in meshB that will be affected by it, we create/update a morph delta entry for that vertex in meshB...

vertexB_deltaX = vertexB_deltaX + (deltaX * weightAB)
vertexB_deltaY = vertexB_deltaY + (deltaY * weightAB)
vertexB_deltaZ = vertexB_deltaZ + (deltaZ * weightAB)

...where weightAB is the morph weighting computed for vertexA relative to vertexB (as described above and earlier in the thread, based on the ray intersection point, relative to the vertexA's that make up the triangle).

That the (long-winded version of the)  broad picture :).  I'm going to stop now before I totally blow up this "reply" software.

Cinema4D Plugins (Home of Riptide, Riptide Pro, Undertow, Morph Mill, KyamaSlide and I/Ogre plugins) Poser products Freelance Modelling, Poser Rigging, UV-mapping work for hire.