The forum on this site is closed for new comments and posts. Continue the conversation in the new forum, and learn more here.

Procedural mesh UV mapping


  • I've got a little further. I realised that the texture has to be a power of 2 so that the tiling will work. Problem now is that it still isn't a uniform tiling.

    Comment actions Permalink
  • Hi Ashley,

    Cool idea! Since we want to tile the texture, we'll want to add extra vertices where one texture ends, so that we can start the next texture. 

     

    So our UV coordinate system looks like this:

    and in the meshBuilder example, we define our vertices like this:

     // Position Normal          UV       Index
    left, top, 0, 0, 0, 1, 0, 1, // 0
    left, bottom, 0, 0, 0, 1, 0, 0, // 1
    right, bottom, 0, 0, 0, 1, 1, 0, // 2
    right, top, 0, 0, 0, 1, 1, 1, // 3

    In other words, we are saying that: our left top vertex, is 0,1 in our UV coordinate, our left bottom vertex is 0,0 in our UV coordinates, and so on. This matches our image above on how the UV coordinate system looks. 

     

    Then we connect these vertices together to create triangles:

     0,1,2, // First Triangle
    2,3,0, // Second Triangle

    which visually looks like:

    So if we want to repeat the texture in the next segment, what we want to do is create another vertices at 3 and 2, but have their UV coordinates be the same as vertices 0 and 1 (the start of the texture). Then we will add 2 more vertices (the end of the texture) to finish up our new segment. 

    Take a look at the bottom of this post for the modified mesh builder example. 

     

    By the way, you can also use the material to repeat the texture within the UV coordinate system! Given the same mesh above, if we use the following material settings:

    you'll notice that in each segment above we get 4 Text (scale 2 x 2) instead of 1.

     

    Let me know if this works for you. Can't wait to see what you come up with. Don't forget to share!

     

    Cheers,

    Jon

     

    Modified mesh builder example: 

    // -----JS CODE-----
    // Builds a quad mesh and applies it to meshVisual
    //@input Component.MeshVisual meshVisual

    var builder = new MeshBuilder([
    { name: "position", components: 3 },
    { name: "normal", components: 3, normalized: true },
    { name: "texture0", components: 2 },
    ]);

    builder.topology = MeshTopology.Triangles;
    builder.indexType = MeshIndexType.UInt16;

    var left = -.5;
    var right = .5;
    var top = .5;
    var bottom = -.5;

    var right2 = 1.5;

    builder.appendVerticesInterleaved([
    // Position Normal UV Index
    left, top, 0, 0, 0, 1, 0, 1, // 0
    left, bottom, 0, 0, 0, 1, 0, 0, // 1
    right, bottom, 0, 0, 0, 1, 1, 0, // 2
    right, top, 0, 0, 0, 1, 1, 1, // 3

    // Position Normal UV Index
    right, top, 0, 0, 0, 1, 0, 1, // 4
    right, bottom, 0, 0, 0, 1, 0, 0, // 5
    right2, bottom, 0, 0, 0, 1, 1, 0, // 6
    right2, top, 0, 0, 0, 1, 1, 1, // 7
    ]);

    builder.appendIndices([
    0,1,2, // First Triangle
    2,3,0, // Second Triangle

    4,5,6, // Third Triangle
    6,7,4, // Fourth Triangle
    ]);

    if(builder.isValid()){
    script.meshVisual.mesh = builder.getMesh();
    builder.updateMesh();
    }
    else{
    print("Mesh data invalid!");
    }

     

    Comment actions Permalink
  • Hi Jon,

    Thanks for the in depth help!

    My code is pretty similar to that.

    The problem is because I add to the mesh when the screen is pressed, each square is a different size.

    That ends up with the tiling to be scaled differently on each square :(

    Any ideas?

    I thought of using trianglestrip topology but from what I tested it seemed to have the same issue.

     

    Thanks for the help!

     

    Comment actions Permalink
  • Hi Ashley,

    Cool!

    Hmm, do you have an example of what you mean? I'm not sure I understand.

    Can you show an image of what you get, and what you expected? 

    Thanks in advance!

    Jon

    Comment actions Permalink
  • Hi,

     

    Below is an example of what happens using the triangles topology.

    This is how far I got with the trianglesStrip topology. Haven't got round to do the UV mapping yet,

    The effect I'm trying to achieve is the tape at the beginning of this: https://www.youtube.com/watch?v=UzNCGqE9MhE

    I'm pretty sure I should be using the trianglesStrip so that it is one long connected piece. 

    Let me know if that makes sense.

    Thanks for all the help, it's well appreciated :)

    Comment actions Permalink
  • Hey Ashley,

    Cool idea! 

    First let's reconsider our mesh builder type. Since each segment is connected to the last, we can actually just use TriangleStrip, which will automatically connect new vertices to the previous ones. Take a look at: https://lensstudio.snapchat.com/api/classes/MeshTopology/ for more info. 

    var builder = new MeshBuilder([
    { name: "position", components: 3 },
    { name: "texture0", components: 2 },
    ]);

    builder.topology = MeshTopology.TriangleStrip;
    builder.indexType = MeshIndexType.UInt16;

    Since we're dynamically drawing where the mesh is being drawn, I set up a cursor object (based on your screenshot, you have already done this so I won't get into it in detail). Basically it's just an object attached to and in front of the camera. We will use the position of this object to know where to draw the next set of points. 

    Next we can rewrite our draw function and put it inside a function so we can call it every time we want to draw a new segment. Since our resulting mesh has a constant height, we just need to focus on the x axis. Then, we can add/subtract it's y coordinate to get the top and bottom of our strip. 

    For now we can just set the Y coordinate of the UV, that is, the top vertex draws the top part of the texture (0), and and the bottom vertex draws the bottom of the texture (1). 

    function drawQuad(cursor)
    {
    var reference = cursor.getTransform();
    var centerPos = reference.getWorldPosition();
    var v1 = centerPos.add(reference.up.uniformScale(-3.0));
    var v2 = centerPos.add(reference.up.uniformScale(3.0));

    builder.appendVerticesInterleaved([
    // Position UV Index
    v1.x, v1.y, v1.z, 0, 0, // 0
    v2.x, v2.y, v2.z, 0, 1, // 1
    ]);

    var startIndex = builder.getIndicesCount();
    builder.appendIndices([
    0 + startIndex, 1 + startIndex, // First Triangle
    ]);

    if(builder.isValid()){
    script.drawingObject.mesh = builder.getMesh();
    builder.updateMesh();
    }
    }

    Now we just need to calculate what to put for the UV's x coordinate in to those vertices. 

    Every time we add a new section to our mesh, we want to give a corresponding increase in UV. While our mesh vertices is in world space, UV coordinate is 0 to 1. So we need to figure out is how much more texture (UV coord between 0 to 1) should we show in the new mesh segment. 

    In other words, what we want to do is: take the length of our new mesh segment (the distance between our last set of vertices to our new one), and divide it by the total length of our mesh. The size depends on how wide a complete strip is. 

     (size 10)

     (size 100)

    Finally, since we're adding on sections, we need to keep track of our last UV's x position, and add it to our new strip.

    var newUVX = cursorPos.distance(lastPoint) / size + lastUVX;

     

    Then, we can pass this info into our draw function from earlier

    drawQuad(cursorPos, newUVX );  

    ...

    builder.appendVerticesInterleaved([
    // Position UV Index
    v1.x, v1.y, v1.z, newUVX, 0, // 0
    v2.x, v2.y, v2.z, newUVX, 1, // 1
    ]);

     

    Finally we'll call this draw function every frame, passing it the position of our cursor object. Don't forget to keep track of where we last drew since we need to do some calculation with it. 

    function drawNewPoints()
    {
    var cursorPos = script.cursor.getTransform().getWorldPosition();
    var newUVX = lastUVX + cursorPos.distance(lastPoint) / size;

    drawQuad(script.cursor, newUVX );

    lastUVX = newUVX;
    lastPoint = cursorPos;
    }

    var event = script.createEvent("UpdateEvent");
    event.bind(function (eventData)
    {
    drawNewPoints();
    });

    and that's it!

    Don't forget to share your final results :)

    Cheers!

    Jon

     

    Your final script should look something like:

    // -----JS CODE-----
    // Builds a quad mesh and applies it to meshVisual
    //@input Component.MeshVisual drawingObject
    //@input SceneObject cursor

    var builder = new MeshBuilder([
    { name: "position", components: 3 },
    { name: "texture0", components: 2 },
    ]);

    builder.topology = MeshTopology.TriangleStrip;
    builder.indexType = MeshIndexType.UInt16;

    const POINT_COUNTER_MAX = 500; // Maximum number of points a user can draw
    var pointCounter = 0; // Counter for drawn surface points
    var lastPoint = script.cursor.getTransform().getWorldPosition(); // Last drawn point
    var size = 100;
    var lastUVX = 0;

    function drawQuad(cursor, newUVX)
    {
    var reference = cursor.getTransform();
    var centerPos = reference.getWorldPosition();
    var v1 = centerPos.add(reference.up.uniformScale(-3.0));
    var v2 = centerPos.add(reference.up.uniformScale(3.0));

    builder.appendVerticesInterleaved([
    // Position UV Index
    v1.x, v1.y, v1.z, newUVX, 0, // 0
    v2.x, v2.y, v2.z, newUVX, 1, // 1
    ]);

    var startIndex = builder.getIndicesCount();
    builder.appendIndices([
    0 + startIndex, 1 + startIndex, // First Triangle
    ]);

    if(builder.isValid()){
    script.drawingObject.mesh = builder.getMesh();
    builder.updateMesh();
    }
    }

    function drawNewPoints()
    {
    var cursorPos = script.cursor.getTransform().getWorldPosition();
    var newUVX = lastUVX + cursorPos.distance(lastPoint) / size;

    drawQuad(script.cursor, newUVX );

    lastUVX = newUVX;
    lastPoint = cursorPos;
    }

    var event = script.createEvent("UpdateEvent");
    event.bind(function (eventData)
    {
    drawNewPoints();
    });
    Comment actions Permalink
  • Thanks so much, it works really well. 

    I'll keep you updated on progress.

    You are the master 🙏

    Comment actions Permalink
  • wow this thread is incredible 😃

    Comment actions Permalink
  • Hi! 

    Is it possible to use a png image? I can't make it work with a transparent image. 

    Comment actions Permalink
  • Hi, Tangerine Kiwi!

    The issue might be in the Blendmode of the Material. Please check, and switch it to Normal if it is set to Disabled

    Best

    Olha

    Comment actions Permalink

