Example #1
0
template<> void CubicHinges<TV3>::add_elastic_differential(RawArray<TV> dF, RawArray<const TV> dX) const {
  GEODE_ASSERT(dF.size()>=nodes_ && dX.size()>=nodes_);
  if (simple_hessian) // In the simple case, the force is linear and the differential is easy
    add_force_helper<true>(bends,info,stiffness,dF,dX);
  else { // Otherwise, we need custom code
    GEODE_ASSERT(dF.size()>=nodes_ && dX.size()>=nodes_);
    const T scale = stiffness;
    if (!scale) return;
    RawArray<const TV> X = this->X;
    for (int b=0;b<bends.size();b++) {
      const auto& I = info[b];
      int i0,i1,i2,i3;bends[b].get(i0,i1,i2,i3);
      const TV x0 = X[i0], x1 = X[i1], x2 = X[i2], x3 = X[i3];
      const TV dx0 = dX[i0], dx1 = dX[i1], dx2 = dX[i2], dx3 = dX[i3];
      const TV dstress = scale*I.dot*(I.c[0]*dx0+I.c[1]*dx1+I.c[2]*dx2+I.c[3]*dx3);
      const T cubic = scale*I.det;
      const TV ce0 = cubic*(x2-x1),
               e1 = x3-x1,
               e2 = x0-x1,
               dce0 = cubic*(dx2-dx1),
               de1 = dx3-dx1,
               de2 = dx0-dx1,
               dcross01 = cross(ce0,de1)+cross(dce0,e1),
               dcross12 = cubic*(cross(e1,de2)+cross(de1,e2)),
               dcross20 = cross(e2,dce0)+cross(de2,ce0);
      dF[i0] -= I.c[0]*dstress+dcross01;
      dF[i1] -= I.c[1]*dstress-dcross01-dcross12-dcross20;
      dF[i2] -= I.c[2]*dstress+dcross12;
      dF[i3] -= I.c[3]*dstress+dcross20;
    }
  }
}
Example #2
0
GEODE_UNUSED static void check_bsp(const TriangleTopology& mesh, RawArray<const Node> bsp, RawField<const Vector<int,2>,FaceId> face_to_bsp, RawField<const Perturbed2,VertexId> X_) {
  if (self_check) {
    cout << "bsp:\n";
    #define CHILD(c) format("%c%d",(c<0?'f':'n'),(c<0?~c:c))
    for (const int n : range(bsp.size()))
      cout << "  "<<n<<" : test "<<bsp[n].test<<", children "<<CHILD(bsp[n].children[0])<<" "<<CHILD(bsp[n].children[1])<<endl;
    cout << "tris = "<<mesh.elements()<<endl;
    cout << "X = "<<X_.flat<<endl;
  }
  for (const int n : range(bsp.size()))
    for (const int i : range(2))
      if (bsp[n].children[i]<0)
        GEODE_ASSERT(face_to_bsp[FaceId(~bsp[n].children[i])].contains(2*n+i));
  auto X = X_.copy();
  for (const auto f : mesh.faces()) {
    int i0,i1;
    face_to_bsp[f].get(i0,i1);
    if (bsp.size())
      GEODE_ASSERT(bsp[i0/2].children[i0&1]==~f.id);
    if (i1>=0)
      GEODE_ASSERT(bsp[i1/2].children[i1&1]==~f.id);
    const auto v = mesh.vertices(f);
    if (!is_sentinel(X[v.x]) && !is_sentinel(X[v.y]) && !is_sentinel(X[v.z])) {
      const auto center = X.append(Perturbed2(X.size(),(X[v.x].value()+X[v.y].value()+X[v.z].value())/3));
      const auto found = bsp_search(bsp,X,center);
      if (found!=f) {
        cout << "bsp search failed: f "<<f<<", v "<<v<<", found "<<found<<endl;
        GEODE_ASSERT(false);
      }
      X.flat.pop();
    }
  }
}
Example #3
0
Ref<SparseMatrix> TriangleSubdivision::loop_matrix() const {
  if (loop_matrix_)
    return ref(loop_matrix_);
  // Build matrix of Loop subdivision weights
  Hashtable<Vector<int,2>,T> A;
  int offset = coarse_mesh->nodes();
  RawArray<const Vector<int,2> > segments = coarse_mesh->segment_soup()->elements;
  Nested<const int> neighbors = coarse_mesh->sorted_neighbors();
  Nested<const int> boundary_neighbors = coarse_mesh->boundary_mesh()->neighbors();
  unordered_set<int> corners_set(corners.begin(),corners.end());
  // Fill in node weights
  for (int i=0;i<offset;i++){
    if (!neighbors.valid(i) || !neighbors.size(i) || corners_set.count(i) || (boundary_neighbors.valid(i) && boundary_neighbors.size(i) && boundary_neighbors.size(i)!=2))
      A.set(vec(i,i),1);
    else if (boundary_neighbors.valid(i) && boundary_neighbors.size(i)==2) { // Regular boundary node
      A.set(vec(i,i),(T).75);
      for (int a=0;a<2;a++)
        A.set(vec(i,boundary_neighbors(i,a)),(T).125);
    } else { // Interior node
      RawArray<const int> ni = neighbors[i];
      T alpha = new_loop_alpha(ni.size());
      A.set(vec(i,i),alpha);
      T other = (1-alpha)/ni.size();
      for (int j : ni)
        A.set(vec(i,j),other);
    }
  }
  // Fill in edge values
  for (int s=0;s<segments.size();s++) {
    int i = offset+s;
    Vector<int,2> e = segments[s];
    RawArray<const int> n[2] = {neighbors.valid(e[0])?neighbors[e[0]]:RawArray<const int>(),
                                neighbors.valid(e[1])?neighbors[e[1]]:RawArray<const int>()};
    if (boundary_neighbors.valid(e[0]) && boundary_neighbors.valid(e[1]) && boundary_neighbors.size(e[0]) && boundary_neighbors.size(e[1]) && boundary_neighbors[e[0]].contains(e[1])) // Boundary edge
      for (int a=0;a<2;a++)
        A.set(vec(i,e[a]),(T).5);
    else if (n[0].size()==6 && n[1].size()==6) { // Edge between regular vertices
      int j = n[0].find(e[1]);
      int c[2] = {n[0][(j-1+n[0].size())%n[0].size()],
                  n[0][(j+1)%n[0].size()]};
      for (int a=0;a<2;a++) {
        A.set(vec(i,e[a]),(T).375);
        A.set(vec(i,c[a]),(T).125);
      }
    } else { // Edge between one or two irregular vertices
      T factor = n[0].size()!=6 && n[1].size()!=6 ?.5:1;
      for (int k=0;k<2;k++)
        if (n[k].size()!=6) {
          A[vec(i,e[k])] += factor*(1-new_loop_beta(n[k].size()));
          int start = n[k].find(e[1-k]);
          for (int j=0;j<n[k].size();j++)
            A[vec(i,n[k][(start+j)%n[k].size()])] += factor*new_loop_weight(n[k].size(),j);
        }
    }
  }
  // Finalize construction
  loop_matrix_ = new_<SparseMatrix>(A,vec(offset+segments.size(),offset));
  return ref(loop_matrix_);
}
Example #4
0
template<> void CubicHinges<TV2>::add_elastic_differential(RawArray<TV> dF, RawArray<const TV> dX) const {
  // 2D forces are unconditionally linear, so we can always reuse force computation
  GEODE_ASSERT(dF.size()>=nodes_ && dX.size()>=nodes_);
  if (simple_hessian)
    add_force_helper<true>(bends,info,stiffness,dF,dX);
  else
    add_force_helper<false>(bends,info,stiffness,dF,dX);
}
Example #5
0
void SurfacePins::add_damping_force(RawArray<TV> F, RawArray<const TV> V) const {
  GEODE_ASSERT(V.size()==mass.size());
  GEODE_ASSERT(F.size()==mass.size());
  for (int i=0;i<particles.size();i++) {
    int p = particles[i];
    F[p] -= kd[i]*dot(V[p],info[i].normal)*info[i].normal;
  }
}
Example #6
0
void SurfacePins::add_elastic_differential(RawArray<TV> dF, RawArray<const TV> dX) const {
  GEODE_ASSERT(dF.size()==mass.size());
  GEODE_ASSERT(dX.size()==mass.size());
  for (int i=0;i<particles.size();i++) {
    int p = particles[i];
    dF[p] -= k[i]*dot(dX[p],info[i].normal)*info[i].normal; // Ignores a curvature term if the closest point is on an edge or vertex
  }
}
Example #7
0
template<int d,int m> static Array<UpperTriangularMatrix<T,d>> compute_Dm_inverse(RawArray<const Vector<int,d+1>> elements, RawArray<const Vector<T,m>> X) {
  Array<UpperTriangularMatrix<T,d>> Dm_inverse(elements.size(),uninit);
  for (int t=0;t<elements.size();t++) {
    const auto R = StrainMeasure<T,d>::Ds(X,elements[t]).R_from_QR_factorization();
    if (R.determinant()<=0)
      throw RuntimeError("StrainMeasure: Inverted or degenerate rest state");
    Dm_inverse[t] = R.inverse();
  }
  return Dm_inverse;
}
Example #8
0
void SparseMatrix::
solve_forward_substitution(RawArray<const T> b,RawArray<T> x) const
{
    GEODE_ASSERT(cholesky && rows()<=x.size() && rows()<=b.size());
    // The result of Incomplete_Cholesky_Factorization has unit diagonals in the lower triangle.
    for(int i=0;i<rows();i++){
        T sum=0;
        for(int index=J.offsets[i];index<diagonal_index[i];index++)
            sum+=A.flat[index]*x[J.flat[index]];
        x[i]=b[i]-sum;}
}
Example #9
0
void SparseMatrix::
solve_backward_substitution(RawArray<const T> b,RawArray<T> x) const
{
    GEODE_ASSERT(cholesky && rows()<=x.size() && rows()<=b.size());
    // The result of Incomplete_Cholesky_Factorization has an inverted diagonal for the upper triangle.
    for(int i=rows()-1;i>=0;i--){
        T sum=0;
        for(int index=diagonal_index[i]+1;index<J.offsets[i+1];index++)
            sum+=A.flat[index]*x[J.flat[index]];
        x[i]=(b[i]-sum)*A.flat[diagonal_index[i]];}
}
Example #10
0
template<class TV> CubicHinges<TV>::CubicHinges(Array<const Vector<int,d+2>> bends, RawArray<const T> angles, RawArray<const TV> X)
  : bends(bends)
  , stiffness(0)
  , damping(0)
  , simple_hessian(false)
  , nodes_(bends.size()?scalar_view(bends).max()+1:0)
  , info(bends.size()) {
  GEODE_ASSERT(bends.size()==angles.size());
  GEODE_ASSERT(X.size()>=nodes_);
  compute_info(bends,angles,X,info);
}
Example #11
0
template<> Array<T> CubicHinges<TV2>::angles(RawArray<const Vector<int,3>> bends, RawArray<const TV2> X) {
  if (bends.size())
    GEODE_ASSERT(X.size()>scalar_view(bends).max());
  Array<T> angles(bends.size(),uninit);
  for (int b=0;b<bends.size();b++) {
    int i0,i1,i2;bends[b].get(i0,i1,i2);
    const TV x0 = X[i0], x1 = X[i1], x2 = X[i2];
    angles[b] = angle_between(x1-x0,x2-x1);
  }
  return angles;
}
Example #12
0
Array<T> TriangleSoup::vertex_areas(RawArray<const TV3> X) const {
  GEODE_ASSERT(X.size()>=nodes());
  Array<T> areas(X.size());
  for (int t=0;t<elements.size();t++) {
    int i,j,k;elements[t].get(i,j,k);
    T area = T(1./6)*magnitude(cross(X[j]-X[i],X[k]-X[i]));
    areas[i] += area;
    areas[j] += area;
    areas[k] += area;
  }
  return areas;
}
Example #13
0
Array<TV3> TriangleSoup::vertex_normals(RawArray<const TV3> X) const {
  GEODE_ASSERT(X.size()>=nodes());
  Array<TV3> normals(X.size());
  for(int t=0;t<elements.size();t++){
    int i,j,k;elements[t].get(i,j,k);
    TV3 n = cross(X[j]-X[i],X[k]-X[i]);
    normals[i]+=n;normals[j]+=n;normals[k]+=n;
  }
  for(int i=0;i<X.size();i++)
    normals[i].normalize();
  return normals;
}
Example #14
0
Array<int> SegmentSoup::nonmanifold_nodes(bool allow_boundary) const {
  Array<int> nonmanifold;
  Nested<const int> incident_elements = this->incident_elements();
  for (int i=0;i<incident_elements.size();i++) {
    RawArray<const int> incident = incident_elements[i];
    if (   incident.size()>2 // Too many segments
        || (incident.size()==1 && !allow_boundary) // Disallowed boundary
        || (incident.size()==2 && (elements[incident[0]][0]==i)==(elements[incident[1]][0]==i))) // Inconsistent orientations
      nonmanifold.append(i);
  }
  return nonmanifold;
}
Example #15
0
static void endgame_sparse_verify(RawArray<const board_t> boards, RawArray<const Vector<super_t,2>> wins, Random& random, int samples) {
  GEODE_ASSERT(boards.size()==wins.size());
  GEODE_ASSERT((unsigned)samples<=(unsigned)boards.size());
  // Check samples in random order
  Array<int> permutation = arange(boards.size()).copy();
  ProgressIndicator progress(samples,true);
  for (int i=0;i<samples;i++) {
    swap(permutation[i],permutation[random.uniform<int>(i,boards.size())]);
    endgame_verify_board("endgame sparse verify",boards[permutation[i]],wins[permutation[i]],true);
    progress.progress();
  }
}
Example #16
0
template<> Array<T> CubicHinges<TV3>::angles(RawArray<const Vector<int,4>> bends, RawArray<const TV3> X) {
  if (bends.size())
    GEODE_ASSERT(X.size()>scalar_view(bends).max());
  Array<T> angles(bends.size(),uninit);
  for (int b=0;b<bends.size();b++) {
    int i0,i1,i2,i3;bends[b].get(i0,i1,i2,i3);
    const TV x0 = X[i0], x1 = X[i1], x2 = X[i2], x3 = X[i3],
             n0 = normal(x0,x2,x1),
             n1 = normal(x1,x2,x3);
    angles[b] = copysign(acos(clamp(dot(n0,n1),-1.,1.)),dot(n1-n0,x3-x0));
  }
  return angles;
}
Example #17
0
Array<const Vector<int,3>> SegmentSoup::bending_tuples() const {
  if (!bending_tuples_valid) {
    Nested<const int> neighbors = this->neighbors();
    Array<Vector<int,3>> tuples;
    for (const int p : range(nodes())) {
      RawArray<const int> near = neighbors[p];
      for (int i=0;i<near.size();i++) for(int j=i+1;j<near.size();j++)
        tuples.append(vec(near[i],p,near[j]));
    }
    bending_tuples_ = tuples;
  }
  return bending_tuples_;
}
Example #18
0
template<class TV> static inline Tuple<T,TV> weighted_average(RawArray<const T> mass, RawArray<const TV> X) {
  const int n = X.size();
  if (mass.size()) {
    GEODE_ASSERT(mass.size()==X.size());
    const T total = mass.sum();
    TV center;
    for (int i=0;i<n;i++)
      center += mass[i]*X[i];
    return tuple(total,center/total);
  } else {
    // Unweighted
    return tuple(n,X.mean());
  }
}
Example #19
0
template<class TV> void SparseMatrix::
multiply_helper(RawArray<const TV> x,RawArray<TV> result) const
{
    const int rows = this->rows();
    GEODE_ASSERT(columns()<=x.size() && rows<=result.size());
    RawArray<const int> offsets = J.offsets;
    RawArray<const int> J_flat = J.flat;
    RawArray<const T> A_flat = A.flat;
    for(int i=0;i<rows;i++){
        int end=offsets[i+1];TV sum=TV();
        for(int index=offsets[i];index<end;index++) sum+=A_flat[index]*x[J_flat[index]];
        result[i]=sum;}
    result.slice(rows,result.size()).zero();
}
Example #20
0
// Prepare a list of points for Delaunay triangulation: randomly assign into logarithmic bins, sort within bins, and add sentinels.
// For details, see Amenta et al., Incremental Constructions con BRIO.
static Array<Perturbed2> partially_sorted_shuffle(RawArray<const EV> Xin) {
  const int n = Xin.size();
  Array<Perturbed2> X(n+3,uninit);

  // Randomly assign input points into bins.  Bin k has 2**k = 1,2,4,8,... and starts at index 2**k-1 = 0,1,3,7,...
  // We fill points into bins as sequentially as possible to maximize cache coherence.
  const int bins = integer_log(n);
  Array<int> bin_counts(bins);
  for (int i=0;i<n;i++) {
    int j = (int)random_permute(n,key,i);
    const int bin = min(integer_log(j+1),bins-1);
    j = (1<<bin)-1+bin_counts[bin]++;
    X[j] = Perturbed2(i,Xin[i]);
  }

  // Spatially sort each bin down to clusters of size 64.
  const int leaf_size = 64;
  for (int bin=0;bin<bins;bin++) {
    const int start = (1<<bin)-1,
              end = bin==bins-1?n:start+(1<<bin);
    assert(bin_counts[bin]==end-start);
    spatial_sort(X.slice(start,end),leaf_size,new_<Random>(key+bin));
  }

  // Add 3 sentinel points at infinity
  X[n+0] = Perturbed2(n+0,EV(-bound,-bound));
  X[n+1] = Perturbed2(n+1,EV( bound, 0)    );
  X[n+2] = Perturbed2(n+2,EV(-bound, bound));

  return X;
}
Example #21
0
void SurfacePins::add_elastic_force(RawArray<TV> F) const {
  GEODE_ASSERT(F.size()==mass.size());
  for (int i=0;i<particles.size();i++) {
    int p = particles[i];
    F[p] -= k[i]*info[i].phi*info[i].normal;
  }
}
Example #22
0
template<bool simple> static void add_gradient_helper(RawArray<const Vector<int,4>> bends, RawArray<const CubicHinges<TV3>::Info> info, const T scale, RawArray<const TV3> X, SolidMatrix<TV3>& matrix) {
  if (!scale) return;
  for (int b=0;b<bends.size();b++) {
    const auto& I = info[b];
    int i0,i1,i2,i3;bends[b].get(i0,i1,i2,i3);
    const T quad = -scale*I.dot;
    matrix.add_entry(i0,quad*sqr(I.c[0]));
    matrix.add_entry(i1,quad*sqr(I.c[1]));
    matrix.add_entry(i2,quad*sqr(I.c[2]));
    matrix.add_entry(i3,quad*sqr(I.c[3]));
    if (simple) {
      matrix.add_entry(i0,i1,quad*I.c[0]*I.c[1]);
      matrix.add_entry(i0,i2,quad*I.c[0]*I.c[2]);
      matrix.add_entry(i0,i3,quad*I.c[0]*I.c[3]);
      matrix.add_entry(i1,i2,quad*I.c[1]*I.c[2]);
      matrix.add_entry(i1,i3,quad*I.c[1]*I.c[3]);
      matrix.add_entry(i2,i3,quad*I.c[2]*I.c[3]);
    } else {
      const T cubic = -scale*I.det;
      const TV3 x0 = cubic*X[i0], x1 = cubic*X[i1], x2 = cubic*X[i2], x3 = cubic*X[i3];
      matrix.add_entry(i0,i1,quad*I.c[0]*I.c[1]+cross_product_matrix(x3-x2)); //  e3
      matrix.add_entry(i0,i2,quad*I.c[0]*I.c[2]+cross_product_matrix(x1-x3)); // -e1
      matrix.add_entry(i0,i3,quad*I.c[0]*I.c[3]+cross_product_matrix(x2-x1)); //  e0
      matrix.add_entry(i1,i2,quad*I.c[1]*I.c[2]+cross_product_matrix(x3-x0));
      matrix.add_entry(i1,i3,quad*I.c[1]*I.c[3]+cross_product_matrix(x0-x2)); //  e4
      matrix.add_entry(i2,i3,quad*I.c[2]*I.c[3]+cross_product_matrix(x1-x0)); // -e2
    }
  }
}
Example #23
0
real circle_arc_area(RawArray<const CircleArc> arcs) {
  const int n = arcs.size();
  real area = 0;
  for (int i=n-1,j=0;j<n;i=j++)
    area += .5*cross(arcs[i].x,arcs[j].x) + .25*sqr_magnitude(arcs[j].x-arcs[i].x)*q_factor(arcs[i].q); // Triangle area plus circular sector area
  return .5*area;
}
Example #24
0
void SurfacePins::add_elastic_gradient_block_diagonal(RawArray<SymmetricMatrix<T,m>> dFdX) const {
  GEODE_ASSERT(dFdX.size()==mass.size());
  for (int i=0;i<particles.size();i++) {
    int p = particles[i];
    dFdX[p] -= scaled_outer_product(k[i],info[i].normal); // Ignores a curvature term if the closest point is on an edge or vertex
  }
}
Example #25
0
void SurfacePins::add_frequency_squared(RawArray<T> frequency_squared) const {
  GEODE_ASSERT(frequency_squared.size()==mass.size());
  for (int i=0;i<particles.size();i++) {
    int p = particles[i];
    frequency_squared[p]+=k[i]/mass[p];
  }
}
Example #26
0
template<bool simple> static void add_force_helper(RawArray<const Vector<int,4>> bends, RawArray<const CubicHinges<TV3>::Info> info, const T scale, RawArray<TV3> F, RawArray<const TV3> X) {
  if (!scale) return;
  for (int b=0;b<bends.size();b++) {
    const auto& I = info[b];
    int i0,i1,i2,i3;bends[b].get(i0,i1,i2,i3);
    const TV3 x0 = X[i0], x1 = X[i1], x2 = X[i2], x3 = X[i3],
              stress = scale*I.dot*(I.c[0]*x0+I.c[1]*x1+I.c[2]*x2+I.c[3]*x3);
    if (simple) {
      F[i0] -= I.c[0]*stress;
      F[i1] -= I.c[1]*stress;
      F[i2] -= I.c[2]*stress;
      F[i3] -= I.c[3]*stress;
    } else {
      const T cubic = scale*I.det;
      const TV3 x0 = X[i0], x1 = X[i1], x2 = X[i2], x3 = X[i3];
      const TV3 ce0 = cubic*(x2-x1),
                e1 = x3-x1,
                e2 = x0-x1,
                cross01 = cross(ce0,e1),
                cross12 = cubic*cross(e1,e2),
                cross20 = cross(e2,ce0);
      F[i0] -= I.c[0]*stress+cross01;
      F[i1] -= I.c[1]*stress-cross01-cross12-cross20;
      F[i2] -= I.c[2]*stress+cross12;
      F[i3] -= I.c[3]*stress+cross20;
    }
  }
}
Example #27
0
 void reduce(RawArray<const T> xe, RawArray<T> xr) const {
   for (int k=0;k<=bcs.size();k++) {
     const int lo = k ? bcs[k-1].x+1 : 0,
               hi = k<bcs.size() ? bcs[k].x : xe.size();
     for (int i=lo;i<hi;i++)
       xr[i-k] = xe[i];
   }
 }
