Exemplo n.º 1
0
def qdome2d(vertices, base, normal, precision=0.0001):
    """Build a convex dome from C{vertices} on top of the two C{base} vertices,
    in the plane with normal C{normal}. This is a helper function for
    L{qhull2d}, and should usually not be called directly.

    :param vertices: The vertices to construct the dome from.
    :param base: Two vertices that serve as a base for the dome.
    :param normal: Orientation of the projection plane used for calculating
        distances.
    :param precision: Distance used to decide whether points lie outside of
        the hull or not.
    :return: A list of vertices that make up a fan of the dome."""

    vert0, vert1 = base
    outer = [(dist, vert) for dist, vert in zip((mathutils.vecDotProduct(
        mathutils.vecCrossProduct(normal, mathutils.vecSub(vert1, vert0)),
        mathutils.vecSub(vert, vert0)) for vert in vertices), vertices)
             if dist > precision]

    if outer:
        pivot = max(outer)[1]
        outer_verts = list(map(operator.itemgetter(1), outer))
        return qdome2d(outer_verts,
                       [vert0, pivot], normal, precision) + qdome2d(
                           outer_verts, [pivot, vert1], normal, precision)[1:]
    else:
        return base
Exemplo n.º 2
0
 def ComputeNormal(self):
     #        v = [[], [], []]
     v0 = self.vertex[0].Vert.Position
     v1 = self.vertex[1].Vert.Position
     v2 = self.vertex[2].Vert.Position
     #        v.append(self.vertex[0].Vert.Position)
     #        v.append(self.vertex[1].Vert.Position)
     #        v.append(self.vertex[2].Vert.Position)
     #        a = vecSub(v[1], v[0])
     #        b = vecSub(v[2], v[0])
     a = vecSub(v1, v0)
     b = vecSub(v2, v0)
     _normal = self.normal
     self.normal = vecCrossProduct(a, b)
     if vecNorm(self.normal) < 0.001:
         return
     self.normal = vecNormalized(self.normal)
     length = vecDotProduct(self.normal, _normal)
     if length < 0:
         self.normal = vecscalarMul(self.normal, -1)
     return
Exemplo n.º 3
0
 def ComputeCost(self, v):
     edgelength = 1.0
     if self.parent.Settings.UseEdgelength:
         length = vecSub(v.Vert.Position, self.Vert.Position)
         edgelength = vecNorm(length)
     if (len(self.Neighbors) == len(v.Neighbors)):
         same_neighbors = True
         for neighbor in self.Neighbors:
             if neighbor == v:
                 continue
             if not v.IsNeighbor(neighbor):
                 same_neighbors = False
                 break
         if same_neighbors:
             #                raw_input("ERROR: ComputeCost() same neighbors detected.")
             return 999999.9
     curvature = 0.001
     sides = list()
     for f in self.Faces:
         if v.IsFace(f):
             sides.append(f)
     if self.parent.Settings.UseCurvature:
         for f in self.Faces:
             mincurv = 1.0
             for s in sides:
                 dotproduct = vecDotProduct(f.normal, s.normal)
                 mincurv = min([mincurv, (1.002 - dotproduct) / 2.0])
             curvature = max([curvature, mincurv])
     if self.IsBorder():
         WEIGHT_BORDER = 100
         curvature = curvature * WEIGHT_BORDER
     if self.parent.Settings.ProtectTexture:
         if not self.IsSameUV(v):
             WEIGHT_UV = 1.5
             curvature = curvature * WEIGHT_UV
     if self.parent.Settings.KeepBorder and self.IsBorder():
         #            raw_input("KEEP BORDER activated, Press ENTER to Continue.")
         curvature = 999999.9
     cost = edgelength * curvature
     #        print "DEBUG: ComputeCost() v[%d] to v[%d], c=%f" % (self.ID, v.ID, cost)
     return cost