We're here to help! We just need a little bit of information...

What system are you using?

Have you downloaded the latest version of Lens Studio?

Please download the latest version of Lens Studio. If you still run into this issue, please come back and report it!

Is this issue causing Lens Studio to crash?

What system do you run Lens Studio on?

Version

Graphics

Follow the next steps to help us solve your issue:

  • Copy and paste this text into your TerminalCommand Window
    open ~/Library/Preferences/Snap/Lens\ Studio/ %LOCALAPPDATA%\Snap\Lens Studio Copy Text
  • Press ReturnEnter to run the command. The Lens Studio folder will automatically open
  • Prepare to upload your files: zip the "Log" Folder by right-clicking and choosing "compress."
    Locate the Log.txt file right above it.

    Attach a screenshot of the issue:

Name:

Email:

What is this most relevant to?

Please enter a brief description of your issue:

Thanks for submitting this issue.

Unfortunately, it's likely due to the operating system or hardware you're using – since they don't meet the system requirements for Lens Studio.

Still, we hear you loud and clear, and are logging the issue in case there's ever a workaround we can provide!

Minimum Requirements

Operating System: Windows 10 (64 bit); MacOS 10.11+

Hardware: Minimum of Intel Core i3 2.5Ghz or AMD Phenom II 2.6Ghz with 4 GB RAM; Intel HD Graphics 4000 / Nvidia GeForce 710 / AMD Radeon HD 6450 or better; screen resolution of 1280x768 or higher

We'll try to resolve this issue as soon as possible. Thanks for letting us know about it!

Keep an eye out for a followup email from us. We may have a couple more questions for you, or we might already have a solution to offer.

Happy creating!