예제 #1
0
    def trim(self, points, radius):
        """
        remove points too close to the cut curve. they dont add anything, and only lead to awkward faces
        """
        #some precomputations
        tree   = KDTree(points)
        cp     = self.vertices[self.faces]
        normal = util.normalize(np.cross(cp[:,0], cp[:,1]))
        mid    = util.normalize(cp.sum(axis=1))
        diff   = np.diff(cp, axis=1)[:,0,:]
        edge_radius = np.sqrt(util.dot(diff, diff)/4 + radius**2)

        index = np.ones(len(points), np.bool)

        #eliminate near edges
        def near_edge(e, p):
            return np.abs(np.dot(points[p]-mid[e], normal[e])) < radius
        for i,(p,r) in enumerate(izip(mid, edge_radius)):
            coarse = tree.query_ball_point(p, r)
            index[[c for c in coarse if near_edge(i, c)]] = 0
        #eliminate near points
        for p in self.vertices:
            coarse = tree.query_ball_point(p, radius)
            index[coarse] = 0

        return points[index]
예제 #2
0
 def compute_angles(self):
     """compute angles for each triangle-vertex"""
     edges = self.edges().reshape(-1, 3, 2)
     vecs = np.diff(self.vertices[edges], axis=2)[:, :, 0]
     vecs = util.normalize(vecs)
     angles = np.arccos(-util.dot(vecs[:, [1, 2, 0]], vecs[:, [2, 0, 1]]))
     assert np.allclose(angles.sum(axis=1), np.pi, rtol=1e-3)
     return angles
예제 #3
0
 def compute_divergence(self, field):
     """compute divergence of vector field at faces at vertices"""
     edges = self.edges().reshape(-1, 3, 2)
     sorted_edges = np.sort(edges, axis=-1)
     vecs = np.diff(self.vertices[sorted_edges], axis=2)[:, :, 0, :]
     inner = util.dot(vecs, field[:, None, :])
     cotan = 1 / np.tan(self.compute_angles())
     vertex_incidence = self.compute_vertex_incidence()
     return vertex_incidence.T * self.remap_edges(inner * cotan) / 2
예제 #4
0
def triangulate(points, curve):
    """
    return a triangulation of the pointset points,
    while being constrained by the boundary dicated by curve
    """
    #test curve for self-intersection
    print 'testing curve for self-intersection'
    curve.self_intersect()

    #trim the pointset, to eliminate points co-linear with the cutting curve
    print 'trimming dataset'
    diff   = np.diff(curve.vertices[curve.faces], axis=1)[:,0,:]
    length = np.linalg.norm(diff, axis=1)
    points = curve.trim(points, length.mean()/4)

    #refine curve iteratively. new points may both obsolete or require novel insertions themselves
    #so only do the most pressing ones first, then iterate to convergence
    while True:
        newcurve = curve.refine(points)
        if len(newcurve.vertices)==len(curve.vertices):
            break
        print 'curve refined'
        curve = newcurve


    """
    we use the nifty property, that a convex hull of a sphere equals a delauney triangulation of its surface
    if we have cleverly refined our boundary curve, this trinagulation should also be 'constrained', in the sense
    of respecting that original boundary curve
    this is the most computationally expensive part of this function, but we should be done in a minute or so

    qhull performance; need 51 sec and 2.7gb for 4M points
    that corresponds to an icosahedron with level 8 subdivision; not too bad
    editor is very unresponsive at this level anyway
    """
    print 'triangulating'
    allpoints = np.concatenate((curve.vertices, points))    #include origin; facilitates clipping
    hull = scipy.spatial.ConvexHull(util.normalize(allpoints))
    triangles = hull.simplices

    #order faces coming from the convex hull
    print 'ordering faces'
    FP        = util.gather(triangles, allpoints)
    mid       = FP.sum(axis=1)
    normal    = util.normals(FP)
    sign      = util.dot(normal, mid) > 0
    triangles = np.where(sign[:,None], triangles[:,::+1], triangles[:,::-1])

    mesh = Mesh(allpoints, triangles)
    assert mesh.is_orientated()

    return mesh, curve
예제 #5
0
def edge_length(*edge):
    """compute spherical edge length.

    Parameters
    ----------
    edge : 2 x ndarray, [n, 3], float
        arc segments described by their start and end position

    Returns
    -------
    lengths: ndarray, [n], float
        length along the unit sphere of each segment
    """
    return np.arccos(util.dot(*edge))
