api-issue-tracker icon indicating copy to clipboard operation
api-issue-tracker copied to clipboard

Depending on vertex order, #add_face may produce inconsistent geometry

Open esterkimx opened this issue 2 years ago • 2 comments

  1. SketchUp/LayOut Version: SketchUp Pro 23.1.318
  2. OS Platform: MacOS

I'm trying to create a square ring and add a triangle to its side (I refer to this mesh as 'VRing' in the snippet). When using Sketchup::Entities#add_face, I've noticed that rotating the order of the triangle's vertices (while maintaining their CCW orientation) yields different outcomes. In the 'bad' case, it ruins the geometry of the ring.

Below, I rotate the vertices of the triangle. The first and the second attempts yield the expected result.

Screenshot 2023-09-27 at 18 26 24

Topology is really broken: Screenshot 2023-09-27 at 19 07 04

class MakeVRing
  # CCW, starting with lowest leftmost point
  SQUARE_PTS = [
    [-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]
  ].map { |a| Geom::Point3d.new(*a) }
  TRIANGLE_PTS = [
    [1, -1, 0], [1, 1, 0], [0, 0, 0]
  ].map { |a| Geom::Point3d.new(*a) }

  def initialize(opts)
    @entities = opts[:entities]
    @radius_ext = opts[:radius]
    @thickness = opts[:thickness]
    @radius_int = @radius_ext - @thickness
  end
  
  def execute(orig_v, idxs)
    orig_v = Geom::Vector3d.new(orig_v)
    add_ring(orig_v)
    add_triangle(orig_v, idxs)
  end
  
  private

  def add_ring(orig_v)
    pts_e = SQUARE_PTS.map do |pt| 
      Geom::Transformation.scaling(@radius_ext) * pt + orig_v
    end
    pts_i = SQUARE_PTS.map do |pt| 
      Geom::Transformation.scaling(@radius_int) * pt + orig_v
    end
    f_e = @entities.add_face(pts_e)
    f_i = @entities.add_face(pts_i)
    f_i.erase!
    f_e.reverse!
  end
  
  def add_triangle(orig_v, idxs)
    pts =
      idxs
        .map { |idx| TRIANGLE_PTS[idx] }
        .map do |pt| 
          Geom::Transformation.scaling(@thickness) * pt + orig_v + [@radius_int, 0, 0]
        end
    @entities.add_face(pts)
  end
end

command = MakeVRing.new({
  entities: Sketchup.active_model.entities,
  radius: 50,
  thickness: 10
})
idxs = [0, 1, 2]
3.times do |i|
  command.execute([100 + i * 150, 100, 0], idxs)
  idxs.rotate!
end

esterkimx avatar Sep 27 '23 16:09 esterkimx

A better example might be constructing a solid torus instead of a ring. In that case, using the 'wrong' vertex order when adding a triangle results in a non-manifold surface.

Screenshot 2023-09-28 at 18 53 00

class MakeVRing
  # CCW, starting with lowest leftmost point
  SQUARE_PTS = [
    [-1, -1, 0], [1, -1, 0], [1, 1, 0], [-1, 1, 0]
  ].map { |a| Geom::Point3d.new(*a) }
  TRIANGLE_PTS = [
    [1, -1, 0], [1, 1, 0], [0, 0, 0]
  ].map { |a| Geom::Point3d.new(*a) }

  def initialize(opts)
    @entities = opts[:entities]
    @radius_ext = opts[:radius]
    @thickness = opts[:thickness]
    @radius_int = @radius_ext - @thickness
  end
  
  def execute(orig_v, idxs)
    orig_v = Geom::Vector3d.new(orig_v)
    add_ring(orig_v)
    add_triangle(orig_v, idxs)
  end
  
  private

  def add_ring(orig_v)
    pts_e = SQUARE_PTS.map do |pt| 
      Geom::Transformation.scaling(@radius_ext) * pt + orig_v
    end
    pts_i = SQUARE_PTS.map do |pt| 
      Geom::Transformation.scaling(@radius_int) * pt + orig_v
    end
    f_e = @entities.add_face(pts_e)
    f_i = @entities.add_face(pts_i)
    f_i.erase!
    f_e.reverse!
    f_e.pushpull(-@thickness)
  end
  
  def add_triangle(orig_v, idxs)
    pts =
      idxs
        .map { |idx| TRIANGLE_PTS[idx] }
        .map do |pt| 
          Geom::Transformation.scaling(@thickness) * pt + orig_v + [@radius_int, 0, 0]
        end
    @entities.add_face(pts)
  end
end

command = MakeVRing.new({
  entities: Sketchup.active_model.entities,
  radius: 50,
  thickness: 10
})
idxs = [0, 1, 2]
3.times do |i|
  command.execute([100 + i * 150, 100, 0], idxs)
  idxs.rotate!
end

esterkimx avatar Sep 28 '23 15:09 esterkimx

Logged as: SKEXT-3886

sketchup[bot] avatar Nov 13 '23 06:11 sketchup[bot]