Exemplo n.º 4
0
def getTangentSpace(vertices=None,
                    normals=None,
                    uvs=None,
                    triangles=None,
                    orientation=False,
                    orthogonal=True):
    """Calculate tangent space data.

    >>> vertices = [(0,0,0), (0,1,0), (1,0,0)]
    >>> normals = [(0,0,1), (0,0,1), (0,0,1)]
    >>> uvs = [(0,0), (0,1), (1,0)]
    >>> triangles = [(0,1,2)]
    >>> getTangentSpace(vertices = vertices, normals = normals, uvs = uvs, triangles = triangles)
    ([(0.0, 1.0, 0.0), (0.0, 1.0, 0.0), (0.0, 1.0, 0.0)], [(1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (1.0, 0.0, 0.0)])

    :param vertices: A list of vertices (triples of floats/ints).
    :param normals: A list of normals (triples of floats/ints).
    :param uvs: A list of uvs (pairs of floats/ints).
    :param triangles: A list of triangle indices (triples of ints).
    :param orientation: Set to ``True`` to return orientation (this is used by
        for instance Crysis).
    :return: Two lists of vectors, tangents and binormals. If C{orientation}
        is ``True``, then returns an extra list with orientations (containing
        floats which describe the total signed surface of all faces sharing
        the particular vertex).
    """

    # validate input
    if len(vertices) != len(normals) or len(vertices) != len(uvs):
        raise ValueError(
            "lists of vertices, normals, and uvs must have the same length")

    bin_norm = [(0, 0, 0) for i in range(len(vertices))]
    tan_norm = [(0, 0, 0) for i in range(len(vertices))]
    orientations = [0 for i in range(len(vertices))]

    # calculate tangents and binormals from vertex and texture coordinates
    for t1, t2, t3 in triangles:
        # skip degenerate triangles
        if t1 == t2 or t2 == t3 or t3 == t1:
            continue

        # get vertices, uvs, and directions of the triangle
        v1 = vertices[t1]
        v2 = vertices[t2]
        v3 = vertices[t3]
        w1 = uvs[t1]
        w2 = uvs[t2]
        w3 = uvs[t3]
        v2v1 = mathutils.vecSub(v2, v1)
        v3v1 = mathutils.vecSub(v3, v1)
        w2w1 = mathutils.vecSub(w2, w1)
        w3w1 = mathutils.vecSub(w3, w1)

        # surface of triangle in texture space
        r = w2w1[0] * w3w1[1] - w3w1[0] * w2w1[1]

        # sign of surface
        r_sign = (1 if r >= 0 else -1)

        # contribution of this triangle to tangents and binormals
        sdir = (r_sign * (w3w1[1] * v2v1[0] - w2w1[1] * v3v1[0]),
                r_sign * (w3w1[1] * v2v1[1] - w2w1[1] * v3v1[1]),
                r_sign * (w3w1[1] * v2v1[2] - w2w1[1] * v3v1[2]))
        try:
            sdir = mathutils.vecNormalized(sdir)
        except ZeroDivisionError:  # catches zero vector
            continue  # skip triangle
        except ValueError:  # catches invalid data
            continue  # skip triangle

        tdir = (r_sign * (w2w1[0] * v3v1[0] - w3w1[0] * v2v1[0]),
                r_sign * (w2w1[0] * v3v1[1] - w3w1[0] * v2v1[1]),
                r_sign * (w2w1[0] * v3v1[2] - w3w1[0] * v2v1[2]))
        try:
            tdir = mathutils.vecNormalized(tdir)
        except ZeroDivisionError:  # catches zero vector
            continue  # skip triangle
        except ValueError:  # catches invalid data
            continue  # skip triangle

        # vector combination algorithm could possibly be improved
        for i in (t1, t2, t3):
            tan_norm[i] = mathutils.vecAdd(tan_norm[i], tdir)
            bin_norm[i] = mathutils.vecAdd(bin_norm[i], sdir)
            orientations[i] += r

    # convert into orthogonal space
    xvec = (1, 0, 0)
    yvec = (0, 1, 0)
    for i, norm in enumerate(normals):
        if abs(1 - mathutils.vecNorm(norm)) > 0.01:
            raise ValueError(
                "tangentspace: unnormalized normal in list of normals (%s, norm is %f)"
                % (norm, mathutils.vecNorm(norm)))
        try:
            # turn norm, bin_norm, tan_norm into a base via Gram-Schmidt
            bin_norm[i] = mathutils.vecSub(
                bin_norm[i],
                mathutils.vecscalarMul(
                    norm, mathutils.vecDotProduct(norm, bin_norm[i])))
            bin_norm[i] = mathutils.vecNormalized(bin_norm[i])
            tan_norm[i] = mathutils.vecSub(
                tan_norm[i],
                mathutils.vecscalarMul(
                    norm, mathutils.vecDotProduct(norm, tan_norm[i])))
            tan_norm[i] = mathutils.vecSub(
                tan_norm[i],
                mathutils.vecscalarMul(
                    bin_norm[i], mathutils.vecDotProduct(norm, bin_norm[i])))
            tan_norm[i] = mathutils.vecNormalized(tan_norm[i])
        except ZeroDivisionError:
            # insuffient data to set tangent space for this vertex
            # in that case pick a space
            bin_norm[i] = mathutils.vecCrossProduct(xvec, norm)
            try:
                bin_norm[i] = mathutils.vecNormalized(bin_norm[i])
            except ZeroDivisionError:
                bin_norm[i] = mathutils.vecCrossProduct(yvec, norm)
                bin_norm[i] = mathutils.vecNormalized(bin_norm[i])
            tan_norm[i] = mathutils.vecCrossProduct(norm, bin_norm[i])

    # return result
    if orientation:
        return tan_norm, bin_norm, orientations
    else:
        return tan_norm, bin_norm
