Fix for face interpolation after ray casts, now supports non-triangle
authorLukas Tönne <lukas.toenne@gmail.com>
Mon, 15 Dec 2014 19:14:58 +0000 (20:14 +0100)
committerLukas Tönne <lukas.toenne@gmail.com>
Mon, 15 Dec 2014 19:14:58 +0000 (20:14 +0100)
ground objects.

The ray case function returns a poly index instead of the plain tessface
index. Now the blob samples store a list of polygon weights instead of
tessface weights, so arbitrary ngons are supported properly.

Note: this code relies on the new mathutils.interpolate submodule.

object_physics_meadow/blob.py
object_physics_meadow/duplimesh.py

index 689dc01..cd77ea5 100644 (file)
@@ -23,12 +23,13 @@ from bpy_extras import object_utils
 from math import *
 from mathutils import *
 from mathutils.kdtree import KDTree
+from mathutils.interpolate import poly_3d_calc
 from itertools import accumulate
 import random
 
 from object_physics_meadow import settings as _settings
 from object_physics_meadow import duplimesh
-from object_physics_meadow.duplimesh import project_on_ground, interp_weights_face
+from object_physics_meadow.duplimesh import project_on_ground
 from object_physics_meadow.util import *
 
 _blob_object_name = "__MeadowBlob__"
@@ -157,25 +158,25 @@ def make_blob_object(context, index, loc, samples, display_radius):
     return ob
 
 class Blob():
-    def __init__(self, loc, nor, face_index):
+    def __init__(self, loc, nor, poly_index):
         self.loc = loc
         self.nor = nor
-        self.face_index = face_index
+        self.poly_index = poly_index
         self.samples = []
     
-    def add_sample(self, loc, nor, face, verts, weights):
-        self.samples.append((loc, nor, face, verts, weights))
+    def add_sample(self, loc, nor, poly, verts, weights):
+        self.samples.append((loc, nor, poly, verts, weights))
     
     # note: Vector instances cannot be pickled directly,
     # therefore define own pickle methods here
     def __getstate__(self):
-        return self.loc[:], self.nor[:], self.face_index, [(sloc[:], snor[:], sface, sverts, sweights) for sloc, snor, sface, sverts, sweights in self.samples]
+        return self.loc[:], self.nor[:], self.poly_index, [(sloc[:], snor[:], spoly, sverts, sweights) for sloc, snor, spoly, sverts, sweights in self.samples]
     
     def __setstate__(self, state):
         self.loc = Vector(state[0])
         self.nor = Vector(state[1])
-        self.face_index = state[2]
-        self.samples = [(Vector(sloc), Vector(snor), sface, sverts, sweights) for sloc, snor, sface, sverts, sweights in state[3]]
+        self.poly_index = state[2]
+        self.samples = [(Vector(sloc), Vector(snor), spoly, sverts, sweights) for sloc, snor, spoly, sverts, sweights in state[3]]
 
 # store blobs list in ID datablock as customdata
 def blobs_to_customprops(data, blobs):
@@ -206,11 +207,10 @@ def make_blobs(context, gridob, groundob, samples2D, display_radius):
     
     for v in gridob.data.vertices:
         co = gridob.matrix_world * v.co
-        ok, loc, nor, face_index = project_on_ground(groundob, co)
-        blobs.append(Blob(loc, nor, face_index) if ok else None)
+        ok, loc, nor, poly_index = project_on_ground(groundob, co)
+        blobs.append(Blob(loc, nor, poly_index) if ok else None)
     
-    groundob.data.calc_tessface()
-    mfaces = groundob.data.tessfaces
+    mpolys = groundob.data.polygons
     mverts = groundob.data.vertices
     for xy in samples2D:
         # note: use only 2D coordinates for weighting, z component should be 0
@@ -222,20 +222,17 @@ def make_blobs(context, gridob, groundob, samples2D, display_radius):
             continue
         
         # project samples onto the ground object
-        ok, sloc, snor, sface = project_on_ground(groundob, xy[0:2]+(0,))
+        ok, sloc, snor, spoly = project_on_ground(groundob, xy[0:2]+(0,))
         if not ok:
             continue
         
