sketchucation logo sketchucation
    • Login
    ℹ️ Licensed Extensions | FredoBatch, ElevationProfile, FredoSketch, LayOps, MatSim and Pic2Shape will require license from Sept 1st More Info

    Vertex Normals... ?

    Scheduled Pinned Locked Moved Developers' Forum
    26 Posts 6 Posters 2.4k Views 6 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • O Offline
      oajfh
      last edited by

      I'm using SU 2015.

      I was just confused mostly by the documentation, heh -I'm still a rather inexperienced Ruby and SU coder, and when I wanted to look it up and check the methods the SU doc site was down, so I thought I might as well ask here and learn a little more.

      If vertex normals can be safely recovered, then perhaps I should use those.

      Hmm. How would that fare performance-wise ? If I want to recover vertex normals, I probably end up checking vertexes multiple times and making sure I don't get a normal from the same vertex more than once, but on the other hand calculating face normals is faster than calculating weighted vertex normals.

      1 Reply Last reply Reply Quote 0
      • Dan RathbunD Offline
        Dan Rathbun
        last edited by

        Face.vertices and Edge.vertices both return an array.

        You'll likely be concat'ing these returned arrays with a "master" array collection.

        After stuffing the master array, you can use the Array#uniq! method to remove duplicates, before processing.

        I'm not here much anymore.

        1 Reply Last reply Reply Quote 0
        • tt_suT Offline
          tt_su
          last edited by

          @oajfh said:

          Hmm. How would that fare performance-wise ? If I want to recover vertex normals, I probably end up checking vertexes multiple times and making sure I don't get a normal from the same vertex more than once, but on the other hand calculating face normals is faster than calculating weighted vertex normals.

          Yea - that's a good point. I don't think you can rely on 100% that the order of the points in PolygonMesh to match the face it came from.

          If performance is critical I think you will get best results from calculating in C/C++ via A Ruby extension which then only iterates the vertices (though you need to collect that from edges or faces - but collecting that into an std::set or something shouldn't affect your performance too much.). You then do the calculations with native code.

          1 Reply Last reply Reply Quote 0
          • O Offline
            oajfh
            last edited by

            Thanks for the tips Dan.

            My code recovers points rather than vertices from a component (it's based off what Anton_S posted in my topic about PolygonMesh triangulation).

            ThomThom, doesn't the add_polygon method simply add any new points at the end of the point array ? That's the behaviour it's exhibited for me so far, so wouldn't applying what Dan said be alright ?

            On another note, the vertex normal seems to be local to the transformation the face's parent possesses. It complexifies the recursion somewhat to recover the transformation at that point in the iteration.

            I'll just post my code, it'll be clearer for you. Thanks again to the both of you and Anton_S, by the way.

            
            # Original credit ; Anton_S
            # Get group/component entities.
            # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity
            # @return [Sketchup;;Entities]
            def get_entities(entity)
            	if entity.is_a?(Sketchup;;ComponentInstance)
            		return entity.definition.entities
            	else 
            		return entity.entities
            	end
            end
            
            # Original credit ; Anton_S
            # Get triangular mesh of the group.
            # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity
            # @param [Boolean] recursive Whether to include all the child groups and
            #   components.
            # @param [Boolean] transform Whether to give points in global coordinates.
            # @param [Integer] Keep face counter so as not to mess up normal exporting
            # @yield A procedure to determine whether particular child group/component
            #   should be considered a part of the collection.
            # @yieldparam [Sketchup;;Group, Sketchup;;ComponentInstance] entity
            # @yieldreturn [Boolean] Pass true to consider an entity as part of the
            #   collection. Pass false to not consider an entity as part of the
            #   collection.
            # @return [Geom;;PolygonMesh]
            def get_triangular_mesh(entity, recursive = true, transform = false,normal_array, &entity_validation)
            
              mesh = Geom;;PolygonMesh.new
              if( entity.is_a?(;;Sketchup;;Face)||entity.is_a?(;;Sketchup;;ComponentInstance)||entity.is_a?(;;Sketchup;;Group))
            	get_entities(entity).each { |e|
            		if e.is_a?(;;Sketchup;;Face)
            
            			e.mesh.polygons.each_index{ |i|
            			pts = e.mesh.polygon_points_at(i+1)
            			
            			mesh_b = e.mesh 4
            
            			index = mesh.add_polygon(pts) # Note ; index not necessary here
            		
            			normal_array << e.normal # add face normal
            			
            			#puts "e"
            			for k in 1..mesh_b.count_points
            				#puts mesh_b.point_at(k) #Not global coordinates
            				#puts mesh_b.normal_at(k) #Not global coordinates... ?
            			end
            			puts "j"
                  }
            		elsif recursive && (e.is_a?(;;Sketchup;;Group) || e.is_a?(;;Sketchup;;ComponentInstance)) && entity_validation.call(e)
            			new_array = []
            			mesh2 = get_triangular_mesh(e, true, true, new_array, &entity_validation)
            			mesh2.polygons.each_index { |i|
            			index = mesh.add_polygon(mesh2.polygon_points_at(i+1))
            			normal_array[index] = new_array[i] # Doesn't seem to be overriding previous values
            			#counter=counter+1
            			#puts counter
            		}
                end
              }
              mesh.transform!(entity.transformation) if transform
              end
              return mesh
            end
            
            
            group = Sketchup.active_model.selection[0] # Lazy component recovery
            
            #TODO begin end
            
            normal_array = [] # Contains the face normals.
            mesh = get_triangular_mesh(group, true, true,normal_array){ |e| true }
            puts mesh.points
            pt = Geom;;Point3d.new(100,0,0)
            mesh.transform!(pt) # Move triangles 100 units on xaxis from original component.
            Sketchup.active_model.entities.add_faces_from_mesh(mesh, 0)
            
            
            #puts mesh.points # translated mesh
            
            

            It isn't super fast though.

            Actually, perhaps the simplest would be to traverse the triangulation a second time ? The generated mesh is all in global coordinates, so the normals would be as well.

            I could probably do that as a temporary solution, until performance becomes too critical and I have to do stuff out of Ruby. I'm still prototyping, so I just need a reliable way of recovering the normals for the moment.

            1 Reply Last reply Reply Quote 0
            • tt_suT Offline
              tt_su
              last edited by

              @oajfh said:

              ThomThom, doesn't the add_polygon method simply add any new points at the end of the point array ? That's the behaviour it's exhibited for me so far, so wouldn't applying what Dan said be alright ?

              PolygonMesh.add_point will add a new point only if there isn't an existing point for the same location (within tolerance). This means it looks up the existing points (which unfortunately is a linear look up.)
              PolygonMesh.add_polygon will internally call this same function.

              The linear nature of add_point I assume is because internally this class is for use with individual faces - so you're not going to have that many points nor duplicates.

              To get max performance when building a PolygonMesh you add all the points first - collecting the indices you get in return. Then add polygons using indices instead of points - this reduce the number of linear lookups.

              I usually use a Hash to build a source => polygon_point_index lookup. This can be vertex => index for instance.

              1 Reply Last reply Reply Quote 0
              • tt_suT Offline
                tt_su
                last edited by

                Btw - if you build your own PolygonMesh from scratch the vertex normals won't be correct I believe...

                1 Reply Last reply Reply Quote 0
                • O Offline
                  oajfh
                  last edited by

                  I'm building it from an existing component, so I'd simply be copying existing normals. Why would the vertex normals be incorrect ?

                  Also, are you sure about the point to point comparison ? If you create different groups within a component, won't it duplicate the points ? I was getting different results after exploding groups and creating the mesh after making a component out of the exploded ex-groups. So the comparison might not only be coordinate-based.

                  Thanks for the tip on performance, too. Will come in handy.

                  1 Reply Last reply Reply Quote 0
                  • tt_suT Offline
                    tt_su
                    last edited by

                    @oajfh said:

                    I'm building it from an existing component, so I'd simply be copying existing normals. Why would the vertex normals be incorrect ?

                    If you query mesh.normal_at(i) on a mesh you created yourself (Geom::PolygonMesh.new) vs face.mesh the normals won't be computed for you.

                    @oajfh said:

                    Also, are you sure about the point to point comparison ? If you create different groups within a component, won't it duplicate the points ?

                    You can observer this in action by inspecting the indices and point count:

                    <span class="syntaxdefault"><br />mesh&nbsp;</span><span class="syntaxkeyword">=&nbsp;</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">PolygonMesh</span><span class="syntaxkeyword">.new<br /></span><span class="syntaxcomment">#<Geom;;PolygonMesh;0x0000000936e850><br /><br /></span><span class="syntaxdefault">index1&nbsp;</span><span class="syntaxkeyword">=&nbsp;</span><span class="syntaxdefault">mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_point</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,&nbsp;</span><span class="syntaxdefault">2</span><span class="syntaxkeyword">,&nbsp;</span><span class="syntaxdefault">3</span><span class="syntaxkeyword">))<br /></span><span class="syntaxdefault">1<br /><br />index2&nbsp;</span><span class="syntaxkeyword">=&nbsp;</span><span class="syntaxdefault">mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_point</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,&nbsp;</span><span class="syntaxdefault">2</span><span class="syntaxkeyword">,&nbsp;</span><span class="syntaxdefault">3</span><span class="syntaxkeyword">))<br /></span><span class="syntaxdefault">1<br /><br />index3&nbsp;</span><span class="syntaxkeyword">=&nbsp;</span><span class="syntaxdefault">mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_point</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,&nbsp;</span><span class="syntaxdefault">2</span><span class="syntaxkeyword">,&nbsp;</span><span class="syntaxdefault">3.5</span><span class="syntaxkeyword">))<br /></span><span class="syntaxdefault">2<br /><br />mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">count_points<br />2<br /></span>
                    

                    Or by adding polygons using 3d points:

                    <span class="syntaxdefault"><br />mesh&nbsp;</span><span class="syntaxkeyword">=&nbsp;</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">PolygonMesh</span><span class="syntaxkeyword">.new<br /></span><span class="syntaxcomment">#<Geom;;PolygonMesh;0x0000000a70ec48><br /><br /></span><span class="syntaxdefault">mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_polygon</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">),&nbsp;</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">5</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">),&nbsp;</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">5</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">5</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">))<br /></span><span class="syntaxdefault">1<br /><br />mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">add_polygon</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">),&nbsp;</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">5</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">),&nbsp;</span><span class="syntaxdefault">Geom</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Point3d</span><span class="syntaxkeyword">.new(</span><span class="syntaxdefault">5</span><span class="syntaxkeyword">,-</span><span class="syntaxdefault">5</span><span class="syntaxkeyword">,</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">))<br /></span><span class="syntaxdefault">2<br /><br />mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">count_polygons<br />2<br /><br />mesh</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">count_points<br />4<br /></span>
                    
                    1 Reply Last reply Reply Quote 0
                    • tt_suT Offline
                      tt_su
                      last edited by

                      @oajfh said:

                      I was getting different results after exploding groups and creating the mesh after making a component out of the exploded ex-groups. So the comparison might not only be coordinate-based.

                      When you explode the coordinates of the vertices are transformed to it's parents - so you will see different values then before they they where local to the component definition.
                      (Also, entities in model.active_entities) are transformed into global coordinates.)

                      1 Reply Last reply Reply Quote 0
                      • O Offline
                        oajfh
                        last edited by

                        Well, after more experimenting, taking into account what's been said :

                        
                        # Original credit ; Anton_S
                        # Get group/component entities.
                        # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity
                        # @return [Sketchup;;Entities]
                        def get_entities(entity)
                        	if entity.is_a?(Sketchup;;ComponentInstance)
                        		return entity.definition.entities
                        	else 
                        		return entity.entities
                        	end
                        end
                        
                        # Original credit ; Anton_S
                        # Get triangular mesh of the group.
                        # @param [Sketchup;;Group, Sketchup;;ComponentInstance] entity
                        # @param [Boolean] recursive Whether to include all the child groups and
                        #   components.
                        # @param [Boolean] transform Whether to give points in global coordinates.
                        # @param [Integer] Keep face counter so as not to mess up normal exporting
                        # @yield A procedure to determine whether particular child group/component
                        #   should be considered a part of the collection.
                        # @yieldparam [Sketchup;;Group, Sketchup;;ComponentInstance] entity
                        # @yieldreturn [Boolean] Pass true to consider an entity as part of the
                        #   collection. Pass false to not consider an entity as part of the
                        #   collection.
                        # @return [Geom;;PolygonMesh]
                        def get_triangular_mesh(entity, recursive = true, transform = false,normal_array, &entity_validation)
                        
                          mesh = Geom;;PolygonMesh.new
                          if( entity.is_a?(;;Sketchup;;Face)||entity.is_a?(;;Sketchup;;ComponentInstance)||entity.is_a?(;;Sketchup;;Group))
                        	get_entities(entity).each { |e|
                        		if e.is_a?(;;Sketchup;;Face)
                        
                        			e.mesh.polygons.each_index{ |i|
                        			pts = e.mesh.polygon_points_at(i+1)
                        			
                        			mesh_b = e.mesh 4 #Temp face with normals
                        
                        			index = mesh.add_polygon(pts)
                        			
                        			#puts "e"
                        			for k in 1..mesh_b.count_points
                        				#puts "k"
                        				#puts mesh_b.point_at(k) #Not global coordinates
                        				#puts mesh_b.normal_at(k)
                        				index = mesh.point_index(mesh_b.point_at(k)) # Get index of current point being examined
                        				#puts index
                        				#puts mesh.point_at(index)
                        				#puts "k"
                        				normal_array[index] = mesh_b.normal_at(k) # Set that point's normal to the current point's normal's value
                        			end
                        			#puts "j"
                              }
                        		elsif recursive && (e.is_a?(;;Sketchup;;Group) || e.is_a?(;;Sketchup;;ComponentInstance)) && entity_validation.call(e)
                        			new_array = []
                        			mesh2 = get_triangular_mesh(e, true, true, new_array, &entity_validation)
                        			mesh2.polygons.each_index { |i|
                        			index = mesh.add_polygon(mesh2.polygon_points_at(i+1))
                        
                        			mesh2.points.each_with_index{|point, index|
                        				ind = mesh.point_index(point)
                        				normal_array[ind]= new_array[index]
                        			}
                        		}
                            end
                          }
                          mesh.transform!(entity.transformation) if transform
                        	for l in 1..normal_array.length-1 #index 0 not used so length is 1 more than expected
                        		#puts l
                        		normal_array[l].transform!(entity.transformation) if transform
                        	end
                          end
                          return mesh
                        end
                        
                        
                        normal_array = [] # Contains the VERTEX normals.
                        mesh = get_triangular_mesh(group, true, true,normal_array){ |e| true }
                        
                        
                        

                        (This code is bad and unoptimised, I'm aware of that. I'm a terrible programmer)

                        I'm not sure what to think of this.

                        If I look at what vertex normals are returned during recursion, I occasionally get different normals for what appears to be the same vertex, and I don't know why. For a cube, I only get global axis-aligned vectors, which isn't really what I was expecting.

                        1 Reply Last reply Reply Quote 0
                        • J Offline
                          Jim
                          last edited by

                          I just wanted to correct my post. A PolygoMesh can calculate normals if the mesh is generated from a face. Read further for more details.

                          So, what were you expecting?

                          A PolygonMesh is just a container for points and triangles. It does not calculate normals.

                          You can retrieve the vertex normals of a mesh when the mesh is made from an existing Face, but those normals will be the normal of the Face and not any calculated value. This is why you get multiple normals from a vertex in your code and why they are all aligned with each Face normal. The normals being aligned to the global axis is because your cube faces are aligned to the global axes, am I right?

                          
                          model = Sketchup.active_model
                          sel = model.selection
                          ents = model.active_entities
                          faces = ents.grep(Sketchup;;Face)
                          
                          faces.each do |face|
                              mesh = face.mesh(4)
                              for idx in (1..mesh.count_points)
                                  norm = mesh.normal_at idx
                                  pt = mesh.point_at idx
                                  pt2 = pt.offset norm
                                  ents.add_cline(pt, pt2)
                              end
                          end
                          
                          

                          2015-06_266.png

                          Hi

                          1 Reply Last reply Reply Quote 0
                          • O Offline
                            oajfh
                            last edited by

                            😳

                            Yes, you are absolutely right. I hadn't quite understood what Thomthom meant. Sorry for dragging this topic on for so long.

                            1 Reply Last reply Reply Quote 0
                            • tt_suT Offline
                              tt_su
                              last edited by

                              Sorry - I should have clarified - the vertex normals depend on whether the edge between the faces are Smooth. Soft doesn't matter - because only Smooth affect shading. The normal is a representation of the surface shading.

                              2015-06-09_12h34_47.png

                              @jim said:

                              [...] but those normals will be the normal of the Face and not any calculated value. This is why you get multiple normals from a vertex in your code and why they are all aligned with each Face normal.

                              They are calculated if and only if the mesh came from face.mesh. When building your own there will be no calculation. This is because PolygonMesh itself doesn't do the calculation, but SketchUp will set the vertex normals as it builds the mesh returned to face.mesh.

                              Under the hood there is a set_normal function similar to set_uv. But this has never been exposed - probably because entities.add_faces_from_mesh and entitites.fill_from_mesh would never use it.

                              1 Reply Last reply Reply Quote 0
                              • J Offline
                                Jim
                                last edited by

                                Well that's interesting. Thanks.

                                Hi

                                1 Reply Last reply Reply Quote 0
                                • Dan RathbunD Offline
                                  Dan Rathbun
                                  last edited by

                                  @oajfh said:

                                  Sorry for dragging this topic on for so long.

                                  Don't worry about it.

                                  This is the kind of nitty-gritty dirty details us consummate geeks really love to ponder.

                                  😛

                                  I'm not here much anymore.

                                  1 Reply Last reply Reply Quote 0
                                  • 1
                                  • 2
                                  • 2 / 2
                                  • First post
                                    Last post
                                  Buy SketchPlus
                                  Buy SUbD
                                  Buy WrapR
                                  Buy eBook
                                  Buy Modelur
                                  Buy Vertex Tools
                                  Buy SketchCuisine
                                  Buy FormFonts

                                  Advertisement