/three-sprite-texture-atlas-manager

Helper library for three.js r126+ to programmatically build sprite texture atlases

Primary LanguageJavaScriptMIT LicenseMIT

three-sprite-texture-atlas-manager

A "sprite texture atlas" manager for three.js (technically r73 and up, but do use v125 and up: CVE-2020-28496). This module helps you to dynamically manage sprites that are shared across as many canvases as are dynamically needed, and you and easily assign these fragments of the texture to individual sprites in your scene. You can also draw in the canvas yourself; you render text or draw with the canvas context functions.

example of a generated sprite atlas

Splitting up a canvas in a texture atlas helps to maximise the use of GPU memory as more recent three.js versions are able to be tricked into sharing the texture on the GPU across sprites by making sure all their .uuid properties are identical; this library takes care of that as well.

This library can be used either synchronously or asynchronously. If you use the async behaviour it makes use of Promises, and to support IE11 with the async interfaces in your WebGL application you'll also need a provide a Promise polyfill. But really, IE11?

Run-time requirements

Examples

WebGL with sprites example: https://jsfiddle.net/Shiari/jzwg8ngn/.

Canvas-only example much like the usage example below: https://jsfiddle.net/Shiari/sbda72k9/.

Missing from these example is how you can more dynamically free and reallocate nodes, which you would need to do if you're changing the text in them dynamically. But if all you need are text labels then have a look at the src/label.js and src/icon-label.js classes; they make it trivial. You'll need to import these as ES2015+ modules as they are not part of the main distribution builds, and there is no documentation for them just yet. There are test scripts for them though.

Usage

This is an asynchronous example, and thus it makes use of Promises. The jsfiddle examples use the slightly simpler synchronous approach which does not need Promises.

The library can be imported as a ES2016+ module, or loaded directly in the browser through its UMD build. See the jsfiddle examples for the UMD approach.

// We want textures of 1024x1024 pixels (always a power of two)
// (this assumes "globals" mode ... for ES6 or node, import or require())
var textureManager = new window.threeSpriteAtlasTextureManager(1024);

// Make the sprite allocation code render some blue, purple and green
// borders in the nodes (this helps visualisation of what's going on)
textureManager.debug = true;

var words = [
    'This', 'is', 'a', 'basic example', 'of', 'building', 'a',
    'texture atlas', 'to', 'build', 'unique', 'sprites', 'and share',
    'as', 'much', 'GPU', 'memory', 'as possible'
];

// Some settings for the text we're creating
var fontStyle = "Bold 120px 'Segoe UI', 'Lucida Grande', 'Tahoma', 'Calibri', 'Roboto', sans-serif";
// A bit of space around the text to try to avoid hitting the edges
var xPadding = 30;
var yPadding = 30;
// Shift the text rendering up or down
var yOffset = -5;

// Need a canvas to determine the text size
var canvas = document.createElement('canvas');
// but its size doesn't matter
canvas.width = canvas.height = 1;

// Keep track of the promises for each node we're creating so that
// we can tell when they're all done
var nodes = [];

words.forEach(function (text) {
  // Calculate the width of the text
  var width = widthOfText(text) + xPadding;
  // You'd base this height on your font size, may take some fiddling
  var height = 120 + yPadding;

  // Allocate a node for the text, this returns a promise
  // which we're adding to the array. On success the
  // promise resolves with a "node":
  nodes.push(
    textureManager.allocateNode( width, height ).then(
      function (node) {
        var context = node.clipContext();
        context.font = fontStyle;
        context.textAlign = 'center';
        context.textBaseline = 'middle';
        context.fillStyle = 'rgb( 0, 0, 0 )';
        context.fillText(text, 0, yOffset);
        node.restoreContext();

        // If we were using WebGL for this example, here'd you'd be
        // creating your sprite. node.texture will be a cloned texture
        // ready to use, with its UV coordinates already set:
        // var material = new THREE.SpriteMaterial({ map: node.texture });
        // node.texture.needsUpdate = true;
        // var sprite = new THREE.Sprite( material ) );
        // scene.add( sprite );
      },
      function (error) {
        console.error("Error allocating node:", error);
      }
    )
  );
});

// When all the promises are resolved, we're ready to pull out the
// canvases and put them in the DOM so that this fiddle shows them
Promise.all(nodes).then(function () {
  textureManager.knapsacks.forEach(function (knapsack) {
    document.getElementById('canvases').appendChild( knapsack.canvas );
  });
});


// Helper function : determine the width required to render the given text.
// You'll need to use the same (relevant) settings as you would when
// rendering the text
function widthOfText(text) {
  var context = canvas.getContext('2d');
  context.font = fontStyle;
  return (Math.floor(context.measureText(text).width));
}

Documentation

Please see the API reference for the public interface.

Development

Install a reasonably modern version of node.js such as v12 or up (on Linux I highly recommend nvm for managing your node installations) and check out this repository. Then:

$ cd three-sprite-texture-atlas-manager/
$ npm install

Now your environment should be entirely set up, and the commands below should run just fine.

To run the tests:

$ npm run test

To run the tests while waiting for changes to any files, re-running the tests:

$ npm run watch

For a test coverage report (the actual coverage report may or may not work correctly yet):

$ npm run coverage

Build the documentation:

$ npm run docs

Build the libraries in the dist/ folder:

$ npm run build

TODO

I don't use this module myself at the moment, so further development on this isn't happening. PR's are welcome.

  • Better usage documentation, right now it may not be obvious how to release and reallocate new nodes properly.
  • Documentation plus examples for the higher level Label and LabelIcon ES2015 modules.
  • Documentation layout/rendering fixes.
  • Fancier allocation algorithm for the asynchronous interface which might allow to maximise the texture allocation.

License

Copyright 2015-2021 Lianna Eeftinck

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.