-        # calculate barycentric vertex weights on the face
-        face = mfaces[sface]
-        verts = [mverts[i] for i in face.vertices]
-        assert(len(verts) in {3, 4})
-        sweights, sverts = interp_weights_face(tuple(v.co for v in verts[0:4]), sloc)
-        # interpolation indices are for the face, make them into mesh indices
-        sverts = tuple(face.vertices[i] for i in sverts)
-        
-        blob.add_sample(sloc, snor, sface, sverts, sweights)
-    
+        # calculate barycentric vertex weights on the poly
+        poly = mpolys[spoly]
+        sverts = list(poly.vertices)
+        sweights = poly_3d_calc(tuple(mverts[i].co for i in sverts), sloc)
+
+        blob.add_sample(sloc, snor, spoly, sverts, sweights)
+
     # common parent empty for blobs
     blob_parent = get_blob_parent(context, groundob.matrix_world)
     
@@ -266,9 +263,9 @@ def assign_sample_patches(groundob, blob, patches):
     
     vgroup_samples = { vg.name : [] for vg in vgroups }
     vgroup_samples[""] = [] # samples for unassigned patches
-    for sloc, snor, sface, sverts, sweights in blob.samples:
+    for sloc, snor, spoly, sverts, sweights in blob.samples:
         verts = [mverts[i] for i in sverts]
-        # accumulate weights for each vertex group by interpolating the face
+        # accumulate weights for each vertex group by interpolating the poly
         weights = [ 0.0 for vg in vgroups ]
         for v, fac in zip(verts, sweights):
             for vg in v.groups:
@@ -301,7 +298,6 @@ def assign_sample_patches(groundob, blob, patches):
 def setup_blob_duplis(context, groundob, display_radius):
     blobs = blobs_from_customprops(groundob.meadow)
 
-    groundob.data.calc_tessface()
     patches = [ob for ob in patch_objects(context) if blobs[ob.meadow.blob_index] is not None]
     
     # common parent empty for blobs
index b67dae1..b0f2658 100644 (file)
@@ -22,63 +22,6 @@ import bpy, sys
 from math import *
 from mathutils import *
 
-def tri_signed_area(v1, v2, v3, i, j):
-    return 0.5 * ((v1[i] - v2[i]) * (v2[j] - v3[j]) + (v1[j] - v2[j]) * (v3[i] - v2[i]))
-
-# get the 2 dominant axis values, 0==X, 1==Y, 2==Z
-def axis_dominant(axis):
-    xn = fabs(axis[0])
-    yn = fabs(axis[1])
-    zn = fabs(axis[2])
-    if zn >= xn and zn >= yn:
-        return 0, 1
-    elif yn >= xn and yn >= zn:
-        return 0, 2
-    else:
-        return 1, 2
-
-def barycentric_weights(v1, v2, v3, co, n):
-    i, j = axis_dominant(n)
-    
-    w = (tri_signed_area(v2, v3, co, i, j),
-         tri_signed_area(v3, v1, co, i, j),
-         tri_signed_area(v1, v2, co, i, j))
-    wtot = w[0] + w[1] + w[2]
-    
-    if fabs(wtot) > sys.float_info.epsilon:
-        inv_w = 1.0 / wtot
-        return True, tuple(x*inv_w for x in w)
-    else:
-        return False, tuple(1.0/3.0 for x in w)
-
-def interp_weights_face(verts, co):
-    w = (0.0, 0.0, 0.0, 0.0)
-    
-    # OpenGL seems to split this way, so we do too
-    if len(verts) > 3:
-        n = (verts[0] - verts[2]).cross(verts[1] - verts[3])
-        
-        ok, w3 = barycentric_weights(verts[0], verts[1], verts[3], co, n)
-        w = (w3[0], w3[1], 0.0, w3[2])
-        idx = (0, 1, 3)
-        
-        if not ok or w[0] < 0.0:
-            # if w[1] is negative, co is on the other side of the v1-v3 edge,
-            # so we interpolate using the other triangle
-            ok, w3 = barycentric_weights(verts[1], verts[2], verts[3], co, n)
-            w = (0.0, w3[0], w3[1], w3[2])
-            idx = (1, 2, 3)
-        
-    else:
-        n = (verts[0] - verts[2]).cross(verts[1] - verts[2])
-        
-        ok, w3 = barycentric_weights(verts[0], verts[1], verts[2], co, n)
-        w = (w3[0], w3[1], w3[2], 0.0)
-        idx = (0, 1, 2)
-    
-    return w, idx
-
-
 def project_on_ground(groundob, co):
     groundmat4 = groundob.matrix_world
     inv_groundmat4 = groundmat4.inverted()