• Runtimes
  • [ts] Overlaying animations over UI components

@Davide I am investigating how this example of overlaying animations over UI components is done <link redacted>

I'm trying to do the same thing, but putting the animated characters inside the boxes below so it looks like a sticker pack.

I copied the code and it places the animated character on the top left corner of the box. However, I'm trying to put it inside the box and I'm not sure how to adjust the code because I don't understand it. I was wondering if you could explain what each step of the code is doing here so I can figure out how to customize it myself. Specifically, I'm not sure what worldToScreen and what the custom drawSkeleton function is doing with the verticies.

render(canvas) {
                let renderer = canvas.renderer;
		renderer.resize(spine.ResizeMode.Expand);
		canvas.clear(0, 0, 0, 0);
		renderer.begin();

                // webgl canvas center
                const vec3 = new spine.Vector3(0, 0);
                renderer.camera.worldToScreen(vec3, canvas.htmlCanvas.clientWidth, canvas.htmlCanvas.clientHeight);

                // loop over the skeleton/div comination
                for (let { divs, skeleton } of Object.values(this.selectorToDiv)) {
                    // loop over each div where to render the current skeleton
                    for (let div of divs) {
                        const rect = div.getBoundingClientRect();
                        rect.x *= window.devicePixelRatio;
                        rect.y *= window.devicePixelRatio;

                            renderer.drawSkeleton(skeleton.skeleton, true, -1, -1, (vertices, size, vertexSize) => {
                                for (let i = 0; i < size; i+=vertexSize) {
                                    vertices[i] = vertices[i] + rect.x - vec3.x * window.devicePixelRatio;
                                    vertices[i+1] = vertices[i+1] - rect.y + vec3.y * window.devicePixelRatio;
                                }
                            });
                    }
                }

				// Complete rendering.
				renderer.end();
			}
		}

Thanks!

    Related Discussions
    ...

    The code you provided is responsible for rendering Spine animations on a WebGL canvas and positioning them relative to HTML elements (divs). Here's a breakdown of what each part of the code is doing:

    1. Renderer Initialization and Canvas Clearing:

         let renderer = canvas.renderer;
         renderer.resize(spine.ResizeMode.Expand);
         canvas.clear(0, 0, 0, 0);
         renderer.begin();
      • renderer.resize(spine.ResizeMode.Expand);: Resizes the renderer to fit the canvas.
      • canvas.clear(0, 0, 0, 0);: Clears the canvas with a transparent background.
      • renderer.begin();: Begins the rendering process.
    2. Centering the WebGL Canvas:

         const vec3 = new spine.Vector3(0, 0);
         renderer.camera.worldToScreen(vec3, canvas.htmlCanvas.clientWidth, canvas.htmlCanvas.clientHeight);
      • const vec3 = new spine.Vector3(0, 0);: Creates a vector at the origin (0, 0).
      • renderer.camera.worldToScreen(vec3, canvas.htmlCanvas.clientWidth, canvas.htmlCanvas.clientHeight);: Converts the world coordinates to screen coordinates, centering the canvas.
    3. Looping Over Skeletons and Divs:

         for (let { divs, skeleton } of Object.values(this.selectorToDiv)) {
             for (let div of divs) {
                 const rect = div.getBoundingClientRect();
                 rect.x *= window.devicePixelRatio;
                 rect.y *= window.devicePixelRatio;
      • for (let { divs, skeleton } of Object.values(this.selectorToDiv)) {: Loops over each skeleton and its associated divs.
      • const rect = div.getBoundingClientRect();: Gets the position and size of the div.
      • rect.x *= window.devicePixelRatio;: Adjusts the x-coordinate for the device's pixel ratio.
      • rect.y *= window.devicePixelRatio;: Adjusts the y-coordinate for the device's pixel ratio.
    4. Custom Drawing Function:

         renderer.drawSkeleton(skeleton.skeleton, true, -1, -1, (vertices, size, vertexSize) => {
             for (let i = 0; i < size; i+=vertexSize) {
                 vertices[i] = vertices[i] + rect.x - vec3.x * window.devicePixelRatio;
                 vertices[i+1] = vertices[i+1] - rect.y + vec3.y * window.devicePixelRatio;
             }
         });
      • renderer.drawSkeleton(skeleton.skeleton, true, -1, -1, (vertices, size, vertexSize) => {: Draws the skeleton with a custom function to adjust the vertices.
      • for (let i = 0; i < size; i+=vertexSize) {: Loops over each vertex.
      • vertices[i] = vertices[i] + rect.x - vec3.x * window.devicePixelRatio;: Adjusts the x-coordinate of the vertex.
      • vertices[i+1] = vertices[i+1] - rect.y + vec3.y * window.devicePixelRatio;: Adjusts the y-coordinate of the vertex.
    5. Completing the Rendering:

         renderer.end();
      • renderer.end();: Ends the rendering process.

    To position the animated character inside the box, you need to adjust the rect.x and rect.y values to account for the position of the box. You can do this by modifying the rect values before the custom drawing function:

    rect.x += boxOffsetX; // Adjust this value to position inside the box
    rect.y += boxOffsetY; // Adjust this value to position inside the box

    Replace boxOffsetX and boxOffsetY with the appropriate values to position the character inside the box.

    I got it to work by making the following adjustment:

            renderer.drawSkeleton(
              skeleton,
              false,
              -1,
              -1,
              (vertices, size, vertexSize) => {
                for (let i = 0; i < size; i += vertexSize) {
                  vertices[i] =
                    vertices[i] +
                    (rect.x + rect.width / 2) -
                    vec3.x * window.devicePixelRatio;
                  vertices[i + 1] =
                    vertices[i + 1] -
                    (rect.y + rect.height) +
                    vec3.y * window.devicePixelRatio;
                }
              },
            );

    Now I'm not too sure how to make the character fit in the box - it's bigger than the box. I have the character's offset and size info from calling skeletonToUpdate.getBounds(offset, size);. I'm not sure how to use that to size the character down to fit into the box though.

    • joo a répondu à ça.

      joo Nevermind, got it to work now I think!

      joo

      I'm happy you are solving your problems! But just a couple of notes.
      The approach used by the proof of concept in the link above had several problems, especially on scrolling, CPU usage, number of draw calls. I took it down since I don't want that to be copied.

      I can confirm that we started working on that. We're making a web component that will make what you want to achieve much easier (and definitely more performant). The idea is that you just insert an html tag like this:

      <spine-widget
          atlas="assets/raptor-pma.atlas"
          skeleton="assets/raptor-pro.skel"
          animation="walk"
      ></spine-widget>

      And that's it. Your spine animation will be rendered in the parent container, fitting it in the most efficient way. Check your DMs, I've sent you a link to the WIP version of it 👍