Example #28
0
static void nasty_denominator(RawArray<mp_limb_t> result, RawArray<const Vector<Exact<1>,2>> X) {
  assert(X.size()==2);
  typename remove_const_reference<decltype(X[0])>::type p1;
  for (int i=0;i<2;i++)
    mpz_set(asarray(p1[i].n),Exact<1>(perturbation<2>(1,nasty_index)[i]));
  const auto d = edet(X[0],p1);
  mpz_set(result,d);
}
Example #29
0
void SparseMatrix::
gauss_seidel_solve(RawArray<T> x,RawArray<const T> b,const T tolerance,const int max_iterations) const
{
    GEODE_ASSERT(rows()==columns() && x.size()==rows() && b.size()==rows());
    const T sqr_tolerance=sqr(tolerance);
    for(int iteration=0;iteration<max_iterations;iteration++){
        T sqr_residual=0;
        for(int i=0;i<rows();i++){
            T rho=0;T diagonal_entry=0;
            for(int index=J.offsets[i];index<J.offsets[i+1];index++){
                if(J.flat[index]==i) diagonal_entry=A.flat[index];
                else rho+=A.flat[index]*x[J.flat[index]];}
            T new_x=x[i]=(b[i]-rho)/diagonal_entry;
            sqr_residual+=sqr(new_x-x[i]);
            x[i]=new_x;}
        if(sqr_residual <= sqr_tolerance) break;}
}
Example #30
0
template<class TV> static Matrix<T,TV::m+1> affine_register_helper(RawArray<const TV> X0, RawArray<const TV> X1, RawArray<const T> mass) {
  typedef typename TV::Scalar T;
  static const int d = TV::m;
  const int n = X0.size();
  GEODE_ASSERT(n==X1.size() && (mass.size()==0 || mass.size()==n));
  const bool weighted = mass.size() == n;
  const T total = weighted ? mass.sum() : n;
  if (!total)
    return Matrix<T,d+1>::identity_matrix();

  // Compute centers of mass
  TV c0, c1;
  if (weighted) {
    for (int i=0;i<n;i++) {
      c0 += mass[i]*X0[i];
      c1 += mass[i]*X1[i];
    }
  } else {
    c0 = X0.sum();
    c1 = X1.sum();
  }
  c0 /= total;
  c1 /= total;

  // Compute covariances
  SymmetricMatrix<T,d> cov00 = scaled_outer_product(-total,c0);
  Matrix<T,d> cov01 = outer_product(-total*c1,c0);
  if (weighted)
    for(int i=0;i<n;i++) {
      cov00 += scaled_outer_product(mass[i],X0[i]);
      cov01 += outer_product(mass[i]*X1[i],X0[i]);
    }
  else
    for (int i=0;i<n;i++) {
      cov00 += outer_product(X0[i]);
      cov01 += outer_product(X1[i],X0[i]);
    }

  // Compute transform
  const Matrix<T,d> A = cov01*cov00.inverse();
  const TV t = c1-A*c0;
  auto tA = Matrix<T,d+1>::from_linear(A);
  tA.set_translation(t);
  return tA;
}