#################################
### COPYRIGHT: Anders Lyhagen ###
#################################

module CAUL_FAQS
module Flow
 
  def self.flowAlong(res_g, gg, pgr, pgr_in, tgr, tgr_out, proj_ve)
    
    #proj_ve = Geom::Vector3d.new(0, 1, 0)
    proj_tr = pgr_in * gg.transformation
    es = gg.entities.grep(Sketchup::Edge)
    vs = es.map{|e| e.vertices}.flatten!.uniq!
    fs = gg.entities.grep(Sketchup::Face)
    return if fs.length == 0
    vs_h = {}
    proj_ve = proj_ve.transform(pgr_in)
    
    #return
    #TODO: Problem with orientation transformation (flip) may cause problem here...
    #Manual solution: Explode all groups and regroup..
    vs.each { |v|
      p0 = v.position.transform(proj_tr)
      uv = pgr.getUVD([p0, proj_ve])
      next if uv == nil
      vs_h[v] = tgr.getPoint(uv[0], uv[1], uv[2]).transform!(tgr_out)
    }
    createGeometry(res_g, fs, vs_h, es)
  end
  

  #gg is assumed to be at the right place
  #pfix and nfix are in world coordinates
  #normal_mode = 0 (use projection normal throughout), = 1 (use local normal)
  def self.projectAlong(res_g, gg, tgr, tgr_in, tgr_out, pfix, nfix, normal_mode)
    
    proj_tr = tgr_in * gg.transformation
    proj_ve = nfix.transform(tgr_in)
    pfix2 = pfix.transform(tgr_in)
    
    es = gg.entities.grep(Sketchup::Edge)
    vs = es.map{|e| e.vertices}.flatten!.uniq!
    fs = gg.entities.grep(Sketchup::Face)
    vs_h = {}
    
    d0 = tgr.getUVD([pfix2, proj_ve])[2]
    
    vs.each { |v|    
      p0 = v.position.transform(proj_tr)      
      uv = tgr.getUVD([p0, proj_ve])
      next if uv == nil
      pt = tgr.getPointOnGrid(uv[0], uv[1])
      next if pt == nil
      d = (p0 - pfix2).dot(proj_ve) + d0
      n = normal_mode == 0 ? proj_ve : tgr.getNormal(uv[0], uv[1]).normalize
      pt = offset(pt, n, d)
      pt.transform!(tgr_out)
      vs_h[v] = pt    
    }
    
    createGeometry(res_g, fs, vs_h, es)
  end
    
  def self.createGeometry(res_g, fs, vs_h, es_source)
    #create a polymesh and add the points
    pm = Geom::PolygonMesh.new(vs_h.size)
    vs_h.each_value { |p| pm.add_point(p) }
    
    res_fs = [] #array with faces that failed to triangulate properly
    face_h = {} #a hash with original face as key and arrays of transformd outer loop positions as data
    #add faces, check if triangulation is needed, if so triangulate
    fs.each { |f|  
      #ignore faces outside the projplane 
      outside = false
      f.outer_loop.vertices.each { |v|
        if vs_h[v] == nil
          outside  = true
          break;
        end
      }
      next if outside
   
      ps = f.outer_loop.vertices.map { |v| vs_h[v]  }
      
      #skip if face is already a triangle...
      pl = Geom::fit_plane_to_points ps
      unless ps.all? { |p| p.on_plane? pl }
        #triangulate
        mesh = f.mesh
        ps.clear
        mesh.polygons.each { |pol|
          ps2 = []
          f_vs = f.vertices
          pol.each { |i| 
            vt = getVertex(f_vs, mesh.point_at(i))
            ps2 << vs_h[vt]
          }
          ps << ps2
        }
        #check whether the meshing succeded, if so, add all polygons to pm.
        if validTriangulation?(ps)
          ps.each { |pa| pm.add_polygon(pa) }
          face_h[f] = ps #ps is here an array of arrays
        else
          face_h[f] = reconstructTriangulation(ps, res_g, res_fs)
        end
      else
        pm.add_polygon(ps)
        face_h[f] = [ps]
      end  
    }
    
    res_g.entities.clear!
    res_g.entities.fill_from_mesh(pm, true, 0, nil) 
    
    #Add degenrate meshes here...
    res_fs.each { |f| res_g.entities.add_face(f) }
 
    #take care of inner loops here. Add face explicitly
    fs.each { |f|
      
      f.loops.each{ |loop|
        break unless f.valid?
        next if loop == f.outer_loop
        outside = false
        loop.vertices.each { |v|
          if vs_h[v] == nil
            outside  = true
            break;
          end
        }
        next if outside
        
        #we should project the vertices of the inner loop onto the surroundong face.
        ps = loop.vertices.map { |v| vs_h[v]  }
        #ps = ps.map{ |p| Geom::intersect_line_plane([p, f.normal], [f.vertices[0].position, f.normal])}
        
        pl = Geom::fit_plane_to_points ps
        next unless ps.all? { |p| p.on_plane? pl }
        f = res_g.entities.add_face(ps)
        f.erase! unless f == nil
      }
    }
    return if face_h.length == 0
    format(face_h, res_g, vs_h, es_source)
  end
  
  
  def self.format(face_h, res_g, vs_h, es_source)
    #get the vertices..
    count_pos = 0
    count_neg = 0
    vh = getVertexHash(res_g)
    face_h.each_pair { |f, pa|
      pa.each { |ps|
        fc = vh.get_face(ps)
        if fc != nil &&  fc.length == 1
          fc[0].material = f.material
          fc[0].back_material = f.back_material
        end
      }
    }
    
    #Format edges so that formatting from the source geometry is preeserved
    #(except intersection edges) AND introduced diagonals are formatted according to
    #the conventional quad definition.
    et_h = {}
    es_source.each { |e|
      v0 = vs_h[e.start]
      v1 = vs_h[e.end]
      next if v0 == nil || v1 == nil
      et = vh.get_edge(v0, v1)
      next if et == nil
      et_h[et] = nil
      et.smooth= e.smooth?
      et.soft= e.soft?
      et.hidden= e.hidden?
      et.casts_shadows= e.casts_shadows?
    }
    
    es_target = res_g.entities.grep(Sketchup::Edge)
    es_target.each { |e|
      next if et_h.has_key?(e)
      e.smooth= true
      e.soft= true
      e.casts_shadows= false
    }
  end
  
  def self.getVertexHash(res_g)
    vs0 = res_g.entities.grep(Sketchup::Edge).map{|e| e.vertices}.flatten!.uniq!
    eps = 0.00001
    bb = Geom::BoundingBox.new
    vs0.each { |v| bb.add v }
    bb_min = bb.min.clone
    bb_max = bb.max.clone
    bb_min.offset! Geom::Vector3d.new(-eps, -eps, -eps) 
    bb_max.offset! Geom::Vector3d.new(eps, eps, eps)
    
    vh = VertexHash.new
    vh.set_divisions(25.0, 25.0, 25.0)
    vh.set_bounds(bb_min, bb_max)
    vh.init
    vs0.each { |v| vh.add_vertex(v) }
    return vh
  end
  
  #if ps contains degenerate triangulations then reconstruct the face
  #in a new group, explode it, and then store the new faces in an array.
  def self.reconstructTriangulation(ps, res_g, res_fs)
    ps2 = []
    rm = Geom::PolygonMesh.new(100)
    ps.each { |pa| pa.each { |p| rm.add_point(p) }}
    ps.each { |pa| rm.add_polygon(pa) }
    g = res_g.entities.add_group
    g.entities.fill_from_mesh(rm, true, 0, nil)
    g.explode
    fs = res_g.entities.grep(Sketchup::Face)
    fs.each { |f|
      arr = f.outer_loop.vertices.map { |v| v.position }
      res_fs << arr
      ps2 << arr
    }
    res_g.entities.clear!
    return ps2
  end
  
  #check for degenerate triangles in the array.
  def self.validTriangulation?(ps)
    ps.each { |pa|
      v0 = (pa[1] - pa[0]).normalize!
      v1 = (pa[2] - pa[1]).normalize!
      return false if v0 == v1 || v0 == v1.reverse!
    }
    return true
  end
  
  def self.offset(p, v, d)
    pc = p.clone
    pc.x = pc.x + v.x * d
    pc.y = pc.y + v.y * d
    pc.z = pc.z + v.z * d
    return pc
  end
  
  def self.getVertex(f_vs, p)
    f_vs.each { |v| return v if v.position == p }
    return nil
  end
        
end
end
