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


#ISSUES:
#
#1) Cut plane depth isn't robust, find better method
#2) For some reason, project works much better with QuadGrid -> TRI, than the flat coordinate system...

module CAUL_FAQS
module Projection

  ##############################
  ######## CUT PLANES ##########
  ##############################
  
  @ERROR_STRING = ''
  
  #dir is assumed to be normalized
  def self.createCutPlanes(cg, tgr, tgr_out, dir, d, gg_bb)
    
    puts dir.length.to_s
    ups = tgr.v_map.inject([]) { |a, v|
      a << tgr.u_map.inject([]) { |b, u| 
        b << tgr.getPointOnGrid(u, v).transform(tgr_out) 
    }}
    vps = tgr.u_map.inject([]) { |a, u|
      a << tgr.v_map.inject([]) { |b, v| 
        b << tgr.getPointOnGrid(u, v).transform(tgr_out) 
    }}    
    
    
    #closely adjust d..
    dir1 = dir.clone; dir1.length = dir.length * d
    dir2 = dir.clone; dir2.length = dir.length * d
    dir2.reverse!
    
    #dir is the ray....
    #we shoot four rays and collcect the bb.
    bb_min = gg_bb.min.offset(Geom::Vector3d.new(-0.005, -0.005, -0.005))   
    bb_max = gg_bb.max.offset(Geom::Vector3d.new(0.005, 0.005, 0.005))
    res0 = [Geom::Point3d.new, Geom::Point3d.new] 
    res1 = [Geom::Point3d.new, Geom::Point3d.new]
        
    #TODO: Adjust the direction lengths
    mesh = Geom::PolygonMesh.new
    (0..ups.length - 1).each { |i|
      (0..ups[i].length - 2).each { |j|  
        resA = cutRay([ups[i][j], dir], bb_min, bb_max, res0)
        resB = cutRay([ups[i][j + 1], dir], bb_min, bb_max, res1)
        next unless resA || resB
        addFace(ups[i][j] , ups[i][j+1], dir1, dir2, mesh)
    }}
    (0..vps.length - 1).each { |i|
      (0..vps[i].length - 2).each { |j|
        resA = cutRay([vps[i][j], dir], bb_min, bb_max, res0)
        resB = cutRay([vps[i][j + 1], dir], bb_min, bb_max, res1)
        next unless resA || resB
        addFace(vps[i][j] , vps[i][j+1], dir1, dir2, mesh)
    }}
    tgr.getFolds().each { |a| 
      resA = cutRay([a[0], dir], bb_min, bb_max, res0)
      resB = cutRay([a[1], dir], bb_min, bb_max, res1)
      next unless resA || resB
      addFace(a[0] , a[1], dir1, dir2, mesh) 
    }
    
    cg.entities.fill_from_mesh mesh
  end
  
  def self.addFace(p0, p1, dir1, dir2, mesh)
    pa0 = p0.offset(dir1)
    pa1 = p0.offset(dir2)
    pa2 = p1.offset(dir2)
    pa3 = p1.offset(dir1)
    mesh.add_point pa0; mesh.add_point pa1;
    mesh.add_point pa2; mesh.add_point pa3;
    mesh.add_polygon pa0, pa1, pa2, pa3
  end
  
  def self.cutRay(ray, bb_min, bb_max, res)
    tmin = -Float::MAX
    tmax =  Float::MAX
        
    if ray[1].x != 0
      x_inv = 1.0 / ray[1].x
      tx1 = (bb_min.x - ray[0].x) * x_inv 
      tx2 = (bb_max.x - ray[0].x) * x_inv
      tmin = tx1 < tx2 ? (tx1 > tmin ? tx1 : tmin) : (tx2 > tmin ? tx2 : tmin)   
      tmax = tx1 > tx2 ? (tx1 < tmax ? tx1 : tmax) : (tx2 < tmax ? tx2 : tmax)
    end
        
    if ray[1].y != 0
      y_inv = 1.0 / ray[1].y
      ty1 = (bb_min.y - ray[0].y) * y_inv 
      ty2 = (bb_max.y - ray[0].y) * y_inv
      tmin = ty1 < ty2 ? (ty1 > tmin ? ty1 : tmin) : (ty2 > tmin ? ty2 : tmin)   
      tmax = ty1 > ty2 ? (ty1 < tmax ? ty1 : tmax) : (ty2 < tmax ? ty2 : tmax)
    end
        
    if ray[1].z != 0
      z_inv = 1.0 / ray[1].z
      tz1 = (bb_min.z - ray[0].z) * z_inv 
      tz2 = (bb_max.z - ray[0].z) * z_inv
      tmin = tz1 < tz2 ? (tz1 > tmin ? tz1 : tmin) : (tz2 > tmin ? tz2 : tmin)   
      tmax = tz1 > tz2 ? (tz1 < tmax ? tz1 : tmax) : (tz2 < tmax ? tz2 : tmax)
    end
        
    return false if tmin > tmax
    res[0].set!(ray[0].x + ray[1].x * tmin, ray[0].y + ray[1].y * tmin, ray[0].z + ray[1].z * tmin)
    res[1].set!(ray[0].x + ray[1].x * tmax, ray[0].y + ray[1].y * tmax, ray[0].z + ray[1].z * tmax) 
    return true
  end

  ##############################
  ######### AUX ################
  ##############################
      
  def self.createOffsetGrid(fd, d, off_g)

    mesh, p0, p1, folds = fd.tgr.getOffsetMesh(d, fd.tgr_out)
    smooth_flags = Geom::PolygonMesh::NO_SMOOTH_OR_HIDE
    off_g.entities.fill_from_mesh(mesh, true, 0, nil)
    
    #format the folds
    if folds.length > 0
      vs = off_g.entities.grep(Sketchup::Edge).map { |e| e.vertices }.flatten!.uniq!
      bb = EdgeHash.getBB(vs)
      eh = EdgeHash.new(bb)
      vs.each { |v| eh.addVertex(v) }
      folds.each { |a|
        e = eh.getEdge(a[0], a[1])
        e.soft=true; e.smooth=true; e.casts_shadows= false
      }
    end
    
    grid =  GridParser.parse(off_g.entities, p0, p1)
    return grid
  end
  
  def self.place(fd, ind, uv_fix, off_gr, off_gr_out)
    #get point on off_grid
    pfix = off_gr.getPoint(uv_fix[0], uv_fix[1], 0).transform(off_gr_out)
    
    #get normal from projgrid and target grid
    ntgr = off_gr.getNormal(uv_fix[0], uv_fix[1]).transform(off_gr_out).normalize
    npgr = fd.pgr.getNormal(uv_fix[0], uv_fix[1]).transform(fd.pgr_out).normalize
    
    #transform the geometry to the fixpoint on the grid
    tr0 = Geom::Transformation.translation(Geom::Point3d.new(0, 0, 0) - fd.fixs[ind])
    tr1 = Matrix.getAlignmentMatrix(npgr, ntgr)
    tr2 = Geom::Transformation.translation(pfix - Geom::Point3d.new(0, 0, 0))
    fd.ggs[ind].transform!(tr2 * tr1 * tr0)
    return pfix, ntgr
  end
  
  def self.cut(ent, fd, ind, uv_fix, off_gr, off_gr_out, cd)
    cg = ent.add_group
    nfix = off_gr.getNormal(uv_fix[0], uv_fix[1]).transform(off_gr_out).normalize
    createCutPlanes(cg, off_gr, off_gr_out, nfix, cd, fd.ggs[ind].bounds)
    return if cg.entities.length == 0
    pfix_w = cg.bounds.center.clone
    scale = fd.scale
    cg_bb = Cut.scale(cg, scale, pfix_w)
    gg_bb = Cut.scale(fd.ggs[ind], scale, pfix_w)
    Cut.cutBB(ent, cg, fd.ggs[ind],  cg_bb, gg_bb)
    Cut.scale(fd.ggs[ind], 1.0 / scale, pfix_w)
    cg.entities.clear!
  end
  
  def self.projectAlong(ent, fd, ind, off_gr, off_gr_in, off_gr_out, pfix, nfix)
    res_g = ent.add_group
    Flow.projectAlong(res_g, fd.ggs[ind], off_gr, off_gr_in, off_gr_out, pfix, nfix, 0)
    to_erase = fd.ggs[ind]
    fd.ggs[ind] = res_g
    to_erase.entities.clear!
  end
  
  
  def self.project(ent, fd, ind)
 
    #project the fix point onto the proj grid
    ve_proj = fd.proj_ve.clone
    uv_fix = fd.pgr.getUVD([fd.fixs[ind].transform(fd.pgr_in), ve_proj.transform(fd.pgr_in)])
     
    if uv_fix == nil
      puts 'ERROR: Fix Point outside projection grid.'
      return
    end
    
    #create offset target grid (i
    off_g = nil
    if(uv_fix[2] > 0.0001)
      off_g = ent.add_group
      off_gr_in = off_g.transformation.inverse
      off_gr_out = off_g.transformation
      off_gr = createOffsetGrid(fd, uv_fix[2], off_g)
    elsif
      off_gr_in = fd.tgr_in
      off_gr_out = fd.tgr_out
      off_gr = fd.tgr
    end
    
    #FIX THIS!: compute cut depth (This doesn't rellay work..)
    cd = (fd.ggs[ind].bounds.max.y - fd.ggs[ind].bounds.min.y) * 5
    
    #place the geometry on the offset grid
    pfix, nfix = place(fd, ind, uv_fix, off_gr, off_gr_out)
    #cut the geometry 
    cut(ent, fd, ind, uv_fix, off_gr, off_gr_out, cd)
    #project the geometry onto the offset grid
    projectAlong(ent, fd, ind, off_gr, off_gr_in, off_gr_out, pfix, nfix)
    
    #Erase the offset grid
    off_g.entities.clear! unless off_g == nil
  end
  
  def self.projectMain(ent, sel)
    fd = Input.parseInput(sel, ent)
    return if fd == nil
    (0..fd.ggs.length - 1).each { |i| 
        t0 = Time.now
        project(ent, fd, i)
        t1 = Time.now
        puts (i+1).to_s+' / '+fd.ggs.length.to_s+'       '+(t1 - t0).to_s+'  s'
    }
  
  end
  
  def self.main
    mod = Sketchup.active_model # Open model
    ent = mod.entities # All entities in model
    sel = mod.selection # Current selection
    
    t0 = Time.now
    mod.start_operation("ProjectOnQuadGrid", true) 
    projectMain(ent, sel)
    mod.commit_operation
    t1 = Time.now
    
    puts 'TOTAL: '+(t1 - t0).to_s+' s'
  end

end
end