예제 #6
0
def triangle_area_from_normals(*edge_planes):
    """compute spherical area from triplet of great circles

    Parameters
    ----------
    edge_planes : 3 x ndarray, [n, 3], float
        edge normal vectors of great circles

    Returns
    -------
    areas : ndarray, [n], float
        spherical area enclosed by the input planes
    """
    edge_planes = [util.normalize(ep) for ep in edge_planes ]
    angles      = [util.dot(edge_planes[p-2], edge_planes[p-1]) for p in xrange(3)] #a3 x [faces, c3]
    areas       = sum(np.arccos(-a) for a in angles) - np.pi                        #faces
    return areas
예제 #7
0
def solve_poisson_rec(hierarchy, forcefield, edges, coefficients):
    """
    poisson solution on a single height level
    """
    complex = hierarchy[-1]

    #decide on recursion
    if len(hierarchy) > 4:
        coefficients, heightfield = solve_poisson_rec(
            hierarchy[:-1],
            hierarchy[-2].restrict_d2(forcefield),
            edges,
            coefficients
            )

    def smooth(x):
        width = 0.005
        return multigrid.diffuse(hierarchy, x, width**2)

    def height_from_force(force):
        return multigrid.solve_poisson(hierarchy, force)

    #external force field
    offset = 0
##    external_force = complex.D2P0 * (forcefield + offset)
    external_force = forcefield


    if len(edges)==0:
        #? why center is p0?
        f = complex.P0D2 * external_force
        f = f - f.mean()
        return None, height_from_force(complex.D2P0 * f) + 1
    else:
        #take midpoints of edges, for correct boundary handling
        edges = np.vstack(edges)
        radius = np.sqrt(util.dot(edges, edges))        #compute target radius at each point

        print 'outer unknowns'
        print len(radius)

        #pick edges against the sphere. give each edge its own mapping
        mapping = brushes.Mapping(hierarchy, edges)


    def sample_residual(height):
        """maps p0 height form to an absolute residual vector"""
        return radius - mapping.sample(height)


    if coefficients is None:
        #init coefs as average of force
        net_external_force = complex.deboundify(external_force).sum()
        base_coefficients = np.ones_like(radius) / len(radius) * net_external_force
    else:
        base_coefficients = coefficients

    print 'net force'
    print base_coefficients.sum()


    if True:
        force_curve = smooth(mapping.inject(base_coefficients))
        force = force_curve - external_force
    else:
        #if no boundary conditions, act only on homogenous part
        force = external_force.mean() - external_force

    base_height = height_from_force(force)

    height_dirs = []
    coeff_dirs  = []

    height       = base_height       + 0
    coefficients = base_coefficients + 0

    #this substraction is key; if we search in homogenous subspace, need to translate rhs there too!
    homogenous_radius = radius - mapping.sample(base_height)

    def find_homogenous_minimizer(basis):
        """
        find the linear combination of height basis vectors, such that
        sampled_basis dot coeff ~= radius
        perform fit modulo ones vector. sampled basis vectors need no sum to zero, but we dont want to fit this component
        """
        B = np.vstack([mapping.sample(b) for b in basis]+[np.ones_like(radius)])
        S = np.dot(B, B.T)
        r = np.dot(B, homogenous_radius)
        c = np.linalg.lstsq(S, r)[0]
##        print c

        return lambda I: sum(i*q for i,q in izip(I, c))


##    for i in range(18-len(hierarchy)):  #iterate until convergence
    for i in range(10):  #iterate until convergence
        print i
        #scale curves with coef estimates as preconditioner?
        coeff_dir = sample_residual(height) #/ scaling
        coeff_dir = coeff_dir - coeff_dir.mean()   #balance forces; move only in space of valid coefficients
        print 'outer convergence',  np.linalg.norm( coeff_dir)

        force_dir   = smooth(mapping.inject(coeff_dir))
        height_dir  = height_from_force(force_dir)

        height_dirs.append(height_dir)
        coeff_dirs .append(coeff_dir)

        minimizer = find_homogenous_minimizer(height_dirs)

        height       = base_height       + minimizer(height_dirs)
        coefficients = base_coefficients + minimizer(coeff_dirs)


    res = sample_residual(height)
    height = height + res.mean()


##    print len(hierarchy)
##    print coefficients


    if False:
        #visualize force field
        force -= force.min()
        force /= force.max()
        return force*0.1+1

    return coefficients, height