Exemplo n.º 5
0
def get_mass_center_inertia_polyhedron(vertices,
                                       triangles,
                                       density=1,
                                       solid=True):
    """Return mass, center of gravity, and inertia matrix for a polyhedron.

    >>> from pyffi.utils.quickhull import qhull3d
    >>> box = [(0,0,0),(1,0,0),(0,2,0),(0,0,3),(1,2,0),(0,2,3),(1,0,3),(1,2,3)]
    >>> vertices, triangles = qhull3d(box)
    >>> mass, center, inertia = get_mass_center_inertia_polyhedron(
    ...     vertices, triangles, density = 4)
    >>> mass
    24.0
    >>> center
    (0.5, 1.0, 1.5)
    >>> inertia
    ((26.0, 0.0, 0.0), (0.0, 20.0, 0.0), (0.0, 0.0, 10.0))
    >>> poly = [(3,0,0),(0,3,0),(-3,0,0),(0,-3,0),(0,0,3),(0,0,-3)] # very rough approximation of a sphere of radius 2
    >>> vertices, triangles = qhull3d(poly)
    >>> mass, center, inertia = get_mass_center_inertia_polyhedron(
    ...     vertices, triangles, density = 3)
    >>> mass
    108.0
    >>> center
    (0.0, 0.0, 0.0)
    >>> abs(inertia[0][0] - 194.4) < 0.0001
    True
    >>> abs(inertia[1][1] - 194.4) < 0.0001
    True
    >>> abs(inertia[2][2] - 194.4) < 0.0001
    True
    >>> abs(inertia[0][1]) < 0.0001
    True
    >>> abs(inertia[0][2]) < 0.0001
    True
    >>> abs(inertia[1][2]) < 0.0001
    True
    >>> sphere = []
    >>> N = 10
    >>> for j in range(-N+1, N):
    ...     theta = j * 0.5 * math.pi / N
    ...     st, ct = math.sin(theta), math.cos(theta)
    ...     M = max(3, int(ct * 2 * N + 0.5))
    ...     for i in range(0, M):
    ...         phi = i * 2 * math.pi / M
    ...         s, c = math.sin(phi), math.cos(phi)
    ...         sphere.append((2*s*ct, 2*c*ct, 2*st)) # construct sphere of radius 2
    >>> sphere.append((0,0,2))
    >>> sphere.append((0,0,-2))
    >>> vertices, triangles = qhull3d(sphere)
    >>> mass, center, inertia = get_mass_center_inertia_polyhedron(
    ...     vertices, triangles, density = 3, solid = True)
    >>> abs(mass - 100.53) < 10 # 3*(4/3)*pi*2^3 = 100.53
    True
    >>> sum(abs(x) for x in center) < 0.01 # is center at origin?
    True
    >>> abs(inertia[0][0] - 160.84) < 10
    True
    >>> mass, center, inertia = get_mass_center_inertia_polyhedron(
    ...     vertices, triangles, density = 3, solid = False)
    >>> abs(mass - 150.79) < 10 # 3*4*pi*2^2 = 150.79
    True
    >>> abs(inertia[0][0] - mass*0.666*4) < 20 # m*(2/3)*2^2
    True
    """

    # 120 times the covariance matrix of the canonical tetrahedron
    # (0,0,0),(1,0,0),(0,1,0),(0,0,1)
    # integrate(integrate(integrate(z*z, x=0..1-y-z), y=0..1-z), z=0..1) = 1/120
    # integrate(integrate(integrate(y*z, x=0..1-y-z), y=0..1-z), z=0..1) = 1/60
    covariance_canonical = ((2, 1, 1), (1, 2, 1), (1, 1, 2))
    covariance_correction = 1.0 / 120

    covariances = []
    masses = []
    centers = []

    # for each triangle
    # construct a tetrahedron from triangle + (0,0,0)
    # find its matrix, mass, and center (for density = 1, will be corrected at
    # the end of the algorithm)
    for triangle in triangles:
        # get vertices
        vert0, vert1, vert2 = mathutils.operator.itemgetter(
            *triangle)(vertices)

        # construct a transform matrix that converts the canonical tetrahedron
        # into (0,0,0),vert0,vert1,vert2
        transform_transposed = (vert0, vert1, vert2)
        transform = mathutils.matTransposed(transform_transposed)

        # find the covariance matrix of the transformed tetrahedron/triangle
        if solid:
            # we shall be needing the determinant more than once, so
            # precalculate it
            determinant = mathutils.matDeterminant(transform)
            # C' = det(A) * A * C * A^T
            covariances.append(
                mathutils.matscalarMul(
                    mathutils.matMul(
                        mathutils.matMul(transform, covariance_canonical),
                        transform_transposed), determinant))
            # m = det(A) / 6.0
            masses.append(determinant / 6.0)
            # find center of gravity of the tetrahedron
            centers.append(
                tuple(0.25 * sum(vert[i] for vert in (vert0, vert1, vert2))
                      for i in range(3)))
        else:
            # find center of gravity of the triangle
            centers.append(
                tuple(
                    sum(vert[i] for vert in (vert0, vert1, vert2)) / 3.0
                    for i in range(3)))
            # find mass of triangle
            # mass is surface, which is half the norm of cross product
            # of two edges
            masses.append(
                mathutils.vecNorm(
                    mathutils.vecCrossProduct(mathutils.vecSub(
                        vert1, vert0), mathutils.vecSub(vert2, vert0))) / 2.0)
            # find covariance at center of this triangle
            # (this is approximate only as it replaces triangle with point mass
            # todo: find better way)
            covariances.append(
                tuple(
                    tuple(masses[-1] * x * y for x in centers[-1])
                    for y in centers[-1]))

    # accumulate the results
    total_mass = sum(masses)
    if total_mass == 0:
        # dimension is probably badly chosen
        # raise ZeroDivisionError("mass is zero (consider calculating inertia with a lower dimension)")
        print("WARNING: mass is nearly zero (%f)" % total_mass)
        return 0, (0, 0, 0), ((0, 0, 0), (0, 0, 0), (0, 0, 0))
    # weighed average of centers with masses
    total_center = (0, 0, 0)
    for center, mass in zip(centers, masses):
        total_center = mathutils.vecAdd(
            total_center, mathutils.vecscalarMul(center, mass / total_mass))
    # add covariances, and correct the values
    total_covariance = ((0, 0, 0), (0, 0, 0), (0, 0, 0))
    for covariance in covariances:
        total_covariance = mathutils.matAdd(total_covariance, covariance)
    if solid:
        total_covariance = mathutils.matscalarMul(total_covariance,
                                                  covariance_correction)

    # translate covariance to center of gravity:
    # C' = C - m * ( x dx^T + dx x^T + dx dx^T )
    # with x the translation vector and dx the center of gravity
    translate_correction = mathutils.matscalarMul(
        tuple(tuple(x * y for x in total_center) for y in total_center),
        total_mass)
    total_covariance = mathutils.matSub(total_covariance, translate_correction)

    # convert covariance matrix into inertia tensor
    trace = sum(total_covariance[i][i] for i in range(3))
    trace_matrix = tuple(
        tuple((trace if i == j else 0) for i in range(3)) for j in range(3))
    total_inertia = mathutils.matSub(trace_matrix, total_covariance)

    # correct for given density
    total_inertia = mathutils.matscalarMul(total_inertia, density)
    total_mass *= density

    # correct negative mass
    if total_mass < 0:
        total_mass = -total_mass
        total_inertia = tuple(tuple(-x for x in row) for row in total_inertia)

    return total_mass, total_center, total_inertia