From ddb6d5e7cca039dcbab4435ee38b3013cf94f071 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Thu, 19 Mar 2020 23:07:24 -0400
Subject: [PATCH 01/14] get the rust+wasm build process working (hopefully)

---
 .eslintignore         |  1 +
 package.json          | 17 +++++++++++------
 src/RenderWebGL.js    | 23 +++++++++++++++++++++++
 swrender/.gitignore   |  7 +++++++
 swrender/Cargo.toml   | 33 +++++++++++++++++++++++++++++++++
 swrender/src/lib.rs   | 19 +++++++++++++++++++
 swrender/src/utils.rs | 10 ++++++++++
 swrender/tests/web.rs | 13 +++++++++++++
 webpack.config.js     | 31 ++++++++++++++++++++-----------
 9 files changed, 137 insertions(+), 17 deletions(-)
 create mode 100644 swrender/.gitignore
 create mode 100644 swrender/Cargo.toml
 create mode 100644 swrender/src/lib.rs
 create mode 100644 swrender/src/utils.rs
 create mode 100644 swrender/tests/web.rs

diff --git a/.eslintignore b/.eslintignore
index 5821b50ad..b732c67a7 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,3 +2,4 @@ dist/*
 node_modules/*
 playground/*
 tap-snapshots/*
+swrender/build/*
diff --git a/package.json b/package.json
index ef1774d15..39c548182 100644
--- a/package.json
+++ b/package.json
@@ -12,23 +12,27 @@
   "main": "./dist/node/scratch-render.js",
   "browser": "./src/index.js",
   "scripts": {
-    "build": "webpack --progress --colors",
+    "build": "npm run build:swrender && webpack --progress --colors",
+    "build:swrender": "cargo build --release --target wasm32-unknown-unknown --manifest-path=\"swrender/Cargo.toml\" && wasm-bindgen --target web swrender/target/wasm32-unknown-unknown/release/swrender.wasm --out-dir swrender/build",
     "docs": "jsdoc -c .jsdoc.json",
     "lint": "eslint .",
     "prepublish": "npm run build",
     "prepublish-watch": "npm run watch",
-    "start": "webpack-dev-server",
+    "start": "npm run build:swrender && webpack-dev-server",
     "tap": "tap test/unit test/integration",
     "test": "npm run lint && npm run docs && npm run build && npm run tap",
     "version": "json -f package.json -I -e \"this.repository.sha = '$(git log -n1 --pretty=format:%H)'\"",
     "watch": "webpack --progress --colors --watch --watch-poll"
   },
   "devDependencies": {
-    "babel-core": "^6.23.1",
+    "@babel/core": "^7.8.7",
+    "@babel/plugin-syntax-import-meta": "^7.8.3",
+    "@babel/polyfill": "^7.8.7",
+    "@babel/preset-env": "^7.8.7",
+    "@wasm-tool/wasm-pack-plugin": "^1.2.0",
     "babel-eslint": "^10.1.0",
-    "babel-loader": "^7.1.4",
-    "babel-polyfill": "^6.22.0",
-    "babel-preset-env": "^1.6.1",
+    "babel-loader": "^8.0.6",
+    "babel-plugin-bundled-import-meta": "^0.3.2",
     "copy-webpack-plugin": "^4.5.1",
     "docdash": "^0.4.0",
     "eslint": "^7.13.0",
@@ -41,6 +45,7 @@
     "tap": "^11.0.0",
     "travis-after-all": "^1.4.4",
     "uglifyjs-webpack-plugin": "^1.2.5",
+    "webassembly-loader": "^1.1.0",
     "webpack": "^4.8.0",
     "webpack-cli": "^3.1.0",
     "webpack-dev-server": "^3.1.4"
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index b3f6a0aaf..c29931928 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -14,6 +14,21 @@ const TextBubbleSkin = require('./TextBubbleSkin');
 const EffectTransform = require('./EffectTransform');
 const log = require('./util/log');
 
+let onLoadSwRender = null;
+let swRenderLoaded = false;
+// eslint-disable-next-line no-unused-vars
+let swrender = null;
+
+const wasm = require('../swrender/build/swrender_bg.wasm');
+const swrenderInit = require('../swrender/build/swrender.js').default;
+
+swrenderInit(wasm)
+    .then(res => {
+        swrender = res;
+        swRenderLoaded = true;
+        if (onLoadSwRender) onLoadSwRender();
+    });
+
 const __isTouchingDrawablesPoint = twgl.v3.create();
 const __candidatesBounds = new Rectangle();
 const __fenceBounds = new Rectangle();
@@ -114,6 +129,14 @@ class RenderWebGL extends EventEmitter {
         return twgl.getWebGLContext(canvas, {alpha: false, stencil: true, antialias: false});
     }
 
+    init () {
+        if (swRenderLoaded) return Promise.resolve();
+
+        return new Promise(resolve => {
+            onLoadSwRender = resolve;
+        });
+    }
+
     /**
      * Create a renderer for drawing Scratch sprites to a canvas using WebGL.
      * Coordinates will default to Scratch 2.0 values if unspecified.
diff --git a/swrender/.gitignore b/swrender/.gitignore
new file mode 100644
index 000000000..9775a7636
--- /dev/null
+++ b/swrender/.gitignore
@@ -0,0 +1,7 @@
+/target
+**/*.rs.bk
+Cargo.lock
+bin/
+build/
+wasm-pack.log
+.cargo-ok
diff --git a/swrender/Cargo.toml b/swrender/Cargo.toml
new file mode 100644
index 000000000..d2a182c0e
--- /dev/null
+++ b/swrender/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "swrender"
+version = "0.1.0"
+authors = ["adroitwhiz <adroitwhiz@protonmail.com>"]
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[features]
+default = ["console_error_panic_hook"]
+
+[dependencies]
+wasm-bindgen = "0.2"
+
+# The `console_error_panic_hook` crate provides better debugging of panics by
+# logging them with `console.error`. This is great for development, but requires
+# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+# code size when deploying.
+console_error_panic_hook = { version = "0.1.1", optional = true }
+
+# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
+# compared to the default allocator's ~10K. It is slower than the default
+# allocator, however.
+#
+# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
+wee_alloc = { version = "0.4.2", optional = true }
+
+[dev-dependencies]
+wasm-bindgen-test = "0.2"
+
+[profile.release]
+opt-level = 3
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
new file mode 100644
index 000000000..579fde4dd
--- /dev/null
+++ b/swrender/src/lib.rs
@@ -0,0 +1,19 @@
+mod utils;
+
+use wasm_bindgen::prelude::*;
+
+// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
+// allocator.
+#[cfg(feature = "wee_alloc")]
+#[global_allocator]
+static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
+
+#[wasm_bindgen]
+extern {
+    fn alert(s: &str);
+}
+
+#[wasm_bindgen]
+pub fn greet() {
+    alert("Hello, swrenderTEST5!");
+}
diff --git a/swrender/src/utils.rs b/swrender/src/utils.rs
new file mode 100644
index 000000000..b1d7929dc
--- /dev/null
+++ b/swrender/src/utils.rs
@@ -0,0 +1,10 @@
+pub fn set_panic_hook() {
+    // When the `console_error_panic_hook` feature is enabled, we can call the
+    // `set_panic_hook` function at least once during initialization, and then
+    // we will get better error messages if our code ever panics.
+    //
+    // For more details see
+    // https://github.com/rustwasm/console_error_panic_hook#readme
+    #[cfg(feature = "console_error_panic_hook")]
+    console_error_panic_hook::set_once();
+}
diff --git a/swrender/tests/web.rs b/swrender/tests/web.rs
new file mode 100644
index 000000000..de5c1dafe
--- /dev/null
+++ b/swrender/tests/web.rs
@@ -0,0 +1,13 @@
+//! Test suite for the Web and headless browsers.
+
+#![cfg(target_arch = "wasm32")]
+
+extern crate wasm_bindgen_test;
+use wasm_bindgen_test::*;
+
+wasm_bindgen_test_configure!(run_in_browser);
+
+#[wasm_bindgen_test]
+fn pass() {
+    assert_eq!(1 + 1, 2);
+}
diff --git a/webpack.config.js b/webpack.config.js
index c01112e8d..145e817f9 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -11,18 +11,27 @@ const base = {
     },
     devtool: 'cheap-module-source-map',
     module: {
-        rules: [
-            {
-                include: [
-                    path.resolve('src')
-                ],
-                test: /\.js$/,
-                loader: 'babel-loader',
-                options: {
-                    presets: [['env', {targets: {browsers: ['last 3 versions', 'Safari >= 8', 'iOS >= 8']}}]]
-                }
+        rules: [{
+            include: path.resolve('swrender'),
+            loader: 'babel-loader',
+            options: {
+                babelrc: false,
+                plugins: [
+                    '@babel/plugin-syntax-import-meta',
+                    ['bundled-import-meta', {
+                        importStyle: 'cjs'
+                    }]
+                ]
             }
-        ]
+        },
+        {
+            test: /\.wasm$/,
+            loader: 'webassembly-loader',
+            type: 'javascript/auto',
+            options: {
+                export: 'buffer'
+            }
+        }]
     },
     optimization: {
         minimizer: [

From 1c872904ce4a74be9cbe24626d242088de60076d Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Fri, 20 Mar 2020 05:37:02 -0400
Subject: [PATCH 02/14] Implement isTouchingDrawables in Rust

---
 src/BitmapSkin.js           |   2 +-
 src/Drawable.js             |  14 ++++-
 src/PenSkin.js              |  10 ++--
 src/RenderWebGL.js          |  34 ++++--------
 src/SVGSkin.js              |  11 ++--
 src/Skin.js                 |  16 +++++-
 src/TextBubbleSkin.js       |   5 +-
 swrender/src/console_log.rs |  15 ++++++
 swrender/src/drawable.rs    |  33 ++++++++++++
 swrender/src/lib.rs         | 101 +++++++++++++++++++++++++++++++++++-
 swrender/src/matrix.rs      |  12 +++++
 swrender/src/silhouette.rs  |  50 ++++++++++++++++++
 12 files changed, 259 insertions(+), 44 deletions(-)
 create mode 100644 swrender/src/console_log.rs
 create mode 100644 swrender/src/drawable.rs
 create mode 100644 swrender/src/matrix.rs
 create mode 100644 swrender/src/silhouette.rs

diff --git a/src/BitmapSkin.js b/src/BitmapSkin.js
index e48ce43a2..b836b2ee6 100644
--- a/src/BitmapSkin.js
+++ b/src/BitmapSkin.js
@@ -10,7 +10,7 @@ class BitmapSkin extends Skin {
      * @param {!RenderWebGL} renderer - The renderer which will use this skin.
      */
     constructor (id, renderer) {
-        super(id);
+        super(id, renderer);
 
         /** @type {!int} */
         this._costumeResolution = 1;
diff --git a/src/Drawable.js b/src/Drawable.js
index 7a2813d88..4ca3f1dec 100644
--- a/src/Drawable.js
+++ b/src/Drawable.js
@@ -59,12 +59,15 @@ class Drawable {
      * An object which can be drawn by the renderer.
      * @todo double-buffer all rendering state (position, skin, effects, etc.)
      * @param {!int} id - This Drawable's unique ID.
+     * @param {!RenderWebGL} renderer - The renderer which will use this skin.
      * @constructor
      */
-    constructor (id) {
+    constructor (id, renderer) {
         /** @type {!int} */
         this._id = id;
 
+        this._renderer = renderer;
+
         /**
          * The uniforms to be used by the vertex and pixel shaders.
          * Some of these are used by other parts of the renderer as well.
@@ -134,6 +137,7 @@ class Drawable {
     dispose () {
         // Use the setter: disconnect events
         this.skin = null;
+        this._renderer.softwareRenderer.remove_drawable(this.id);
     }
 
     /**
@@ -655,6 +659,14 @@ class Drawable {
 
             this.isTouching = this._isTouchingNever;
         }
+
+        this._renderer.softwareRenderer.set_drawable(
+            this.id,
+            this._uniforms.u_modelMatrix,
+            // TODO: calculate inverse matrix in the Rust side
+            this._inverseMatrix,
+            this.skin.id
+        );
     }
 
     /**
diff --git a/src/PenSkin.js b/src/PenSkin.js
index 8248b1f8d..2961a2580 100644
--- a/src/PenSkin.js
+++ b/src/PenSkin.js
@@ -39,13 +39,7 @@ class PenSkin extends Skin {
      * @listens RenderWebGL#event:NativeSizeChanged
      */
     constructor (id, renderer) {
-        super(id);
-
-        /**
-         * @private
-         * @type {RenderWebGL}
-         */
-        this._renderer = renderer;
+        super(id, renderer);
 
         /** @type {Array<number>} */
         this._size = null;
@@ -339,6 +333,8 @@ class PenSkin extends Skin {
                 gl.RGBA, gl.UNSIGNED_BYTE, this._silhouettePixels
             );
 
+            this._newSilhouette.set_data(this._canvas.width, this._canvas.height, this._silhouettePixels);
+
             this._silhouetteImageData.data.set(this._silhouettePixels);
             this._silhouette.update(this._silhouetteImageData, true /* isPremultiplied */);
 
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index c29931928..465daf611 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -17,14 +17,14 @@ const log = require('./util/log');
 let onLoadSwRender = null;
 let swRenderLoaded = false;
 // eslint-disable-next-line no-unused-vars
-let swrender = null;
+const swrender = require('../swrender/build/swrender.js');
 
 const wasm = require('../swrender/build/swrender_bg.wasm');
 const swrenderInit = require('../swrender/build/swrender.js').default;
 
 swrenderInit(wasm)
-    .then(res => {
-        swrender = res;
+    .then(() => {
+        window.swrender = swrender;
         swRenderLoaded = true;
         if (onLoadSwRender) onLoadSwRender();
     });
@@ -134,6 +134,10 @@ class RenderWebGL extends EventEmitter {
 
         return new Promise(resolve => {
             onLoadSwRender = resolve;
+        }).then(() => {
+            this.swrender = swrender;
+
+            this.softwareRenderer = swrender.SoftwareRenderer.new();
         });
     }
 
@@ -500,7 +504,7 @@ class RenderWebGL extends EventEmitter {
             return;
         }
         const drawableID = this._nextDrawableId++;
-        const drawable = new Drawable(drawableID);
+        const drawable = new Drawable(drawableID, this);
         this._allDrawables[drawableID] = drawable;
         this._addToDrawList(drawableID, group);
 
@@ -992,28 +996,9 @@ class RenderWebGL extends EventEmitter {
         const bounds = this._candidatesBounds(candidates);
 
         const drawable = this._allDrawables[drawableID];
-        const point = __isTouchingDrawablesPoint;
-
         drawable.updateCPURenderAttributes();
 
-        // This is an EXTREMELY brute force collision detector, but it is
-        // still faster than asking the GPU to give us the pixels.
-        for (let x = bounds.left; x <= bounds.right; x++) {
-            // Scratch Space - +y is top
-            point[0] = x;
-            for (let y = bounds.bottom; y <= bounds.top; y++) {
-                point[1] = y;
-                if (drawable.isTouching(point)) {
-                    for (let index = 0; index < candidates.length; index++) {
-                        if (candidates[index].drawable.isTouching(point)) {
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-
-        return false;
+        return this.softwareRenderer.is_touching_drawables(drawableID, candidates.map(c => c.id), bounds);
     }
 
     /**
@@ -1475,6 +1460,7 @@ class RenderWebGL extends EventEmitter {
      */
     _candidatesTouching (drawableID, candidateIDs) {
         const bounds = this._touchingBounds(drawableID);
+        bounds.snapToInt();
         const result = [];
         if (bounds === null) {
             return result;
diff --git a/src/SVGSkin.js b/src/SVGSkin.js
index bf1aeca88..18c17c57d 100644
--- a/src/SVGSkin.js
+++ b/src/SVGSkin.js
@@ -23,10 +23,7 @@ class SVGSkin extends Skin {
      * @extends Skin
      */
     constructor (id, renderer) {
-        super(id);
-
-        /** @type {RenderWebGL} */
-        this._renderer = renderer;
+        super(id, renderer);
 
         /** @type {SvgRenderer} */
         this._svgRenderer = new SvgRenderer();
@@ -119,6 +116,12 @@ class SVGSkin extends Skin {
         // Check if this is the largest MIP created so far. Currently, silhouettes only get scaled up.
         if (this._largestMIPScale < scale) {
             this._silhouette.update(textureData);
+            this._renderer.softwareRenderer.set_silhouette(
+                this._id,
+                textureData.width,
+                textureData.height,
+                textureData.data
+            );
             this._largestMIPScale = scale;
         }
 
diff --git a/src/Skin.js b/src/Skin.js
index ae98d50c9..18c17e999 100644
--- a/src/Skin.js
+++ b/src/Skin.js
@@ -9,11 +9,15 @@ class Skin extends EventEmitter {
     /**
      * Create a Skin, which stores and/or generates textures for use in rendering.
      * @param {int} id - The unique ID for this Skin.
+     * @param {!RenderWebGL} renderer - The renderer which will use this skin.
      * @constructor
      */
-    constructor (id) {
+    constructor (id, renderer) {
         super();
 
+        /** @type {RenderWebGL} */
+        this._renderer = renderer;
+
         /** @type {int} */
         this._id = id;
 
@@ -49,6 +53,8 @@ class Skin extends EventEmitter {
          */
         this._silhouette = new Silhouette();
 
+        renderer.softwareRenderer.set_silhouette(id, 0, 0, new Uint8Array(0));
+
         this.setMaxListeners(RenderConstants.SKIN_SHARE_SOFT_LIMIT);
     }
 
@@ -57,6 +63,7 @@ class Skin extends EventEmitter {
      */
     dispose () {
         this._id = RenderConstants.ID_NONE;
+        this._newSilhouette.free();
     }
 
     /**
@@ -155,6 +162,12 @@ class Skin extends EventEmitter {
         gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
 
         this._silhouette.update(textureData);
+        this._renderer.softwareRenderer.set_silhouette(
+            this._id,
+            textureData.width,
+            textureData.height,
+            textureData.data
+        );
     }
 
     /**
@@ -189,6 +202,7 @@ class Skin extends EventEmitter {
         this._rotationCenter[1] = 0;
 
         this._silhouette.update(this._emptyImageData);
+        this._renderer.softwareRenderer.set_silhouette(this._id, 1, 1, this._emptyImageData);
         this.emit(Skin.Events.WasAltered);
     }
 
diff --git a/src/TextBubbleSkin.js b/src/TextBubbleSkin.js
index 0ce6ac1a2..01aa34b0e 100644
--- a/src/TextBubbleSkin.js
+++ b/src/TextBubbleSkin.js
@@ -34,10 +34,7 @@ class TextBubbleSkin extends Skin {
      * @extends Skin
      */
     constructor (id, renderer) {
-        super(id);
-
-        /** @type {RenderWebGL} */
-        this._renderer = renderer;
+        super(id, renderer);
 
         /** @type {HTMLCanvasElement} */
         this._canvas = document.createElement('canvas');
diff --git a/swrender/src/console_log.rs b/swrender/src/console_log.rs
new file mode 100644
index 000000000..7ade76788
--- /dev/null
+++ b/swrender/src/console_log.rs
@@ -0,0 +1,15 @@
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+extern {
+    #[wasm_bindgen(js_namespace = console)]
+    pub fn log(s: &str);
+}
+
+#[macro_use]
+mod console_log {
+    #[macro_export]
+    macro_rules! console_log {
+        ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
+    }
+}
diff --git a/swrender/src/drawable.rs b/swrender/src/drawable.rs
new file mode 100644
index 000000000..b3fbe03d9
--- /dev/null
+++ b/swrender/src/drawable.rs
@@ -0,0 +1,33 @@
+use crate::silhouette::*;
+use crate::matrix::*;
+
+pub type DrawableID = u32;
+
+pub struct Drawable {
+    pub matrix: Mat4,
+    pub inverse_matrix: Mat4,
+    pub silhouette: SilhouetteID,
+    pub id: DrawableID
+}
+
+impl Drawable {
+    pub fn get_local_position(&self, vec: Vec2) -> Vec2 {
+        let v0 = vec.0 - 0.5;
+        let v1 = vec.1 + 0.5;
+        let m = self.inverse_matrix;
+        let d = (v0 * m[3]) + (v1 * m[7]) + m[15];
+        // The RenderWebGL quad flips the texture's X axis. So rendered bottom
+        // left is 1, 0 and the top right is 0, 1. Flip the X axis so
+        // localPosition matches that transformation.
+        let out_x = 0.5 - (((v0 * m[0]) + (v1 * m[4]) + m[12]) / d);
+        let out_y = (((v0 * m[1]) + (v1 * m[5]) + m[13]) / d) + 0.5;
+
+        (out_x, out_y)
+    }
+
+    #[inline(always)]
+    pub fn is_touching(&self, position: Vec2, silhouette: &Silhouette) -> bool {
+        let local_position = self.get_local_position(position);
+        silhouette.get_point((local_position.0 * silhouette.width as f32) as i32, (local_position.1 * silhouette.height as f32) as i32)
+    }
+}
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
index 579fde4dd..f7251999a 100644
--- a/swrender/src/lib.rs
+++ b/swrender/src/lib.rs
@@ -1,7 +1,13 @@
 mod utils;
+mod matrix;
+pub mod silhouette;
+pub mod drawable;
 
 use wasm_bindgen::prelude::*;
 
+use std::collections::HashMap;
+use std::convert::TryInto;
+
 // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
 // allocator.
 #[cfg(feature = "wee_alloc")]
@@ -11,9 +17,100 @@ static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
 #[wasm_bindgen]
 extern {
     fn alert(s: &str);
+
+    pub type Rectangle;
+
+    #[wasm_bindgen(method, getter)]
+    fn left(this: &Rectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    fn right(this: &Rectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    fn bottom(this: &Rectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    fn top(this: &Rectangle) -> f64;
 }
 
+const ID_NONE: u32 = u32::max_value();
+
 #[wasm_bindgen]
-pub fn greet() {
-    alert("Hello, swrenderTEST5!");
+pub struct SoftwareRenderer {
+    drawables: HashMap<drawable::DrawableID, drawable::Drawable>,
+    silhouettes: HashMap<silhouette::SilhouetteID, silhouette::Silhouette>
+}
+
+#[wasm_bindgen]
+impl SoftwareRenderer {
+    pub fn new() -> SoftwareRenderer {
+        let mut renderer = SoftwareRenderer {
+            drawables: HashMap::new(),
+            silhouettes: HashMap::new()
+        };
+
+        renderer.silhouettes.insert(ID_NONE, silhouette::Silhouette::new(ID_NONE));
+
+        utils::set_panic_hook();
+        renderer
+    }
+
+    pub fn set_drawable(&mut self, id: drawable::DrawableID, matrix: Box<[f32]>, inverse_matrix: Box<[f32]>, silhouette: Option<silhouette::SilhouetteID>) {
+        let d = self.drawables.entry(id).or_insert(drawable::Drawable {
+            matrix: [0.0; 16],
+            inverse_matrix: [0.0; 16],
+            silhouette: match silhouette {
+                Some(s) => s,
+                None => ID_NONE
+            },
+            id
+        });
+
+        d.matrix = (*matrix).try_into().expect("drawable's matrix contains 16 elements");
+        d.inverse_matrix = (*inverse_matrix).try_into().expect("drawable's inverse matrix contains 16 elements");
+        if let Some(s) = silhouette {
+            d.silhouette = s;
+        }
+    }
+
+    pub fn remove_drawable(&mut self, id: drawable::DrawableID) {
+        self.drawables.remove(&id);
+    }
+
+    pub fn set_silhouette(&mut self, id: silhouette::SilhouetteID, w: u32, h: u32, data: Box<[u8]>) {
+        let s = self.silhouettes.entry(id).or_insert(silhouette::Silhouette::new(id));
+        s.set_data(w, h, data);
+    }
+
+    pub fn remove_silhouette(&mut self, id: silhouette::SilhouetteID) {
+        self.silhouettes.remove(&id);
+    }
+
+    pub fn is_touching_drawables(&mut self, drawable: drawable::DrawableID, candidates: Vec<drawable::DrawableID>, rect: Rectangle) -> bool {
+        let left = rect.left() as i32;
+        let right = rect.right() as i32 + 1;
+        let bottom = rect.bottom() as i32 - 1;
+        let top = rect.top() as i32;
+
+        let drawable = self.drawables.get(&drawable).expect("Drawable should exist");
+        let silhouette = self.silhouettes.get(&drawable.silhouette).unwrap();
+        let candidates: Vec<(&drawable::Drawable, &silhouette::Silhouette)> = candidates.into_iter()
+            .map(|c| {
+                let d = self.drawables.get(&c).expect("Candidate drawable should exist");
+                let s = self.silhouettes.get(&d.silhouette).unwrap();
+                (d, s)
+            }).collect();
+
+        for x in left..right {
+            for y in bottom..top {
+                let position = (x as f32, y as f32);
+                if drawable.is_touching(position, silhouette) {
+                    for candidate in &candidates {
+                        if candidate.0.is_touching(position, candidate.1) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+
+        false
+    }
 }
diff --git a/swrender/src/matrix.rs b/swrender/src/matrix.rs
new file mode 100644
index 000000000..b4bcee9f5
--- /dev/null
+++ b/swrender/src/matrix.rs
@@ -0,0 +1,12 @@
+pub type Mat4 = [f32; 16];
+pub type Vec2 = (f32, f32);
+
+trait Matrix {
+    fn inverse(&self) -> Self;
+}
+
+impl Matrix for Mat4 {
+    fn inverse(&self) -> Mat4 {
+        unimplemented!()
+    }
+}
diff --git a/swrender/src/silhouette.rs b/swrender/src/silhouette.rs
new file mode 100644
index 000000000..277e8492c
--- /dev/null
+++ b/swrender/src/silhouette.rs
@@ -0,0 +1,50 @@
+use wasm_bindgen::prelude::*;
+
+pub type SilhouetteID = u32;
+
+#[wasm_bindgen]
+pub struct Silhouette {
+    pub width: u32,
+    pub height: u32,
+    pub id: SilhouetteID,
+    data: Box<[u8]>,
+    _blank: Box<[u8; 4]>
+}
+
+impl Silhouette {
+    pub fn new(id: SilhouetteID) -> Silhouette {
+        Silhouette {
+            width: 0,
+            height: 0,
+            id,
+            data: Box::new([0, 0, 0, 0]),
+            _blank: Box::new([0, 0, 0, 0])
+        }
+    }
+
+    pub fn set_data(&mut self, w: u32, h: u32, data: Box<[u8]>) {
+        assert_eq!(data.len(), (w * h * 4) as usize, "silhouette data is improperly sized");
+
+        self.width = w;
+        self.height = h;
+        self.data = data;
+    }
+
+    pub fn get_point(&self, x: i32, y: i32) -> bool {
+        if x < 0 || y < 0 || (x as u32) >= self.width || (y as u32) >= self.height {
+            false
+        } else {
+            let idx = (((y as u32 * self.width) + x as u32) * 4) as usize;
+            self.data[idx+3] != 0u8
+        }
+    }
+
+    pub fn get_color(&self, x: i32, y: i32) -> &[u8] {
+        if x < 0 || y < 0 || (x as u32) >= self.width || (y as u32) >= self.height {
+            &self._blank[0..4]
+        } else {
+            let idx = (((y as u32 * self.width) + x as u32) * 4) as usize;
+            &self.data[idx..idx+4]
+        }
+    }
+}

From 84cea1fef730b7e90fa2dd801b2b7197fbb65c0e Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Fri, 20 Mar 2020 19:53:57 -0400
Subject: [PATCH 03/14] Add distortion effects

---
 src/Drawable.js                  |  19 ++++-
 src/PenSkin.js                   |   4 +-
 src/RenderWebGL.js               |   2 +-
 src/SVGSkin.js                   |   7 +-
 src/Skin.js                      |  24 ++++--
 swrender/src/drawable.rs         |  19 ++++-
 swrender/src/effect_transform.rs | 135 +++++++++++++++++++++++++++++
 swrender/src/lib.rs              |  63 +++++++++-----
 swrender/src/matrix.rs           | 142 ++++++++++++++++++++++++++++++-
 swrender/src/rectangle.rs        |  33 +++++++
 swrender/src/silhouette.rs       |  12 +--
 11 files changed, 406 insertions(+), 54 deletions(-)
 create mode 100644 swrender/src/effect_transform.rs
 create mode 100644 swrender/src/rectangle.rs

diff --git a/src/Drawable.js b/src/Drawable.js
index 4ca3f1dec..872e7287e 100644
--- a/src/Drawable.js
+++ b/src/Drawable.js
@@ -116,6 +116,8 @@ class Drawable {
          * @type {int} */
         this.enabledEffects = 0;
 
+        this._effectsDirty = true;
+
         /** @todo move convex hull functionality, maybe bounds functionality overall, to Skin classes */
         this._convexHullPoints = null;
         this._convexHullDirty = true;
@@ -255,6 +257,10 @@ class Drawable {
         }
     }
 
+    setEffectsDirty () {
+        this._effectsDirty = true;
+    }
+
     /**
      * Update an effect. Marks the convex hull as dirty if the effect changes shape.
      * @param {string} effectName The name of the effect.
@@ -272,6 +278,7 @@ class Drawable {
         if (effectInfo.shapeChanges) {
             this.setConvexHullDirty();
         }
+        this.setEffectsDirty();
     }
 
     /**
@@ -660,12 +667,18 @@ class Drawable {
             this.isTouching = this._isTouchingNever;
         }
 
+        let effects = null;
+        if (this._effectsDirty) {
+            effects = this._uniforms;
+            this._effectsDirty = false;
+        }
+
         this._renderer.softwareRenderer.set_drawable(
             this.id,
             this._uniforms.u_modelMatrix,
-            // TODO: calculate inverse matrix in the Rust side
-            this._inverseMatrix,
-            this.skin.id
+            this.skin.id,
+            effects,
+            this.enabledEffects
         );
     }
 
diff --git a/src/PenSkin.js b/src/PenSkin.js
index 2961a2580..c2096219a 100644
--- a/src/PenSkin.js
+++ b/src/PenSkin.js
@@ -333,11 +333,11 @@ class PenSkin extends Skin {
                 gl.RGBA, gl.UNSIGNED_BYTE, this._silhouettePixels
             );
 
-            this._newSilhouette.set_data(this._canvas.width, this._canvas.height, this._silhouettePixels);
-
             this._silhouetteImageData.data.set(this._silhouettePixels);
             this._silhouette.update(this._silhouetteImageData, true /* isPremultiplied */);
 
+            this._setSilhouetteFromData(this._silhouetteImageData);
+
             this._silhouetteDirty = false;
         }
     }
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index 465daf611..beb1272db 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -1460,11 +1460,11 @@ class RenderWebGL extends EventEmitter {
      */
     _candidatesTouching (drawableID, candidateIDs) {
         const bounds = this._touchingBounds(drawableID);
-        bounds.snapToInt();
         const result = [];
         if (bounds === null) {
             return result;
         }
+        bounds.snapToInt();
         // iterate through the drawables list BACKWARDS - we want the top most item to be the first we check
         for (let index = candidateIDs.length - 1; index >= 0; index--) {
             const id = candidateIDs[index];
diff --git a/src/SVGSkin.js b/src/SVGSkin.js
index 18c17c57d..3d3ec5234 100644
--- a/src/SVGSkin.js
+++ b/src/SVGSkin.js
@@ -116,12 +116,7 @@ class SVGSkin extends Skin {
         // Check if this is the largest MIP created so far. Currently, silhouettes only get scaled up.
         if (this._largestMIPScale < scale) {
             this._silhouette.update(textureData);
-            this._renderer.softwareRenderer.set_silhouette(
-                this._id,
-                textureData.width,
-                textureData.height,
-                textureData.data
-            );
+            this._setSilhouetteFromData(textureData);
             this._largestMIPScale = scale;
         }
 
diff --git a/src/Skin.js b/src/Skin.js
index 18c17e999..3ceb60802 100644
--- a/src/Skin.js
+++ b/src/Skin.js
@@ -63,7 +63,7 @@ class Skin extends EventEmitter {
      */
     dispose () {
         this._id = RenderConstants.ID_NONE;
-        this._newSilhouette.free();
+        if (this._newSilhouette) this._newSilhouette.free();
     }
 
     /**
@@ -162,12 +162,7 @@ class Skin extends EventEmitter {
         gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
 
         this._silhouette.update(textureData);
-        this._renderer.softwareRenderer.set_silhouette(
-            this._id,
-            textureData.width,
-            textureData.height,
-            textureData.data
-        );
+        this._setSilhouetteFromData(textureData);
     }
 
     /**
@@ -202,10 +197,23 @@ class Skin extends EventEmitter {
         this._rotationCenter[1] = 0;
 
         this._silhouette.update(this._emptyImageData);
-        this._renderer.softwareRenderer.set_silhouette(this._id, 1, 1, this._emptyImageData);
+        this._setSilhouetteFromData(this._emptyImageData);
         this.emit(Skin.Events.WasAltered);
     }
 
+    _setSilhouetteFromData (data) {
+        const size = this.size;
+        this._renderer.softwareRenderer.set_silhouette(
+            this._id,
+            data.width,
+            data.height,
+            data.data,
+
+            size[0],
+            size[1]
+        );
+    }
+
     /**
      * Does this point touch an opaque or translucent point on this skin?
      * Nearest Neighbor version
diff --git a/swrender/src/drawable.rs b/swrender/src/drawable.rs
index b3fbe03d9..f7e04ef89 100644
--- a/swrender/src/drawable.rs
+++ b/swrender/src/drawable.rs
@@ -1,5 +1,6 @@
 use crate::silhouette::*;
 use crate::matrix::*;
+use crate::effect_transform::{Effects, EffectBits, transform_point, DISTORTION_EFFECT_MASK};
 
 pub type DrawableID = u32;
 
@@ -7,7 +8,9 @@ pub struct Drawable {
     pub matrix: Mat4,
     pub inverse_matrix: Mat4,
     pub silhouette: SilhouetteID,
-    pub id: DrawableID
+    pub id: DrawableID,
+    pub effects: Effects,
+    pub effect_bits: EffectBits
 }
 
 impl Drawable {
@@ -22,12 +25,24 @@ impl Drawable {
         let out_x = 0.5 - (((v0 * m[0]) + (v1 * m[4]) + m[12]) / d);
         let out_y = (((v0 * m[1]) + (v1 * m[5]) + m[13]) / d) + 0.5;
 
-        (out_x, out_y)
+        Vec2(out_x, out_y)
+    }
+
+    pub fn get_transformed_position(&self, vec: Vec2, skin_size: Vec2) -> Vec2 {
+        if (self.effect_bits & DISTORTION_EFFECT_MASK) == 0 {
+            vec
+        } else {
+            transform_point(vec, &self.effects, &self.effect_bits, skin_size)
+        }
     }
 
     #[inline(always)]
     pub fn is_touching(&self, position: Vec2, silhouette: &Silhouette) -> bool {
         let local_position = self.get_local_position(position);
+        if local_position.0 < 0f32 || local_position.0 >= 1f32 || local_position.1 < 0f32 || local_position.1 >= 1f32 {
+            return false;
+        }
+        let local_position = self.get_transformed_position(local_position, silhouette.nominal_size);
         silhouette.get_point((local_position.0 * silhouette.width as f32) as i32, (local_position.1 * silhouette.height as f32) as i32)
     }
 }
diff --git a/swrender/src/effect_transform.rs b/swrender/src/effect_transform.rs
new file mode 100644
index 000000000..8ef23d6ef
--- /dev/null
+++ b/swrender/src/effect_transform.rs
@@ -0,0 +1,135 @@
+use crate::matrix::*;
+
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+extern {
+    pub type JSEffectMap;
+
+    #[wasm_bindgen(method, getter)]
+    pub fn u_color(this: &JSEffectMap) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn u_fisheye(this: &JSEffectMap) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn u_whirl(this: &JSEffectMap) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn u_pixelate(this: &JSEffectMap) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn u_mosaic(this: &JSEffectMap) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn u_brightness(this: &JSEffectMap) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn u_ghost(this: &JSEffectMap) -> f64;
+}
+
+#[derive(Default)]
+pub struct Effects {
+    pub color: f32,
+    pub fisheye: f32,
+    pub whirl: f32,
+    pub pixelate: f32,
+    pub mosaic: f32,
+    pub brightness: f32,
+    pub ghost: f32,
+}
+
+pub type EffectBits = u32;
+pub enum EffectBitfield {
+    Color = 0,
+    Fisheye = 1,
+    Whirl = 2,
+    Pixelate = 3,
+    Mosaic = 4,
+    Brightness = 5,
+    Ghost = 6,
+}
+
+pub const DISTORTION_EFFECT_MASK: EffectBits =
+    1 << (EffectBitfield::Fisheye as u32) |
+    1 << (EffectBitfield::Whirl as u32) |
+    1 << (EffectBitfield::Pixelate as u32) |
+    1 << (EffectBitfield::Mosaic as u32);
+
+impl Effects {
+    pub fn set_from_js(&mut self, effects: JSEffectMap) {
+        self.color = effects.u_color() as f32;
+        self.fisheye = effects.u_fisheye() as f32;
+        self.whirl = effects.u_whirl() as f32;
+        self.pixelate = effects.u_pixelate() as f32;
+        self.mosaic = effects.u_mosaic() as f32;
+        self.brightness = effects.u_brightness() as f32;
+        self.ghost = effects.u_ghost() as f32;
+    }
+}
+
+const CENTER: Vec2 = Vec2(0.5, 0.5);
+
+pub fn transform_point(point: Vec2, effects: &Effects, effect_bits: &EffectBits, skin_size: Vec2) -> Vec2 {
+    let mut out = point;
+
+    if effect_bits & (1 << (EffectBitfield::Mosaic as u32)) != 0 {
+        /*texcoord0 = fract(u_mosaic * texcoord0);*/
+        out = Vec2(
+            f32::fract(effects.mosaic * out.0),
+            f32::fract(effects.mosaic * out.1)
+        );
+    }
+
+    if effect_bits & (1 << (EffectBitfield::Pixelate as u32)) != 0 {
+        /*vec2 pixelTexelSize = u_skinSize / u_pixelate;
+        texcoord0 = (floor(texcoord0 * pixelTexelSize) + kCenter) / pixelTexelSize;*/
+        let pixel_texel_size_x = skin_size.0 / effects.pixelate;
+        let pixel_texel_size_y = skin_size.1 / effects.pixelate;
+
+        out = Vec2(
+            (f32::floor(out.0 * pixel_texel_size_x) + CENTER.0) / pixel_texel_size_x,
+            (f32::floor(out.1 * pixel_texel_size_y) + CENTER.1) / pixel_texel_size_y
+        );
+    }
+
+    if effect_bits & (1 << (EffectBitfield::Whirl as u32)) != 0 {
+        /*const float kRadius = 0.5;
+        vec2 offset = texcoord0 - kCenter;
+        float offsetMagnitude = length(offset);
+        float whirlFactor = max(1.0 - (offsetMagnitude / kRadius), 0.0);
+        float whirlActual = u_whirl * whirlFactor * whirlFactor;
+        float sinWhirl = sin(whirlActual);
+        float cosWhirl = cos(whirlActual);
+        mat2 rotationMatrix = mat2(
+            cosWhirl, -sinWhirl,
+            sinWhirl, cosWhirl
+        );
+
+        texcoord0 = rotationMatrix * offset + kCenter;*/
+
+        const RADIUS: f32 = 0.5;
+        let offset = out - CENTER;
+        let offset_magnitude = offset.length();
+        let whirl_factor = f32::max(1.0 - (offset_magnitude / RADIUS), 0.0);
+        let whirl_actual = effects.whirl * whirl_factor * whirl_factor;
+        let (sin_whirl, cos_whirl) = f32::sin_cos(whirl_actual);
+
+        // texcoord0 = rotationMatrix * offset + kCenter;
+        out.0 = (cos_whirl * offset.0) + (sin_whirl * offset.1) + CENTER.0;
+        out.1 = (cos_whirl * offset.1) - (sin_whirl * offset.0) + CENTER.1;
+    }
+
+    if effect_bits & (1 << (EffectBitfield::Fisheye as u32)) != 0 {
+        /* vec2 vec = (texcoord0 - kCenter) / kCenter;
+        float vecLength = length(vec);
+        float r = pow(min(vecLength, 1.0), u_fisheye) * max(1.0, vecLength);
+        vec2 unit = vec / vecLength;
+
+        texcoord0 = kCenter + r * unit * kCenter;*/
+
+        let v = (out - CENTER) / CENTER;
+
+        let len = v.length();
+        let r = f32::powf(f32::min(len, 1.0), effects.fisheye) * f32::max(1.0, len);
+        let unit: Vec2 = v / Vec2(len, len);
+
+        out = CENTER + Vec2(r, r) * unit * CENTER;
+    }
+
+    out
+}
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
index f7251999a..b3f29594e 100644
--- a/swrender/src/lib.rs
+++ b/swrender/src/lib.rs
@@ -1,5 +1,7 @@
 mod utils;
+mod rectangle;
 mod matrix;
+mod effect_transform;
 pub mod silhouette;
 pub mod drawable;
 
@@ -8,28 +10,14 @@ use wasm_bindgen::prelude::*;
 use std::collections::HashMap;
 use std::convert::TryInto;
 
+use matrix::Matrix;
+
 // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
 // allocator.
 #[cfg(feature = "wee_alloc")]
 #[global_allocator]
 static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
 
-#[wasm_bindgen]
-extern {
-    fn alert(s: &str);
-
-    pub type Rectangle;
-
-    #[wasm_bindgen(method, getter)]
-    fn left(this: &Rectangle) -> f64;
-    #[wasm_bindgen(method, getter)]
-    fn right(this: &Rectangle) -> f64;
-    #[wasm_bindgen(method, getter)]
-    fn bottom(this: &Rectangle) -> f64;
-    #[wasm_bindgen(method, getter)]
-    fn top(this: &Rectangle) -> f64;
-}
-
 const ID_NONE: u32 = u32::max_value();
 
 #[wasm_bindgen]
@@ -52,10 +40,19 @@ impl SoftwareRenderer {
         renderer
     }
 
-    pub fn set_drawable(&mut self, id: drawable::DrawableID, matrix: Box<[f32]>, inverse_matrix: Box<[f32]>, silhouette: Option<silhouette::SilhouetteID>) {
+    pub fn set_drawable(
+        &mut self,
+        id: drawable::DrawableID,
+        matrix: Option<Box<[f32]>>,
+        silhouette: Option<silhouette::SilhouetteID>,
+        effects: Option<effect_transform::JSEffectMap>,
+        effect_bits: effect_transform::EffectBits
+    ) {
         let d = self.drawables.entry(id).or_insert(drawable::Drawable {
             matrix: [0.0; 16],
             inverse_matrix: [0.0; 16],
+            effects: effect_transform::Effects::default(),
+            effect_bits: 0,
             silhouette: match silhouette {
                 Some(s) => s,
                 None => ID_NONE
@@ -63,27 +60,47 @@ impl SoftwareRenderer {
             id
         });
 
-        d.matrix = (*matrix).try_into().expect("drawable's matrix contains 16 elements");
-        d.inverse_matrix = (*inverse_matrix).try_into().expect("drawable's inverse matrix contains 16 elements");
+        if let Some(m) = matrix {
+            d.matrix = (*m).try_into().expect("drawable's matrix contains 16 elements");
+            d.inverse_matrix = d.matrix.inverse();
+        }
         if let Some(s) = silhouette {
             d.silhouette = s;
         }
+        if let Some(fx) = effects {
+            d.effects.set_from_js(fx);
+        }
+        d.effect_bits = effect_bits;
     }
 
     pub fn remove_drawable(&mut self, id: drawable::DrawableID) {
         self.drawables.remove(&id);
     }
 
-    pub fn set_silhouette(&mut self, id: silhouette::SilhouetteID, w: u32, h: u32, data: Box<[u8]>) {
+    pub fn set_silhouette(
+        &mut self,
+        id: silhouette::SilhouetteID,
+        w: u32,
+        h: u32,
+        data: Box<[u8]>,
+        nominal_width:
+        f64, nominal_height: f64
+    ) {
         let s = self.silhouettes.entry(id).or_insert(silhouette::Silhouette::new(id));
-        s.set_data(w, h, data);
+        s.set_data(w, h, data, matrix::Vec2(nominal_width as f32, nominal_height as f32));
     }
 
     pub fn remove_silhouette(&mut self, id: silhouette::SilhouetteID) {
         self.silhouettes.remove(&id);
     }
 
-    pub fn is_touching_drawables(&mut self, drawable: drawable::DrawableID, candidates: Vec<drawable::DrawableID>, rect: Rectangle) -> bool {
+    pub fn is_touching_drawables(
+        &mut self,
+        drawable: drawable::DrawableID,
+        candidates:
+        Vec<drawable::DrawableID>,
+        rect: rectangle::JSRectangle
+    ) -> bool {
         let left = rect.left() as i32;
         let right = rect.right() as i32 + 1;
         let bottom = rect.bottom() as i32 - 1;
@@ -100,7 +117,7 @@ impl SoftwareRenderer {
 
         for x in left..right {
             for y in bottom..top {
-                let position = (x as f32, y as f32);
+                let position = matrix::Vec2(x as f32, y as f32);
                 if drawable.is_touching(position, silhouette) {
                     for candidate in &candidates {
                         if candidate.0.is_touching(position, candidate.1) {
diff --git a/swrender/src/matrix.rs b/swrender/src/matrix.rs
index b4bcee9f5..5610bd7c7 100644
--- a/swrender/src/matrix.rs
+++ b/swrender/src/matrix.rs
@@ -1,12 +1,146 @@
+use std::ops;
+use std::f32;
+
 pub type Mat4 = [f32; 16];
-pub type Vec2 = (f32, f32);
 
-trait Matrix {
+#[derive(Copy, Clone)]
+pub struct Vec2(pub f32, pub f32);
+
+impl ops::Add for Vec2 {
+    type Output = Vec2;
+
+    fn add(self, other: Vec2) -> Vec2 {
+        Vec2(self.0 + other.0, self.1 + other.1)
+    }
+}
+
+impl ops::Sub for Vec2 {
+    type Output = Vec2;
+
+    fn sub(self, other: Vec2) -> Vec2 {
+        Vec2(self.0 - other.0, self.1 - other.1)
+    }
+}
+
+impl ops::Mul for Vec2 {
+    type Output = Vec2;
+
+    fn mul(self, other: Vec2) -> Vec2 {
+        Vec2(self.0 * other.0, self.1 * other.1)
+    }
+}
+
+impl ops::Div for Vec2 {
+    type Output = Vec2;
+
+    fn div(self, other: Vec2) -> Vec2 {
+        Vec2(self.0 / other.0, self.1 / other.1)
+    }
+}
+
+impl ops::Neg for Vec2 {
+    type Output = Vec2;
+
+    fn neg(self) -> Vec2 {
+        Vec2(-self.0, -self.1)
+    }
+}
+
+impl Vec2 {
+    pub fn length(&self) -> f32 {
+        f32::sqrt(self.0 * self.0 + self.1 * self.1)
+    }
+}
+
+pub trait Matrix {
     fn inverse(&self) -> Self;
 }
 
 impl Matrix for Mat4 {
-    fn inverse(&self) -> Mat4 {
-        unimplemented!()
+    fn inverse(&self) -> Self {
+        let m00 = self[0 * 4 + 0];
+        let m01 = self[0 * 4 + 1];
+        let m02 = self[0 * 4 + 2];
+        let m03 = self[0 * 4 + 3];
+        let m10 = self[1 * 4 + 0];
+        let m11 = self[1 * 4 + 1];
+        let m12 = self[1 * 4 + 2];
+        let m13 = self[1 * 4 + 3];
+        let m20 = self[2 * 4 + 0];
+        let m21 = self[2 * 4 + 1];
+        let m22 = self[2 * 4 + 2];
+        let m23 = self[2 * 4 + 3];
+        let m30 = self[3 * 4 + 0];
+        let m31 = self[3 * 4 + 1];
+        let m32 = self[3 * 4 + 2];
+        let m33 = self[3 * 4 + 3];
+        let tmp_0  = m22 * m33;
+        let tmp_1  = m32 * m23;
+        let tmp_2  = m12 * m33;
+        let tmp_3  = m32 * m13;
+        let tmp_4  = m12 * m23;
+        let tmp_5  = m22 * m13;
+        let tmp_6  = m02 * m33;
+        let tmp_7  = m32 * m03;
+        let tmp_8  = m02 * m23;
+        let tmp_9  = m22 * m03;
+        let tmp_10 = m02 * m13;
+        let tmp_11 = m12 * m03;
+        let tmp_12 = m20 * m31;
+        let tmp_13 = m30 * m21;
+        let tmp_14 = m10 * m31;
+        let tmp_15 = m30 * m11;
+        let tmp_16 = m10 * m21;
+        let tmp_17 = m20 * m11;
+        let tmp_18 = m00 * m31;
+        let tmp_19 = m30 * m01;
+        let tmp_20 = m00 * m21;
+        let tmp_21 = m20 * m01;
+        let tmp_22 = m00 * m11;
+        let tmp_23 = m10 * m01;
+
+        let t0: f32 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
+            (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
+        let t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
+            (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
+        let t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
+            (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
+        let t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
+            (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
+
+        let d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
+
+        let mut dst: Mat4 = [0f32; 16];
+
+        dst[ 0] = d * t0;
+        dst[ 1] = d * t1;
+        dst[ 2] = d * t2;
+        dst[ 3] = d * t3;
+        dst[ 4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
+                (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30));
+        dst[ 5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
+                (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30));
+        dst[ 6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
+                (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30));
+        dst[ 7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
+                (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20));
+        dst[ 8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
+                (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33));
+        dst[ 9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
+                (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33));
+        dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
+                (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33));
+        dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
+                (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23));
+        dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
+                (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22));
+        dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
+                (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02));
+        dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
+                (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12));
+        dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
+                (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02));
+
+        dst
     }
 }
diff --git a/swrender/src/rectangle.rs b/swrender/src/rectangle.rs
new file mode 100644
index 000000000..e4ad40b23
--- /dev/null
+++ b/swrender/src/rectangle.rs
@@ -0,0 +1,33 @@
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+extern {
+    pub type JSRectangle;
+
+    #[wasm_bindgen(method, getter)]
+    pub fn left(this: &JSRectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn right(this: &JSRectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn bottom(this: &JSRectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn top(this: &JSRectangle) -> f64;
+}
+
+pub struct Rectangle<T> {
+    left: T,
+    right: T,
+    bottom: T,
+    top: T
+}
+
+impl Rectangle<i32> {
+    pub fn fromJSRectangle(rect: JSRectangle) -> Self {
+        Rectangle {
+            left: rect.left().floor() as i32,
+            right: rect.right().ceil() as i32,
+            bottom: rect.bottom().floor() as i32,
+            top: rect.top().ceil() as i32
+        }
+    }
+}
diff --git a/swrender/src/silhouette.rs b/swrender/src/silhouette.rs
index 277e8492c..d089ec667 100644
--- a/swrender/src/silhouette.rs
+++ b/swrender/src/silhouette.rs
@@ -1,12 +1,12 @@
-use wasm_bindgen::prelude::*;
+use crate::matrix::Vec2;
 
 pub type SilhouetteID = u32;
 
-#[wasm_bindgen]
 pub struct Silhouette {
+    pub id: SilhouetteID,
     pub width: u32,
     pub height: u32,
-    pub id: SilhouetteID,
+    pub nominal_size: Vec2,
     data: Box<[u8]>,
     _blank: Box<[u8; 4]>
 }
@@ -14,20 +14,22 @@ pub struct Silhouette {
 impl Silhouette {
     pub fn new(id: SilhouetteID) -> Silhouette {
         Silhouette {
+            id,
             width: 0,
             height: 0,
-            id,
+            nominal_size: Vec2(0f32, 0f32),
             data: Box::new([0, 0, 0, 0]),
             _blank: Box::new([0, 0, 0, 0])
         }
     }
 
-    pub fn set_data(&mut self, w: u32, h: u32, data: Box<[u8]>) {
+    pub fn set_data(&mut self, w: u32, h: u32, data: Box<[u8]>, nominal_size: Vec2) {
         assert_eq!(data.len(), (w * h * 4) as usize, "silhouette data is improperly sized");
 
         self.width = w;
         self.height = h;
         self.data = data;
+        self.nominal_size = nominal_size;
     }
 
     pub fn get_point(&self, x: i32, y: i32) -> bool {

From be15bc6879793b92e4f45c68b0e7b925f64cae21 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Sat, 21 Mar 2020 20:04:32 -0400
Subject: [PATCH 04/14] Add touching color

---
 src/Drawable.js                  |  34 +----
 src/EffectTransform.js           |  95 --------------
 src/PenSkin.js                   |   2 +-
 src/RenderWebGL.js               | 118 +++++-------------
 src/ShaderManager.js             |   3 +-
 src/Silhouette.js                | 123 +-----------------
 src/Skin.js                      |   8 +-
 swrender/src/drawable.rs         |  38 +++++-
 swrender/src/effect_transform.rs | 137 +++++++++++++++++++-
 swrender/src/lib.rs              | 206 +++++++++++++++++++++++++++----
 swrender/src/rectangle.rs        |  33 -----
 swrender/src/silhouette.rs       |  64 ++++++++--
 12 files changed, 447 insertions(+), 414 deletions(-)
 delete mode 100644 swrender/src/rectangle.rs

diff --git a/src/Drawable.js b/src/Drawable.js
index 872e7287e..18de2a8e2 100644
--- a/src/Drawable.js
+++ b/src/Drawable.js
@@ -678,7 +678,8 @@ class Drawable {
             this._uniforms.u_modelMatrix,
             this.skin.id,
             effects,
-            this.enabledEffects
+            this.enabledEffects,
+            this.skin.useNearest(this._scale, this)
         );
     }
 
@@ -723,37 +724,6 @@ class Drawable {
         id |= (b & 255) << 16;
         return id + RenderConstants.ID_NONE;
     }
-
-    /**
-     * Sample a color from a drawable's texture.
-     * The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date.
-     * @see updateCPURenderAttributes
-     * @param {twgl.v3} vec The scratch space [x,y] vector
-     * @param {Drawable} drawable The drawable to sample the texture from
-     * @param {Uint8ClampedArray} dst The "color4b" representation of the texture at point.
-     * @param {number} [effectMask] A bitmask for which effects to use. Optional.
-     * @returns {Uint8ClampedArray} The dst object filled with the color4b
-     */
-    static sampleColor4b (vec, drawable, dst, effectMask) {
-        const localPosition = getLocalPosition(drawable, vec);
-        if (localPosition[0] < 0 || localPosition[1] < 0 ||
-            localPosition[0] > 1 || localPosition[1] > 1) {
-            dst[0] = 0;
-            dst[1] = 0;
-            dst[2] = 0;
-            dst[3] = 0;
-            return dst;
-        }
-
-        const textColor =
-        // commenting out to only use nearest for now
-        // drawable.skin.useNearest(drawable._scale, drawable) ?
-             drawable.skin._silhouette.colorAtNearest(localPosition, dst);
-        // : drawable.skin._silhouette.colorAtLinear(localPosition, dst);
-
-        if (drawable.enabledEffects === 0) return textColor;
-        return EffectTransform.transformColor(drawable, textColor, effectMask);
-    }
 }
 
 module.exports = Drawable;
diff --git a/src/EffectTransform.js b/src/EffectTransform.js
index 7ffae18ab..33be6959e 100644
--- a/src/EffectTransform.js
+++ b/src/EffectTransform.js
@@ -21,103 +21,8 @@ const CENTER_X = 0.5;
  */
 const CENTER_Y = 0.5;
 
-/**
- * Reused memory location for storing an HSV color value.
- * @type {Array<number>}
- */
-const __hsv = [0, 0, 0];
-
 class EffectTransform {
 
-    /**
-     * Transform a color in-place given the drawable's effect uniforms.  Will apply
-     * Ghost and Color and Brightness effects.
-     * @param {Drawable} drawable The drawable to get uniforms from.
-     * @param {Uint8ClampedArray} inOutColor The color to transform.
-     * @param {number} [effectMask] A bitmask for which effects to use. Optional.
-     * @returns {Uint8ClampedArray} dst filled with the transformed color
-     */
-    static transformColor (drawable, inOutColor, effectMask) {
-        // If the color is fully transparent, don't bother attempting any transformations.
-        if (inOutColor[3] === 0) {
-            return inOutColor;
-        }
-
-        let effects = drawable.enabledEffects;
-        if (typeof effectMask === 'number') effects &= effectMask;
-        const uniforms = drawable.getUniforms();
-
-        const enableColor = (effects & ShaderManager.EFFECT_INFO.color.mask) !== 0;
-        const enableBrightness = (effects & ShaderManager.EFFECT_INFO.brightness.mask) !== 0;
-
-        if (enableColor || enableBrightness) {
-            // gl_FragColor.rgb /= gl_FragColor.a + epsilon;
-            // Here, we're dividing by the (previously pre-multiplied) alpha to ensure HSV is properly calculated
-            // for partially transparent pixels.
-            // epsilon is present in the shader because dividing by 0 (fully transparent pixels) messes up calculations.
-            // We're doing this with a Uint8ClampedArray here, so dividing by 0 just gives 255. We're later multiplying
-            // by 0 again, so it won't affect results.
-            const alpha = inOutColor[3] / 255;
-            inOutColor[0] /= alpha;
-            inOutColor[1] /= alpha;
-            inOutColor[2] /= alpha;
-
-            if (enableColor) {
-                // vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
-                const hsv = rgbToHsv(inOutColor, __hsv);
-
-                // this code forces grayscale values to be slightly saturated
-                // so that some slight change of hue will be visible
-                // const float minLightness = 0.11 / 2.0;
-                const minV = 0.11 / 2.0;
-                // const float minSaturation = 0.09;
-                const minS = 0.09;
-                // if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
-                if (hsv[2] < minV) {
-                    hsv[0] = 0;
-                    hsv[1] = 1;
-                    hsv[2] = minV;
-                // else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
-                } else if (hsv[1] < minS) {
-                    hsv[0] = 0;
-                    hsv[1] = minS;
-                }
-
-                // hsv.x = mod(hsv.x + u_color, 1.0);
-                // if (hsv.x < 0.0) hsv.x += 1.0;
-                hsv[0] = (uniforms.u_color + hsv[0] + 1);
-
-                // gl_FragColor.rgb = convertHSV2RGB(hsl);
-                hsvToRgb(hsv, inOutColor);
-            }
-
-            if (enableBrightness) {
-                const brightness = uniforms.u_brightness * 255;
-                // gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1));
-                // We don't need to clamp because the Uint8ClampedArray does that for us
-                inOutColor[0] += brightness;
-                inOutColor[1] += brightness;
-                inOutColor[2] += brightness;
-            }
-
-            // gl_FragColor.rgb *= gl_FragColor.a + epsilon;
-            // Now we're doing the reverse, premultiplying by the alpha once again.
-            inOutColor[0] *= alpha;
-            inOutColor[1] *= alpha;
-            inOutColor[2] *= alpha;
-        }
-
-        if ((effects & ShaderManager.EFFECT_INFO.ghost.mask) !== 0) {
-            // gl_FragColor *= u_ghost
-            inOutColor[0] *= uniforms.u_ghost;
-            inOutColor[1] *= uniforms.u_ghost;
-            inOutColor[2] *= uniforms.u_ghost;
-            inOutColor[3] *= uniforms.u_ghost;
-        }
-
-        return inOutColor;
-    }
-
     /**
      * Transform a texture coordinate to one that would be select after applying shader effects.
      * @param {Drawable} drawable The drawable whose effects to emulate.
diff --git a/src/PenSkin.js b/src/PenSkin.js
index c2096219a..7bcd4cd0c 100644
--- a/src/PenSkin.js
+++ b/src/PenSkin.js
@@ -336,7 +336,7 @@ class PenSkin extends Skin {
             this._silhouetteImageData.data.set(this._silhouettePixels);
             this._silhouette.update(this._silhouetteImageData, true /* isPremultiplied */);
 
-            this._setSilhouetteFromData(this._silhouetteImageData);
+            this._setSilhouetteFromData(this._silhouetteImageData, true /* isPremultiplied */);
 
             this._silhouetteDirty = false;
         }
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index beb1272db..fbd346cd3 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -64,21 +64,6 @@ const MASK_TOUCHING_COLOR_TOLERANCE = 2;
  */
 const MAX_EXTRACTED_DRAWABLE_DIMENSION = 2048;
 
-/**
- * Determines if the mask color is "close enough" (only test the 6 top bits for
- * each color).  These bit masks are what scratch 2 used to use, so we do the same.
- * @param {Uint8Array} a A color3b or color4b value.
- * @param {Uint8Array} b A color3b or color4b value.
- * @returns {boolean} If the colors match within the parameters.
- */
-const maskMatches = (a, b) => (
-    // has some non-alpha component to test against
-    a[3] > 0 &&
-    (a[0] & 0b11111100) === (b[0] & 0b11111100) &&
-    (a[1] & 0b11111100) === (b[1] & 0b11111100) &&
-    (a[2] & 0b11111100) === (b[2] & 0b11111100)
-);
-
 /**
  * Determines if the given color is "close enough" (only test the 5 top bits for
  * red and green, 4 bits for blue).  These bit masks are what scratch 2 used to use,
@@ -167,7 +152,8 @@ class RenderWebGL extends EventEmitter {
         }
 
         /** @type {RenderWebGL.UseGpuModes} */
-        this._useGpuMode = RenderWebGL.UseGpuModes.Automatic;
+        // this._useGpuMode = RenderWebGL.UseGpuModes.Automatic;
+        this._useGpuMode = RenderWebGL.UseGpuModes.ForceCPU;
 
         /** @type {Drawable[]} */
         this._allDrawables = [];
@@ -817,46 +803,40 @@ class RenderWebGL extends EventEmitter {
             this._debugCanvas.height = bounds.height;
         }
 
+        const candidateIDs = candidates.map(c => c.id);
+
         // if there are just too many pixels to CPU render efficiently, we need to let readPixels happen
         if (bounds.width * bounds.height * (candidates.length + 1) >= maxPixelsForCPU) {
-            this._isTouchingColorGpuStart(drawableID, candidates.map(({id}) => id).reverse(), bounds, color3b, mask3b);
+            return this._isTouchingColorGpu(
+                drawableID,
+                candidateIDs.reverse(),
+                bounds,
+                color3b,
+                mask3b
+            );
         }
 
         const drawable = this._allDrawables[drawableID];
-        const point = __isTouchingDrawablesPoint;
-        const color = __touchingColor;
         const hasMask = Boolean(mask3b);
 
         drawable.updateCPURenderAttributes();
 
-        // Masked drawable ignores ghost effect
-        const effectMask = ~ShaderManager.EFFECT_INFO.ghost.mask;
-
-        // Scratch Space - +y is top
-        for (let y = bounds.bottom; y <= bounds.top; y++) {
-            if (bounds.width * (y - bounds.bottom) * (candidates.length + 1) >= maxPixelsForCPU) {
-                return this._isTouchingColorGpuFin(bounds, color3b, y - bounds.bottom);
-            }
-            for (let x = bounds.left; x <= bounds.right; x++) {
-                point[1] = y;
-                point[0] = x;
-                // if we use a mask, check our sample color...
-                if (hasMask ?
-                    maskMatches(Drawable.sampleColor4b(point, drawable, color, effectMask), mask3b) :
-                    drawable.isTouching(point)) {
-                    RenderWebGL.sampleColor3b(point, candidates, color);
-                    if (debugCanvasContext) {
-                        debugCanvasContext.fillStyle = `rgb(${color[0]},${color[1]},${color[2]})`;
-                        debugCanvasContext.fillRect(x - bounds.left, bounds.bottom - y, 1, 1);
-                    }
-                    // ...and the target color is drawn at this pixel
-                    if (colorMatches(color, color3b, 0)) {
-                        return true;
-                    }
-                }
-            }
+        if (hasMask) {
+            return this.softwareRenderer.color_is_touching_color(
+                drawableID,
+                candidateIDs,
+                bounds,
+                color3b,
+                mask3b
+            );
         }
-        return false;
+
+        return this.softwareRenderer.is_touching_color(
+            drawableID,
+            candidateIDs,
+            bounds,
+            color3b
+        );
     }
 
     _getMaxPixelsForCPU () {
@@ -884,7 +864,7 @@ class RenderWebGL extends EventEmitter {
         gl.enable(gl.BLEND);
     }
 
-    _isTouchingColorGpuStart (drawableID, candidateIDs, bounds, color3b, mask3b) {
+    _isTouchingColorGpu (drawableID, candidateIDs, bounds, color3b, mask3b) {
         this._doExitDrawRegion();
 
         const gl = this._gl;
@@ -951,18 +931,15 @@ class RenderWebGL extends EventEmitter {
             gl.disable(gl.STENCIL_TEST);
             this._doExitDrawRegion();
         }
-    }
 
-    _isTouchingColorGpuFin (bounds, color3b, stop) {
-        const gl = this._gl;
-        const pixels = new Uint8Array(Math.floor(bounds.width * (bounds.height - stop) * 4));
-        gl.readPixels(0, 0, bounds.width, (bounds.height - stop), gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+        const pixels = new Uint8Array(Math.floor(bounds.width * bounds.height * 4));
+        gl.readPixels(0, 0, bounds.width, bounds.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
 
         if (this._debugCanvas) {
             this._debugCanvas.width = bounds.width;
             this._debugCanvas.height = bounds.height;
             const context = this._debugCanvas.getContext('2d');
-            const imageData = context.getImageData(0, 0, bounds.width, bounds.height - stop);
+            const imageData = context.getImageData(0, 0, bounds.width, bounds.heigh);
             imageData.data.set(pixels);
             context.putImageData(imageData, 0, 0);
         }
@@ -2057,41 +2034,6 @@ class RenderWebGL extends EventEmitter {
         return hull(hullPoints, Infinity);
     }
 
-    /**
-     * Sample a "final" color from an array of drawables at a given scratch space.
-     * Will blend any alpha values with the drawables "below" it.
-     * @param {twgl.v3} vec Scratch Vector Space to sample
-     * @param {Array<Drawables>} drawables A list of drawables with the "top most"
-     *              drawable at index 0
-     * @param {Uint8ClampedArray} dst The color3b space to store the answer in.
-     * @return {Uint8ClampedArray} The dst vector with everything blended down.
-     */
-    static sampleColor3b (vec, drawables, dst) {
-        dst = dst || new Uint8ClampedArray(3);
-        dst.fill(0);
-        let blendAlpha = 1;
-        for (let index = 0; blendAlpha !== 0 && index < drawables.length; index++) {
-            /*
-            if (left > vec[0] || right < vec[0] ||
-                bottom > vec[1] || top < vec[0]) {
-                continue;
-            }
-            */
-            Drawable.sampleColor4b(vec, drawables[index].drawable, __blendColor);
-            // Equivalent to gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
-            dst[0] += __blendColor[0] * blendAlpha;
-            dst[1] += __blendColor[1] * blendAlpha;
-            dst[2] += __blendColor[2] * blendAlpha;
-            blendAlpha *= (1 - (__blendColor[3] / 255));
-        }
-        // Backdrop could be transparent, so we need to go to the "clear color" of the
-        // draw scene (white) as a fallback if everything was alpha
-        dst[0] += blendAlpha * 255;
-        dst[1] += blendAlpha * 255;
-        dst[2] += blendAlpha * 255;
-        return dst;
-    }
-
     /**
      * @callback RenderWebGL#snapshotCallback
      * @param {string} dataURI Data URI of the snapshot of the renderer
diff --git a/src/ShaderManager.js b/src/ShaderManager.js
index 40822356a..b7881539b 100644
--- a/src/ShaderManager.js
+++ b/src/ShaderManager.js
@@ -90,7 +90,8 @@ ShaderManager.EFFECT_INFO = {
     color: {
         uniformName: 'u_color',
         mask: 1 << 0,
-        converter: x => (x / 200) % 1,
+        // ensure modulo (and hence hue shift) is kept positive
+        converter: x => (((x / 200) % 1) + 1) % 1,
         shapeChanges: false
     },
     /** Fisheye effect */
diff --git a/src/Silhouette.js b/src/Silhouette.js
index b96348a81..615ecda34 100644
--- a/src/Silhouette.js
+++ b/src/Silhouette.js
@@ -10,11 +10,6 @@
  */
 let __SilhouetteUpdateCanvas;
 
-// Optimized Math.min and Math.max for integers;
-// taken from https://web.archive.org/web/20190716181049/http://guihaire.com/code/?p=549
-const intMin = (i, j) => j ^ ((i ^ j) & ((i - j) >> 31));
-const intMax = (i, j) => i ^ ((i ^ j) & ((i - j) >> 31));
-
 /**
  * Internal helper function (in hopes that compiler can inline).  Get a pixel
  * from silhouette data, or 0 if outside it's bounds.
@@ -42,57 +37,6 @@ const __cornerWork = [
     new Uint8ClampedArray(4)
 ];
 
-/**
- * Get the color from a given silhouette at an x/y local texture position.
- * Multiply color values by alpha for proper blending.
- * @param {Silhouette} $0 The silhouette to sample.
- * @param {number} x X position of texture [0, width).
- * @param {number} y Y position of texture [0, height).
- * @param {Uint8ClampedArray} dst A color 4b space.
- * @return {Uint8ClampedArray} The dst vector.
- */
-const getColor4b = ({_width: width, _height: height, _colorData: data}, x, y, dst) => {
-    // Clamp coords to edge, matching GL_CLAMP_TO_EDGE.
-    // (See github.com/LLK/scratch-render/blob/954cfff02b08069a082cbedd415c1fecd9b1e4fb/src/BitmapSkin.js#L88)
-    x = intMax(0, intMin(x, width - 1));
-    y = intMax(0, intMin(y, height - 1));
-
-    // 0 if outside bounds, otherwise read from data.
-    if (x >= width || y >= height || x < 0 || y < 0) {
-        return dst.fill(0);
-    }
-    const offset = ((y * width) + x) * 4;
-    // premultiply alpha
-    const alpha = data[offset + 3] / 255;
-    dst[0] = data[offset] * alpha;
-    dst[1] = data[offset + 1] * alpha;
-    dst[2] = data[offset + 2] * alpha;
-    dst[3] = data[offset + 3];
-    return dst;
-};
-
-/**
- * Get the color from a given silhouette at an x/y local texture position.
- * Do not multiply color values by alpha, as it has already been done.
- * @param {Silhouette} $0 The silhouette to sample.
- * @param {number} x X position of texture [0, width).
- * @param {number} y Y position of texture [0, height).
- * @param {Uint8ClampedArray} dst A color 4b space.
- * @return {Uint8ClampedArray} The dst vector.
- */
-const getPremultipliedColor4b = ({_width: width, _height: height, _colorData: data}, x, y, dst) => {
-    // Clamp coords to edge, matching GL_CLAMP_TO_EDGE.
-    x = intMax(0, intMin(x, width - 1));
-    y = intMax(0, intMin(y, height - 1));
-
-    const offset = ((y * width) + x) * 4;
-    dst[0] = data[offset];
-    dst[1] = data[offset + 1];
-    dst[2] = data[offset + 2];
-    dst[3] = data[offset + 3];
-    return dst;
-};
-
 class Silhouette {
     constructor () {
         /**
@@ -112,13 +56,6 @@ class Silhouette {
          * @type {Uint8ClampedArray}
          */
         this._colorData = null;
-
-        // By default, silhouettes are assumed not to contain premultiplied image data,
-        // so when we get a color, we want to multiply it by its alpha channel.
-        // Point `_getColor` to the version of the function that multiplies.
-        this._getColor = getColor4b;
-
-        this.colorAtNearest = this.colorAtLinear = (_, dst) => dst.fill(0);
     }
 
     /**
@@ -127,7 +64,7 @@ class Silhouette {
      * @param {boolean} isPremultiplied True if the source bitmap data comes premultiplied (e.g. from readPixels).
      * rendering can be queried from.
      */
-    update (bitmapData, isPremultiplied = false) {
+    update (bitmapData) {
         let imageData;
         if (bitmapData instanceof ImageData) {
             // If handed ImageData directly, use it directly.
@@ -150,65 +87,7 @@ class Silhouette {
             imageData = ctx.getImageData(0, 0, width, height);
         }
 
-        if (isPremultiplied) {
-            this._getColor = getPremultipliedColor4b;
-        } else {
-            this._getColor = getColor4b;
-        }
-
         this._colorData = imageData.data;
-        // delete our custom overriden "uninitalized" color functions
-        // let the prototype work for itself
-        delete this.colorAtNearest;
-        delete this.colorAtLinear;
-    }
-
-    /**
-     * Sample a color from the silhouette at a given local position using
-     * "nearest neighbor"
-     * @param {twgl.v3} vec [x,y] texture space (0-1)
-     * @param {Uint8ClampedArray} dst The memory buffer to store the value in. (4 bytes)
-     * @returns {Uint8ClampedArray} dst
-     */
-    colorAtNearest (vec, dst) {
-        return this._getColor(
-            this,
-            Math.floor(vec[0] * (this._width - 1)),
-            Math.floor(vec[1] * (this._height - 1)),
-            dst
-        );
-    }
-
-    /**
-     * Sample a color from the silhouette at a given local position using
-     * "linear interpolation"
-     * @param {twgl.v3} vec [x,y] texture space (0-1)
-     * @param {Uint8ClampedArray} dst The memory buffer to store the value in. (4 bytes)
-     * @returns {Uint8ClampedArray} dst
-     */
-    colorAtLinear (vec, dst) {
-        const x = vec[0] * (this._width - 1);
-        const y = vec[1] * (this._height - 1);
-
-        const x1D = x % 1;
-        const y1D = y % 1;
-        const x0D = 1 - x1D;
-        const y0D = 1 - y1D;
-
-        const xFloor = Math.floor(x);
-        const yFloor = Math.floor(y);
-
-        const x0y0 = this._getColor(this, xFloor, yFloor, __cornerWork[0]);
-        const x1y0 = this._getColor(this, xFloor + 1, yFloor, __cornerWork[1]);
-        const x0y1 = this._getColor(this, xFloor, yFloor + 1, __cornerWork[2]);
-        const x1y1 = this._getColor(this, xFloor + 1, yFloor + 1, __cornerWork[3]);
-
-        dst[0] = (x0y0[0] * x0D * y0D) + (x0y1[0] * x0D * y1D) + (x1y0[0] * x1D * y0D) + (x1y1[0] * x1D * y1D);
-        dst[1] = (x0y0[1] * x0D * y0D) + (x0y1[1] * x0D * y1D) + (x1y0[1] * x1D * y0D) + (x1y1[1] * x1D * y1D);
-        dst[2] = (x0y0[2] * x0D * y0D) + (x0y1[2] * x0D * y1D) + (x1y0[2] * x1D * y0D) + (x1y1[2] * x1D * y1D);
-        dst[3] = (x0y0[3] * x0D * y0D) + (x0y1[3] * x0D * y1D) + (x1y0[3] * x1D * y0D) + (x1y1[3] * x1D * y1D);
-
-        return dst;
     }
 
     /**
diff --git a/src/Skin.js b/src/Skin.js
index 3ceb60802..4f95a3cc2 100644
--- a/src/Skin.js
+++ b/src/Skin.js
@@ -53,7 +53,7 @@ class Skin extends EventEmitter {
          */
         this._silhouette = new Silhouette();
 
-        renderer.softwareRenderer.set_silhouette(id, 0, 0, new Uint8Array(0));
+        renderer.softwareRenderer.set_silhouette(id, 0, 0, new Uint8Array(0), 1, 1, true);
 
         this.setMaxListeners(RenderConstants.SKIN_SHARE_SOFT_LIMIT);
     }
@@ -201,7 +201,7 @@ class Skin extends EventEmitter {
         this.emit(Skin.Events.WasAltered);
     }
 
-    _setSilhouetteFromData (data) {
+    _setSilhouetteFromData (data, premultiplied = false) {
         const size = this.size;
         this._renderer.softwareRenderer.set_silhouette(
             this._id,
@@ -210,7 +210,9 @@ class Skin extends EventEmitter {
             data.data,
 
             size[0],
-            size[1]
+            size[1],
+
+            premultiplied
         );
     }
 
diff --git a/swrender/src/drawable.rs b/swrender/src/drawable.rs
index f7e04ef89..eaa2a6ae6 100644
--- a/swrender/src/drawable.rs
+++ b/swrender/src/drawable.rs
@@ -1,16 +1,17 @@
 use crate::silhouette::*;
 use crate::matrix::*;
-use crate::effect_transform::{Effects, EffectBits, transform_point, DISTORTION_EFFECT_MASK};
+use crate::effect_transform::{Effects, EffectBits, transform_point, DISTORTION_EFFECT_MASK, transform_color, COLOR_EFFECT_MASK};
 
 pub type DrawableID = u32;
 
 pub struct Drawable {
+    pub id: DrawableID,
     pub matrix: Mat4,
     pub inverse_matrix: Mat4,
     pub silhouette: SilhouetteID,
-    pub id: DrawableID,
     pub effects: Effects,
-    pub effect_bits: EffectBits
+    pub effect_bits: EffectBits,
+    pub use_nearest_neighbor: bool
 }
 
 impl Drawable {
@@ -32,7 +33,7 @@ impl Drawable {
         if (self.effect_bits & DISTORTION_EFFECT_MASK) == 0 {
             vec
         } else {
-            transform_point(vec, &self.effects, &self.effect_bits, skin_size)
+            transform_point(vec, &self.effects, self.effect_bits, skin_size)
         }
     }
 
@@ -43,6 +44,33 @@ impl Drawable {
             return false;
         }
         let local_position = self.get_transformed_position(local_position, silhouette.nominal_size);
-        silhouette.get_point((local_position.0 * silhouette.width as f32) as i32, (local_position.1 * silhouette.height as f32) as i32)
+
+        if self.use_nearest_neighbor {
+            silhouette.is_touching_nearest(local_position)
+        } else {
+            silhouette.is_touching_linear(local_position)
+        }
+    }
+
+    #[inline(always)]
+    pub fn sample_color<'a>(&self, position: Vec2, silhouette: &'a Silhouette) -> [u8; 4] {
+        let local_position = self.get_local_position(position);
+        if local_position.0 < 0f32 || local_position.0 >= 1f32 || local_position.1 < 0f32 || local_position.1 >= 1f32 {
+            return [0, 0, 0, 0];
+        }
+        let local_position = self.get_transformed_position(local_position, silhouette.nominal_size);
+
+        // TODO: linear sampling
+        let color = if self.use_nearest_neighbor {
+            silhouette.color_at_nearest(local_position)
+        } else {
+            silhouette.color_at_nearest(local_position)
+        };
+
+        if (self.effect_bits & COLOR_EFFECT_MASK) == 0 {
+            color
+        } else {
+            transform_color(color, &self.effects, self.effect_bits)
+        }
     }
 }
diff --git a/swrender/src/effect_transform.rs b/swrender/src/effect_transform.rs
index 8ef23d6ef..3daadfc4b 100644
--- a/swrender/src/effect_transform.rs
+++ b/swrender/src/effect_transform.rs
@@ -1,5 +1,6 @@
 use crate::matrix::*;
 
+use std::f32;
 use wasm_bindgen::prelude::*;
 
 #[wasm_bindgen]
@@ -44,6 +45,11 @@ pub enum EffectBitfield {
     Ghost = 6,
 }
 
+pub const COLOR_EFFECT_MASK: EffectBits =
+    1 << (EffectBitfield::Color as u32) |
+    1 << (EffectBitfield::Brightness as u32) |
+    1 << (EffectBitfield::Ghost as u32);
+
 pub const DISTORTION_EFFECT_MASK: EffectBits =
     1 << (EffectBitfield::Fisheye as u32) |
     1 << (EffectBitfield::Whirl as u32) |
@@ -62,9 +68,138 @@ impl Effects {
     }
 }
 
+fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
+    let mut r = r;
+    let mut g = g;
+    let mut b = b;
+
+    let mut tmp: f32;
+
+    let mut k = 0f32;
+
+    if g < b {
+        tmp = g;
+        g = b;
+        b = tmp;
+        k = -1f32;
+    }
+
+    if r < g {
+        tmp = g;
+        g = r;
+        r = tmp;
+        k = (-2f32 / 6f32) - k;
+    }
+
+    let chroma = r - f32::min(g, b);
+
+    let h = f32::abs(k + (g - b) / (6f32 * chroma + f32::EPSILON));
+    let s = chroma / (r + f32::EPSILON);
+    let v = r;
+
+    (h, s, v)
+}
+
+fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
+    if s < 1e-18 {
+        return (v, v, v);
+    }
+
+    let i = (h * 6f32).floor();
+    let f = (h * 6f32) - i;
+    let p = v * (1f32 - s);
+    let q = v * (1f32 - (s * f));
+    let t = v * (1f32 - (s * (1f32 - f)));
+
+    match i as u32 {
+        0 => (v, t, p),
+        1 => (q, v, p),
+        2 => (p, v, t),
+        3 => (p, q, v),
+        4 => (t, p, v),
+        5 => (v, p, q),
+        _ => unreachable!()
+    }
+}
+
+pub fn transform_color<'a>(color: [u8; 4], effects: &Effects, effect_bits: EffectBits) -> [u8; 4] {
+    const COLOR_DIVISOR: f32 = 1f32 / 255f32;
+    let mut rgba: [f32; 4] = [
+        (color[0] as f32) * COLOR_DIVISOR,
+        (color[1] as f32) * COLOR_DIVISOR,
+        (color[2] as f32) * COLOR_DIVISOR,
+        (color[3] as f32) * COLOR_DIVISOR
+    ];
+
+    let enable_color = effect_bits & (1 << (EffectBitfield::Color as u32)) != 0;
+    let enable_brightness = effect_bits & (1 << (EffectBitfield::Brightness as u32)) != 0;
+
+    if enable_brightness || enable_color {
+        let alpha = rgba[3] + f32::EPSILON;
+        rgba[0] /= alpha;
+        rgba[1] /= alpha;
+        rgba[2] /= alpha;
+
+        if enable_color {
+            /*vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
+
+            // this code forces grayscale values to be slightly saturated
+            // so that some slight change of hue will be visible
+            const float minLightness = 0.11 / 2.0;
+            const float minSaturation = 0.09;
+            if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
+            else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
+
+            hsv.x = mod(hsv.x + u_color, 1.0);
+            if (hsv.x < 0.0) hsv.x += 1.0;
+
+            gl_FragColor.rgb = convertHSV2RGB(hsv);*/
+
+            let (mut h, mut s, mut v) = rgb_to_hsv(rgba[0], rgba[1], rgba[2]);
+
+            const MIN_LIGHTNESS: f32 = 0.11 / 2f32;
+            const MIN_SATURATION: f32 = 0.09;
+
+            if v < MIN_LIGHTNESS {
+                v = MIN_LIGHTNESS
+            } else if s < MIN_SATURATION {
+                s = MIN_SATURATION
+            }
+
+            h = f32::fract(h + effects.color);
+
+            let (r, g, b) = hsv_to_rgb(h, s, v);
+            rgba[0] = r;
+            rgba[1] = g;
+            rgba[2] = b;
+        }
+
+        if enable_brightness {
+            // gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1));
+            rgba[0] = (rgba[0] + effects.brightness).min(1f32).max(0f32);
+            rgba[1] = (rgba[1] + effects.brightness).min(1f32).max(0f32);
+            rgba[2] = (rgba[2] + effects.brightness).min(1f32).max(0f32);
+        }
+
+        rgba[0] *= alpha;
+        rgba[1] *= alpha;
+        rgba[2] *= alpha;
+    }
+
+    // gl_FragColor *= u_ghost
+    if effect_bits & (1 << (EffectBitfield::Ghost as u32)) != 0 {
+        rgba[0] *= effects.ghost;
+        rgba[1] *= effects.ghost;
+        rgba[2] *= effects.ghost;
+        rgba[3] *= effects.ghost;
+    }
+
+    [(rgba[0] * 255f32) as u8, (rgba[1] * 255f32) as u8, (rgba[2] * 255f32) as u8, (rgba[3] * 255f32) as u8]
+}
+
 const CENTER: Vec2 = Vec2(0.5, 0.5);
 
-pub fn transform_point(point: Vec2, effects: &Effects, effect_bits: &EffectBits, skin_size: Vec2) -> Vec2 {
+pub fn transform_point(point: Vec2, effects: &Effects, effect_bits: EffectBits, skin_size: Vec2) -> Vec2 {
     let mut out = point;
 
     if effect_bits & (1 << (EffectBitfield::Mosaic as u32)) != 0 {
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
index b3f29594e..a68bc96c8 100644
--- a/swrender/src/lib.rs
+++ b/swrender/src/lib.rs
@@ -1,5 +1,4 @@
 mod utils;
-mod rectangle;
 mod matrix;
 mod effect_transform;
 pub mod silhouette;
@@ -12,6 +11,20 @@ use std::convert::TryInto;
 
 use matrix::Matrix;
 
+#[wasm_bindgen]
+extern {
+    pub type JSRectangle;
+
+    #[wasm_bindgen(method, getter)]
+    pub fn left(this: &JSRectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn right(this: &JSRectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn bottom(this: &JSRectangle) -> f64;
+    #[wasm_bindgen(method, getter)]
+    pub fn top(this: &JSRectangle) -> f64;
+}
+
 // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
 // allocator.
 #[cfg(feature = "wee_alloc")]
@@ -46,7 +59,8 @@ impl SoftwareRenderer {
         matrix: Option<Box<[f32]>>,
         silhouette: Option<silhouette::SilhouetteID>,
         effects: Option<effect_transform::JSEffectMap>,
-        effect_bits: effect_transform::EffectBits
+        effect_bits: effect_transform::EffectBits,
+        use_nearest_neighbor: bool
     ) {
         let d = self.drawables.entry(id).or_insert(drawable::Drawable {
             matrix: [0.0; 16],
@@ -57,6 +71,7 @@ impl SoftwareRenderer {
                 Some(s) => s,
                 None => ID_NONE
             },
+            use_nearest_neighbor,
             id
         });
 
@@ -71,6 +86,7 @@ impl SoftwareRenderer {
             d.effects.set_from_js(fx);
         }
         d.effect_bits = effect_bits;
+        d.use_nearest_neighbor = use_nearest_neighbor;
     }
 
     pub fn remove_drawable(&mut self, id: drawable::DrawableID) {
@@ -83,24 +99,44 @@ impl SoftwareRenderer {
         w: u32,
         h: u32,
         data: Box<[u8]>,
-        nominal_width:
-        f64, nominal_height: f64
+        nominal_width: f64,
+        nominal_height: f64,
+        premultiplied: bool,
     ) {
         let s = self.silhouettes.entry(id).or_insert(silhouette::Silhouette::new(id));
-        s.set_data(w, h, data, matrix::Vec2(nominal_width as f32, nominal_height as f32));
+        s.set_data(w, h, data, matrix::Vec2(nominal_width as f32, nominal_height as f32), premultiplied);
     }
 
     pub fn remove_silhouette(&mut self, id: silhouette::SilhouetteID) {
         self.silhouettes.remove(&id);
     }
 
-    pub fn is_touching_drawables(
-        &mut self,
+    fn map_candidates(
+        &self,
+        candidates: Vec<drawable::DrawableID>
+    ) -> Vec<(&drawable::Drawable, &silhouette::Silhouette)> {
+        candidates.into_iter()
+        .map(|c| {
+            let d = self.drawables.get(&c).expect("Candidate drawable should exist");
+            let s = self.silhouettes.get(&d.silhouette).unwrap();
+            (d, s)
+        }).collect()
+    }
+
+    fn per_rect_pixel<F>(
+        &self,
+        func: F,
+        rect: JSRectangle,
         drawable: drawable::DrawableID,
-        candidates:
-        Vec<drawable::DrawableID>,
-        rect: rectangle::JSRectangle
-    ) -> bool {
+        candidates: Vec<drawable::DrawableID>
+    ) -> bool
+        where F: Fn(
+            matrix::Vec2,
+            &drawable::Drawable,
+            &silhouette::Silhouette,
+            &Vec<(&drawable::Drawable, &silhouette::Silhouette)>
+        ) -> bool {
+
         let left = rect.left() as i32;
         let right = rect.right() as i32 + 1;
         let bottom = rect.bottom() as i32 - 1;
@@ -108,26 +144,144 @@ impl SoftwareRenderer {
 
         let drawable = self.drawables.get(&drawable).expect("Drawable should exist");
         let silhouette = self.silhouettes.get(&drawable.silhouette).unwrap();
-        let candidates: Vec<(&drawable::Drawable, &silhouette::Silhouette)> = candidates.into_iter()
-            .map(|c| {
-                let d = self.drawables.get(&c).expect("Candidate drawable should exist");
-                let s = self.silhouettes.get(&d.silhouette).unwrap();
-                (d, s)
-            }).collect();
-
-        for x in left..right {
-            for y in bottom..top {
+        let candidates = self.map_candidates(candidates);
+
+        for y in bottom..top {
+            for x in left..right {
                 let position = matrix::Vec2(x as f32, y as f32);
-                if drawable.is_touching(position, silhouette) {
-                    for candidate in &candidates {
-                        if candidate.0.is_touching(position, candidate.1) {
-                            return true;
-                        }
-                    }
+                if func(position, drawable, silhouette, &candidates) {
+                    return true;
                 }
             }
         }
 
         false
     }
+
+    pub fn is_touching_drawables(
+        &mut self,
+        drawable: drawable::DrawableID,
+        candidates: Vec<drawable::DrawableID>,
+        rect: JSRectangle
+    ) -> bool {
+        self.per_rect_pixel(|
+            position,
+            drawable,
+            silhouette,
+            candidates
+        | {
+            if drawable.is_touching(position, silhouette) {
+                for candidate in candidates {
+                    if candidate.0.is_touching(position, candidate.1) {
+                        return true;
+                    }
+                }
+            }
+            false
+        }, rect, drawable, candidates)
+    }
+
+    #[inline(always)]
+    fn color_matches(
+        a: [u8; 3],
+        b: [u8; 3]
+    ) -> bool {
+        (
+            ((a[0] ^ b[0]) & 0b11111000) |
+            ((a[1] ^ b[1]) & 0b11111000) |
+            ((a[2] ^ b[2]) & 0b11110000)
+        ) == 0
+    }
+
+    #[inline(always)]
+    fn mask_matches(
+        a: [u8; 4],
+        b: [u8; 3]
+    ) -> bool {
+        a[3] != 0 &&
+        (
+            ((a[0] ^ b[0]) & 0b11111100) |
+            ((a[1] ^ b[1]) & 0b11111100) |
+            ((a[2] ^ b[2]) & 0b11111100)
+        ) == 0
+    }
+
+    pub fn color_is_touching_color(
+        &mut self,
+        drawable: drawable::DrawableID,
+        candidates: Vec<drawable::DrawableID>,
+        rect: JSRectangle,
+        color: &[u8],
+        mask: &[u8]
+    ) -> bool {
+        let color: [u8; 3] = (*color).try_into().expect("color contains 3 elements");
+        let mask: [u8; 3] = (*mask).try_into().expect("mask contains 3 elements");
+
+        self.per_rect_pixel(|
+            position,
+            drawable,
+            silhouette,
+            candidates
+        | {
+            if Self::mask_matches(drawable.sample_color(position, silhouette), mask) {
+                let sample_color = self.sample_color(position, &candidates);
+                if Self::color_matches(color, sample_color) {
+                    return true;
+                }
+            }
+            false
+        }, rect, drawable, candidates)
+    }
+
+    pub fn is_touching_color(
+        &mut self,
+        drawable: drawable::DrawableID,
+        candidates: Vec<drawable::DrawableID>,
+        rect: JSRectangle,
+        color: &[u8]
+    ) -> bool {
+        let color: [u8; 3] = (*color).try_into().expect("color contains 3 elements");
+        self.per_rect_pixel(|
+            position,
+            drawable,
+            silhouette,
+            candidates
+        | {
+            if drawable.is_touching(position, silhouette) {
+                let sample_color = self.sample_color(position, &candidates);
+                if Self::color_matches(color, sample_color) {
+                    return true;
+                }
+            }
+            false
+        }, rect, drawable, candidates)
+    }
+
+    fn sample_color(
+        &self,
+        position: matrix::Vec2,
+        candidates: &Vec<(&drawable::Drawable, &silhouette::Silhouette)>
+    ) -> [u8; 3] {
+        let mut dst_color: (f32, f32, f32, f32) = (0f32, 0f32, 0f32, 0f32);
+        let mut blend_alpha = 1f32;
+
+        for candidate in candidates.into_iter() {
+            let col = candidate.0.sample_color(position, candidate.1);
+            dst_color.0 += (col[0] as f32) * blend_alpha;
+            dst_color.1 += (col[1] as f32) * blend_alpha;
+            dst_color.2 += (col[2] as f32) * blend_alpha;
+            blend_alpha *= 1f32 - (col[3] as f32 / 255f32);
+
+            if blend_alpha == 0f32 {
+                break;
+            }
+        }
+
+        let alpha8 = blend_alpha * 255f32;
+        dst_color.0 += alpha8;
+        dst_color.1 += alpha8;
+        dst_color.2 += alpha8;
+
+        [dst_color.0 as u8, dst_color.1 as u8, dst_color.2 as u8]
+    }
 }
diff --git a/swrender/src/rectangle.rs b/swrender/src/rectangle.rs
deleted file mode 100644
index e4ad40b23..000000000
--- a/swrender/src/rectangle.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use wasm_bindgen::prelude::*;
-
-#[wasm_bindgen]
-extern {
-    pub type JSRectangle;
-
-    #[wasm_bindgen(method, getter)]
-    pub fn left(this: &JSRectangle) -> f64;
-    #[wasm_bindgen(method, getter)]
-    pub fn right(this: &JSRectangle) -> f64;
-    #[wasm_bindgen(method, getter)]
-    pub fn bottom(this: &JSRectangle) -> f64;
-    #[wasm_bindgen(method, getter)]
-    pub fn top(this: &JSRectangle) -> f64;
-}
-
-pub struct Rectangle<T> {
-    left: T,
-    right: T,
-    bottom: T,
-    top: T
-}
-
-impl Rectangle<i32> {
-    pub fn fromJSRectangle(rect: JSRectangle) -> Self {
-        Rectangle {
-            left: rect.left().floor() as i32,
-            right: rect.right().ceil() as i32,
-            bottom: rect.bottom().floor() as i32,
-            top: rect.top().ceil() as i32
-        }
-    }
-}
diff --git a/swrender/src/silhouette.rs b/swrender/src/silhouette.rs
index d089ec667..772cf4285 100644
--- a/swrender/src/silhouette.rs
+++ b/swrender/src/silhouette.rs
@@ -2,13 +2,28 @@ use crate::matrix::Vec2;
 
 pub type SilhouetteID = u32;
 
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen]
+extern {
+    #[wasm_bindgen(js_namespace = console)]
+    pub fn time(s: &str);
+
+    #[wasm_bindgen(js_namespace = console)]
+    pub fn timeEnd(s: &str);
+
+    #[wasm_bindgen(js_namespace = console)]
+    pub fn log(s: &str);
+}
+
+
 pub struct Silhouette {
     pub id: SilhouetteID,
     pub width: u32,
     pub height: u32,
     pub nominal_size: Vec2,
     data: Box<[u8]>,
-    _blank: Box<[u8; 4]>
+    _blank: [u8; 4]
 }
 
 impl Silhouette {
@@ -19,17 +34,34 @@ impl Silhouette {
             height: 0,
             nominal_size: Vec2(0f32, 0f32),
             data: Box::new([0, 0, 0, 0]),
-            _blank: Box::new([0, 0, 0, 0])
+            _blank: [0, 0, 0, 0]
         }
     }
 
-    pub fn set_data(&mut self, w: u32, h: u32, data: Box<[u8]>, nominal_size: Vec2) {
+    pub fn set_data(&mut self, w: u32, h: u32, mut data: Box<[u8]>, nominal_size: Vec2, premultiplied: bool) {
         assert_eq!(data.len(), (w * h * 4) as usize, "silhouette data is improperly sized");
 
         self.width = w;
         self.height = h;
-        self.data = data;
         self.nominal_size = nominal_size;
+
+        if !premultiplied {
+            let pixels = (*data).chunks_mut(4);
+
+            for pixel in pixels {
+                // This is indeed one branch per pixel. However, the branch predictor does a pretty good job of
+                // eliminating branch overhead and this saves us several instructions per pixel.
+                if pixel[3] == 0u8 {continue}
+
+                let alpha = (pixel[3] as f32) / 255f32;
+
+                pixel[0] = ((pixel[0] as f32) * alpha) as u8;
+                pixel[1] = ((pixel[1] as f32) * alpha) as u8;
+                pixel[2] = ((pixel[2] as f32) * alpha) as u8;
+            }
+        }
+
+        self.data = data;
     }
 
     pub fn get_point(&self, x: i32, y: i32) -> bool {
@@ -41,12 +73,30 @@ impl Silhouette {
         }
     }
 
-    pub fn get_color(&self, x: i32, y: i32) -> &[u8] {
+    pub fn get_color(&self, x: i32, y: i32) -> [u8; 4] {
         if x < 0 || y < 0 || (x as u32) >= self.width || (y as u32) >= self.height {
-            &self._blank[0..4]
+            self._blank
         } else {
             let idx = (((y as u32 * self.width) + x as u32) * 4) as usize;
-            &self.data[idx..idx+4]
+            [self.data[idx], self.data[idx + 1], self.data[idx + 2], self.data[idx + 3]]
         }
     }
+
+    pub fn is_touching_nearest(&self, vec: Vec2) -> bool {
+        self.get_point((vec.0 * self.width as f32) as i32, (vec.1 * self.height as f32) as i32)
+    }
+
+    pub fn color_at_nearest(&self, vec: Vec2) -> [u8; 4] {
+        self.get_color((vec.0 * self.width as f32) as i32, (vec.1 * self.height as f32) as i32)
+    }
+
+    pub fn is_touching_linear(&self, vec: Vec2) -> bool {
+        let x = ((vec.0 * self.width as f32) - 0.5) as i32;
+        let y = ((vec.1 * self.height as f32) - 0.5) as i32;
+
+        self.get_point(x, y) ||
+        self.get_point(x + 1, y) ||
+        self.get_point(x, y + 1) ||
+        self.get_point(x + 1, y + 1)
+    }
 }

From 71523fe95f12eff92896cf9b25f0f2df47ad2596 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Sat, 21 Mar 2020 23:57:18 -0400
Subject: [PATCH 05/14] Add pick + drawableTouching

---
 src/Drawable.js            | 95 +-------------------------------------
 src/RenderWebGL.js         | 50 ++------------------
 swrender/src/drawable.rs   |  4 +-
 swrender/src/lib.rs        | 93 ++++++++++++++++++++++++++++++-------
 swrender/src/silhouette.rs |  2 +-
 5 files changed, 83 insertions(+), 161 deletions(-)

diff --git a/src/Drawable.js b/src/Drawable.js
index 18de2a8e2..965ad5143 100644
--- a/src/Drawable.js
+++ b/src/Drawable.js
@@ -4,55 +4,6 @@ const Rectangle = require('./Rectangle');
 const RenderConstants = require('./RenderConstants');
 const ShaderManager = require('./ShaderManager');
 const Skin = require('./Skin');
-const EffectTransform = require('./EffectTransform');
-const log = require('./util/log');
-
-/**
- * An internal workspace for calculating texture locations from world vectors
- * this is REUSED for memory conservation reasons
- * @type {twgl.v3}
- */
-const __isTouchingPosition = twgl.v3.create();
-const FLOATING_POINT_ERROR_ALLOWANCE = 1e-6;
-
-/**
- * Convert a scratch space location into a texture space float.  Uses the
- * internal __isTouchingPosition as a return value, so this should be copied
- * if you ever need to get two local positions and store both.  Requires that
- * the drawable inverseMatrix is up to date.
- *
- * @param {Drawable} drawable The drawable to get the inverse matrix and uniforms from
- * @param {twgl.v3} vec [x,y] scratch space vector
- * @return {twgl.v3} [x,y] texture space float vector - transformed by effects and matrix
- */
-const getLocalPosition = (drawable, vec) => {
-    // Transfrom from world coordinates to Drawable coordinates.
-    const localPosition = __isTouchingPosition;
-    const v0 = vec[0];
-    const v1 = vec[1];
-    const m = drawable._inverseMatrix;
-    // var v2 = v[2];
-    const d = (v0 * m[3]) + (v1 * m[7]) + m[15];
-    // The RenderWebGL quad flips the texture's X axis. So rendered bottom
-    // left is 1, 0 and the top right is 0, 1. Flip the X axis so
-    // localPosition matches that transformation.
-    localPosition[0] = 0.5 - (((v0 * m[0]) + (v1 * m[4]) + m[12]) / d);
-    localPosition[1] = (((v0 * m[1]) + (v1 * m[5]) + m[13]) / d) + 0.5;
-    // Fix floating point issues near 0. Filed https://github.com/LLK/scratch-render/issues/688 that
-    // they're happening in the first place.
-    // TODO: Check if this can be removed after render pull 479 is merged
-    if (Math.abs(localPosition[0]) < FLOATING_POINT_ERROR_ALLOWANCE) localPosition[0] = 0;
-    if (Math.abs(localPosition[1]) < FLOATING_POINT_ERROR_ALLOWANCE) localPosition[1] = 0;
-    // Apply texture effect transform if the localPosition is within the drawable's space,
-    // and any effects are currently active.
-    if (drawable.enabledEffects !== 0 &&
-        (localPosition[0] >= 0 && localPosition[0] < 1) &&
-        (localPosition[1] >= 0 && localPosition[1] < 1)) {
-
-        EffectTransform.transformPoint(drawable, localPosition, localPosition);
-    }
-    return localPosition;
-};
 
 class Drawable {
     /**
@@ -129,8 +80,6 @@ class Drawable {
         this._transformedHullDirty = true;
 
         this._skinWasAltered = this._skinWasAltered.bind(this);
-
-        this.isTouching = this._isTouchingNever;
     }
 
     /**
@@ -485,36 +434,6 @@ class Drawable {
         this._transformedHullDirty = true;
     }
 
-    /**
-     * @function
-     * @name isTouching
-     * Check if the world position touches the skin.
-     * The caller is responsible for ensuring this drawable's inverse matrix & its skin's silhouette are up-to-date.
-     * @see updateCPURenderAttributes
-     * @param {twgl.v3} vec World coordinate vector.
-     * @return {boolean} True if the world position touches the skin.
-     */
-
-    // `updateCPURenderAttributes` sets this Drawable instance's `isTouching` method
-    // to one of the following three functions:
-    // If this drawable has no skin, set it to `_isTouchingNever`.
-    // Otherwise, if this drawable uses nearest-neighbor scaling at its current scale, set it to `_isTouchingNearest`.
-    // Otherwise, set it to `_isTouchingLinear`.
-    // This allows several checks to be moved from the `isTouching` function to `updateCPURenderAttributes`.
-
-    // eslint-disable-next-line no-unused-vars
-    _isTouchingNever (vec) {
-        return false;
-    }
-
-    _isTouchingNearest (vec) {
-        return this.skin.isTouchingNearest(getLocalPosition(this, vec));
-    }
-
-    _isTouchingLinear (vec) {
-        return this.skin.isTouchingLinear(getLocalPosition(this, vec));
-    }
-
     /**
      * Get the precise bounds for a Drawable.
      * This function applies the transform matrix to the known convex hull,
@@ -652,20 +571,8 @@ class Drawable {
      */
     updateCPURenderAttributes () {
         this.updateMatrix();
-        // CPU rendering always occurs at the "native" size, so no need to scale up this._scale
-        if (this.skin) {
-            this.skin.updateSilhouette(this._scale);
-
-            if (this.skin.useNearest(this._scale, this)) {
-                this.isTouching = this._isTouchingNearest;
-            } else {
-                this.isTouching = this._isTouchingLinear;
-            }
-        } else {
-            log.warn(`Could not find skin for drawable with id: ${this._id}`);
 
-            this.isTouching = this._isTouchingNever;
-        }
+        if (this.skin) this.skin.updateSilhouette(this._scale);
 
         let effects = null;
         if (this._effectsDirty) {
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index fbd346cd3..7dc7c71b7 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -29,11 +29,8 @@ swrenderInit(wasm)
         if (onLoadSwRender) onLoadSwRender();
     });
 
-const __isTouchingDrawablesPoint = twgl.v3.create();
 const __candidatesBounds = new Rectangle();
 const __fenceBounds = new Rectangle();
-const __touchingColor = new Uint8ClampedArray(4);
-const __blendColor = new Uint8ClampedArray(4);
 
 // More pixels than this and we give up to the GPU and take the cost of readPixels
 // Width * Height * Number of drawables at location
@@ -1032,18 +1029,9 @@ class RenderWebGL extends EventEmitter {
             return false;
         }
         const bounds = this.clientSpaceToScratchBounds(centerX, centerY, touchWidth, touchHeight);
-        const worldPos = twgl.v3.create();
-
         drawable.updateCPURenderAttributes();
 
-        for (worldPos[1] = bounds.bottom; worldPos[1] <= bounds.top; worldPos[1]++) {
-            for (worldPos[0] = bounds.left; worldPos[0] <= bounds.right; worldPos[0]++) {
-                if (drawable.isTouching(worldPos)) {
-                    return true;
-                }
-            }
-        }
-        return false;
+        return this.softwareRenderer.drawable_touching_rect(drawableID, bounds);
     }
 
     /**
@@ -1078,43 +1066,12 @@ class RenderWebGL extends EventEmitter {
                 return true;
             }
             return false;
-        });
+        }).reverse();
         if (candidateIDs.length === 0) {
             return false;
         }
 
-        const hits = [];
-        const worldPos = twgl.v3.create(0, 0, 0);
-        // Iterate over the scratch pixels and check if any candidate can be
-        // touched at that point.
-        for (worldPos[1] = bounds.bottom; worldPos[1] <= bounds.top; worldPos[1]++) {
-            for (worldPos[0] = bounds.left; worldPos[0] <= bounds.right; worldPos[0]++) {
-
-                // Check candidates in the reverse order they would have been
-                // drawn. This will determine what candiate's silhouette pixel
-                // would have been drawn at the point.
-                for (let d = candidateIDs.length - 1; d >= 0; d--) {
-                    const id = candidateIDs[d];
-                    const drawable = this._allDrawables[id];
-                    if (drawable.isTouching(worldPos)) {
-                        hits[id] = (hits[id] || 0) + 1;
-                        break;
-                    }
-                }
-            }
-        }
-
-        // Bias toward selecting anything over nothing
-        hits[RenderConstants.ID_NONE] = 0;
-
-        let hit = RenderConstants.ID_NONE;
-        for (const hitID in hits) {
-            if (Object.prototype.hasOwnProperty.call(hits, hitID) && (hits[hitID] > hits[hit])) {
-                hit = hitID;
-            }
-        }
-
-        return Number(hit);
+        return this.softwareRenderer.pick(candidateIDs, bounds);
     }
 
     /**
@@ -1441,7 +1398,6 @@ class RenderWebGL extends EventEmitter {
         if (bounds === null) {
             return result;
         }
-        bounds.snapToInt();
         // iterate through the drawables list BACKWARDS - we want the top most item to be the first we check
         for (let index = candidateIDs.length - 1; index >= 0; index--) {
             const id = candidateIDs[index];
diff --git a/swrender/src/drawable.rs b/swrender/src/drawable.rs
index eaa2a6ae6..805ea81c8 100644
--- a/swrender/src/drawable.rs
+++ b/swrender/src/drawable.rs
@@ -2,7 +2,7 @@ use crate::silhouette::*;
 use crate::matrix::*;
 use crate::effect_transform::{Effects, EffectBits, transform_point, DISTORTION_EFFECT_MASK, transform_color, COLOR_EFFECT_MASK};
 
-pub type DrawableID = u32;
+pub type DrawableID = i32;
 
 pub struct Drawable {
     pub id: DrawableID,
@@ -16,7 +16,7 @@ pub struct Drawable {
 
 impl Drawable {
     pub fn get_local_position(&self, vec: Vec2) -> Vec2 {
-        let v0 = vec.0 - 0.5;
+        let v0 = vec.0 + 0.5;
         let v1 = vec.1 + 0.5;
         let m = self.inverse_matrix;
         let d = (v0 * m[3]) + (v1 * m[7]) + m[15];
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
index a68bc96c8..778f1473d 100644
--- a/swrender/src/lib.rs
+++ b/swrender/src/lib.rs
@@ -31,7 +31,7 @@ extern {
 #[global_allocator]
 static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
 
-const ID_NONE: u32 = u32::max_value();
+const ID_NONE: drawable::DrawableID = -1;
 
 #[wasm_bindgen]
 pub struct SoftwareRenderer {
@@ -127,14 +127,12 @@ impl SoftwareRenderer {
         &self,
         func: F,
         rect: JSRectangle,
-        drawable: drawable::DrawableID,
-        candidates: Vec<drawable::DrawableID>
+        drawable: drawable::DrawableID
     ) -> bool
         where F: Fn(
             matrix::Vec2,
             &drawable::Drawable,
-            &silhouette::Silhouette,
-            &Vec<(&drawable::Drawable, &silhouette::Silhouette)>
+            &silhouette::Silhouette
         ) -> bool {
 
         let left = rect.left() as i32;
@@ -144,12 +142,11 @@ impl SoftwareRenderer {
 
         let drawable = self.drawables.get(&drawable).expect("Drawable should exist");
         let silhouette = self.silhouettes.get(&drawable.silhouette).unwrap();
-        let candidates = self.map_candidates(candidates);
 
         for y in bottom..top {
             for x in left..right {
                 let position = matrix::Vec2(x as f32, y as f32);
-                if func(position, drawable, silhouette, &candidates) {
+                if func(position, drawable, silhouette) {
                     return true;
                 }
             }
@@ -164,21 +161,21 @@ impl SoftwareRenderer {
         candidates: Vec<drawable::DrawableID>,
         rect: JSRectangle
     ) -> bool {
+        let candidates = self.map_candidates(candidates);
         self.per_rect_pixel(|
             position,
             drawable,
-            silhouette,
-            candidates
+            silhouette
         | {
             if drawable.is_touching(position, silhouette) {
-                for candidate in candidates {
+                for candidate in &candidates {
                     if candidate.0.is_touching(position, candidate.1) {
                         return true;
                     }
                 }
             }
             false
-        }, rect, drawable, candidates)
+        }, rect, drawable)
     }
 
     #[inline(always)]
@@ -216,12 +213,12 @@ impl SoftwareRenderer {
     ) -> bool {
         let color: [u8; 3] = (*color).try_into().expect("color contains 3 elements");
         let mask: [u8; 3] = (*mask).try_into().expect("mask contains 3 elements");
+        let candidates = self.map_candidates(candidates);
 
         self.per_rect_pixel(|
             position,
             drawable,
-            silhouette,
-            candidates
+            silhouette
         | {
             if Self::mask_matches(drawable.sample_color(position, silhouette), mask) {
                 let sample_color = self.sample_color(position, &candidates);
@@ -230,7 +227,7 @@ impl SoftwareRenderer {
                 }
             }
             false
-        }, rect, drawable, candidates)
+        }, rect, drawable)
     }
 
     pub fn is_touching_color(
@@ -241,11 +238,11 @@ impl SoftwareRenderer {
         color: &[u8]
     ) -> bool {
         let color: [u8; 3] = (*color).try_into().expect("color contains 3 elements");
+        let candidates = self.map_candidates(candidates);
         self.per_rect_pixel(|
             position,
             drawable,
-            silhouette,
-            candidates
+            silhouette
         | {
             if drawable.is_touching(position, silhouette) {
                 let sample_color = self.sample_color(position, &candidates);
@@ -254,7 +251,7 @@ impl SoftwareRenderer {
                 }
             }
             false
-        }, rect, drawable, candidates)
+        }, rect, drawable)
     }
 
     fn sample_color(
@@ -284,4 +281,66 @@ impl SoftwareRenderer {
 
         [dst_color.0 as u8, dst_color.1 as u8, dst_color.2 as u8]
     }
+
+    pub fn drawable_touching_rect(
+        &mut self,
+        drawable: drawable::DrawableID,
+        rect: JSRectangle
+    ) -> bool {
+        self.per_rect_pixel(|
+            position,
+            drawable,
+            silhouette
+        | {
+            if drawable.is_touching(position, silhouette) {
+                return true;
+            }
+            false
+        }, rect, drawable)
+    }
+
+    pub fn pick(
+        &mut self,
+        candidates: Vec<drawable::DrawableID>,
+        rect: JSRectangle
+    ) -> drawable::DrawableID {
+        let mut hits: HashMap<drawable::DrawableID, u32> = HashMap::new();
+        hits.insert(ID_NONE, 0);
+
+        let candidates = self.map_candidates(candidates);
+
+        // TODO: deduplicate with per_rect_pixel
+        let left = rect.left() as i32;
+        let right = rect.right() as i32 + 1;
+        let bottom = rect.bottom() as i32 - 1;
+        let top = rect.top() as i32;
+
+        for y in bottom..top {
+            for x in left..right {
+                let position = matrix::Vec2(x as f32, y as f32);
+                for candidate in &candidates {
+                    if candidate.0.is_touching(position, candidate.1) {
+                        hits
+                            .entry(candidate.0.id)
+                            .and_modify(|hit| {*hit += 1})
+                            .or_insert(1);
+
+                        break;
+                    }
+                }
+            }
+        }
+
+        let mut hit: drawable::DrawableID = ID_NONE;
+        let mut highest_hits: u32 = 0;
+
+        for (id, num_hits) in hits.iter() {
+            if *num_hits > highest_hits {
+                hit = *id;
+                highest_hits = *num_hits;
+            }
+        }
+
+        hit
+    }
 }
diff --git a/swrender/src/silhouette.rs b/swrender/src/silhouette.rs
index 772cf4285..29faa2943 100644
--- a/swrender/src/silhouette.rs
+++ b/swrender/src/silhouette.rs
@@ -1,6 +1,6 @@
 use crate::matrix::Vec2;
 
-pub type SilhouetteID = u32;
+pub type SilhouetteID = i32;
 
 use wasm_bindgen::prelude::*;
 

From dafd370092e269cf9f56348e9bcc7db205a8f44a Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Sun, 22 Mar 2020 02:50:00 -0400
Subject: [PATCH 06/14] Add convex hull

---
 src/Drawable.js             |  27 +++++--
 src/EffectTransform.js      | 102 ---------------------------
 src/PenSkin.js              |   4 +-
 src/RenderWebGL.js          | 129 +++-------------------------------
 src/SVGSkin.js              |   1 -
 src/Silhouette.js           | 136 ------------------------------------
 src/Skin.js                 |  41 ++++-------
 swrender/src/convex_hull.rs |  88 +++++++++++++++++++++++
 swrender/src/lib.rs         |  17 +++++
 9 files changed, 147 insertions(+), 398 deletions(-)
 delete mode 100644 src/EffectTransform.js
 delete mode 100644 src/Silhouette.js
 create mode 100644 swrender/src/convex_hull.rs

diff --git a/src/Drawable.js b/src/Drawable.js
index 965ad5143..84b5840c8 100644
--- a/src/Drawable.js
+++ b/src/Drawable.js
@@ -80,6 +80,7 @@ class Drawable {
         this._transformedHullDirty = true;
 
         this._skinWasAltered = this._skinWasAltered.bind(this);
+        this._silhouetteWasUpdated = this._silhouetteWasUpdated.bind(this);
     }
 
     /**
@@ -122,10 +123,12 @@ class Drawable {
         if (this._skin !== newSkin) {
             if (this._skin) {
                 this._skin.removeListener(Skin.Events.WasAltered, this._skinWasAltered);
+                this._skin.removeListener(Skin.Events.SilhouetteUpdated, this._silhouetteWasUpdated);
             }
             this._skin = newSkin;
             if (this._skin) {
                 this._skin.addListener(Skin.Events.WasAltered, this._skinWasAltered);
+                this._skin.addListener(Skin.Events.SilhouetteUpdated, this._silhouetteWasUpdated);
             }
             this._skinWasAltered();
         }
@@ -453,6 +456,14 @@ class Drawable {
         // Search through transformed points to generate box on axes.
         result = result || new Rectangle();
         result.initFromPointsAABB(transformedHullPoints);
+
+        // Expand bounds by half a pixel per side because convex hull points lie in the centers of pixels
+        const silhouetteHalfPixel = (this.scale[0] / 200) * (this.skin.size[0] / this.skin.silhouetteSize[0]);
+        result.left -= silhouetteHalfPixel;
+        result.right += silhouetteHalfPixel;
+        result.bottom -= silhouetteHalfPixel;
+        result.top += silhouetteHalfPixel;
+
         return result;
     }
 
@@ -527,16 +538,12 @@ class Drawable {
         }
 
         const projection = twgl.m4.ortho(-1, 1, -1, 1, -1, 1);
-        const skinSize = this.skin.size;
-        const halfXPixel = 1 / skinSize[0] / 2;
-        const halfYPixel = 1 / skinSize[1] / 2;
         const tm = twgl.m4.multiply(this._uniforms.u_modelMatrix, projection);
         for (let i = 0; i < this._convexHullPoints.length; i++) {
             const point = this._convexHullPoints[i];
             const dstPoint = this._transformedHullPoints[i];
-
-            dstPoint[0] = 0.5 + (-point[0] / skinSize[0]) - halfXPixel;
-            dstPoint[1] = (point[1] / skinSize[1]) - 0.5 + halfYPixel;
+            dstPoint[0] = 0.5 - point[0];
+            dstPoint[1] = point[1] - 0.5;
             twgl.m4.transformPoint(tm, dstPoint, dstPoint);
         }
 
@@ -601,6 +608,14 @@ class Drawable {
         this.setTransformDirty();
     }
 
+    /**
+     * Respond to an internal change in the current Skin's silhouette.
+     * @private
+     */
+    _silhouetteWasUpdated () {
+        this.setConvexHullDirty();
+    }
+
     /**
      * Calculate a color to represent the given ID number. At least one component of
      * the resulting color will be non-zero if the ID is not RenderConstants.ID_NONE.
diff --git a/src/EffectTransform.js b/src/EffectTransform.js
deleted file mode 100644
index 33be6959e..000000000
--- a/src/EffectTransform.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * @fileoverview
- * A utility to transform a texture coordinate to another texture coordinate
- * representing how the shaders apply effects.
- */
-
-const twgl = require('twgl.js');
-
-const {rgbToHsv, hsvToRgb} = require('./util/color-conversions');
-const ShaderManager = require('./ShaderManager');
-
-/**
- * A texture coordinate is between 0 and 1. 0.5 is the center position.
- * @const {number}
- */
-const CENTER_X = 0.5;
-
-/**
- * A texture coordinate is between 0 and 1. 0.5 is the center position.
- * @const {number}
- */
-const CENTER_Y = 0.5;
-
-class EffectTransform {
-
-    /**
-     * Transform a texture coordinate to one that would be select after applying shader effects.
-     * @param {Drawable} drawable The drawable whose effects to emulate.
-     * @param {twgl.v3} vec The texture coordinate to transform.
-     * @param {twgl.v3} dst A place to store the output coordinate.
-     * @return {twgl.v3} dst - The coordinate after being transform by effects.
-     */
-    static transformPoint (drawable, vec, dst) {
-        twgl.v3.copy(vec, dst);
-
-        const effects = drawable.enabledEffects;
-        const uniforms = drawable.getUniforms();
-        if ((effects & ShaderManager.EFFECT_INFO.mosaic.mask) !== 0) {
-            // texcoord0 = fract(u_mosaic * texcoord0);
-            dst[0] = uniforms.u_mosaic * dst[0] % 1;
-            dst[1] = uniforms.u_mosaic * dst[1] % 1;
-        }
-        if ((effects & ShaderManager.EFFECT_INFO.pixelate.mask) !== 0) {
-            const skinUniforms = drawable.skin.getUniforms();
-            // vec2 pixelTexelSize = u_skinSize / u_pixelate;
-            const texelX = skinUniforms.u_skinSize[0] / uniforms.u_pixelate;
-            const texelY = skinUniforms.u_skinSize[1] / uniforms.u_pixelate;
-            // texcoord0 = (floor(texcoord0 * pixelTexelSize) + kCenter) /
-            //   pixelTexelSize;
-            dst[0] = (Math.floor(dst[0] * texelX) + CENTER_X) / texelX;
-            dst[1] = (Math.floor(dst[1] * texelY) + CENTER_Y) / texelY;
-        }
-        if ((effects & ShaderManager.EFFECT_INFO.whirl.mask) !== 0) {
-            // const float kRadius = 0.5;
-            const RADIUS = 0.5;
-            // vec2 offset = texcoord0 - kCenter;
-            const offsetX = dst[0] - CENTER_X;
-            const offsetY = dst[1] - CENTER_Y;
-            // float offsetMagnitude = length(offset);
-            const offsetMagnitude = Math.sqrt(Math.pow(offsetX, 2) + Math.pow(offsetY, 2));
-            // float whirlFactor = max(1.0 - (offsetMagnitude / kRadius), 0.0);
-            const whirlFactor = Math.max(1.0 - (offsetMagnitude / RADIUS), 0.0);
-            // float whirlActual = u_whirl * whirlFactor * whirlFactor;
-            const whirlActual = uniforms.u_whirl * whirlFactor * whirlFactor;
-            // float sinWhirl = sin(whirlActual);
-            const sinWhirl = Math.sin(whirlActual);
-            // float cosWhirl = cos(whirlActual);
-            const cosWhirl = Math.cos(whirlActual);
-            // mat2 rotationMatrix = mat2(
-            //     cosWhirl, -sinWhirl,
-            //     sinWhirl, cosWhirl
-            // );
-            const rot1 = cosWhirl;
-            const rot2 = -sinWhirl;
-            const rot3 = sinWhirl;
-            const rot4 = cosWhirl;
-
-            // texcoord0 = rotationMatrix * offset + kCenter;
-            dst[0] = (rot1 * offsetX) + (rot3 * offsetY) + CENTER_X;
-            dst[1] = (rot2 * offsetX) + (rot4 * offsetY) + CENTER_Y;
-        }
-        if ((effects & ShaderManager.EFFECT_INFO.fisheye.mask) !== 0) {
-            // vec2 vec = (texcoord0 - kCenter) / kCenter;
-            const vX = (dst[0] - CENTER_X) / CENTER_X;
-            const vY = (dst[1] - CENTER_Y) / CENTER_Y;
-            // float vecLength = length(vec);
-            const vLength = Math.sqrt((vX * vX) + (vY * vY));
-            // float r = pow(min(vecLength, 1.0), u_fisheye) * max(1.0, vecLength);
-            const r = Math.pow(Math.min(vLength, 1), uniforms.u_fisheye) * Math.max(1, vLength);
-            // vec2 unit = vec / vecLength;
-            const unitX = vX / vLength;
-            const unitY = vY / vLength;
-            // texcoord0 = kCenter + r * unit * kCenter;
-            dst[0] = CENTER_X + (r * unitX * CENTER_X);
-            dst[1] = CENTER_Y + (r * unitY * CENTER_Y);
-        }
-
-        return dst;
-    }
-}
-
-module.exports = EffectTransform;
diff --git a/src/PenSkin.js b/src/PenSkin.js
index 7bcd4cd0c..5e5fbd998 100644
--- a/src/PenSkin.js
+++ b/src/PenSkin.js
@@ -334,9 +334,7 @@ class PenSkin extends Skin {
             );
 
             this._silhouetteImageData.data.set(this._silhouettePixels);
-            this._silhouette.update(this._silhouetteImageData, true /* isPremultiplied */);
-
-            this._setSilhouetteFromData(this._silhouetteImageData, true /* isPremultiplied */);
+            this._setSilhouetteFromData(this._silhouetteImageData, true /* premultiplied */);
 
             this._silhouetteDirty = false;
         }
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index 7dc7c71b7..6e0b31711 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -1,6 +1,5 @@
 const EventEmitter = require('events');
 
-const hull = require('hull.js');
 const twgl = require('twgl.js');
 
 const BitmapSkin = require('./BitmapSkin');
@@ -11,7 +10,6 @@ const RenderConstants = require('./RenderConstants');
 const ShaderManager = require('./ShaderManager');
 const SVGSkin = require('./SVGSkin');
 const TextBubbleSkin = require('./TextBubbleSkin');
-const EffectTransform = require('./EffectTransform');
 const log = require('./util/log');
 
 let onLoadSwRender = null;
@@ -1864,130 +1862,19 @@ class RenderWebGL extends EventEmitter {
      */
     _getConvexHullPointsForDrawable (drawableID) {
         const drawable = this._allDrawables[drawableID];
-
-        const [width, height] = drawable.skin.size;
-        // No points in the hull if invisible or size is 0.
-        if (!drawable.getVisible() || width === 0 || height === 0) {
-            return [];
-        }
-
         drawable.updateCPURenderAttributes();
+        const pointValues = this.softwareRenderer.drawable_convex_hull_points(drawableID);
 
-        /**
-         * Return the determinant of two vectors, the vector from A to B and the vector from A to C.
-         *
-         * The determinant is useful in this case to know if AC is counter-clockwise from AB.
-         * A positive value means that AC is counter-clockwise from AB. A negative value means AC is clockwise from AB.
-         *
-         * @param {Float32Array} A A 2d vector in space.
-         * @param {Float32Array} B A 2d vector in space.
-         * @param {Float32Array} C A 2d vector in space.
-         * @return {number} Greater than 0 if counter clockwise, less than if clockwise, 0 if all points are on a line.
-         */
-        const determinant = function (A, B, C) {
-            // AB = B - A
-            // AC = C - A
-            // det (AB BC) = AB0 * AC1 - AB1 * AC0
-            return (((B[0] - A[0]) * (C[1] - A[1])) - ((B[1] - A[1]) * (C[0] - A[0])));
-        };
-
-        // This algorithm for calculating the convex hull somewhat resembles the monotone chain algorithm.
-        // The main difference is that instead of sorting the points by x-coordinate, and y-coordinate in case of ties,
-        // it goes through them by y-coordinate in the outer loop and x-coordinate in the inner loop.
-        // This gives us "left" and "right" hulls, whereas the monotone chain algorithm gives "top" and "bottom" hulls.
-        // Adapted from https://github.com/LLK/scratch-flash/blob/dcbeeb59d44c3be911545dfe54d46a32404f8e69/src/scratch/ScratchCostume.as#L369-L413
-
-        const leftHull = [];
-        const rightHull = [];
-
-        // While convex hull algorithms usually push and pop values from the list of hull points,
-        // here, we keep indices for the "last" point in each array. Any points past these indices are ignored.
-        // This is functionally equivalent to pushing and popping from a "stack" of hull points.
-        let leftEndPointIndex = -1;
-        let rightEndPointIndex = -1;
-
-        const _pixelPos = twgl.v3.create();
-        const _effectPos = twgl.v3.create();
-
-        let currentPoint;
-
-        // *Not* Scratch Space-- +y is bottom
-        // Loop over all rows of pixels, starting at the top
-        for (let y = 0; y < height; y++) {
-            _pixelPos[1] = y / height;
-
-            // We start at the leftmost point, then go rightwards until we hit an opaque pixel
-            let x = 0;
-            for (; x < width; x++) {
-                _pixelPos[0] = x / width;
-                EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
-                if (drawable.skin.isTouchingLinear(_effectPos)) {
-                    currentPoint = [x, y];
-                    break;
-                }
-            }
-
-            // If we managed to loop all the way through, there are no opaque pixels on this row. Go to the next one
-            if (x >= width) {
-                continue;
-            }
-
-            // Because leftEndPointIndex is initialized to -1, this is skipped for the first two rows.
-            // It runs only when there are enough points in the left hull to make at least one line.
-            // If appending the current point to the left hull makes a counter-clockwise turn,
-            // we want to append the current point. Otherwise, we decrement the index of the "last" hull point until the
-            // current point makes a counter-clockwise turn.
-            // This decrementing has the same effect as popping from the point list, but is hopefully faster.
-            while (leftEndPointIndex > 0) {
-                if (determinant(leftHull[leftEndPointIndex], leftHull[leftEndPointIndex - 1], currentPoint) > 0) {
-                    break;
-                } else {
-                    // leftHull.pop();
-                    --leftEndPointIndex;
-                }
-            }
-
-            // This has the same effect as pushing to the point list.
-            // This "list head pointer" coding style leaves excess points dangling at the end of the list,
-            // but that doesn't matter; we simply won't copy them over to the final hull.
-
-            // leftHull.push(currentPoint);
-            leftHull[++leftEndPointIndex] = currentPoint;
-
-            // Now we repeat the process for the right side, looking leftwards for a pixel.
-            for (x = width - 1; x >= 0; x--) {
-                _pixelPos[0] = x / width;
-                EffectTransform.transformPoint(drawable, _pixelPos, _effectPos);
-                if (drawable.skin.isTouchingLinear(_effectPos)) {
-                    currentPoint = [x, y];
-                    break;
-                }
-            }
-
-            // Because we're coming at this from the right, it goes clockwise this time.
-            while (rightEndPointIndex > 0) {
-                if (determinant(rightHull[rightEndPointIndex], rightHull[rightEndPointIndex - 1], currentPoint) < 0) {
-                    break;
-                } else {
-                    --rightEndPointIndex;
-                }
-            }
-
-            rightHull[++rightEndPointIndex] = currentPoint;
-        }
+        const points = [];
 
-        // Start off "hullPoints" with the left hull points.
-        const hullPoints = leftHull;
-        // This is where we get rid of those dangling extra points.
-        hullPoints.length = leftEndPointIndex + 1;
-        // Add points from the right side in reverse order so all points are ordered clockwise.
-        for (let j = rightEndPointIndex; j >= 0; --j) {
-            hullPoints.push(rightHull[j]);
+        for (let i = 0; i < pointValues.length; i += 2) {
+            const point = new Float32Array(2);
+            point[0] = pointValues[i];
+            point[1] = pointValues[i + 1];
+            points.push(point);
         }
 
-        // Simplify boundary points using hull.js.
-        // TODO: Remove this; this algorithm already generates convex hulls.
-        return hull(hullPoints, Infinity);
+        return points;
     }
 
     /**
diff --git a/src/SVGSkin.js b/src/SVGSkin.js
index 3d3ec5234..8cd92367a 100644
--- a/src/SVGSkin.js
+++ b/src/SVGSkin.js
@@ -115,7 +115,6 @@ class SVGSkin extends Skin {
 
         // Check if this is the largest MIP created so far. Currently, silhouettes only get scaled up.
         if (this._largestMIPScale < scale) {
-            this._silhouette.update(textureData);
             this._setSilhouetteFromData(textureData);
             this._largestMIPScale = scale;
         }
diff --git a/src/Silhouette.js b/src/Silhouette.js
deleted file mode 100644
index 615ecda34..000000000
--- a/src/Silhouette.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * @fileoverview
- * A representation of a Skin's silhouette that can test if a point on the skin
- * renders a pixel where it is drawn.
- */
-
-/**
- * <canvas> element used to update Silhouette data from skin bitmap data.
- * @type {CanvasElement}
- */
-let __SilhouetteUpdateCanvas;
-
-/**
- * Internal helper function (in hopes that compiler can inline).  Get a pixel
- * from silhouette data, or 0 if outside it's bounds.
- * @private
- * @param {Silhouette} silhouette - has data width and height
- * @param {number} x - x
- * @param {number} y - y
- * @return {number} Alpha value for x/y position
- */
-const getPoint = ({_width: width, _height: height, _colorData: data}, x, y) => {
-    // 0 if outside bounds, otherwise read from data.
-    if (x >= width || y >= height || x < 0 || y < 0) {
-        return 0;
-    }
-    return data[(((y * width) + x) * 4) + 3];
-};
-
-/**
- * Memory buffers for doing 4 corner sampling for linear interpolation
- */
-const __cornerWork = [
-    new Uint8ClampedArray(4),
-    new Uint8ClampedArray(4),
-    new Uint8ClampedArray(4),
-    new Uint8ClampedArray(4)
-];
-
-class Silhouette {
-    constructor () {
-        /**
-         * The width of the data representing the current skin data.
-         * @type {number}
-         */
-        this._width = 0;
-
-        /**
-         * The height of the data representing the current skin date.
-         * @type {number}
-         */
-        this._height = 0;
-
-        /**
-         * The data representing a skin's silhouette shape.
-         * @type {Uint8ClampedArray}
-         */
-        this._colorData = null;
-    }
-
-    /**
-     * Update this silhouette with the bitmapData for a skin.
-     * @param {ImageData|HTMLCanvasElement|HTMLImageElement} bitmapData An image, canvas or other element that the skin
-     * @param {boolean} isPremultiplied True if the source bitmap data comes premultiplied (e.g. from readPixels).
-     * rendering can be queried from.
-     */
-    update (bitmapData) {
-        let imageData;
-        if (bitmapData instanceof ImageData) {
-            // If handed ImageData directly, use it directly.
-            imageData = bitmapData;
-            this._width = bitmapData.width;
-            this._height = bitmapData.height;
-        } else {
-            // Draw about anything else to our update canvas and poll image data
-            // from that.
-            const canvas = Silhouette._updateCanvas();
-            const width = this._width = canvas.width = bitmapData.width;
-            const height = this._height = canvas.height = bitmapData.height;
-            const ctx = canvas.getContext('2d');
-
-            if (!(width && height)) {
-                return;
-            }
-            ctx.clearRect(0, 0, width, height);
-            ctx.drawImage(bitmapData, 0, 0, width, height);
-            imageData = ctx.getImageData(0, 0, width, height);
-        }
-
-        this._colorData = imageData.data;
-    }
-
-    /**
-     * Test if texture coordinate touches the silhouette using nearest neighbor.
-     * @param {twgl.v3} vec A texture coordinate.
-     * @return {boolean} If the nearest pixel has an alpha value.
-     */
-    isTouchingNearest (vec) {
-        if (!this._colorData) return;
-        return getPoint(
-            this,
-            Math.floor(vec[0] * (this._width - 1)),
-            Math.floor(vec[1] * (this._height - 1))
-        ) > 0;
-    }
-
-    /**
-     * Test to see if any of the 4 pixels used in the linear interpolate touch
-     * the silhouette.
-     * @param {twgl.v3} vec A texture coordinate.
-     * @return {boolean} Any of the pixels have some alpha.
-     */
-    isTouchingLinear (vec) {
-        if (!this._colorData) return;
-        const x = Math.floor(vec[0] * (this._width - 1));
-        const y = Math.floor(vec[1] * (this._height - 1));
-        return getPoint(this, x, y) > 0 ||
-            getPoint(this, x + 1, y) > 0 ||
-            getPoint(this, x, y + 1) > 0 ||
-            getPoint(this, x + 1, y + 1) > 0;
-    }
-
-    /**
-     * Get the canvas element reused by Silhouettes to update their data with.
-     * @private
-     * @return {CanvasElement} A canvas to draw bitmap data to.
-     */
-    static _updateCanvas () {
-        if (typeof __SilhouetteUpdateCanvas === 'undefined') {
-            __SilhouetteUpdateCanvas = document.createElement('canvas');
-        }
-        return __SilhouetteUpdateCanvas;
-    }
-}
-
-module.exports = Silhouette;
diff --git a/src/Skin.js b/src/Skin.js
index 4f95a3cc2..7916d9142 100644
--- a/src/Skin.js
+++ b/src/Skin.js
@@ -3,7 +3,6 @@ const EventEmitter = require('events');
 const twgl = require('twgl.js');
 
 const RenderConstants = require('./RenderConstants');
-const Silhouette = require('./Silhouette');
 
 class Skin extends EventEmitter {
     /**
@@ -51,7 +50,8 @@ class Skin extends EventEmitter {
          * A silhouette to store touching data, skins are responsible for keeping it up to date.
          * @private
          */
-        this._silhouette = new Silhouette();
+
+        this.silhouetteSize = [0, 0];
 
         renderer.softwareRenderer.set_silhouette(id, 0, 0, new Uint8Array(0), 1, 1, true);
 
@@ -161,7 +161,6 @@ class Skin extends EventEmitter {
         gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureData);
         gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
 
-        this._silhouette.update(textureData);
         this._setSilhouetteFromData(textureData);
     }
 
@@ -196,7 +195,6 @@ class Skin extends EventEmitter {
         this._rotationCenter[0] = 0;
         this._rotationCenter[1] = 0;
 
-        this._silhouette.update(this._emptyImageData);
         this._setSilhouetteFromData(this._emptyImageData);
         this.emit(Skin.Events.WasAltered);
     }
@@ -214,32 +212,11 @@ class Skin extends EventEmitter {
 
             premultiplied
         );
-    }
 
-    /**
-     * Does this point touch an opaque or translucent point on this skin?
-     * Nearest Neighbor version
-     * The caller is responsible for ensuring this skin's silhouette is up-to-date.
-     * @see updateSilhouette
-     * @see Drawable.updateCPURenderAttributes
-     * @param {twgl.v3} vec A texture coordinate.
-     * @return {boolean} Did it touch?
-     */
-    isTouchingNearest (vec) {
-        return this._silhouette.isTouchingNearest(vec);
-    }
+        this.silhouetteSize[0] = data.width;
+        this.silhouetteSize[1] = data.height;
 
-    /**
-     * Does this point touch an opaque or translucent point on this skin?
-     * Linear Interpolation version
-     * The caller is responsible for ensuring this skin's silhouette is up-to-date.
-     * @see updateSilhouette
-     * @see Drawable.updateCPURenderAttributes
-     * @param {twgl.v3} vec A texture coordinate.
-     * @return {boolean} Did it touch?
-     */
-    isTouchingLinear (vec) {
-        return this._silhouette.isTouchingLinear(vec);
+        this.emit(Skin.Events.SilhouetteUpdated);
     }
 
 }
@@ -253,7 +230,13 @@ Skin.Events = {
      * Emitted when anything about the Skin has been altered, such as the appearance or rotation center.
      * @event Skin.event:WasAltered
      */
-    WasAltered: 'WasAltered'
+    WasAltered: 'WasAltered',
+
+    /**
+     * Emitted whenever this skin's silhouette changes.
+     * @event Skin.event:SilhouetteUpdated
+     */
+    SilhouetteUpdated: 'SilhouetteUpdated'
 };
 
 module.exports = Skin;
diff --git a/swrender/src/convex_hull.rs b/swrender/src/convex_hull.rs
new file mode 100644
index 000000000..d07410762
--- /dev/null
+++ b/swrender/src/convex_hull.rs
@@ -0,0 +1,88 @@
+use crate::silhouette::Silhouette;
+use crate::drawable::Drawable;
+use crate::matrix::Vec2;
+
+use crate::effect_transform::transform_point;
+
+/// Return the determinant of two vector, the vector from A to B and the vector from A to C.
+///
+/// The determinant is useful in this case to know if AC is counter-clockwise from AB.
+/// A positive value means that AC is counter-clockwise from AB. A negative value means AC is clockwise from AB.
+fn determinant(a: Vec2, b: Vec2, c: Vec2) -> f32 {
+    ((b.0 - a.0) * (c.1 - a.1)) - ((b.1 - a.1) * (c.0 - a.0))
+}
+
+pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouette) -> Vec<Vec2> {
+    let mut left_hull: Vec<Vec2> = Vec::new();
+    let mut right_hull: Vec<Vec2> = Vec::new();
+
+    let transform = |p| transform_point(
+        p,
+        &drawable.effects,
+        drawable.effect_bits,
+        silhouette.nominal_size
+    );
+
+    let mut current_point = Vec2(0f32, 0f32);
+
+    for y in 0..silhouette.height {
+        let mut x: u32 = 0;
+        while x < silhouette.width {
+            let local_point = Vec2((x as f32 + 0.5) / silhouette.width as f32, (y as f32 + 0.5) / silhouette.height as f32);
+            let point = transform(local_point);
+
+            if silhouette.is_touching_nearest(point) {
+                current_point = local_point;
+                break;
+            }
+
+            x += 1;
+        }
+
+        if x >= silhouette.width {
+            continue;
+        }
+
+        while left_hull.len() >= 2 {
+            let len = left_hull.len();
+            if determinant(left_hull[len - 1], left_hull[len - 2], current_point) > 0f32 {
+                break;
+            } else {
+                left_hull.pop();
+            }
+        }
+
+        left_hull.push(Vec2(current_point.0 as f32, current_point.1 as f32));
+
+        x = silhouette.width - 1;
+
+        while x != 0 {
+            let local_point = Vec2((x as f32 + 0.5) / silhouette.width as f32, (y as f32 + 0.5) / silhouette.height as f32);
+            let point = transform(local_point);
+
+            if silhouette.is_touching_nearest(point) {
+                current_point = local_point;
+                break;
+            }
+
+            x -= 1;
+        }
+
+        while right_hull.len() >= 2 {
+            let len = right_hull.len();
+            if determinant(right_hull[len - 1], right_hull[len - 2], current_point) < 0f32 {
+                break;
+            } else {
+                right_hull.pop();
+            }
+        }
+
+        right_hull.push(Vec2(current_point.0 as f32, current_point.1 as f32));
+    }
+
+    right_hull.reverse();
+
+    left_hull.append(&mut right_hull);
+
+    left_hull
+}
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
index 778f1473d..50ddac14b 100644
--- a/swrender/src/lib.rs
+++ b/swrender/src/lib.rs
@@ -1,6 +1,7 @@
 mod utils;
 mod matrix;
 mod effect_transform;
+mod convex_hull;
 pub mod silhouette;
 pub mod drawable;
 
@@ -343,4 +344,20 @@ impl SoftwareRenderer {
 
         hit
     }
+
+    pub fn drawable_convex_hull_points(&mut self, drawable: drawable::DrawableID) -> Vec<f32> {
+        let drawable = self.drawables.get(&drawable).expect("Drawable should exist");
+        let silhouette = self.silhouettes.get(&drawable.silhouette).unwrap();
+
+        let hull = convex_hull::calculate_drawable_convex_hull(drawable, silhouette);
+
+        let mut points: Vec<f32> = Vec::new();
+
+        for point in hull {
+            points.push(point.0);
+            points.push(point.1);
+        }
+
+        points
+    }
 }

From f6b195021f6fc48215489b5e77ce39347d778c72 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Sun, 22 Mar 2020 03:37:34 -0400
Subject: [PATCH 07/14] Reformat Rust code

---
 swrender/rustfmt.toml            |   1 +
 swrender/src/console_log.rs      |  15 ---
 swrender/src/convex_hull.rs      |  26 ++--
 swrender/src/drawable.rs         |  21 ++-
 swrender/src/effect_transform.rs |  38 +++---
 swrender/src/lib.rs              | 219 ++++++++++++++++---------------
 swrender/src/matrix.rs           | 106 ++++++++-------
 swrender/src/silhouette.rs       |  51 +++++--
 8 files changed, 265 insertions(+), 212 deletions(-)
 create mode 100644 swrender/rustfmt.toml
 delete mode 100644 swrender/src/console_log.rs

diff --git a/swrender/rustfmt.toml b/swrender/rustfmt.toml
new file mode 100644
index 000000000..8dc0f76e3
--- /dev/null
+++ b/swrender/rustfmt.toml
@@ -0,0 +1 @@
+force_explicit_abi = false
diff --git a/swrender/src/console_log.rs b/swrender/src/console_log.rs
deleted file mode 100644
index 7ade76788..000000000
--- a/swrender/src/console_log.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-use wasm_bindgen::prelude::*;
-
-#[wasm_bindgen]
-extern {
-    #[wasm_bindgen(js_namespace = console)]
-    pub fn log(s: &str);
-}
-
-#[macro_use]
-mod console_log {
-    #[macro_export]
-    macro_rules! console_log {
-        ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
-    }
-}
diff --git a/swrender/src/convex_hull.rs b/swrender/src/convex_hull.rs
index d07410762..b3683f3db 100644
--- a/swrender/src/convex_hull.rs
+++ b/swrender/src/convex_hull.rs
@@ -1,6 +1,6 @@
-use crate::silhouette::Silhouette;
 use crate::drawable::Drawable;
 use crate::matrix::Vec2;
+use crate::silhouette::Silhouette;
 
 use crate::effect_transform::transform_point;
 
@@ -16,19 +16,24 @@ pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouet
     let mut left_hull: Vec<Vec2> = Vec::new();
     let mut right_hull: Vec<Vec2> = Vec::new();
 
-    let transform = |p| transform_point(
-        p,
-        &drawable.effects,
-        drawable.effect_bits,
-        silhouette.nominal_size
-    );
+    let transform = |p| {
+        transform_point(
+            p,
+            &drawable.effects,
+            drawable.effect_bits,
+            silhouette.nominal_size,
+        )
+    };
 
     let mut current_point = Vec2(0f32, 0f32);
 
     for y in 0..silhouette.height {
         let mut x: u32 = 0;
         while x < silhouette.width {
-            let local_point = Vec2((x as f32 + 0.5) / silhouette.width as f32, (y as f32 + 0.5) / silhouette.height as f32);
+            let local_point = Vec2(
+                (x as f32 + 0.5) / silhouette.width as f32,
+                (y as f32 + 0.5) / silhouette.height as f32,
+            );
             let point = transform(local_point);
 
             if silhouette.is_touching_nearest(point) {
@@ -57,7 +62,10 @@ pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouet
         x = silhouette.width - 1;
 
         while x != 0 {
-            let local_point = Vec2((x as f32 + 0.5) / silhouette.width as f32, (y as f32 + 0.5) / silhouette.height as f32);
+            let local_point = Vec2(
+                (x as f32 + 0.5) / silhouette.width as f32,
+                (y as f32 + 0.5) / silhouette.height as f32,
+            );
             let point = transform(local_point);
 
             if silhouette.is_touching_nearest(point) {
diff --git a/swrender/src/drawable.rs b/swrender/src/drawable.rs
index 805ea81c8..757dab633 100644
--- a/swrender/src/drawable.rs
+++ b/swrender/src/drawable.rs
@@ -1,6 +1,9 @@
-use crate::silhouette::*;
+use crate::effect_transform::{
+    transform_color, transform_point, EffectBits, Effects, COLOR_EFFECT_MASK,
+    DISTORTION_EFFECT_MASK,
+};
 use crate::matrix::*;
-use crate::effect_transform::{Effects, EffectBits, transform_point, DISTORTION_EFFECT_MASK, transform_color, COLOR_EFFECT_MASK};
+use crate::silhouette::*;
 
 pub type DrawableID = i32;
 
@@ -11,7 +14,7 @@ pub struct Drawable {
     pub silhouette: SilhouetteID,
     pub effects: Effects,
     pub effect_bits: EffectBits,
-    pub use_nearest_neighbor: bool
+    pub use_nearest_neighbor: bool,
 }
 
 impl Drawable {
@@ -40,7 +43,11 @@ impl Drawable {
     #[inline(always)]
     pub fn is_touching(&self, position: Vec2, silhouette: &Silhouette) -> bool {
         let local_position = self.get_local_position(position);
-        if local_position.0 < 0f32 || local_position.0 >= 1f32 || local_position.1 < 0f32 || local_position.1 >= 1f32 {
+        if local_position.0 < 0f32
+            || local_position.0 >= 1f32
+            || local_position.1 < 0f32
+            || local_position.1 >= 1f32
+        {
             return false;
         }
         let local_position = self.get_transformed_position(local_position, silhouette.nominal_size);
@@ -55,7 +62,11 @@ impl Drawable {
     #[inline(always)]
     pub fn sample_color<'a>(&self, position: Vec2, silhouette: &'a Silhouette) -> [u8; 4] {
         let local_position = self.get_local_position(position);
-        if local_position.0 < 0f32 || local_position.0 >= 1f32 || local_position.1 < 0f32 || local_position.1 >= 1f32 {
+        if local_position.0 < 0f32
+            || local_position.0 >= 1f32
+            || local_position.1 < 0f32
+            || local_position.1 >= 1f32
+        {
             return [0, 0, 0, 0];
         }
         let local_position = self.get_transformed_position(local_position, silhouette.nominal_size);
diff --git a/swrender/src/effect_transform.rs b/swrender/src/effect_transform.rs
index 3daadfc4b..ebb55935f 100644
--- a/swrender/src/effect_transform.rs
+++ b/swrender/src/effect_transform.rs
@@ -45,16 +45,14 @@ pub enum EffectBitfield {
     Ghost = 6,
 }
 
-pub const COLOR_EFFECT_MASK: EffectBits =
-    1 << (EffectBitfield::Color as u32) |
-    1 << (EffectBitfield::Brightness as u32) |
-    1 << (EffectBitfield::Ghost as u32);
+pub const COLOR_EFFECT_MASK: EffectBits = 1 << (EffectBitfield::Color as u32)
+    | 1 << (EffectBitfield::Brightness as u32)
+    | 1 << (EffectBitfield::Ghost as u32);
 
-pub const DISTORTION_EFFECT_MASK: EffectBits =
-    1 << (EffectBitfield::Fisheye as u32) |
-    1 << (EffectBitfield::Whirl as u32) |
-    1 << (EffectBitfield::Pixelate as u32) |
-    1 << (EffectBitfield::Mosaic as u32);
+pub const DISTORTION_EFFECT_MASK: EffectBits = 1 << (EffectBitfield::Fisheye as u32)
+    | 1 << (EffectBitfield::Whirl as u32)
+    | 1 << (EffectBitfield::Pixelate as u32)
+    | 1 << (EffectBitfield::Mosaic as u32);
 
 impl Effects {
     pub fn set_from_js(&mut self, effects: JSEffectMap) {
@@ -118,7 +116,7 @@ fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
         3 => (p, q, v),
         4 => (t, p, v),
         5 => (v, p, q),
-        _ => unreachable!()
+        _ => unreachable!(),
     }
 }
 
@@ -128,7 +126,7 @@ pub fn transform_color<'a>(color: [u8; 4], effects: &Effects, effect_bits: Effec
         (color[0] as f32) * COLOR_DIVISOR,
         (color[1] as f32) * COLOR_DIVISOR,
         (color[2] as f32) * COLOR_DIVISOR,
-        (color[3] as f32) * COLOR_DIVISOR
+        (color[3] as f32) * COLOR_DIVISOR,
     ];
 
     let enable_color = effect_bits & (1 << (EffectBitfield::Color as u32)) != 0;
@@ -194,19 +192,29 @@ pub fn transform_color<'a>(color: [u8; 4], effects: &Effects, effect_bits: Effec
         rgba[3] *= effects.ghost;
     }
 
-    [(rgba[0] * 255f32) as u8, (rgba[1] * 255f32) as u8, (rgba[2] * 255f32) as u8, (rgba[3] * 255f32) as u8]
+    [
+        (rgba[0] * 255f32) as u8,
+        (rgba[1] * 255f32) as u8,
+        (rgba[2] * 255f32) as u8,
+        (rgba[3] * 255f32) as u8,
+    ]
 }
 
 const CENTER: Vec2 = Vec2(0.5, 0.5);
 
-pub fn transform_point(point: Vec2, effects: &Effects, effect_bits: EffectBits, skin_size: Vec2) -> Vec2 {
+pub fn transform_point(
+    point: Vec2,
+    effects: &Effects,
+    effect_bits: EffectBits,
+    skin_size: Vec2,
+) -> Vec2 {
     let mut out = point;
 
     if effect_bits & (1 << (EffectBitfield::Mosaic as u32)) != 0 {
         /*texcoord0 = fract(u_mosaic * texcoord0);*/
         out = Vec2(
             f32::fract(effects.mosaic * out.0),
-            f32::fract(effects.mosaic * out.1)
+            f32::fract(effects.mosaic * out.1),
         );
     }
 
@@ -218,7 +226,7 @@ pub fn transform_point(point: Vec2, effects: &Effects, effect_bits: EffectBits,
 
         out = Vec2(
             (f32::floor(out.0 * pixel_texel_size_x) + CENTER.0) / pixel_texel_size_x,
-            (f32::floor(out.1 * pixel_texel_size_y) + CENTER.1) / pixel_texel_size_y
+            (f32::floor(out.1 * pixel_texel_size_y) + CENTER.1) / pixel_texel_size_y,
         );
     }
 
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
index 50ddac14b..690ba50c6 100644
--- a/swrender/src/lib.rs
+++ b/swrender/src/lib.rs
@@ -1,9 +1,9 @@
-mod utils;
-mod matrix;
-mod effect_transform;
 mod convex_hull;
-pub mod silhouette;
 pub mod drawable;
+mod effect_transform;
+mod matrix;
+pub mod silhouette;
+mod utils;
 
 use wasm_bindgen::prelude::*;
 
@@ -37,7 +37,7 @@ const ID_NONE: drawable::DrawableID = -1;
 #[wasm_bindgen]
 pub struct SoftwareRenderer {
     drawables: HashMap<drawable::DrawableID, drawable::Drawable>,
-    silhouettes: HashMap<silhouette::SilhouetteID, silhouette::Silhouette>
+    silhouettes: HashMap<silhouette::SilhouetteID, silhouette::Silhouette>,
 }
 
 #[wasm_bindgen]
@@ -45,10 +45,12 @@ impl SoftwareRenderer {
     pub fn new() -> SoftwareRenderer {
         let mut renderer = SoftwareRenderer {
             drawables: HashMap::new(),
-            silhouettes: HashMap::new()
+            silhouettes: HashMap::new(),
         };
 
-        renderer.silhouettes.insert(ID_NONE, silhouette::Silhouette::new(ID_NONE));
+        renderer
+            .silhouettes
+            .insert(ID_NONE, silhouette::Silhouette::new(ID_NONE));
 
         utils::set_panic_hook();
         renderer
@@ -61,7 +63,7 @@ impl SoftwareRenderer {
         silhouette: Option<silhouette::SilhouetteID>,
         effects: Option<effect_transform::JSEffectMap>,
         effect_bits: effect_transform::EffectBits,
-        use_nearest_neighbor: bool
+        use_nearest_neighbor: bool,
     ) {
         let d = self.drawables.entry(id).or_insert(drawable::Drawable {
             matrix: [0.0; 16],
@@ -70,14 +72,16 @@ impl SoftwareRenderer {
             effect_bits: 0,
             silhouette: match silhouette {
                 Some(s) => s,
-                None => ID_NONE
+                None => ID_NONE,
             },
             use_nearest_neighbor,
-            id
+            id,
         });
 
         if let Some(m) = matrix {
-            d.matrix = (*m).try_into().expect("drawable's matrix contains 16 elements");
+            d.matrix = (*m)
+                .try_into()
+                .expect("drawable's matrix contains 16 elements");
             d.inverse_matrix = d.matrix.inverse();
         }
         if let Some(s) = silhouette {
@@ -104,8 +108,17 @@ impl SoftwareRenderer {
         nominal_height: f64,
         premultiplied: bool,
     ) {
-        let s = self.silhouettes.entry(id).or_insert(silhouette::Silhouette::new(id));
-        s.set_data(w, h, data, matrix::Vec2(nominal_width as f32, nominal_height as f32), premultiplied);
+        let s = self
+            .silhouettes
+            .entry(id)
+            .or_insert(silhouette::Silhouette::new(id));
+        s.set_data(
+            w,
+            h,
+            data,
+            matrix::Vec2(nominal_width as f32, nominal_height as f32),
+            premultiplied,
+        );
     }
 
     pub fn remove_silhouette(&mut self, id: silhouette::SilhouetteID) {
@@ -114,34 +127,34 @@ impl SoftwareRenderer {
 
     fn map_candidates(
         &self,
-        candidates: Vec<drawable::DrawableID>
+        candidates: Vec<drawable::DrawableID>,
     ) -> Vec<(&drawable::Drawable, &silhouette::Silhouette)> {
-        candidates.into_iter()
-        .map(|c| {
-            let d = self.drawables.get(&c).expect("Candidate drawable should exist");
-            let s = self.silhouettes.get(&d.silhouette).unwrap();
-            (d, s)
-        }).collect()
+        candidates
+            .into_iter()
+            .map(|c| {
+                let d = self
+                    .drawables
+                    .get(&c)
+                    .expect("Candidate drawable should exist");
+                let s = self.silhouettes.get(&d.silhouette).unwrap();
+                (d, s)
+            })
+            .collect()
     }
 
-    fn per_rect_pixel<F>(
-        &self,
-        func: F,
-        rect: JSRectangle,
-        drawable: drawable::DrawableID
-    ) -> bool
-        where F: Fn(
-            matrix::Vec2,
-            &drawable::Drawable,
-            &silhouette::Silhouette
-        ) -> bool {
-
+    fn per_rect_pixel<F>(&self, func: F, rect: JSRectangle, drawable: drawable::DrawableID) -> bool
+    where
+        F: Fn(matrix::Vec2, &drawable::Drawable, &silhouette::Silhouette) -> bool,
+    {
         let left = rect.left() as i32;
         let right = rect.right() as i32 + 1;
         let bottom = rect.bottom() as i32 - 1;
         let top = rect.top() as i32;
 
-        let drawable = self.drawables.get(&drawable).expect("Drawable should exist");
+        let drawable = self
+            .drawables
+            .get(&drawable)
+            .expect("Drawable should exist");
         let silhouette = self.silhouettes.get(&drawable.silhouette).unwrap();
 
         for y in bottom..top {
@@ -160,48 +173,38 @@ impl SoftwareRenderer {
         &mut self,
         drawable: drawable::DrawableID,
         candidates: Vec<drawable::DrawableID>,
-        rect: JSRectangle
+        rect: JSRectangle,
     ) -> bool {
         let candidates = self.map_candidates(candidates);
-        self.per_rect_pixel(|
-            position,
-            drawable,
-            silhouette
-        | {
-            if drawable.is_touching(position, silhouette) {
-                for candidate in &candidates {
-                    if candidate.0.is_touching(position, candidate.1) {
-                        return true;
+        self.per_rect_pixel(
+            |position, drawable, silhouette| {
+                if drawable.is_touching(position, silhouette) {
+                    for candidate in &candidates {
+                        if candidate.0.is_touching(position, candidate.1) {
+                            return true;
+                        }
                     }
                 }
-            }
-            false
-        }, rect, drawable)
+                false
+            },
+            rect,
+            drawable,
+        )
     }
 
     #[inline(always)]
-    fn color_matches(
-        a: [u8; 3],
-        b: [u8; 3]
-    ) -> bool {
-        (
-            ((a[0] ^ b[0]) & 0b11111000) |
-            ((a[1] ^ b[1]) & 0b11111000) |
-            ((a[2] ^ b[2]) & 0b11110000)
-        ) == 0
+    fn color_matches(a: [u8; 3], b: [u8; 3]) -> bool {
+        (((a[0] ^ b[0]) & 0b11111000) | ((a[1] ^ b[1]) & 0b11111000) | ((a[2] ^ b[2]) & 0b11110000))
+            == 0
     }
 
     #[inline(always)]
-    fn mask_matches(
-        a: [u8; 4],
-        b: [u8; 3]
-    ) -> bool {
-        a[3] != 0 &&
-        (
-            ((a[0] ^ b[0]) & 0b11111100) |
-            ((a[1] ^ b[1]) & 0b11111100) |
-            ((a[2] ^ b[2]) & 0b11111100)
-        ) == 0
+    fn mask_matches(a: [u8; 4], b: [u8; 3]) -> bool {
+        a[3] != 0
+            && (((a[0] ^ b[0]) & 0b11111100)
+                | ((a[1] ^ b[1]) & 0b11111100)
+                | ((a[2] ^ b[2]) & 0b11111100))
+                == 0
     }
 
     pub fn color_is_touching_color(
@@ -210,25 +213,25 @@ impl SoftwareRenderer {
         candidates: Vec<drawable::DrawableID>,
         rect: JSRectangle,
         color: &[u8],
-        mask: &[u8]
+        mask: &[u8],
     ) -> bool {
         let color: [u8; 3] = (*color).try_into().expect("color contains 3 elements");
         let mask: [u8; 3] = (*mask).try_into().expect("mask contains 3 elements");
         let candidates = self.map_candidates(candidates);
 
-        self.per_rect_pixel(|
-            position,
-            drawable,
-            silhouette
-        | {
-            if Self::mask_matches(drawable.sample_color(position, silhouette), mask) {
-                let sample_color = self.sample_color(position, &candidates);
-                if Self::color_matches(color, sample_color) {
-                    return true;
+        self.per_rect_pixel(
+            |position, drawable, silhouette| {
+                if Self::mask_matches(drawable.sample_color(position, silhouette), mask) {
+                    let sample_color = self.sample_color(position, &candidates);
+                    if Self::color_matches(color, sample_color) {
+                        return true;
+                    }
                 }
-            }
-            false
-        }, rect, drawable)
+                false
+            },
+            rect,
+            drawable,
+        )
     }
 
     pub fn is_touching_color(
@@ -236,29 +239,29 @@ impl SoftwareRenderer {
         drawable: drawable::DrawableID,
         candidates: Vec<drawable::DrawableID>,
         rect: JSRectangle,
-        color: &[u8]
+        color: &[u8],
     ) -> bool {
         let color: [u8; 3] = (*color).try_into().expect("color contains 3 elements");
         let candidates = self.map_candidates(candidates);
-        self.per_rect_pixel(|
-            position,
-            drawable,
-            silhouette
-        | {
-            if drawable.is_touching(position, silhouette) {
-                let sample_color = self.sample_color(position, &candidates);
-                if Self::color_matches(color, sample_color) {
-                    return true;
+        self.per_rect_pixel(
+            |position, drawable, silhouette| {
+                if drawable.is_touching(position, silhouette) {
+                    let sample_color = self.sample_color(position, &candidates);
+                    if Self::color_matches(color, sample_color) {
+                        return true;
+                    }
                 }
-            }
-            false
-        }, rect, drawable)
+                false
+            },
+            rect,
+            drawable,
+        )
     }
 
     fn sample_color(
         &self,
         position: matrix::Vec2,
-        candidates: &Vec<(&drawable::Drawable, &silhouette::Silhouette)>
+        candidates: &Vec<(&drawable::Drawable, &silhouette::Silhouette)>,
     ) -> [u8; 3] {
         let mut dst_color: (f32, f32, f32, f32) = (0f32, 0f32, 0f32, 0f32);
         let mut blend_alpha = 1f32;
@@ -286,24 +289,24 @@ impl SoftwareRenderer {
     pub fn drawable_touching_rect(
         &mut self,
         drawable: drawable::DrawableID,
-        rect: JSRectangle
+        rect: JSRectangle,
     ) -> bool {
-        self.per_rect_pixel(|
-            position,
+        self.per_rect_pixel(
+            |position, drawable, silhouette| {
+                if drawable.is_touching(position, silhouette) {
+                    return true;
+                }
+                false
+            },
+            rect,
             drawable,
-            silhouette
-        | {
-            if drawable.is_touching(position, silhouette) {
-                return true;
-            }
-            false
-        }, rect, drawable)
+        )
     }
 
     pub fn pick(
         &mut self,
         candidates: Vec<drawable::DrawableID>,
-        rect: JSRectangle
+        rect: JSRectangle,
     ) -> drawable::DrawableID {
         let mut hits: HashMap<drawable::DrawableID, u32> = HashMap::new();
         hits.insert(ID_NONE, 0);
@@ -321,9 +324,8 @@ impl SoftwareRenderer {
                 let position = matrix::Vec2(x as f32, y as f32);
                 for candidate in &candidates {
                     if candidate.0.is_touching(position, candidate.1) {
-                        hits
-                            .entry(candidate.0.id)
-                            .and_modify(|hit| {*hit += 1})
+                        hits.entry(candidate.0.id)
+                            .and_modify(|hit| *hit += 1)
                             .or_insert(1);
 
                         break;
@@ -346,7 +348,10 @@ impl SoftwareRenderer {
     }
 
     pub fn drawable_convex_hull_points(&mut self, drawable: drawable::DrawableID) -> Vec<f32> {
-        let drawable = self.drawables.get(&drawable).expect("Drawable should exist");
+        let drawable = self
+            .drawables
+            .get(&drawable)
+            .expect("Drawable should exist");
         let silhouette = self.silhouettes.get(&drawable.silhouette).unwrap();
 
         let hull = convex_hull::calculate_drawable_convex_hull(drawable, silhouette);
diff --git a/swrender/src/matrix.rs b/swrender/src/matrix.rs
index 5610bd7c7..bcc0c32ed 100644
--- a/swrender/src/matrix.rs
+++ b/swrender/src/matrix.rs
@@ -1,5 +1,5 @@
-use std::ops;
 use std::f32;
+use std::ops;
 
 pub type Mat4 = [f32; 16];
 
@@ -74,16 +74,16 @@ impl Matrix for Mat4 {
         let m31 = self[3 * 4 + 1];
         let m32 = self[3 * 4 + 2];
         let m33 = self[3 * 4 + 3];
-        let tmp_0  = m22 * m33;
-        let tmp_1  = m32 * m23;
-        let tmp_2  = m12 * m33;
-        let tmp_3  = m32 * m13;
-        let tmp_4  = m12 * m23;
-        let tmp_5  = m22 * m13;
-        let tmp_6  = m02 * m33;
-        let tmp_7  = m32 * m03;
-        let tmp_8  = m02 * m23;
-        let tmp_9  = m22 * m03;
+        let tmp_0 = m22 * m33;
+        let tmp_1 = m32 * m23;
+        let tmp_2 = m12 * m33;
+        let tmp_3 = m32 * m13;
+        let tmp_4 = m12 * m23;
+        let tmp_5 = m22 * m13;
+        let tmp_6 = m02 * m33;
+        let tmp_7 = m32 * m03;
+        let tmp_8 = m02 * m23;
+        let tmp_9 = m22 * m03;
         let tmp_10 = m02 * m13;
         let tmp_11 = m12 * m03;
         let tmp_12 = m20 * m31;
@@ -99,47 +99,59 @@ impl Matrix for Mat4 {
         let tmp_22 = m00 * m11;
         let tmp_23 = m10 * m01;
 
-        let t0: f32 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) -
-            (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
-        let t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) -
-            (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
-        let t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) -
-            (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
-        let t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) -
-            (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
+        let t0: f32 =
+            (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
+        let t1 =
+            (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
+        let t2 =
+            (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
+        let t3 =
+            (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);
 
         let d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);
 
         let mut dst: Mat4 = [0f32; 16];
 
-        dst[ 0] = d * t0;
-        dst[ 1] = d * t1;
-        dst[ 2] = d * t2;
-        dst[ 3] = d * t3;
-        dst[ 4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) -
-                (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30));
-        dst[ 5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) -
-                (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30));
-        dst[ 6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) -
-                (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30));
-        dst[ 7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) -
-                (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20));
-        dst[ 8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) -
-                (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33));
-        dst[ 9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) -
-                (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33));
-        dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) -
-                (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33));
-        dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) -
-                (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23));
-        dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) -
-                (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22));
-        dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) -
-                (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02));
-        dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) -
-                (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12));
-        dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) -
-                (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02));
+        dst[0] = d * t0;
+        dst[1] = d * t1;
+        dst[2] = d * t2;
+        dst[3] = d * t3;
+        dst[4] = d
+            * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30)
+                - (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30));
+        dst[5] = d
+            * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30)
+                - (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30));
+        dst[6] = d
+            * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30)
+                - (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30));
+        dst[7] = d
+            * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20)
+                - (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20));
+        dst[8] = d
+            * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33)
+                - (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33));
+        dst[9] = d
+            * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33)
+                - (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33));
+        dst[10] = d
+            * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33)
+                - (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33));
+        dst[11] = d
+            * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23)
+                - (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23));
+        dst[12] = d
+            * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12)
+                - (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22));
+        dst[13] = d
+            * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22)
+                - (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02));
+        dst[14] = d
+            * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02)
+                - (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12));
+        dst[15] = d
+            * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12)
+                - (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02));
 
         dst
     }
diff --git a/swrender/src/silhouette.rs b/swrender/src/silhouette.rs
index 29faa2943..efbcae39a 100644
--- a/swrender/src/silhouette.rs
+++ b/swrender/src/silhouette.rs
@@ -16,14 +16,13 @@ extern {
     pub fn log(s: &str);
 }
 
-
 pub struct Silhouette {
     pub id: SilhouetteID,
     pub width: u32,
     pub height: u32,
     pub nominal_size: Vec2,
     data: Box<[u8]>,
-    _blank: [u8; 4]
+    _blank: [u8; 4],
 }
 
 impl Silhouette {
@@ -34,12 +33,23 @@ impl Silhouette {
             height: 0,
             nominal_size: Vec2(0f32, 0f32),
             data: Box::new([0, 0, 0, 0]),
-            _blank: [0, 0, 0, 0]
+            _blank: [0, 0, 0, 0],
         }
     }
 
-    pub fn set_data(&mut self, w: u32, h: u32, mut data: Box<[u8]>, nominal_size: Vec2, premultiplied: bool) {
-        assert_eq!(data.len(), (w * h * 4) as usize, "silhouette data is improperly sized");
+    pub fn set_data(
+        &mut self,
+        w: u32,
+        h: u32,
+        mut data: Box<[u8]>,
+        nominal_size: Vec2,
+        premultiplied: bool,
+    ) {
+        assert_eq!(
+            data.len(),
+            (w * h * 4) as usize,
+            "silhouette data is improperly sized"
+        );
 
         self.width = w;
         self.height = h;
@@ -51,7 +61,9 @@ impl Silhouette {
             for pixel in pixels {
                 // This is indeed one branch per pixel. However, the branch predictor does a pretty good job of
                 // eliminating branch overhead and this saves us several instructions per pixel.
-                if pixel[3] == 0u8 {continue}
+                if pixel[3] == 0u8 {
+                    continue;
+                }
 
                 let alpha = (pixel[3] as f32) / 255f32;
 
@@ -69,7 +81,7 @@ impl Silhouette {
             false
         } else {
             let idx = (((y as u32 * self.width) + x as u32) * 4) as usize;
-            self.data[idx+3] != 0u8
+            self.data[idx + 3] != 0u8
         }
     }
 
@@ -78,25 +90,36 @@ impl Silhouette {
             self._blank
         } else {
             let idx = (((y as u32 * self.width) + x as u32) * 4) as usize;
-            [self.data[idx], self.data[idx + 1], self.data[idx + 2], self.data[idx + 3]]
+            [
+                self.data[idx],
+                self.data[idx + 1],
+                self.data[idx + 2],
+                self.data[idx + 3],
+            ]
         }
     }
 
     pub fn is_touching_nearest(&self, vec: Vec2) -> bool {
-        self.get_point((vec.0 * self.width as f32) as i32, (vec.1 * self.height as f32) as i32)
+        self.get_point(
+            (vec.0 * self.width as f32) as i32,
+            (vec.1 * self.height as f32) as i32,
+        )
     }
 
     pub fn color_at_nearest(&self, vec: Vec2) -> [u8; 4] {
-        self.get_color((vec.0 * self.width as f32) as i32, (vec.1 * self.height as f32) as i32)
+        self.get_color(
+            (vec.0 * self.width as f32) as i32,
+            (vec.1 * self.height as f32) as i32,
+        )
     }
 
     pub fn is_touching_linear(&self, vec: Vec2) -> bool {
         let x = ((vec.0 * self.width as f32) - 0.5) as i32;
         let y = ((vec.1 * self.height as f32) - 0.5) as i32;
 
-        self.get_point(x, y) ||
-        self.get_point(x + 1, y) ||
-        self.get_point(x, y + 1) ||
-        self.get_point(x + 1, y + 1)
+        self.get_point(x, y)
+            || self.get_point(x + 1, y)
+            || self.get_point(x, y + 1)
+            || self.get_point(x + 1, y + 1)
     }
 }

From 831b3277a0e7297d23df56036312b48bf03167f3 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Sun, 22 Mar 2020 04:08:32 -0400
Subject: [PATCH 08/14] Document + clean up Rust code

---
 package.json                     |  1 -
 swrender/src/convex_hull.rs      | 13 +++++++++++--
 swrender/src/drawable.rs         |  8 ++++++--
 swrender/src/effect_transform.rs | 13 +++++++++++--
 swrender/src/lib.rs              | 29 ++++++++++++++++++++++++++---
 swrender/src/silhouette.rs       | 26 ++++++++++----------------
 6 files changed, 64 insertions(+), 26 deletions(-)

diff --git a/package.json b/package.json
index 39c548182..4b52af6e8 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,6 @@
   },
   "dependencies": {
     "grapheme-breaker": "0.3.2",
-    "hull.js": "0.2.10",
     "ify-loader": "1.0.4",
     "linebreak": "0.3.0",
     "minilog": "3.1.0",
diff --git a/swrender/src/convex_hull.rs b/swrender/src/convex_hull.rs
index b3683f3db..4a880f46a 100644
--- a/swrender/src/convex_hull.rs
+++ b/swrender/src/convex_hull.rs
@@ -12,6 +12,7 @@ fn determinant(a: Vec2, b: Vec2, c: Vec2) -> f32 {
     ((b.0 - a.0) * (c.1 - a.1)) - ((b.1 - a.1) * (c.0 - a.0))
 }
 
+/// Calculate the convex hull of a particular Drawable.
 pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouette) -> Vec<Vec2> {
     let mut left_hull: Vec<Vec2> = Vec::new();
     let mut right_hull: Vec<Vec2> = Vec::new();
@@ -27,7 +28,10 @@ pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouet
 
     let mut current_point = Vec2(0f32, 0f32);
 
+    // *Not* "Scratch-space"-- +y is down
+    // Loop over all rows of pixels in the silhouette, starting at the top
     for y in 0..silhouette.height {
+        // We start at the leftmost point, then go rightwards until we hit an opaque pixel
         let mut x: u32 = 0;
         while x < silhouette.width {
             let local_point = Vec2(
@@ -44,10 +48,14 @@ pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouet
             x += 1;
         }
 
+        // If we managed to loop all the way through, there are no opaque pixels on this row. Go to the next one
         if x >= silhouette.width {
             continue;
         }
 
+        // If appending the current point to the left hull makes a counter-clockwise turn,
+        // we want to append the current point. Otherwise, we remove hull points until the
+        // current point makes a counter-clockwise turn with the last two points.
         while left_hull.len() >= 2 {
             let len = left_hull.len();
             if determinant(left_hull[len - 1], left_hull[len - 2], current_point) > 0f32 {
@@ -59,8 +67,8 @@ pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouet
 
         left_hull.push(Vec2(current_point.0 as f32, current_point.1 as f32));
 
+        // Now we repeat the process for the right side, looking leftwards for a pixel.
         x = silhouette.width - 1;
-
         while x != 0 {
             let local_point = Vec2(
                 (x as f32 + 0.5) / silhouette.width as f32,
@@ -76,6 +84,7 @@ pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouet
             x -= 1;
         }
 
+        // Because we're coming at this from the right, it goes clockwise this time.
         while right_hull.len() >= 2 {
             let len = right_hull.len();
             if determinant(right_hull[len - 1], right_hull[len - 2], current_point) < 0f32 {
@@ -88,8 +97,8 @@ pub fn calculate_drawable_convex_hull(drawable: &Drawable, silhouette: &Silhouet
         right_hull.push(Vec2(current_point.0 as f32, current_point.1 as f32));
     }
 
+    // Add points from the right side in reverse order so all points are ordered clockwise.
     right_hull.reverse();
-
     left_hull.append(&mut right_hull);
 
     left_hull
diff --git a/swrender/src/drawable.rs b/swrender/src/drawable.rs
index 757dab633..838e8b6c9 100644
--- a/swrender/src/drawable.rs
+++ b/swrender/src/drawable.rs
@@ -7,9 +7,10 @@ use crate::silhouette::*;
 
 pub type DrawableID = i32;
 
+/// The software-renderer version of a Drawable.
+/// The `id` matches up with the corresponding JS-world Drawable.
 pub struct Drawable {
     pub id: DrawableID,
-    pub matrix: Mat4,
     pub inverse_matrix: Mat4,
     pub silhouette: SilhouetteID,
     pub effects: Effects,
@@ -18,6 +19,7 @@ pub struct Drawable {
 }
 
 impl Drawable {
+    /// Convert a "Scratch-space" location into a texture-space (0-1) location.
     pub fn get_local_position(&self, vec: Vec2) -> Vec2 {
         let v0 = vec.0 + 0.5;
         let v1 = vec.1 + 0.5;
@@ -32,7 +34,7 @@ impl Drawable {
         Vec2(out_x, out_y)
     }
 
-    pub fn get_transformed_position(&self, vec: Vec2, skin_size: Vec2) -> Vec2 {
+    fn get_transformed_position(&self, vec: Vec2, skin_size: Vec2) -> Vec2 {
         if (self.effect_bits & DISTORTION_EFFECT_MASK) == 0 {
             vec
         } else {
@@ -40,6 +42,7 @@ impl Drawable {
         }
     }
 
+    /// Check if the "Scratch-space" position touches the passed silhouette.
     #[inline(always)]
     pub fn is_touching(&self, position: Vec2, silhouette: &Silhouette) -> bool {
         let local_position = self.get_local_position(position);
@@ -59,6 +62,7 @@ impl Drawable {
         }
     }
 
+    /// Sample a color from the given "Scratch-space" position of the passed silhouette.
     #[inline(always)]
     pub fn sample_color<'a>(&self, position: Vec2, silhouette: &'a Silhouette) -> [u8; 4] {
         let local_position = self.get_local_position(position);
diff --git a/swrender/src/effect_transform.rs b/swrender/src/effect_transform.rs
index ebb55935f..4e0c931c0 100644
--- a/swrender/src/effect_transform.rs
+++ b/swrender/src/effect_transform.rs
@@ -66,6 +66,9 @@ impl Effects {
     }
 }
 
+/// Converts an RGB color value to HSV. Conversion formula
+/// adapted from http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv.
+/// Assumes all channels are in the range [0, 1].
 fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
     let mut r = r;
     let mut g = g;
@@ -98,6 +101,9 @@ fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
     (h, s, v)
 }
 
+/// Converts an HSV color value to RRB. Conversion formula
+/// adapted from https://gist.github.com/mjackson/5311256.
+/// Assumes all channels are in the range [0, 1].
 fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
     if s < 1e-18 {
         return (v, v, v);
@@ -120,6 +126,8 @@ fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
     }
 }
 
+/// Transform a color in-place according to the passed effects + effect bits.  Will apply
+/// Ghost and Color and Brightness effects.
 pub fn transform_color<'a>(color: [u8; 4], effects: &Effects, effect_bits: EffectBits) -> [u8; 4] {
     const COLOR_DIVISOR: f32 = 1f32 / 255f32;
     let mut rgba: [f32; 4] = [
@@ -200,14 +208,15 @@ pub fn transform_color<'a>(color: [u8; 4], effects: &Effects, effect_bits: Effec
     ]
 }
 
-const CENTER: Vec2 = Vec2(0.5, 0.5);
-
+/// Transform a texture coordinate to one that would be used after applying shader effects.
 pub fn transform_point(
     point: Vec2,
     effects: &Effects,
     effect_bits: EffectBits,
     skin_size: Vec2,
 ) -> Vec2 {
+    const CENTER: Vec2 = Vec2(0.5, 0.5);
+
     let mut out = point;
 
     if effect_bits & (1 << (EffectBitfield::Mosaic as u32)) != 0 {
diff --git a/swrender/src/lib.rs b/swrender/src/lib.rs
index 690ba50c6..7d4411bb0 100644
--- a/swrender/src/lib.rs
+++ b/swrender/src/lib.rs
@@ -56,6 +56,8 @@ impl SoftwareRenderer {
         renderer
     }
 
+    /// Update the given CPU-side drawable's attributes given its ID.
+    /// Will create a new drawable on the CPU side if one doesn't yet exist.
     pub fn set_drawable(
         &mut self,
         id: drawable::DrawableID,
@@ -66,7 +68,6 @@ impl SoftwareRenderer {
         use_nearest_neighbor: bool,
     ) {
         let d = self.drawables.entry(id).or_insert(drawable::Drawable {
-            matrix: [0.0; 16],
             inverse_matrix: [0.0; 16],
             effects: effect_transform::Effects::default(),
             effect_bits: 0,
@@ -79,10 +80,10 @@ impl SoftwareRenderer {
         });
 
         if let Some(m) = matrix {
-            d.matrix = (*m)
+            let mat: matrix::Mat4 = (*m)
                 .try_into()
                 .expect("drawable's matrix contains 16 elements");
-            d.inverse_matrix = d.matrix.inverse();
+            d.inverse_matrix = mat.inverse();
         }
         if let Some(s) = silhouette {
             d.silhouette = s;
@@ -94,10 +95,13 @@ impl SoftwareRenderer {
         d.use_nearest_neighbor = use_nearest_neighbor;
     }
 
+    /// Delete the CPU-side drawable with the given ID.
     pub fn remove_drawable(&mut self, id: drawable::DrawableID) {
         self.drawables.remove(&id);
     }
 
+    /// Update the given silhouette's attributes and data given the corresponding skin's ID.
+    /// Will create a new silhouette if one does not exist.
     pub fn set_silhouette(
         &mut self,
         id: silhouette::SilhouetteID,
@@ -121,10 +125,12 @@ impl SoftwareRenderer {
         );
     }
 
+    /// Delete the silhouette that corresponds to the skin with the given ID.
     pub fn remove_silhouette(&mut self, id: silhouette::SilhouetteID) {
         self.silhouettes.remove(&id);
     }
 
+    /// Map a set of drawable IDs to a Vec of tuples of the given drawables + their silhouettes,
     fn map_candidates(
         &self,
         candidates: Vec<drawable::DrawableID>,
@@ -142,6 +148,8 @@ impl SoftwareRenderer {
             .collect()
     }
 
+    /// Perform the given function on a given drawable once per pixel inside the given rectangle,
+    /// stopping and returning true once the function does.
     fn per_rect_pixel<F>(&self, func: F, rect: JSRectangle, drawable: drawable::DrawableID) -> bool
     where
         F: Fn(matrix::Vec2, &drawable::Drawable, &silhouette::Silhouette) -> bool,
@@ -169,6 +177,8 @@ impl SoftwareRenderer {
         false
     }
 
+    /// Check if a particular Drawable is touching any in a set of Drawables.
+    /// Will only check inside the given bounds.
     pub fn is_touching_drawables(
         &mut self,
         drawable: drawable::DrawableID,
@@ -192,12 +202,17 @@ impl SoftwareRenderer {
         )
     }
 
+    /// Determines if the given color is "close enough" (only test the 5 top bits for
+    /// red and green, 4 bits for blue).  These bit masks are what Scratch 2 used to use,
+    /// so we do the same.
     #[inline(always)]
     fn color_matches(a: [u8; 3], b: [u8; 3]) -> bool {
         (((a[0] ^ b[0]) & 0b11111000) | ((a[1] ^ b[1]) & 0b11111000) | ((a[2] ^ b[2]) & 0b11110000))
             == 0
     }
 
+    /// Determines if the mask color is "close enough" (only test the 6 top bits for
+    /// each color).  These bit masks are what Scratch 2 used to use, so we do the same.
     #[inline(always)]
     fn mask_matches(a: [u8; 4], b: [u8; 3]) -> bool {
         a[3] != 0
@@ -207,6 +222,7 @@ impl SoftwareRenderer {
                 == 0
     }
 
+    /// Check if a certain color in a drawable is touching a particular color.
     pub fn color_is_touching_color(
         &mut self,
         drawable: drawable::DrawableID,
@@ -234,6 +250,7 @@ impl SoftwareRenderer {
         )
     }
 
+    /// Check if a certain drawable is touching a particular color.
     pub fn is_touching_color(
         &mut self,
         drawable: drawable::DrawableID,
@@ -258,6 +275,8 @@ impl SoftwareRenderer {
         )
     }
 
+    /// Sample a pixel from the stage at a given "Scratch-space" coordinate.
+    /// Will only render the passed drawables.
     fn sample_color(
         &self,
         position: matrix::Vec2,
@@ -286,6 +305,7 @@ impl SoftwareRenderer {
         [dst_color.0 as u8, dst_color.1 as u8, dst_color.2 as u8]
     }
 
+    /// Check if the drawable with the given ID is touching any pixel in the given rectangle.
     pub fn drawable_touching_rect(
         &mut self,
         drawable: drawable::DrawableID,
@@ -303,6 +323,8 @@ impl SoftwareRenderer {
         )
     }
 
+    /// Return the ID of the drawable that covers the most pixels in the given rectangle.
+    /// Drawables earlier in the list will occlude those lower in the list.
     pub fn pick(
         &mut self,
         candidates: Vec<drawable::DrawableID>,
@@ -347,6 +369,7 @@ impl SoftwareRenderer {
         hit
     }
 
+    /// Calculate the convex hull points for the drawable with the given ID.
     pub fn drawable_convex_hull_points(&mut self, drawable: drawable::DrawableID) -> Vec<f32> {
         let drawable = self
             .drawables
diff --git a/swrender/src/silhouette.rs b/swrender/src/silhouette.rs
index efbcae39a..8ad92411c 100644
--- a/swrender/src/silhouette.rs
+++ b/swrender/src/silhouette.rs
@@ -2,20 +2,7 @@ use crate::matrix::Vec2;
 
 pub type SilhouetteID = i32;
 
-use wasm_bindgen::prelude::*;
-
-#[wasm_bindgen]
-extern {
-    #[wasm_bindgen(js_namespace = console)]
-    pub fn time(s: &str);
-
-    #[wasm_bindgen(js_namespace = console)]
-    pub fn timeEnd(s: &str);
-
-    #[wasm_bindgen(js_namespace = console)]
-    pub fn log(s: &str);
-}
-
+/// The CPU-side version of a Skin.
 pub struct Silhouette {
     pub id: SilhouetteID,
     pub width: u32,
@@ -37,6 +24,7 @@ impl Silhouette {
         }
     }
 
+    /// Update this silhouette with the bitmap data passed in from a Skin.
     pub fn set_data(
         &mut self,
         w: u32,
@@ -76,7 +64,8 @@ impl Silhouette {
         self.data = data;
     }
 
-    pub fn get_point(&self, x: i32, y: i32) -> bool {
+    /// Returns whether the pixel at the given "silhouette-space" position has an alpha > 0.
+    fn get_point(&self, x: i32, y: i32) -> bool {
         if x < 0 || y < 0 || (x as u32) >= self.width || (y as u32) >= self.height {
             false
         } else {
@@ -85,7 +74,8 @@ impl Silhouette {
         }
     }
 
-    pub fn get_color(&self, x: i32, y: i32) -> [u8; 4] {
+    /// Get the color from a given silhouette at the given "silhouette-space" position.
+    fn get_color(&self, x: i32, y: i32) -> [u8; 4] {
         if x < 0 || y < 0 || (x as u32) >= self.width || (y as u32) >= self.height {
             self._blank
         } else {
@@ -99,6 +89,7 @@ impl Silhouette {
         }
     }
 
+    /// Test if the given texture coordinate (in range [0, 1]) touches the silhouette, using nearest-neighbor interpolation.
     pub fn is_touching_nearest(&self, vec: Vec2) -> bool {
         self.get_point(
             (vec.0 * self.width as f32) as i32,
@@ -106,6 +97,7 @@ impl Silhouette {
         )
     }
 
+    /// Sample a color at the given texture coordinates (in range [0, 1]) using nearest-neighbor interpolation.
     pub fn color_at_nearest(&self, vec: Vec2) -> [u8; 4] {
         self.get_color(
             (vec.0 * self.width as f32) as i32,
@@ -113,7 +105,9 @@ impl Silhouette {
         )
     }
 
+    /// Test if the given texture coordinate (in range [0, 1]) touches the silhouette, using linear interpolation.
     pub fn is_touching_linear(&self, vec: Vec2) -> bool {
+        // TODO: this often gives incorrect results, especially for coordinates whose fractional part is close to 0.5
         let x = ((vec.0 * self.width as f32) - 0.5) as i32;
         let y = ((vec.1 * self.height as f32) - 0.5) as i32;
 

From b8dbc426e5cd38558be9c7635c93bb9b2c8d7b6d Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Sun, 22 Mar 2020 04:14:12 -0400
Subject: [PATCH 09/14] Fix color-is-touching-color for ghosted sprites

---
 src/Drawable.js    | 8 ++++++--
 src/RenderWebGL.js | 3 ++-
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/src/Drawable.js b/src/Drawable.js
index 84b5840c8..e1ca60140 100644
--- a/src/Drawable.js
+++ b/src/Drawable.js
@@ -575,8 +575,9 @@ class Drawable {
 
     /**
      * Update everything necessary to render this drawable on the CPU.
+     * @param {int} [effectMask] An optional bitmask of effects that will be applied to this drawable on the CPU.
      */
-    updateCPURenderAttributes () {
+    updateCPURenderAttributes (effectMask) {
         this.updateMatrix();
 
         if (this.skin) this.skin.updateSilhouette(this._scale);
@@ -587,12 +588,15 @@ class Drawable {
             this._effectsDirty = false;
         }
 
+        let {enabledEffects} = this;
+        if (effectMask) enabledEffects &= effectMask;
+
         this._renderer.softwareRenderer.set_drawable(
             this.id,
             this._uniforms.u_modelMatrix,
             this.skin.id,
             effects,
-            this.enabledEffects,
+            enabledEffects,
             this.skin.useNearest(this._scale, this)
         );
     }
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index 6e0b31711..57d9e86d1 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -814,7 +814,8 @@ class RenderWebGL extends EventEmitter {
         const drawable = this._allDrawables[drawableID];
         const hasMask = Boolean(mask3b);
 
-        drawable.updateCPURenderAttributes();
+        // "Color is touching color" should not ghost the drawable whose color is being masked
+        drawable.updateCPURenderAttributes(~ShaderManager.EFFECT_INFO.ghost.mask);
 
         if (hasMask) {
             return this.softwareRenderer.color_is_touching_color(

From 9f45802bf8e0960ca558043512800751fe0fec75 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Fri, 3 Apr 2020 21:18:44 -0400
Subject: [PATCH 10/14] use normal import

---
 package.json       |  2 +-
 src/RenderWebGL.js | 20 +++++++-------------
 2 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/package.json b/package.json
index 4b52af6e8..57a928db9 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
   "browser": "./src/index.js",
   "scripts": {
     "build": "npm run build:swrender && webpack --progress --colors",
-    "build:swrender": "cargo build --release --target wasm32-unknown-unknown --manifest-path=\"swrender/Cargo.toml\" && wasm-bindgen --target web swrender/target/wasm32-unknown-unknown/release/swrender.wasm --out-dir swrender/build",
+    "build:swrender": "cargo build --release --target wasm32-unknown-unknown --manifest-path=\"swrender/Cargo.toml\" && wasm-bindgen --target bundler swrender/target/wasm32-unknown-unknown/release/swrender.wasm --out-dir swrender/build",
     "docs": "jsdoc -c .jsdoc.json",
     "lint": "eslint .",
     "prepublish": "npm run build",
diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index 57d9e86d1..b11b0943c 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -14,15 +14,11 @@ const log = require('./util/log');
 
 let onLoadSwRender = null;
 let swRenderLoaded = false;
-// eslint-disable-next-line no-unused-vars
-const swrender = require('../swrender/build/swrender.js');
+let swrender = null;
 
-const wasm = require('../swrender/build/swrender_bg.wasm');
-const swrenderInit = require('../swrender/build/swrender.js').default;
-
-swrenderInit(wasm)
-    .then(() => {
-        window.swrender = swrender;
+import('../swrender/build/swrender')
+    .then(swrenderImport => {
+        swrender = swrenderImport;
         swRenderLoaded = true;
         if (onLoadSwRender) onLoadSwRender();
     });
@@ -110,13 +106,11 @@ class RenderWebGL extends EventEmitter {
     }
 
     init () {
-        if (swRenderLoaded) return Promise.resolve();
-
-        return new Promise(resolve => {
+        const swRenderPromise = swRenderLoaded ? Promise.resolve() : new Promise(resolve => {
             onLoadSwRender = resolve;
-        }).then(() => {
-            this.swrender = swrender;
+        });
 
+        return swRenderPromise.then(() => {
             this.softwareRenderer = swrender.SoftwareRenderer.new();
         });
     }

From f800e80b572f21ae04c749532fc2215ada8b8058 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Fri, 3 Apr 2020 21:42:07 -0400
Subject: [PATCH 11/14] include swrender build in repo

---
 swrender/.gitignore             |   1 -
 swrender/build/swrender.d.ts    |  91 ++++++++
 swrender/build/swrender.js      | 398 ++++++++++++++++++++++++++++++++
 swrender/build/swrender_bg.d.ts |  18 ++
 swrender/build/swrender_bg.wasm | Bin 0 -> 99119 bytes
 5 files changed, 507 insertions(+), 1 deletion(-)
 create mode 100644 swrender/build/swrender.d.ts
 create mode 100644 swrender/build/swrender.js
 create mode 100644 swrender/build/swrender_bg.d.ts
 create mode 100644 swrender/build/swrender_bg.wasm

diff --git a/swrender/.gitignore b/swrender/.gitignore
index 9775a7636..0a2f752b9 100644
--- a/swrender/.gitignore
+++ b/swrender/.gitignore
@@ -2,6 +2,5 @@
 **/*.rs.bk
 Cargo.lock
 bin/
-build/
 wasm-pack.log
 .cargo-ok
diff --git a/swrender/build/swrender.d.ts b/swrender/build/swrender.d.ts
new file mode 100644
index 000000000..27d0cb106
--- /dev/null
+++ b/swrender/build/swrender.d.ts
@@ -0,0 +1,91 @@
+/* tslint:disable */
+/* eslint-disable */
+export class SoftwareRenderer {
+  free(): void;
+/**
+* @returns {SoftwareRenderer} 
+*/
+  static new(): SoftwareRenderer;
+/**
+* Update the given CPU-side drawable\'s attributes given its ID.
+* Will create a new drawable on the CPU side if one doesn\'t yet exist.
+* @param {number} id 
+* @param {Float32Array | undefined} matrix 
+* @param {number | undefined} silhouette 
+* @param {any | undefined} effects 
+* @param {number} effect_bits 
+* @param {boolean} use_nearest_neighbor 
+*/
+  set_drawable(id: number, matrix: Float32Array | undefined, silhouette: number | undefined, effects: any | undefined, effect_bits: number, use_nearest_neighbor: boolean): void;
+/**
+* Delete the CPU-side drawable with the given ID.
+* @param {number} id 
+*/
+  remove_drawable(id: number): void;
+/**
+* Update the given silhouette\'s attributes and data given the corresponding skin\'s ID.
+* Will create a new silhouette if one does not exist.
+* @param {number} id 
+* @param {number} w 
+* @param {number} h 
+* @param {Uint8Array} data 
+* @param {number} nominal_width 
+* @param {number} nominal_height 
+* @param {boolean} premultiplied 
+*/
+  set_silhouette(id: number, w: number, h: number, data: Uint8Array, nominal_width: number, nominal_height: number, premultiplied: boolean): void;
+/**
+* Delete the silhouette that corresponds to the skin with the given ID.
+* @param {number} id 
+*/
+  remove_silhouette(id: number): void;
+/**
+* Check if a particular Drawable is touching any in a set of Drawables.
+* Will only check inside the given bounds.
+* @param {number} drawable 
+* @param {Int32Array} candidates 
+* @param {any} rect 
+* @returns {boolean} 
+*/
+  is_touching_drawables(drawable: number, candidates: Int32Array, rect: any): boolean;
+/**
+* Check if a certain color in a drawable is touching a particular color.
+* @param {number} drawable 
+* @param {Int32Array} candidates 
+* @param {any} rect 
+* @param {Uint8Array} color 
+* @param {Uint8Array} mask 
+* @returns {boolean} 
+*/
+  color_is_touching_color(drawable: number, candidates: Int32Array, rect: any, color: Uint8Array, mask: Uint8Array): boolean;
+/**
+* Check if a certain drawable is touching a particular color.
+* @param {number} drawable 
+* @param {Int32Array} candidates 
+* @param {any} rect 
+* @param {Uint8Array} color 
+* @returns {boolean} 
+*/
+  is_touching_color(drawable: number, candidates: Int32Array, rect: any, color: Uint8Array): boolean;
+/**
+* Check if the drawable with the given ID is touching any pixel in the given rectangle.
+* @param {number} drawable 
+* @param {any} rect 
+* @returns {boolean} 
+*/
+  drawable_touching_rect(drawable: number, rect: any): boolean;
+/**
+* Return the ID of the drawable that covers the most pixels in the given rectangle.
+* Drawables earlier in the list will occlude those lower in the list.
+* @param {Int32Array} candidates 
+* @param {any} rect 
+* @returns {number} 
+*/
+  pick(candidates: Int32Array, rect: any): number;
+/**
+* Calculate the convex hull points for the drawable with the given ID.
+* @param {number} drawable 
+* @returns {Float32Array} 
+*/
+  drawable_convex_hull_points(drawable: number): Float32Array;
+}
diff --git a/swrender/build/swrender.js b/swrender/build/swrender.js
new file mode 100644
index 000000000..358a25c55
--- /dev/null
+++ b/swrender/build/swrender.js
@@ -0,0 +1,398 @@
+import * as wasm from './swrender_bg.wasm';
+
+const heap = new Array(32).fill(undefined);
+
+heap.push(undefined, null, true, false);
+
+function getObject(idx) { return heap[idx]; }
+
+let heap_next = heap.length;
+
+function dropObject(idx) {
+    if (idx < 36) return;
+    heap[idx] = heap_next;
+    heap_next = idx;
+}
+
+function takeObject(idx) {
+    const ret = getObject(idx);
+    dropObject(idx);
+    return ret;
+}
+
+const lTextDecoder = typeof TextDecoder === 'undefined' ? require('util').TextDecoder : TextDecoder;
+
+let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+cachedTextDecoder.decode();
+
+let cachegetUint8Memory0 = null;
+function getUint8Memory0() {
+    if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
+        cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+    }
+    return cachegetUint8Memory0;
+}
+
+function getStringFromWasm0(ptr, len) {
+    return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+
+let cachegetFloat32Memory0 = null;
+function getFloat32Memory0() {
+    if (cachegetFloat32Memory0 === null || cachegetFloat32Memory0.buffer !== wasm.memory.buffer) {
+        cachegetFloat32Memory0 = new Float32Array(wasm.memory.buffer);
+    }
+    return cachegetFloat32Memory0;
+}
+
+let WASM_VECTOR_LEN = 0;
+
+function passArrayF32ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 4);
+    getFloat32Memory0().set(arg, ptr / 4);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+function isLikeNone(x) {
+    return x === undefined || x === null;
+}
+
+function addHeapObject(obj) {
+    if (heap_next === heap.length) heap.push(heap.length + 1);
+    const idx = heap_next;
+    heap_next = heap[idx];
+
+    heap[idx] = obj;
+    return idx;
+}
+
+function passArray8ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 1);
+    getUint8Memory0().set(arg, ptr / 1);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+let cachegetUint32Memory0 = null;
+function getUint32Memory0() {
+    if (cachegetUint32Memory0 === null || cachegetUint32Memory0.buffer !== wasm.memory.buffer) {
+        cachegetUint32Memory0 = new Uint32Array(wasm.memory.buffer);
+    }
+    return cachegetUint32Memory0;
+}
+
+function passArray32ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 4);
+    getUint32Memory0().set(arg, ptr / 4);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+let cachegetInt32Memory0 = null;
+function getInt32Memory0() {
+    if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
+        cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
+    }
+    return cachegetInt32Memory0;
+}
+
+function getArrayF32FromWasm0(ptr, len) {
+    return getFloat32Memory0().subarray(ptr / 4, ptr / 4 + len);
+}
+
+const lTextEncoder = typeof TextEncoder === 'undefined' ? require('util').TextEncoder : TextEncoder;
+
+let cachedTextEncoder = new lTextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+    ? function (arg, view) {
+    return cachedTextEncoder.encodeInto(arg, view);
+}
+    : function (arg, view) {
+    const buf = cachedTextEncoder.encode(arg);
+    view.set(buf);
+    return {
+        read: arg.length,
+        written: buf.length
+    };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+    if (realloc === undefined) {
+        const buf = cachedTextEncoder.encode(arg);
+        const ptr = malloc(buf.length);
+        getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+        WASM_VECTOR_LEN = buf.length;
+        return ptr;
+    }
+
+    let len = arg.length;
+    let ptr = malloc(len);
+
+    const mem = getUint8Memory0();
+
+    let offset = 0;
+
+    for (; offset < len; offset++) {
+        const code = arg.charCodeAt(offset);
+        if (code > 0x7F) break;
+        mem[ptr + offset] = code;
+    }
+
+    if (offset !== len) {
+        if (offset !== 0) {
+            arg = arg.slice(offset);
+        }
+        ptr = realloc(ptr, len, len = offset + arg.length * 3);
+        const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+        const ret = encodeString(arg, view);
+
+        offset += ret.written;
+    }
+
+    WASM_VECTOR_LEN = offset;
+    return ptr;
+}
+/**
+*/
+export class SoftwareRenderer {
+
+    static __wrap(ptr) {
+        const obj = Object.create(SoftwareRenderer.prototype);
+        obj.ptr = ptr;
+
+        return obj;
+    }
+
+    free() {
+        const ptr = this.ptr;
+        this.ptr = 0;
+
+        wasm.__wbg_softwarerenderer_free(ptr);
+    }
+    /**
+    * @returns {SoftwareRenderer}
+    */
+    static new() {
+        var ret = wasm.softwarerenderer_new();
+        return SoftwareRenderer.__wrap(ret);
+    }
+    /**
+    * Update the given CPU-side drawable\'s attributes given its ID.
+    * Will create a new drawable on the CPU side if one doesn\'t yet exist.
+    * @param {number} id
+    * @param {Float32Array | undefined} matrix
+    * @param {number | undefined} silhouette
+    * @param {any | undefined} effects
+    * @param {number} effect_bits
+    * @param {boolean} use_nearest_neighbor
+    */
+    set_drawable(id, matrix, silhouette, effects, effect_bits, use_nearest_neighbor) {
+        var ptr0 = isLikeNone(matrix) ? 0 : passArrayF32ToWasm0(matrix, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        wasm.softwarerenderer_set_drawable(this.ptr, id, ptr0, len0, !isLikeNone(silhouette), isLikeNone(silhouette) ? 0 : silhouette, isLikeNone(effects) ? 0 : addHeapObject(effects), effect_bits, use_nearest_neighbor);
+    }
+    /**
+    * Delete the CPU-side drawable with the given ID.
+    * @param {number} id
+    */
+    remove_drawable(id) {
+        wasm.softwarerenderer_remove_drawable(this.ptr, id);
+    }
+    /**
+    * Update the given silhouette\'s attributes and data given the corresponding skin\'s ID.
+    * Will create a new silhouette if one does not exist.
+    * @param {number} id
+    * @param {number} w
+    * @param {number} h
+    * @param {Uint8Array} data
+    * @param {number} nominal_width
+    * @param {number} nominal_height
+    * @param {boolean} premultiplied
+    */
+    set_silhouette(id, w, h, data, nominal_width, nominal_height, premultiplied) {
+        var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        wasm.softwarerenderer_set_silhouette(this.ptr, id, w, h, ptr0, len0, nominal_width, nominal_height, premultiplied);
+    }
+    /**
+    * Delete the silhouette that corresponds to the skin with the given ID.
+    * @param {number} id
+    */
+    remove_silhouette(id) {
+        wasm.softwarerenderer_remove_silhouette(this.ptr, id);
+    }
+    /**
+    * Check if a particular Drawable is touching any in a set of Drawables.
+    * Will only check inside the given bounds.
+    * @param {number} drawable
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @returns {boolean}
+    */
+    is_touching_drawables(drawable, candidates, rect) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_is_touching_drawables(this.ptr, drawable, ptr0, len0, addHeapObject(rect));
+        return ret !== 0;
+    }
+    /**
+    * Check if a certain color in a drawable is touching a particular color.
+    * @param {number} drawable
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @param {Uint8Array} color
+    * @param {Uint8Array} mask
+    * @returns {boolean}
+    */
+    color_is_touching_color(drawable, candidates, rect, color, mask) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ptr1 = passArray8ToWasm0(color, wasm.__wbindgen_malloc);
+        var len1 = WASM_VECTOR_LEN;
+        var ptr2 = passArray8ToWasm0(mask, wasm.__wbindgen_malloc);
+        var len2 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_color_is_touching_color(this.ptr, drawable, ptr0, len0, addHeapObject(rect), ptr1, len1, ptr2, len2);
+        return ret !== 0;
+    }
+    /**
+    * Check if a certain drawable is touching a particular color.
+    * @param {number} drawable
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @param {Uint8Array} color
+    * @returns {boolean}
+    */
+    is_touching_color(drawable, candidates, rect, color) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ptr1 = passArray8ToWasm0(color, wasm.__wbindgen_malloc);
+        var len1 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_is_touching_color(this.ptr, drawable, ptr0, len0, addHeapObject(rect), ptr1, len1);
+        return ret !== 0;
+    }
+    /**
+    * Check if the drawable with the given ID is touching any pixel in the given rectangle.
+    * @param {number} drawable
+    * @param {any} rect
+    * @returns {boolean}
+    */
+    drawable_touching_rect(drawable, rect) {
+        var ret = wasm.softwarerenderer_drawable_touching_rect(this.ptr, drawable, addHeapObject(rect));
+        return ret !== 0;
+    }
+    /**
+    * Return the ID of the drawable that covers the most pixels in the given rectangle.
+    * Drawables earlier in the list will occlude those lower in the list.
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @returns {number}
+    */
+    pick(candidates, rect) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_pick(this.ptr, ptr0, len0, addHeapObject(rect));
+        return ret;
+    }
+    /**
+    * Calculate the convex hull points for the drawable with the given ID.
+    * @param {number} drawable
+    * @returns {Float32Array}
+    */
+    drawable_convex_hull_points(drawable) {
+        wasm.softwarerenderer_drawable_convex_hull_points(8, this.ptr, drawable);
+        var r0 = getInt32Memory0()[8 / 4 + 0];
+        var r1 = getInt32Memory0()[8 / 4 + 1];
+        var v0 = getArrayF32FromWasm0(r0, r1).slice();
+        wasm.__wbindgen_free(r0, r1 * 4);
+        return v0;
+    }
+}
+
+export const __wbg_left_e0e87a2e66be13a6 = function(arg0) {
+    var ret = getObject(arg0).left;
+    return ret;
+};
+
+export const __wbg_right_7b7bac033ade0b86 = function(arg0) {
+    var ret = getObject(arg0).right;
+    return ret;
+};
+
+export const __wbg_bottom_4666a55ceceeee8a = function(arg0) {
+    var ret = getObject(arg0).bottom;
+    return ret;
+};
+
+export const __wbg_top_84c6cfb6e6a6bd02 = function(arg0) {
+    var ret = getObject(arg0).top;
+    return ret;
+};
+
+export const __wbindgen_object_drop_ref = function(arg0) {
+    takeObject(arg0);
+};
+
+export const __wbg_ucolor_ec62c5e559a2a5a3 = function(arg0) {
+    var ret = getObject(arg0).u_color;
+    return ret;
+};
+
+export const __wbg_ufisheye_6aa56ae214de6428 = function(arg0) {
+    var ret = getObject(arg0).u_fisheye;
+    return ret;
+};
+
+export const __wbg_uwhirl_677f66c116ae8d9b = function(arg0) {
+    var ret = getObject(arg0).u_whirl;
+    return ret;
+};
+
+export const __wbg_upixelate_eb81083d476dfa89 = function(arg0) {
+    var ret = getObject(arg0).u_pixelate;
+    return ret;
+};
+
+export const __wbg_umosaic_7bc9d9ddd07459c3 = function(arg0) {
+    var ret = getObject(arg0).u_mosaic;
+    return ret;
+};
+
+export const __wbg_ubrightness_d29d8f78f9c8e71d = function(arg0) {
+    var ret = getObject(arg0).u_brightness;
+    return ret;
+};
+
+export const __wbg_ughost_d81ebfbc362e40b0 = function(arg0) {
+    var ret = getObject(arg0).u_ghost;
+    return ret;
+};
+
+export const __wbg_new_59cb74e423758ede = function() {
+    var ret = new Error();
+    return addHeapObject(ret);
+};
+
+export const __wbg_stack_558ba5917b466edd = function(arg0, arg1) {
+    var ret = getObject(arg1).stack;
+    var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+    var len0 = WASM_VECTOR_LEN;
+    getInt32Memory0()[arg0 / 4 + 1] = len0;
+    getInt32Memory0()[arg0 / 4 + 0] = ptr0;
+};
+
+export const __wbg_error_4bb6c2a97407129a = function(arg0, arg1) {
+    try {
+        console.error(getStringFromWasm0(arg0, arg1));
+    } finally {
+        wasm.__wbindgen_free(arg0, arg1);
+    }
+};
+
+export const __wbindgen_throw = function(arg0, arg1) {
+    throw new Error(getStringFromWasm0(arg0, arg1));
+};
+
diff --git a/swrender/build/swrender_bg.d.ts b/swrender/build/swrender_bg.d.ts
new file mode 100644
index 000000000..0cc51ba12
--- /dev/null
+++ b/swrender/build/swrender_bg.d.ts
@@ -0,0 +1,18 @@
+/* tslint:disable */
+/* eslint-disable */
+export const memory: WebAssembly.Memory;
+export function __wbg_softwarerenderer_free(a: number): void;
+export function softwarerenderer_new(): number;
+export function softwarerenderer_set_drawable(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): void;
+export function softwarerenderer_remove_drawable(a: number, b: number): void;
+export function softwarerenderer_set_silhouette(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): void;
+export function softwarerenderer_remove_silhouette(a: number, b: number): void;
+export function softwarerenderer_is_touching_drawables(a: number, b: number, c: number, d: number, e: number): number;
+export function softwarerenderer_color_is_touching_color(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): number;
+export function softwarerenderer_is_touching_color(a: number, b: number, c: number, d: number, e: number, f: number, g: number): number;
+export function softwarerenderer_drawable_touching_rect(a: number, b: number, c: number): number;
+export function softwarerenderer_pick(a: number, b: number, c: number, d: number): number;
+export function softwarerenderer_drawable_convex_hull_points(a: number, b: number, c: number): void;
+export function __wbindgen_malloc(a: number): number;
+export function __wbindgen_free(a: number, b: number): void;
+export function __wbindgen_realloc(a: number, b: number, c: number): number;
diff --git a/swrender/build/swrender_bg.wasm b/swrender/build/swrender_bg.wasm
new file mode 100644
index 0000000000000000000000000000000000000000..abbdcdb0f92e8ee9bb9e6bc897f35d24c5eaa281
GIT binary patch
literal 99119
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
Y)!OBSioQ_pAttjRc8B4fy3myWKVvC4g8%>k

literal 0
HcmV?d00001


From 2f8808e84e78cf0edd2dc32d75bff27e0814a2ed Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Mon, 22 Jun 2020 17:01:09 -0400
Subject: [PATCH 12/14] fix typo

---
 src/RenderWebGL.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js
index b11b0943c..dc9a83c89 100644
--- a/src/RenderWebGL.js
+++ b/src/RenderWebGL.js
@@ -929,7 +929,7 @@ class RenderWebGL extends EventEmitter {
             this._debugCanvas.width = bounds.width;
             this._debugCanvas.height = bounds.height;
             const context = this._debugCanvas.getContext('2d');
-            const imageData = context.getImageData(0, 0, bounds.width, bounds.heigh);
+            const imageData = context.getImageData(0, 0, bounds.width, bounds.height);
             imageData.data.set(pixels);
             context.putImageData(imageData, 0, 0);
         }

From db2e2dcfc6dfcc18b3f5e05904698a69fc1f01d9 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Mon, 22 Jun 2020 17:01:15 -0400
Subject: [PATCH 13/14] properly dispose of silhouettes

---
 src/Skin.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Skin.js b/src/Skin.js
index 7916d9142..9cc005235 100644
--- a/src/Skin.js
+++ b/src/Skin.js
@@ -63,7 +63,7 @@ class Skin extends EventEmitter {
      */
     dispose () {
         this._id = RenderConstants.ID_NONE;
-        if (this._newSilhouette) this._newSilhouette.free();
+        this._renderer.softwareRenderer.remove_silhouette(this.id);
     }
 
     /**

From 5260e2e29152ce1ead6bb94fb0e820306d49bb70 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Fri, 19 Feb 2021 14:47:24 -0500
Subject: [PATCH 14/14] rerun build with newer wasm-bindgen

---
 swrender/build/swrender.d.ts         |  86 +++---
 swrender/build/swrender.js           | 400 +-------------------------
 swrender/build/swrender_bg.js        | 403 +++++++++++++++++++++++++++
 swrender/build/swrender_bg.wasm      | Bin 99119 -> 117048 bytes
 swrender/build/swrender_bg.wasm.d.ts |  19 ++
 5 files changed, 468 insertions(+), 440 deletions(-)
 create mode 100644 swrender/build/swrender_bg.js
 create mode 100644 swrender/build/swrender_bg.wasm.d.ts

diff --git a/swrender/build/swrender.d.ts b/swrender/build/swrender.d.ts
index 27d0cb106..3b9fd9556 100644
--- a/swrender/build/swrender.d.ts
+++ b/swrender/build/swrender.d.ts
@@ -1,91 +1,93 @@
 /* tslint:disable */
 /* eslint-disable */
+/**
+*/
 export class SoftwareRenderer {
   free(): void;
 /**
-* @returns {SoftwareRenderer} 
+* @returns {SoftwareRenderer}
 */
   static new(): SoftwareRenderer;
 /**
-* Update the given CPU-side drawable\'s attributes given its ID.
-* Will create a new drawable on the CPU side if one doesn\'t yet exist.
-* @param {number} id 
-* @param {Float32Array | undefined} matrix 
-* @param {number | undefined} silhouette 
-* @param {any | undefined} effects 
-* @param {number} effect_bits 
-* @param {boolean} use_nearest_neighbor 
+* Update the given CPU-side drawable's attributes given its ID.
+* Will create a new drawable on the CPU side if one doesn't yet exist.
+* @param {number} id
+* @param {Float32Array | undefined} matrix
+* @param {number | undefined} silhouette
+* @param {any | undefined} effects
+* @param {number} effect_bits
+* @param {boolean} use_nearest_neighbor
 */
   set_drawable(id: number, matrix: Float32Array | undefined, silhouette: number | undefined, effects: any | undefined, effect_bits: number, use_nearest_neighbor: boolean): void;
 /**
 * Delete the CPU-side drawable with the given ID.
-* @param {number} id 
+* @param {number} id
 */
   remove_drawable(id: number): void;
 /**
-* Update the given silhouette\'s attributes and data given the corresponding skin\'s ID.
+* Update the given silhouette's attributes and data given the corresponding skin's ID.
 * Will create a new silhouette if one does not exist.
-* @param {number} id 
-* @param {number} w 
-* @param {number} h 
-* @param {Uint8Array} data 
-* @param {number} nominal_width 
-* @param {number} nominal_height 
-* @param {boolean} premultiplied 
+* @param {number} id
+* @param {number} w
+* @param {number} h
+* @param {Uint8Array} data
+* @param {number} nominal_width
+* @param {number} nominal_height
+* @param {boolean} premultiplied
 */
   set_silhouette(id: number, w: number, h: number, data: Uint8Array, nominal_width: number, nominal_height: number, premultiplied: boolean): void;
 /**
 * Delete the silhouette that corresponds to the skin with the given ID.
-* @param {number} id 
+* @param {number} id
 */
   remove_silhouette(id: number): void;
 /**
 * Check if a particular Drawable is touching any in a set of Drawables.
 * Will only check inside the given bounds.
-* @param {number} drawable 
-* @param {Int32Array} candidates 
-* @param {any} rect 
-* @returns {boolean} 
+* @param {number} drawable
+* @param {Int32Array} candidates
+* @param {any} rect
+* @returns {boolean}
 */
   is_touching_drawables(drawable: number, candidates: Int32Array, rect: any): boolean;
 /**
 * Check if a certain color in a drawable is touching a particular color.
-* @param {number} drawable 
-* @param {Int32Array} candidates 
-* @param {any} rect 
-* @param {Uint8Array} color 
-* @param {Uint8Array} mask 
-* @returns {boolean} 
+* @param {number} drawable
+* @param {Int32Array} candidates
+* @param {any} rect
+* @param {Uint8Array} color
+* @param {Uint8Array} mask
+* @returns {boolean}
 */
   color_is_touching_color(drawable: number, candidates: Int32Array, rect: any, color: Uint8Array, mask: Uint8Array): boolean;
 /**
 * Check if a certain drawable is touching a particular color.
-* @param {number} drawable 
-* @param {Int32Array} candidates 
-* @param {any} rect 
-* @param {Uint8Array} color 
-* @returns {boolean} 
+* @param {number} drawable
+* @param {Int32Array} candidates
+* @param {any} rect
+* @param {Uint8Array} color
+* @returns {boolean}
 */
   is_touching_color(drawable: number, candidates: Int32Array, rect: any, color: Uint8Array): boolean;
 /**
 * Check if the drawable with the given ID is touching any pixel in the given rectangle.
-* @param {number} drawable 
-* @param {any} rect 
-* @returns {boolean} 
+* @param {number} drawable
+* @param {any} rect
+* @returns {boolean}
 */
   drawable_touching_rect(drawable: number, rect: any): boolean;
 /**
 * Return the ID of the drawable that covers the most pixels in the given rectangle.
 * Drawables earlier in the list will occlude those lower in the list.
-* @param {Int32Array} candidates 
-* @param {any} rect 
-* @returns {number} 
+* @param {Int32Array} candidates
+* @param {any} rect
+* @returns {number}
 */
   pick(candidates: Int32Array, rect: any): number;
 /**
 * Calculate the convex hull points for the drawable with the given ID.
-* @param {number} drawable 
-* @returns {Float32Array} 
+* @param {number} drawable
+* @returns {Float32Array}
 */
   drawable_convex_hull_points(drawable: number): Float32Array;
 }
diff --git a/swrender/build/swrender.js b/swrender/build/swrender.js
index 358a25c55..e70a70410 100644
--- a/swrender/build/swrender.js
+++ b/swrender/build/swrender.js
@@ -1,398 +1,2 @@
-import * as wasm from './swrender_bg.wasm';
-
-const heap = new Array(32).fill(undefined);
-
-heap.push(undefined, null, true, false);
-
-function getObject(idx) { return heap[idx]; }
-
-let heap_next = heap.length;
-
-function dropObject(idx) {
-    if (idx < 36) return;
-    heap[idx] = heap_next;
-    heap_next = idx;
-}
-
-function takeObject(idx) {
-    const ret = getObject(idx);
-    dropObject(idx);
-    return ret;
-}
-
-const lTextDecoder = typeof TextDecoder === 'undefined' ? require('util').TextDecoder : TextDecoder;
-
-let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
-
-cachedTextDecoder.decode();
-
-let cachegetUint8Memory0 = null;
-function getUint8Memory0() {
-    if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
-        cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
-    }
-    return cachegetUint8Memory0;
-}
-
-function getStringFromWasm0(ptr, len) {
-    return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
-}
-
-let cachegetFloat32Memory0 = null;
-function getFloat32Memory0() {
-    if (cachegetFloat32Memory0 === null || cachegetFloat32Memory0.buffer !== wasm.memory.buffer) {
-        cachegetFloat32Memory0 = new Float32Array(wasm.memory.buffer);
-    }
-    return cachegetFloat32Memory0;
-}
-
-let WASM_VECTOR_LEN = 0;
-
-function passArrayF32ToWasm0(arg, malloc) {
-    const ptr = malloc(arg.length * 4);
-    getFloat32Memory0().set(arg, ptr / 4);
-    WASM_VECTOR_LEN = arg.length;
-    return ptr;
-}
-
-function isLikeNone(x) {
-    return x === undefined || x === null;
-}
-
-function addHeapObject(obj) {
-    if (heap_next === heap.length) heap.push(heap.length + 1);
-    const idx = heap_next;
-    heap_next = heap[idx];
-
-    heap[idx] = obj;
-    return idx;
-}
-
-function passArray8ToWasm0(arg, malloc) {
-    const ptr = malloc(arg.length * 1);
-    getUint8Memory0().set(arg, ptr / 1);
-    WASM_VECTOR_LEN = arg.length;
-    return ptr;
-}
-
-let cachegetUint32Memory0 = null;
-function getUint32Memory0() {
-    if (cachegetUint32Memory0 === null || cachegetUint32Memory0.buffer !== wasm.memory.buffer) {
-        cachegetUint32Memory0 = new Uint32Array(wasm.memory.buffer);
-    }
-    return cachegetUint32Memory0;
-}
-
-function passArray32ToWasm0(arg, malloc) {
-    const ptr = malloc(arg.length * 4);
-    getUint32Memory0().set(arg, ptr / 4);
-    WASM_VECTOR_LEN = arg.length;
-    return ptr;
-}
-
-let cachegetInt32Memory0 = null;
-function getInt32Memory0() {
-    if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
-        cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
-    }
-    return cachegetInt32Memory0;
-}
-
-function getArrayF32FromWasm0(ptr, len) {
-    return getFloat32Memory0().subarray(ptr / 4, ptr / 4 + len);
-}
-
-const lTextEncoder = typeof TextEncoder === 'undefined' ? require('util').TextEncoder : TextEncoder;
-
-let cachedTextEncoder = new lTextEncoder('utf-8');
-
-const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
-    ? function (arg, view) {
-    return cachedTextEncoder.encodeInto(arg, view);
-}
-    : function (arg, view) {
-    const buf = cachedTextEncoder.encode(arg);
-    view.set(buf);
-    return {
-        read: arg.length,
-        written: buf.length
-    };
-});
-
-function passStringToWasm0(arg, malloc, realloc) {
-
-    if (realloc === undefined) {
-        const buf = cachedTextEncoder.encode(arg);
-        const ptr = malloc(buf.length);
-        getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
-        WASM_VECTOR_LEN = buf.length;
-        return ptr;
-    }
-
-    let len = arg.length;
-    let ptr = malloc(len);
-
-    const mem = getUint8Memory0();
-
-    let offset = 0;
-
-    for (; offset < len; offset++) {
-        const code = arg.charCodeAt(offset);
-        if (code > 0x7F) break;
-        mem[ptr + offset] = code;
-    }
-
-    if (offset !== len) {
-        if (offset !== 0) {
-            arg = arg.slice(offset);
-        }
-        ptr = realloc(ptr, len, len = offset + arg.length * 3);
-        const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
-        const ret = encodeString(arg, view);
-
-        offset += ret.written;
-    }
-
-    WASM_VECTOR_LEN = offset;
-    return ptr;
-}
-/**
-*/
-export class SoftwareRenderer {
-
-    static __wrap(ptr) {
-        const obj = Object.create(SoftwareRenderer.prototype);
-        obj.ptr = ptr;
-
-        return obj;
-    }
-
-    free() {
-        const ptr = this.ptr;
-        this.ptr = 0;
-
-        wasm.__wbg_softwarerenderer_free(ptr);
-    }
-    /**
-    * @returns {SoftwareRenderer}
-    */
-    static new() {
-        var ret = wasm.softwarerenderer_new();
-        return SoftwareRenderer.__wrap(ret);
-    }
-    /**
-    * Update the given CPU-side drawable\'s attributes given its ID.
-    * Will create a new drawable on the CPU side if one doesn\'t yet exist.
-    * @param {number} id
-    * @param {Float32Array | undefined} matrix
-    * @param {number | undefined} silhouette
-    * @param {any | undefined} effects
-    * @param {number} effect_bits
-    * @param {boolean} use_nearest_neighbor
-    */
-    set_drawable(id, matrix, silhouette, effects, effect_bits, use_nearest_neighbor) {
-        var ptr0 = isLikeNone(matrix) ? 0 : passArrayF32ToWasm0(matrix, wasm.__wbindgen_malloc);
-        var len0 = WASM_VECTOR_LEN;
-        wasm.softwarerenderer_set_drawable(this.ptr, id, ptr0, len0, !isLikeNone(silhouette), isLikeNone(silhouette) ? 0 : silhouette, isLikeNone(effects) ? 0 : addHeapObject(effects), effect_bits, use_nearest_neighbor);
-    }
-    /**
-    * Delete the CPU-side drawable with the given ID.
-    * @param {number} id
-    */
-    remove_drawable(id) {
-        wasm.softwarerenderer_remove_drawable(this.ptr, id);
-    }
-    /**
-    * Update the given silhouette\'s attributes and data given the corresponding skin\'s ID.
-    * Will create a new silhouette if one does not exist.
-    * @param {number} id
-    * @param {number} w
-    * @param {number} h
-    * @param {Uint8Array} data
-    * @param {number} nominal_width
-    * @param {number} nominal_height
-    * @param {boolean} premultiplied
-    */
-    set_silhouette(id, w, h, data, nominal_width, nominal_height, premultiplied) {
-        var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
-        var len0 = WASM_VECTOR_LEN;
-        wasm.softwarerenderer_set_silhouette(this.ptr, id, w, h, ptr0, len0, nominal_width, nominal_height, premultiplied);
-    }
-    /**
-    * Delete the silhouette that corresponds to the skin with the given ID.
-    * @param {number} id
-    */
-    remove_silhouette(id) {
-        wasm.softwarerenderer_remove_silhouette(this.ptr, id);
-    }
-    /**
-    * Check if a particular Drawable is touching any in a set of Drawables.
-    * Will only check inside the given bounds.
-    * @param {number} drawable
-    * @param {Int32Array} candidates
-    * @param {any} rect
-    * @returns {boolean}
-    */
-    is_touching_drawables(drawable, candidates, rect) {
-        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
-        var len0 = WASM_VECTOR_LEN;
-        var ret = wasm.softwarerenderer_is_touching_drawables(this.ptr, drawable, ptr0, len0, addHeapObject(rect));
-        return ret !== 0;
-    }
-    /**
-    * Check if a certain color in a drawable is touching a particular color.
-    * @param {number} drawable
-    * @param {Int32Array} candidates
-    * @param {any} rect
-    * @param {Uint8Array} color
-    * @param {Uint8Array} mask
-    * @returns {boolean}
-    */
-    color_is_touching_color(drawable, candidates, rect, color, mask) {
-        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
-        var len0 = WASM_VECTOR_LEN;
-        var ptr1 = passArray8ToWasm0(color, wasm.__wbindgen_malloc);
-        var len1 = WASM_VECTOR_LEN;
-        var ptr2 = passArray8ToWasm0(mask, wasm.__wbindgen_malloc);
-        var len2 = WASM_VECTOR_LEN;
-        var ret = wasm.softwarerenderer_color_is_touching_color(this.ptr, drawable, ptr0, len0, addHeapObject(rect), ptr1, len1, ptr2, len2);
-        return ret !== 0;
-    }
-    /**
-    * Check if a certain drawable is touching a particular color.
-    * @param {number} drawable
-    * @param {Int32Array} candidates
-    * @param {any} rect
-    * @param {Uint8Array} color
-    * @returns {boolean}
-    */
-    is_touching_color(drawable, candidates, rect, color) {
-        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
-        var len0 = WASM_VECTOR_LEN;
-        var ptr1 = passArray8ToWasm0(color, wasm.__wbindgen_malloc);
-        var len1 = WASM_VECTOR_LEN;
-        var ret = wasm.softwarerenderer_is_touching_color(this.ptr, drawable, ptr0, len0, addHeapObject(rect), ptr1, len1);
-        return ret !== 0;
-    }
-    /**
-    * Check if the drawable with the given ID is touching any pixel in the given rectangle.
-    * @param {number} drawable
-    * @param {any} rect
-    * @returns {boolean}
-    */
-    drawable_touching_rect(drawable, rect) {
-        var ret = wasm.softwarerenderer_drawable_touching_rect(this.ptr, drawable, addHeapObject(rect));
-        return ret !== 0;
-    }
-    /**
-    * Return the ID of the drawable that covers the most pixels in the given rectangle.
-    * Drawables earlier in the list will occlude those lower in the list.
-    * @param {Int32Array} candidates
-    * @param {any} rect
-    * @returns {number}
-    */
-    pick(candidates, rect) {
-        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
-        var len0 = WASM_VECTOR_LEN;
-        var ret = wasm.softwarerenderer_pick(this.ptr, ptr0, len0, addHeapObject(rect));
-        return ret;
-    }
-    /**
-    * Calculate the convex hull points for the drawable with the given ID.
-    * @param {number} drawable
-    * @returns {Float32Array}
-    */
-    drawable_convex_hull_points(drawable) {
-        wasm.softwarerenderer_drawable_convex_hull_points(8, this.ptr, drawable);
-        var r0 = getInt32Memory0()[8 / 4 + 0];
-        var r1 = getInt32Memory0()[8 / 4 + 1];
-        var v0 = getArrayF32FromWasm0(r0, r1).slice();
-        wasm.__wbindgen_free(r0, r1 * 4);
-        return v0;
-    }
-}
-
-export const __wbg_left_e0e87a2e66be13a6 = function(arg0) {
-    var ret = getObject(arg0).left;
-    return ret;
-};
-
-export const __wbg_right_7b7bac033ade0b86 = function(arg0) {
-    var ret = getObject(arg0).right;
-    return ret;
-};
-
-export const __wbg_bottom_4666a55ceceeee8a = function(arg0) {
-    var ret = getObject(arg0).bottom;
-    return ret;
-};
-
-export const __wbg_top_84c6cfb6e6a6bd02 = function(arg0) {
-    var ret = getObject(arg0).top;
-    return ret;
-};
-
-export const __wbindgen_object_drop_ref = function(arg0) {
-    takeObject(arg0);
-};
-
-export const __wbg_ucolor_ec62c5e559a2a5a3 = function(arg0) {
-    var ret = getObject(arg0).u_color;
-    return ret;
-};
-
-export const __wbg_ufisheye_6aa56ae214de6428 = function(arg0) {
-    var ret = getObject(arg0).u_fisheye;
-    return ret;
-};
-
-export const __wbg_uwhirl_677f66c116ae8d9b = function(arg0) {
-    var ret = getObject(arg0).u_whirl;
-    return ret;
-};
-
-export const __wbg_upixelate_eb81083d476dfa89 = function(arg0) {
-    var ret = getObject(arg0).u_pixelate;
-    return ret;
-};
-
-export const __wbg_umosaic_7bc9d9ddd07459c3 = function(arg0) {
-    var ret = getObject(arg0).u_mosaic;
-    return ret;
-};
-
-export const __wbg_ubrightness_d29d8f78f9c8e71d = function(arg0) {
-    var ret = getObject(arg0).u_brightness;
-    return ret;
-};
-
-export const __wbg_ughost_d81ebfbc362e40b0 = function(arg0) {
-    var ret = getObject(arg0).u_ghost;
-    return ret;
-};
-
-export const __wbg_new_59cb74e423758ede = function() {
-    var ret = new Error();
-    return addHeapObject(ret);
-};
-
-export const __wbg_stack_558ba5917b466edd = function(arg0, arg1) {
-    var ret = getObject(arg1).stack;
-    var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
-    var len0 = WASM_VECTOR_LEN;
-    getInt32Memory0()[arg0 / 4 + 1] = len0;
-    getInt32Memory0()[arg0 / 4 + 0] = ptr0;
-};
-
-export const __wbg_error_4bb6c2a97407129a = function(arg0, arg1) {
-    try {
-        console.error(getStringFromWasm0(arg0, arg1));
-    } finally {
-        wasm.__wbindgen_free(arg0, arg1);
-    }
-};
-
-export const __wbindgen_throw = function(arg0, arg1) {
-    throw new Error(getStringFromWasm0(arg0, arg1));
-};
-
+import * as wasm from "./swrender_bg.wasm";
+export * from "./swrender_bg.js";
\ No newline at end of file
diff --git a/swrender/build/swrender_bg.js b/swrender/build/swrender_bg.js
new file mode 100644
index 000000000..29301cb16
--- /dev/null
+++ b/swrender/build/swrender_bg.js
@@ -0,0 +1,403 @@
+import * as wasm from './swrender_bg.wasm';
+
+const heap = new Array(32).fill(undefined);
+
+heap.push(undefined, null, true, false);
+
+function getObject(idx) { return heap[idx]; }
+
+let heap_next = heap.length;
+
+function dropObject(idx) {
+    if (idx < 36) return;
+    heap[idx] = heap_next;
+    heap_next = idx;
+}
+
+function takeObject(idx) {
+    const ret = getObject(idx);
+    dropObject(idx);
+    return ret;
+}
+
+const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder;
+
+let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+cachedTextDecoder.decode();
+
+let cachegetUint8Memory0 = null;
+function getUint8Memory0() {
+    if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
+        cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
+    }
+    return cachegetUint8Memory0;
+}
+
+function getStringFromWasm0(ptr, len) {
+    return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
+}
+
+let cachegetFloat32Memory0 = null;
+function getFloat32Memory0() {
+    if (cachegetFloat32Memory0 === null || cachegetFloat32Memory0.buffer !== wasm.memory.buffer) {
+        cachegetFloat32Memory0 = new Float32Array(wasm.memory.buffer);
+    }
+    return cachegetFloat32Memory0;
+}
+
+let WASM_VECTOR_LEN = 0;
+
+function passArrayF32ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 4);
+    getFloat32Memory0().set(arg, ptr / 4);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+function isLikeNone(x) {
+    return x === undefined || x === null;
+}
+
+function addHeapObject(obj) {
+    if (heap_next === heap.length) heap.push(heap.length + 1);
+    const idx = heap_next;
+    heap_next = heap[idx];
+
+    heap[idx] = obj;
+    return idx;
+}
+
+function passArray8ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 1);
+    getUint8Memory0().set(arg, ptr / 1);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+let cachegetUint32Memory0 = null;
+function getUint32Memory0() {
+    if (cachegetUint32Memory0 === null || cachegetUint32Memory0.buffer !== wasm.memory.buffer) {
+        cachegetUint32Memory0 = new Uint32Array(wasm.memory.buffer);
+    }
+    return cachegetUint32Memory0;
+}
+
+function passArray32ToWasm0(arg, malloc) {
+    const ptr = malloc(arg.length * 4);
+    getUint32Memory0().set(arg, ptr / 4);
+    WASM_VECTOR_LEN = arg.length;
+    return ptr;
+}
+
+let cachegetInt32Memory0 = null;
+function getInt32Memory0() {
+    if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
+        cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
+    }
+    return cachegetInt32Memory0;
+}
+
+function getArrayF32FromWasm0(ptr, len) {
+    return getFloat32Memory0().subarray(ptr / 4, ptr / 4 + len);
+}
+
+const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder;
+
+let cachedTextEncoder = new lTextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+    ? function (arg, view) {
+    return cachedTextEncoder.encodeInto(arg, view);
+}
+    : function (arg, view) {
+    const buf = cachedTextEncoder.encode(arg);
+    view.set(buf);
+    return {
+        read: arg.length,
+        written: buf.length
+    };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+    if (realloc === undefined) {
+        const buf = cachedTextEncoder.encode(arg);
+        const ptr = malloc(buf.length);
+        getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
+        WASM_VECTOR_LEN = buf.length;
+        return ptr;
+    }
+
+    let len = arg.length;
+    let ptr = malloc(len);
+
+    const mem = getUint8Memory0();
+
+    let offset = 0;
+
+    for (; offset < len; offset++) {
+        const code = arg.charCodeAt(offset);
+        if (code > 0x7F) break;
+        mem[ptr + offset] = code;
+    }
+
+    if (offset !== len) {
+        if (offset !== 0) {
+            arg = arg.slice(offset);
+        }
+        ptr = realloc(ptr, len, len = offset + arg.length * 3);
+        const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
+        const ret = encodeString(arg, view);
+
+        offset += ret.written;
+    }
+
+    WASM_VECTOR_LEN = offset;
+    return ptr;
+}
+/**
+*/
+export class SoftwareRenderer {
+
+    static __wrap(ptr) {
+        const obj = Object.create(SoftwareRenderer.prototype);
+        obj.ptr = ptr;
+
+        return obj;
+    }
+
+    free() {
+        const ptr = this.ptr;
+        this.ptr = 0;
+
+        wasm.__wbg_softwarerenderer_free(ptr);
+    }
+    /**
+    * @returns {SoftwareRenderer}
+    */
+    static new() {
+        var ret = wasm.softwarerenderer_new();
+        return SoftwareRenderer.__wrap(ret);
+    }
+    /**
+    * Update the given CPU-side drawable's attributes given its ID.
+    * Will create a new drawable on the CPU side if one doesn't yet exist.
+    * @param {number} id
+    * @param {Float32Array | undefined} matrix
+    * @param {number | undefined} silhouette
+    * @param {any | undefined} effects
+    * @param {number} effect_bits
+    * @param {boolean} use_nearest_neighbor
+    */
+    set_drawable(id, matrix, silhouette, effects, effect_bits, use_nearest_neighbor) {
+        var ptr0 = isLikeNone(matrix) ? 0 : passArrayF32ToWasm0(matrix, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        wasm.softwarerenderer_set_drawable(this.ptr, id, ptr0, len0, !isLikeNone(silhouette), isLikeNone(silhouette) ? 0 : silhouette, isLikeNone(effects) ? 0 : addHeapObject(effects), effect_bits, use_nearest_neighbor);
+    }
+    /**
+    * Delete the CPU-side drawable with the given ID.
+    * @param {number} id
+    */
+    remove_drawable(id) {
+        wasm.softwarerenderer_remove_drawable(this.ptr, id);
+    }
+    /**
+    * Update the given silhouette's attributes and data given the corresponding skin's ID.
+    * Will create a new silhouette if one does not exist.
+    * @param {number} id
+    * @param {number} w
+    * @param {number} h
+    * @param {Uint8Array} data
+    * @param {number} nominal_width
+    * @param {number} nominal_height
+    * @param {boolean} premultiplied
+    */
+    set_silhouette(id, w, h, data, nominal_width, nominal_height, premultiplied) {
+        var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        wasm.softwarerenderer_set_silhouette(this.ptr, id, w, h, ptr0, len0, nominal_width, nominal_height, premultiplied);
+    }
+    /**
+    * Delete the silhouette that corresponds to the skin with the given ID.
+    * @param {number} id
+    */
+    remove_silhouette(id) {
+        wasm.softwarerenderer_remove_silhouette(this.ptr, id);
+    }
+    /**
+    * Check if a particular Drawable is touching any in a set of Drawables.
+    * Will only check inside the given bounds.
+    * @param {number} drawable
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @returns {boolean}
+    */
+    is_touching_drawables(drawable, candidates, rect) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_is_touching_drawables(this.ptr, drawable, ptr0, len0, addHeapObject(rect));
+        return ret !== 0;
+    }
+    /**
+    * Check if a certain color in a drawable is touching a particular color.
+    * @param {number} drawable
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @param {Uint8Array} color
+    * @param {Uint8Array} mask
+    * @returns {boolean}
+    */
+    color_is_touching_color(drawable, candidates, rect, color, mask) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ptr1 = passArray8ToWasm0(color, wasm.__wbindgen_malloc);
+        var len1 = WASM_VECTOR_LEN;
+        var ptr2 = passArray8ToWasm0(mask, wasm.__wbindgen_malloc);
+        var len2 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_color_is_touching_color(this.ptr, drawable, ptr0, len0, addHeapObject(rect), ptr1, len1, ptr2, len2);
+        return ret !== 0;
+    }
+    /**
+    * Check if a certain drawable is touching a particular color.
+    * @param {number} drawable
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @param {Uint8Array} color
+    * @returns {boolean}
+    */
+    is_touching_color(drawable, candidates, rect, color) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ptr1 = passArray8ToWasm0(color, wasm.__wbindgen_malloc);
+        var len1 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_is_touching_color(this.ptr, drawable, ptr0, len0, addHeapObject(rect), ptr1, len1);
+        return ret !== 0;
+    }
+    /**
+    * Check if the drawable with the given ID is touching any pixel in the given rectangle.
+    * @param {number} drawable
+    * @param {any} rect
+    * @returns {boolean}
+    */
+    drawable_touching_rect(drawable, rect) {
+        var ret = wasm.softwarerenderer_drawable_touching_rect(this.ptr, drawable, addHeapObject(rect));
+        return ret !== 0;
+    }
+    /**
+    * Return the ID of the drawable that covers the most pixels in the given rectangle.
+    * Drawables earlier in the list will occlude those lower in the list.
+    * @param {Int32Array} candidates
+    * @param {any} rect
+    * @returns {number}
+    */
+    pick(candidates, rect) {
+        var ptr0 = passArray32ToWasm0(candidates, wasm.__wbindgen_malloc);
+        var len0 = WASM_VECTOR_LEN;
+        var ret = wasm.softwarerenderer_pick(this.ptr, ptr0, len0, addHeapObject(rect));
+        return ret;
+    }
+    /**
+    * Calculate the convex hull points for the drawable with the given ID.
+    * @param {number} drawable
+    * @returns {Float32Array}
+    */
+    drawable_convex_hull_points(drawable) {
+        try {
+            const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
+            wasm.softwarerenderer_drawable_convex_hull_points(retptr, this.ptr, drawable);
+            var r0 = getInt32Memory0()[retptr / 4 + 0];
+            var r1 = getInt32Memory0()[retptr / 4 + 1];
+            var v0 = getArrayF32FromWasm0(r0, r1).slice();
+            wasm.__wbindgen_free(r0, r1 * 4);
+            return v0;
+        } finally {
+            wasm.__wbindgen_add_to_stack_pointer(16);
+        }
+    }
+}
+
+export const __wbg_left_e0e87a2e66be13a6 = function(arg0) {
+    var ret = getObject(arg0).left;
+    return ret;
+};
+
+export const __wbg_right_7b7bac033ade0b86 = function(arg0) {
+    var ret = getObject(arg0).right;
+    return ret;
+};
+
+export const __wbg_bottom_4666a55ceceeee8a = function(arg0) {
+    var ret = getObject(arg0).bottom;
+    return ret;
+};
+
+export const __wbg_top_84c6cfb6e6a6bd02 = function(arg0) {
+    var ret = getObject(arg0).top;
+    return ret;
+};
+
+export const __wbindgen_object_drop_ref = function(arg0) {
+    takeObject(arg0);
+};
+
+export const __wbg_ucolor_ec62c5e559a2a5a3 = function(arg0) {
+    var ret = getObject(arg0).u_color;
+    return ret;
+};
+
+export const __wbg_ufisheye_6aa56ae214de6428 = function(arg0) {
+    var ret = getObject(arg0).u_fisheye;
+    return ret;
+};
+
+export const __wbg_uwhirl_677f66c116ae8d9b = function(arg0) {
+    var ret = getObject(arg0).u_whirl;
+    return ret;
+};
+
+export const __wbg_upixelate_eb81083d476dfa89 = function(arg0) {
+    var ret = getObject(arg0).u_pixelate;
+    return ret;
+};
+
+export const __wbg_umosaic_7bc9d9ddd07459c3 = function(arg0) {
+    var ret = getObject(arg0).u_mosaic;
+    return ret;
+};
+
+export const __wbg_ubrightness_d29d8f78f9c8e71d = function(arg0) {
+    var ret = getObject(arg0).u_brightness;
+    return ret;
+};
+
+export const __wbg_ughost_d81ebfbc362e40b0 = function(arg0) {
+    var ret = getObject(arg0).u_ghost;
+    return ret;
+};
+
+export const __wbg_new_59cb74e423758ede = function() {
+    var ret = new Error();
+    return addHeapObject(ret);
+};
+
+export const __wbg_stack_558ba5917b466edd = function(arg0, arg1) {
+    var ret = getObject(arg1).stack;
+    var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+    var len0 = WASM_VECTOR_LEN;
+    getInt32Memory0()[arg0 / 4 + 1] = len0;
+    getInt32Memory0()[arg0 / 4 + 0] = ptr0;
+};
+
+export const __wbg_error_4bb6c2a97407129a = function(arg0, arg1) {
+    try {
+        console.error(getStringFromWasm0(arg0, arg1));
+    } finally {
+        wasm.__wbindgen_free(arg0, arg1);
+    }
+};
+
+export const __wbindgen_throw = function(arg0, arg1) {
+    throw new Error(getStringFromWasm0(arg0, arg1));
+};
+
diff --git a/swrender/build/swrender_bg.wasm b/swrender/build/swrender_bg.wasm
index abbdcdb0f92e8ee9bb9e6bc897f35d24c5eaa281..48dca247dc696b96b318e7f8366d8bb36d4f6130 100644
GIT binary patch
literal 117048
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

literal 99119
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
Y)!OBSioQ_pAttjRc8B4fy3myWKVvC4g8%>k

diff --git a/swrender/build/swrender_bg.wasm.d.ts b/swrender/build/swrender_bg.wasm.d.ts
new file mode 100644
index 000000000..0ba1f4645
--- /dev/null
+++ b/swrender/build/swrender_bg.wasm.d.ts
@@ -0,0 +1,19 @@
+/* tslint:disable */
+/* eslint-disable */
+export const memory: WebAssembly.Memory;
+export function __wbg_softwarerenderer_free(a: number): void;
+export function softwarerenderer_new(): number;
+export function softwarerenderer_set_drawable(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): void;
+export function softwarerenderer_remove_drawable(a: number, b: number): void;
+export function softwarerenderer_set_silhouette(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): void;
+export function softwarerenderer_remove_silhouette(a: number, b: number): void;
+export function softwarerenderer_is_touching_drawables(a: number, b: number, c: number, d: number, e: number): number;
+export function softwarerenderer_color_is_touching_color(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number): number;
+export function softwarerenderer_is_touching_color(a: number, b: number, c: number, d: number, e: number, f: number, g: number): number;
+export function softwarerenderer_drawable_touching_rect(a: number, b: number, c: number): number;
+export function softwarerenderer_pick(a: number, b: number, c: number, d: number): number;
+export function softwarerenderer_drawable_convex_hull_points(a: number, b: number, c: number): void;
+export function __wbindgen_malloc(a: number): number;
+export function __wbindgen_add_to_stack_pointer(a: number): number;
+export function __wbindgen_free(a: number, b: number): void;
+export function __wbindgen_realloc(a: number, b: number, c: number): number;