예제 #8
0
def solve_poisson(datamodel):
    """
    take a datamodel
    and return a heightfield that conforms to the immersed boundary conditions

    inner iteration is the mg solve from force to height

    outer iteration is a krylov subspace method
    is has several custom steps, to cope with the left and right nullspace problem

    """
    hierachy = datamodel.hierarchy
    complex = hierachy[-1]

    def smooth(x):
        width = 0.005
        return multigrid.diffuse(hierachy, x, width**2)

    def height_from_force(force):
##        return multigrid.solve_poisson(hierachy, force)
        return complex.P0D2 * multigrid.solve_poisson_full(hierachy, force)

    #external force field
    offset = 0
    scaling = -1
    external_force = complex.D2P0 * (datamodel.forcefield * scaling + offset)

##    def map_edge(edge):
##        points = edge.instantiate()[0,0]
##        points = (points[1:]+points[:-1])/2
##        radius = np.sqrt(util.dot(points, points))        #compute target radius at each point
##        mapping = brushes.Mapping(hierachy, points)
##
##    edges = [map_edge(edge) for edge in datamodel.edges if edge.driving]


    #instantiate all curves; pick first occurance
    edges = [edge.instantiate()[0,0] for edge in datamodel.edges if edge.driving]
    if len(edges)==0:
        f = complex.P0D2 * external_force
        f = f - f.mean()
        return height_from_force(complex.D2P0 * f) + 1
    #take midpoints of edges, for correct boundary handling
    edges = [(edge[1:]+edge[:-1])/2 for edge in edges]
    edges = np.vstack(edges)
    radius = np.sqrt(util.dot(edges, edges))        #compute target radius at each point

    print 'outer unknowns'
    print len(radius)

    #pick edges against the sphere. give each edge its own mapping
    mapping = brushes.Mapping(hierachy, edges)


    #preconditioner. less response in denser regions. somehow, this is a complete disaster
##    scaling = mapping.sample(smooth(mapping.inject(np.ones_like(radius))))
####    scaling = np.sqrt(scaling)
##    print 'scaling'
##    print scaling
##    print scaling.min(), scaling.max()


    def sample_residual(height):
        """maps p0 height form to an absolute residual vector"""
        return radius - mapping.sample(height)


    #init coefs as average of force
    net_external_force = complex.deboundify(external_force).sum()
    base_coefficients = np.ones_like(radius) / len(radius) * net_external_force

    if True:
        force_curve = smooth(mapping.inject(base_coefficients))
        force = force_curve - external_force
    else:
        #if no boundary conditions, act only on homogenous part
        force = external_force.mean() - external_force

    base_height = height_from_force(force)

    height_dirs = []
    coeff_dirs  = []

    height       = base_height       + 0
    coefficients = base_coefficients + 0

    #this substraction is key; if we search in homogenous subspace, need to translate rhs there too!
    homogenous_radius = radius - mapping.sample(base_height)

    def find_homogenous_minimizer(basis):
        """
        find the linear combination of height basis vectors, such that
        sampled_basis dot coeff ~= radius
        perform fit modulo ones vector. sampled basis vectors need no sum to zero, but we dont want to fit this component
        """
        B = np.vstack([mapping.sample(b) for b in basis]+[np.ones_like(radius)])
        S = np.dot(B, B.T)
        r = np.dot(B, homogenous_radius)
##        c = np.linalg.lstsq(S, r)[0]
        c = np.linalg.solve(S, r)
##        print c

        return lambda I: sum(i*q for i,q in izip(I, c))


    for i in range(15):  #iterate until convergence
        print i
        #scale curves with coef estimates as preconditioner?
        coeff_dir = sample_residual(height) #/ scaling
        coeff_dir = coeff_dir - coeff_dir.mean()   #balance forces; move only in space of valid coefficients
        print 'outer convergence'
        print np.linalg.norm( coeff_dir)

        force_dir   = smooth(mapping.inject(coeff_dir))
        height_dir  = height_from_force(force_dir)

        height_dirs.append(height_dir)
        coeff_dirs .append(coeff_dir)

        minimizer = find_homogenous_minimizer(height_dirs)

        height       = base_height       + minimizer(height_dirs)
        coefficients = base_coefficients + minimizer(coeff_dirs)


    res = sample_residual(height)
    height = height + res.mean()


    print coefficients


    if False:
        #visualize force field
        force -= force.min()
        force /= force.max()
        return force*0.1+1

    return height