• Login
sketchucation logo sketchucation
  • Login
⚠️ Libfredo 15.4b | Minor release with bugfixes and improvements Update

Issues with a simple material copy raycast script

Scheduled Pinned Locked Moved Developers' Forum
25 Posts 2 Posters 7.0k Views 2 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.
  • C Offline
    chanz
    last edited by 2 Sept 2018, 19:35

    Hey guys. I am a full time Unity/C#/C++ developer and I need a plugin and it didn't exist so I tried to take a stab at it. Forgive me if my Ruby isn't up to par πŸ˜„

    I am working with an old game format and to make rendering quicker, they would subdivide the levels based on plane BSP division (similar to how Quake did it). While that was faster back then, it's a lot of overhead now in triangle count and filesize. Here is an example:

    https://i.imgur.com/Z1vgeuz.jpg

    There are a ton of extra divisions. Plugins like CleanUp help a bit but in this case, it would be better to recreate the terrain. I have come up with a workflow where I fix the divisions by creating an equal size terrain via the sandbox tool and then use dropvertices by TIG to get the height correct. Awesome plugin - and it works great.

    The only issue is that I am left to repaint the mesh and it's very time consuming so I wrote a plugin to do it for me.

    The idea is that for every face (really, I want this to be a quad but it looks like it's operating on triangles), we raycast down until we hit another face. Then apply the material.

    This is the starting point:

    https://i.imgur.com/HSomZEq.png

    Here is the code (I edited DropVertices in case it looks familiar):

    MAKE SURE YOU SELECT THE EDGES OF THE MODEL FIRST

    ### CopyBelowMaterials.rb
    ### by Chanz - Based on DropVertices by TIG
    require 'sketchup.rb'
    ###
    module DAN
    ###
    def self.copymaterials()
    
      model=Sketchup.active_model
      ents=model.active_entities
      
      faces = ents.grep(Sketchup;;Face)
    
      faces.each {|x| 
      
        rayt=model.raytest(x.bounds.center, Z_AXIS.reverse)
    	if(rayt)
    
    		rayt[1].each{|entity|
    		
    		if entity.class == Sketchup;;Face
    				x.material = entity.material
    		end
    		}	
    	end
      }
      begin
        model.start_operation("DAN.copymaterials", true)
      rescue
        model.start_operation("DAN.copymaterials")
      end
      model.commit_operation
    end
    ###
    ### menu
    unless file_loaded?(File.basename(__FILE__)) 
      menu=UI.menu("Plugins").add_submenu("Copy Materials...")
      menu.add_item("To Nearest Below"){DAN.copymaterials()}
    end
    file_loaded(File.basename(__FILE__)) 
    
    end
    

    Here are the results:

    https://i.imgur.com/iazCdpx.png

    I am however having some issues.

    1. A bunch of faces have no material. It seems like all raycasts are hitting below so I am not sure what the deal is. A lot of the triangle faces are white.

    2. The texture scaling seems to be wrong (so it looks like a solid color). If I sample the below material by hand and then apply it directly to my model, it looks fine. Not sure how to resolve this issue.

    Any ideas? I have attached the test scene and plugin. Thanks πŸ˜„


    The plugin


    The file to test. Make sure you select all edges first

    1 Reply Last reply Reply Quote 0
    • T Offline
      TIG Moderator
      last edited by 2 Sept 2018, 20:05

      The faces that keep the default [white] material are triangles.
      If they are right-angled their bounds.center falls on an edge, not a face when projected downwards.
      So you get no face to have a material to 'clone'...
      Perhaps moving a vertex point inwards along the bisector of the vertex's two edges [belonging to that face] by say 1mm and testing that raytest would work ?

      To keep the exact UV mapping of the found texture and applying it to the reused one is convoluted...
      But not impossible.
      Look at some of my Texture Tools...

      TIG

      1 Reply Last reply Reply Quote 0
      • C Offline
        chanz
        last edited by 2 Sept 2018, 20:27

        @tig said:

        The faces that keep the default [white] material are triangles.
        If they are right-angled their bounds.center falls on an edge, not a face when projected downwards.
        So you get no face to have a material to 'clone'...
        Perhaps moving a vertex point inwards along the bisector of the vertex's two edges [belonging to that face] by say 1mm and testing that raytest would work ?

        To keep the exact UV mapping of the found texture and applying it to the reused one is convoluted...
        But not impossible.
        Look at some of my Texture Tools...

        Wow! It's TIG himself. Thanks for the help. That fixed the first issue. Great idea.

        For the second one, could you narrow down my search as to what part of TextureTools deals with this?

        In this particular model, each square face is 100m x 100m. If I change the x and y scaling of the material to 100, it looks fine on the recreated terrain but of course messed up on the one below. Clearly I don't want this as when I do it by hand, it works fine. Is there a way to edit the UVs or scaling of the new mesh via script?

        Again, thanks πŸ˜„! Made my day! πŸ˜„

        1 Reply Last reply Reply Quote 0
        • T Offline
          TIG Moderator
          last edited by 2 Sept 2018, 22:15

          Search my Plugins for Texture and Material...
          There are several regarding repeating UVs on faces etc...

          TIG

          1 Reply Last reply Reply Quote 0
          • C Offline
            chanz
            last edited by 3 Sept 2018, 22:22

            @tig said:

            Search my Plugins for Texture and Material...
            There are several regarding repeating UVs on faces etc...

            The best reference I found was TextureTools and I copied over the TextureScale function to fix the difference (value (scale * x = 1) * tile size)) - for example if in the material inspector, the scale is 0.25 and my terrain tile size is 100, then I scale it by 400. It scales up just as I need but of course the UVs are messed up.

            https://i.imgur.com/9am1FW1.jpg

            It really blows my mind that there isn't an easier way to simply slap a texture on a surface. I even tried UV Toolkit with saving the UVs before dropping the vertices and then restoring them and it doesn't do the trick perfectly either.

            Does anyone have any other suggestions? I essentially need it to match the UV mapping of the face that it raycasts to below. I know SketchUp hasn't always been great for UV mapping but I really didn't think this would be so difficult. And plus, it isn't like this is complex mapping...

            Any and all suggestions welcome! As much as I love messing around in a new language, I vastly prefer developing games. If someone wants to take a stab at it perhaps, I would gladly toss 20 euro or dollars their way! πŸ˜„

            1 Reply Last reply Reply Quote 0
            • T Offline
              TIG Moderator
              last edited by 4 Sept 2018, 10:22

              Look at this:
              https://sketchucation.com/pluginstore?pln=FixReversedFaceMaterials

              Its code can extract UVs from one side of a face and apply them to the other etc - there are various tools - so you could glean some tips from it...
              Note the flattenUVQ() method it uses to get UVs...

              TIG

              1 Reply Last reply Reply Quote 0
              • C Offline
                chanz
                last edited by 4 Sept 2018, 11:50

                @tig said:

                Look at this:
                https://sketchucation.com/pluginstore?pln=FixReversedFaceMaterials

                Its code can extract UVs from one side of a face and apply them to the other etc - there are various tools - so you could glean some tips from it...
                Note the flattenUVQ() method it uses to get UVs...

                Thanks for the direction. Will try this tonight. I know there are a ton of really great scripts already written but it's hard to find which actually applies to my current situation.

                1 Reply Last reply Reply Quote 0
                • C Offline
                  chanz
                  last edited by 4 Sept 2018, 19:13

                  I have found what look like a relevant function in FixReversedFaceMaterials.rb and have adapted it but it doesn't seem to do what I want. I changed the function to accept two faces, the one that needs the correct UVs (face_one) and the other that was hit during the raycast (face_two).

                  def self.process_face(face_one, face_two)
                    
                  	# yes, I can create this outside and pass it in
                      texture_writer=Sketchup.create_texture_writer
                  	
                  	if face_two.material.texture==nil 
                  		# no texture on the raycast hit! skip it.
                  	else
                  	
                  		# it looks like we are sampling the uvs from face two - which was hit in the raycast
                  		samples = []
                  		samples << face_two.vertices[0].position			   ### 0,0 | Origin
                  		samples << samples[0].offset(face_two.normal.axes.x) ### 1,0 | Offset Origin in X
                  		samples << samples[0].offset(face_two.normal.axes.y) ### 0,1 | Offset Origin in Y
                  		samples << samples[1].offset(face_two.normal.axes.y) ### 1,1 | Offset X in Y
                  		xyz = [];uv = []### Arrays containing 3D and UV points.
                  		uvh = face_two.get_UVHelper(true, true, texture_writer)
                  		samples.each { |position|
                  		xyz << position ### XYZ 3D coordinates
                  		
                  		# I switched this to front_UVQ
                  		uvq = uvh.get_front_UVQ(position) ### UV 2D coordinates
                  		uv << self.flattenUVQ(uvq)
                  		}
                  	  
                  		pts = [] ### Position texture.
                  		(0..3).each { |i|
                  		pts << xyz[i]
                  		pts << uv[i]
                  		}
                  	  
                  		# set the position and material of face_one
                  		mat = face_two.material
                  		face_one.position_material(mat, pts, true)
                  
                  
                  	end
                  end
                  

                  It does copy the material but the UVs are still incorrect.

                  I have left some comments explaining my thought process. I am curious if it's different because yours works on a front face/back face and this is different.

                  Thanks for taking a look.

                  1 Reply Last reply Reply Quote 0
                  • T Offline
                    TIG Moderator
                    last edited by 4 Sept 2018, 19:41

                    Read up on this:
                    http://ruby.sketchup.com/Sketchup/UVHelper.html
                    and this:
                    http://ruby.sketchup.com/Sketchup/Face.html#position_material-instance_method

                    PS: Please format your code examples using

                    [code]...[/code]
                    

                    rather that

                    [ruby]...[/ruby]
                    

                    It's much easier to read...

                    TIG

                    1 Reply Last reply Reply Quote 0
                    • C Offline
                      chanz
                      last edited by 4 Sept 2018, 20:16

                      @tig said:

                      Read up on this:
                      http://ruby.sketchup.com/Sketchup/UVHelper.html
                      and this:
                      http://ruby.sketchup.com/Sketchup/Face.html#position_material-instance_method

                      PS: Please format your code examples using

                      [code]...[/code]
                      

                      rather that

                      [ruby]...[/ruby]
                      

                      It's much easier to read...

                      Always wondered why my posts looked so strange right after being posted but then fixed themselves.

                      Thanks for the links.

                      So what I understand from this block:

                      		samples = []
                      		samples << face_two.vertices[0].position			   ### 0,0 | Origin
                      		samples << samples[0].offset(face_two.normal.axes.x) ### 1,0 | Offset Origin in X
                      		samples << samples[0].offset(face_two.normal.axes.y) ### 0,1 | Offset Origin in Y
                      		samples << samples[1].offset(face_two.normal.axes.y) ### 1,1 | Offset X in Y
                      		xyz = [];uv = []### Arrays containing 3D and UV points.
                      		uvh = face_two.get_UVHelper(true, true, texture_writer)
                      		samples.each { |position|
                      		xyz << position ### XYZ 3D coordinates
                      		
                      		# I switched this to front_UVQ
                      		uvq = uvh.get_front_UVQ(position) ### UV 2D coordinates
                      		uv << self.flattenUVQ(uvq)
                      		}
                      	  
                      		pts = [] ### Position texture.
                      		(0..3).each { |i|
                      		pts << xyz[i]
                      		pts << uv[i]
                      		}
                      	  
                      		# set the position and material of face_one
                      		mat = face_two.material
                      		face_one.position_material(mat, pts, true)
                      
                      

                      From this function that I have adapted from your script, it seems like it should be correct.

                      1. Gets the position of the four points from the face which we want to sample (0, 0), (1, 0), (0, 1), and (1, 1)
                      2. Put the vertex position and UV coordinates (flattened - not sure why although not flattening had no effect) into two arrays (UVs returned in a point form, but only the x and y are used as u and v).
                      3. Interlace these arrays as the position_material function calls for.
                      4. Call position_material. Supply the correct material (which I do), the points (which I obviously don't) and true as it is a front face.

                      I understand what's going on here. I am a bit confused about why we are flattening the UVs but the math behind the function to do so makes sense.

                      Still stumped on what exactly I am getting wrong about the point array...

                      Edit: Interestingly, if I just leave the model flat and raycast down, it gets the texture and orientation correct. Now I am wondering if it's the raycast, but I really doubt it...

                      Also, it didn't seem to matter if I included just three points (0,0), (0,1) and (1,1) for both the points and uvs.

                      Edit: I have attached what I have so far (code wise)


                      The latest episode in my endless struggle for simplification πŸ™‚

                      1 Reply Last reply Reply Quote 0
                      • C Offline
                        chanz
                        last edited by 6 Sept 2018, 09:08

                        Played around with it a bit more last night. Couldn't find anything that jumped out as incorrect. Does anyone have any more suggestions? Happy to try anything πŸ˜„

                        I appreciate it!

                        1 Reply Last reply Reply Quote 0
                        • T Offline
                          TIG Moderator
                          last edited by 7 Sept 2018, 17:46

                          Try this example.

                          ### CopyBelowMaterials.rb
                          ### by Chanz
                          require 'sketchup.rb'
                          ###
                          module DAN
                            ###
                            def self.copymaterials()
                              model=Sketchup.active_model
                              ents=model.active_entities
                              #ss=model.selection ### use selected upper faces to speed it up
                              faces = ents.grep(Sketchup;;Face)
                              #faces = ss.grep(Sketchup;;Face) ### use selected upper faces to speed it up
                              ###
                              return nil unless faces[0]
                              ###
                              begin
                                model.start_operation("DAN.copymaterials", true)
                              rescue
                                model.start_operation("DAN.copymaterials")
                              end
                              ###
                              faces.each {|face| 
                                rayt = model.raytest(self.find_centroid(face), Z_AXIS.reverse)
                                if rayt
                                  rayt[1].each{|f|
                                    if f.is_a?(Sketchup;;Face)
                                      self.process_face(face, f)
                                    end
                                  }
                                end
                              }
                              ###
                              model.commit_operation
                              ###
                            end
                          
                            # Finds an adjusted point, so we don't raycast down to an edge ;)
                            ### OK if triangulated... 
                            ### BUT IF the face is L shaped the centroid might not fall on it !!!
                            def self.find_centroid(face)
                              vertices=face.vertices
                              vertices[vertices.length]=vertices[0]
                              centroid=Geom;;Point3d.new()
                              a_sum=0.0
                              y_sum=0.0
                              x_sum=0.0
                              for i in (0...vertices.length-1)
                                temp=(vertices[i].position.x*vertices[i+1].position.y)-(vertices[i+1].position.x*vertices[i].position.y)
                                a_sum+=temp
                                x_sum+=(vertices[i+1].position.x+vertices[i].position.x)*temp
                                y_sum+=(vertices[i+1].position.y+vertices[i].position.y)*temp
                              end###for
                              centroid.x=x_sum/(3*a_sum)
                              centroid.y=y_sum/(3*a_sum)
                              centroid.z=face.bounds.min.z
                              return centroid
                            end
                            
                            # NOTE; face_upper is in the model.ents||selection, face_lower is the face the raytest hit
                            def self.process_face(face_upper, face_lower)
                              ###
                              texture_writer=Sketchup.create_texture_writer
                              ###
                              if face_upper.material && ! face_upper.material.texture
                                ### paint the plain material if it exists but it has no texture
                                face_lower.material=face_upper.material
                              elsif face_upper.material ### A material and it has a texture...
                                # Sample the UVs from UPPER face
                                samples = []
                                samples << face_upper.vertices[0].position             ### 0,0 | Origin
                                samples << samples[0].offset(face_upper.normal.axes.x) ### 1,0 | Offset Origin in X
                                samples << samples[0].offset(face_upper.normal.axes.y) ### 0,1 | Offset Origin in Y
                                samples << samples[1].offset(face_upper.normal.axes.y) ### 1,1 | Offset X in Y
                                xyz = [];uv = [] ### Arrays containing 3D and UV points.
                                uvh = face_upper.get_UVHelper(true, true, texture_writer)
                                samples.each { |position|
                                  xyz << position ### XYZ 3D coordinates
                                  uvq = uvh.get_front_UVQ(position) ### UV 2D coordinates
                                  uv << self.flattenUVQ(uvq)
                                }
                                pts = [] ### Position texture.
                                (0..3).each { |i|
                                  pts << xyz[i]
                                  pts << uv[i]
                                }
                                # Set the position for textured material from face_upper onto face_lower front
                                face_lower.position_material(face_upper.material, pts, true)
                              end
                              ###
                            end
                            
                            ### Get UV coordinates from UVQ matrix.
                            def self.flattenUVQ(uvq)
                              return Geom;;Point3d.new(uvq.x / uvq.z, uvq.y / uvq.z, 1.0)
                            end
                            
                            ###
                            ### menu
                            unless file_loaded?(__FILE__) 
                              menu=UI.menu("Plugins").add_submenu("Copy Materials...")
                              menu.add_item("To Nearest Below"){self.copymaterials()}
                            end
                            file_loaded(__FILE__)
                          
                          end
                          

                          You had muddled face1 and face2 - I renamed them face_upper and face_lower to avoid confusion.
                          Also note the following...
                          ss=model.selection... allows you to select just the upper mesh faces, halving the processing, since the lower will be tested and ignorred anyway !
                          return nil unless faces[0] is added to skip out if no faces
                          model.start_operation... is moved into the right place in the code.
                          self.find_centroid(face) could produce unexpected results in a concave L shaped faces, but will work OK if it's triangulated...
                          I also let it copy un-textured material from upper to lower too...
                          I've also made a few more minor tweaks elsewhere...

                          TIG

                          1 Reply Last reply Reply Quote 0
                          • C Offline
                            chanz
                            last edited by 7 Sept 2018, 19:33

                            Hey TIG! Thanks a bunch for the reply πŸ˜„

                            I think there was maybe some confusion about what I was trying to accomplish here and perhaps the function name is misleading.

                            I have textured terrain from an old game with way too many subdivisions and I want to simplify it. So I make a terrain with the correct size, and then use Drop Vertices to make the terrains the same height. I then move the new terrain up directly over the old terrain and then run my script which would do the raycast and copies from the model below. I think you assumed I would do it the other way around and my function name "Copy Materials -> To Nearest Below" was totally misleading. My apologies.

                            I switched the the face arguments in the process_face function and it works again.

                            When I run the script, I still get some strange looking results. In the image below, the left is the original and the right is the one that the script made.

                            https://i.imgur.com/efmfMP1.png

                            Notice that the UV mapping looks quite off, still. If I run the script on a flat sandbox mesh above the original mesh, it copies the UVs fine but I need it to be fine when the mesh is not flat. Or find a way to drop the vertices without messing up the UVs.

                            I have reduced the size of the model I am using to test so hopefully you're able to try it with this test case.

                            Again, your time is greatly appreciated. Thank you!


                            A test scene for this plugin. Select the faces in the top (untextured) model and go to Extensions->Copy Materials->From Nearest Below


                            The script with TIG's revisions and the argument order in the process_faces reversed

                            1 Reply Last reply Reply Quote 0
                            • T Offline
                              TIG Moderator
                              last edited by 7 Sept 2018, 21:47

                              If you want to recast [all but flip ?] it, so the face_below.material BELOW gets 'projected' onto the face_above... then you firstly need to swap my naming conventions etc, AND also change the Z_AXIS code to project your raytest upward, rather than down...

                              I'm sure that if we can agree a common 'framework' this can be resolved...

                              TIG

                              1 Reply Last reply Reply Quote 0
                              • C Offline
                                chanz
                                last edited by 7 Sept 2018, 22:22

                                @tig said:

                                If you want to recast [all but flip ?] it, so the face_below.material BELOW gets 'projected' onto the face_above... then you firstly need to swap my naming conventions etc, AND also change the Z_AXIS code to project your raytest upward, rather than down...

                                I'm sure that if we can agree a common 'framework' this can be resolved...

                                Isn't face_above raytesting down to face_below and then sampling the UVs the same as face_below raytesting up to face_above and setting the UVs?

                                The original script I had written did the raytest downwards from the face_above and got the material and UV from face_below.

                                Here is a terrible illustration to show what I mean:

                                https://i.imgur.com/COl2dsP.png

                                Either way, it doesn't bother me which way is preferred. All I want is for the Texture mapping uvs to look on the above mesh, just like the below mesh.

                                The easiest way to see what I am talking about would be to download the sample project and script I attached. Select the faces in the model that's on top and then run the script.

                                Thanks TIG πŸ˜„

                                1 Reply Last reply Reply Quote 0
                                • T Offline
                                  TIG Moderator
                                  last edited by 7 Sept 2018, 23:03

                                  In your image face_below is the 'source' face [the inverse of my assumptions].
                                  And therefore face_above is the 'target', so your code needs to swap them to suit.
                                  AND the ' raytest' then needs to follow Z_AXIS.reverse, so it look up NOT down...

                                  BOTH will work, BUT just be consistent...

                                  TIG

                                  1 Reply Last reply Reply Quote 0
                                  • C Offline
                                    chanz
                                    last edited by 7 Sept 2018, 23:30

                                    @tig said:

                                    In your image face_below is the 'source' face [the inverse of my assumptions].
                                    And therefore face_above is the 'target', so your code needs to swap them to suit.
                                    AND the ' raytest' then needs to follow Z_AXIS.reverse, so it look up NOT down...

                                    BOTH will work, BUT just be consistent...

                                    Exactly. So we are raytesting from face_above downwards to face_below (the source) to get the materials and UV. Apologies for the confusion.

                                    This still has no effect on the UVs though, which are still not working. I have attached the script with the above agreed upon naming convention and additional clarifying comments. Try it in the test scene if you get a chance. Thanks TIG. πŸ˜„


                                    Fixed naming conventions

                                    1 Reply Last reply Reply Quote 0
                                    • T Offline
                                      TIG Moderator
                                      last edited by 9 Sept 2018, 18:04

                                      So your convention is upper surface has 'no-material', lower surface has 'textures'.
                                      Unless you known the two meshes have identical faces you can't safely tale a point on an upper facet and project down and be sure of hitting a lower facet...
                                      But for now let's assume all goes well and we have 'hits', how are you going to get the lower facet's texture ?
                                      Isn't it better to process each lower textured facet, and project upwards to get a matching upper facet.
                                      You then need to get the UV mapping for the lower facet, and apply it to the lower facet's material which is applied onto the equivalent upper facet.

                                      Just think about which is which...

                                      TIG

                                      1 Reply Last reply Reply Quote 0
                                      • C Offline
                                        chanz
                                        last edited by 9 Sept 2018, 18:23

                                        @tig said:

                                        So your convention is upper surface has 'no-material', lower surface has 'textures'.
                                        Unless you known the two meshes have identical faces you can't safely tale a point on an upper facet and project down and be sure of hitting a lower facet...
                                        But for now let's assume all goes well and we have 'hits', how are you going to get the lower facet's texture ?
                                        Isn't it better to process each lower textured facet, and project upwards to get a matching upper facet.
                                        You then need to get the UV mapping for the lower facet, and apply it to the lower facet's material which is applied onto the equivalent upper facet.

                                        Just think about which is which...

                                        It really doesn't matter to me which direction is taken. I'm absolutely fine using the method you suggest where we raycast up applying the material and UVs from the raycast source face to the hit face. Works for me.

                                        I think your edits to my script were doing just that. But I got the same results as mine where the UVs were messed up

                                        Thinking about it, because I'm using drop vertices and the meshes are nearly identical but not perfectly the same, could that be causing issues with the UVs? Perhaps that's the issue where it copies it just fine but because the faces differ, we get issues.

                                        So if that's the case, the next questions is how difficult it would be to modify drop vertices to preserve the UVs when it drops. Vertex Tools has a similar feature where you can lock the UVs.

                                        Would that be an easier approach?

                                        Thanks as always, TIG.

                                        1 Reply Last reply Reply Quote 0
                                        • T Offline
                                          TIG Moderator
                                          last edited by 9 Sept 2018, 19:47

                                          In my earlier version, if I make the to textured and project it onto a lower surface the top one's UV mapping is repeated on the lower surface.
                                          So I know it works.
                                          You just need to swap top/bottom properly to apply to you own chosen set up...

                                          If you use a blank face's UV mapping you won't get what's on the UV-mapped face...

                                          Try it with just a few facets first...

                                          TIG

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

                                          Advertisement