@scratch/scratch-render 11.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2016, Massachusetts Institute of Technology
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ ## scratch-render
2
+ #### WebGL-based rendering engine for Scratch 3.0
3
+
4
+ [![CircleCI](https://circleci.com/gh/LLK/scratch-render/tree/develop.svg?style=shield&circle-token=310da166a745295d515b3b90f3bad10f23b84405)](https://circleci.com/gh/LLK/scratch-render?branch=develop)
5
+
6
+ [![Greenkeeper badge](https://badges.greenkeeper.io/LLK/scratch-render.svg)](https://greenkeeper.io/)
7
+
8
+ ## Installation
9
+ ```bash
10
+ npm install https://github.com/scratchfoundation/scratch-render.git
11
+ ```
12
+
13
+ ## Setup
14
+ ```html
15
+ <!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8">
19
+ <title>Scratch WebGL rendering demo</title>
20
+ </head>
21
+
22
+ <body>
23
+ <canvas id="myStage"></canvas>
24
+ <canvas id="myDebug"></canvas>
25
+ </body>
26
+ </html>
27
+ ```
28
+
29
+ ```js
30
+ var canvas = document.getElementById('myStage');
31
+ var debug = document.getElementById('myDebug');
32
+
33
+ // Instantiate the renderer
34
+ var renderer = new require('@scratch/scratch-render')(canvas);
35
+
36
+ // Connect to debug canvas
37
+ renderer.setDebugCanvas(debug);
38
+
39
+ // Start drawing
40
+ function drawStep() {
41
+ renderer.draw();
42
+ requestAnimationFrame(drawStep);
43
+ }
44
+ drawStep();
45
+
46
+ // Connect to worker (see "playground" example)
47
+ var worker = new Worker('worker.js');
48
+ renderer.connectWorker(worker);
49
+ ```
50
+
51
+ ## Standalone Build
52
+ ```bash
53
+ npm run build
54
+ ```
55
+
56
+ ```html
57
+ <script src="/path/to/render.js"></script>
58
+ <script>
59
+ var renderer = new window.RenderWebGLLocal();
60
+ // do things
61
+ </script>
62
+ ```
63
+
64
+ ## Testing
65
+ ```bash
66
+ npm test
67
+ ```
68
+
69
+ ## Donate
70
+ We provide [Scratch](https://scratch.mit.edu) free of charge, and want to keep it that way! Please consider making a [donation](https://secure.donationpay.org/scratchfoundation/) to support our continued engineering, design, community, and resource development efforts. Donations of any size are appreciated. Thank you!
71
+
72
+ ## Committing
73
+
74
+ This project uses [semantic release](https://github.com/semantic-release/semantic-release) to ensure version bumps
75
+ follow semver so that projects depending on it don't break unexpectedly.
76
+
77
+ In order to automatically determine version updates, semantic release expects commit messages to follow the
78
+ [conventional-changelog](https://github.com/bcoe/conventional-changelog-standard/blob/master/convention.md)
79
+ specification.
80
+
81
+ You can use the [commitizen CLI](https://github.com/commitizen/cz-cli) to make commits formatted in this way:
82
+
83
+ ```bash
84
+ npm install -g commitizen@latest cz-conventional-changelog@latest
85
+ ```
86
+
87
+ Now you're ready to make commits using `git cz`.
package/TRADEMARK ADDED
@@ -0,0 +1 @@
1
+ The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission.
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ extends: ['@commitlint/config-conventional'],
3
+ ignores: [message => message.startsWith('chore(release):')]
4
+ };
@@ -0,0 +1,192 @@
1
+ # Rectangle AABB Matrix
2
+
3
+ Initialize a Rectangle to a 1 unit square centered at 0 x 0 transformed by a model matrix.
4
+
5
+ -----
6
+
7
+ Every drawable is a 1 x 1 unit square that is rotated by its direction, scaled by its skin size and scale, and offset by its rotation center and position. The square representation is made up of 4 points that are transformed by the drawable properties. Often we want a shape that simplifies those 4 points into a non-rotated shape, a axis aligned bounding box.
8
+
9
+ One approach is to compare the x and y components of each transformed vector and find the minimum and maximum x component and the minimum and maximum y component.
10
+
11
+ We can start from this approach and determine an alternative one that prodcues the same output with less work.
12
+
13
+ Starting with transforming one point, here is a 3D point, `v`, transformation by a matrix, `m`.
14
+
15
+ ```js
16
+ const v0 = v[0];
17
+ const v1 = v[1];
18
+ const v2 = v[2];
19
+
20
+ const d = v0 * m[(0 * 4) + 3] + v1 * m[(1 * 4) + 3] + v2 * m[(2 * 4) + 3] + m[(3 * 4) + 3];
21
+ dst[0] = (v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + v2 * m[(2 * 4) + 0] + m[(3 * 4) + 0]) / d;
22
+ dst[1] = (v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + v2 * m[(2 * 4) + 1] + m[(3 * 4) + 1]) / d;
23
+ dst[2] = (v0 * m[(0 * 4) + 2] + v1 * m[(1 * 4) + 2] + v2 * m[(2 * 4) + 2] + m[(3 * 4) + 2]) / d;
24
+ ```
25
+
26
+ As this is a 2D rectangle we can cancel out the third dimension, and the determinant, 'd'.
27
+
28
+ ```js
29
+ const v0 = v[0];
30
+ const v1 = v[1];
31
+
32
+ dst = [
33
+ v0 * m[(0 * 4) + 0] + v1 * m[(1 * 4) + 0] + m[(3 * 4) + 0,
34
+ v0 * m[(0 * 4) + 1] + v1 * m[(1 * 4) + 1] + m[(3 * 4) + 1
35
+ ];
36
+ ```
37
+
38
+ Let's set the matrix points to shorter names for convenience.
39
+
40
+ ```js
41
+ const m00 = m[(0 * 4) + 0];
42
+ const m01 = m[(0 * 4) + 1];
43
+ const m10 = m[(1 * 4) + 0];
44
+ const m11 = m[(1 * 4) + 1];
45
+ const m30 = m[(3 * 4) + 0];
46
+ const m31 = m[(3 * 4) + 1];
47
+ ```
48
+
49
+ We need 4 points with positive and negative 0.5 values so the square has sides of length 1.
50
+
51
+ ```js
52
+ let p = [0.5, 0.5];
53
+ let q = [-0.5, 0.5];
54
+ let r = [-0.5, -0.5];
55
+ let s = [0.5, -0.5];
56
+ ```
57
+
58
+ Transform the points by the matrix.
59
+
60
+ ```js
61
+ p = [
62
+ 0.5 * m00 + 0.5 * m10 + m30,
63
+ 0.5 * m01 + 0.5 * m11 + m31
64
+ ];
65
+ q = [
66
+ -0.5 * m00 + -0.5 * m10 + m30,
67
+ 0.5 * m01 + 0.5 * m11 + m31
68
+ ];
69
+ r = [
70
+ -0.5 * m00 + -0.5 * m10 + m30,
71
+ -0.5 * m01 + -0.5 * m11 + m31
72
+ ];
73
+ s = [
74
+ 0.5 * m00 + 0.5 * m10 + m30,
75
+ -0.5 * m01 + -0.5 * m11 + m31
76
+ ];
77
+ ```
78
+
79
+ With 4 transformed points we can build the left, right, top, and bottom values for the Rectangle. Each will use the minimum or the maximum of one of the components of all points.
80
+
81
+ ```js
82
+ const left = Math.min(p[0], q[0], r[0], s[0]);
83
+ const right = Math.max(p[0], q[0], r[0], s[0]);
84
+ const top = Math.max(p[1], q[1], r[1], s[1]);
85
+ const bottom = Math.min(p[1], q[1], r[1], s[1]);
86
+ ```
87
+
88
+ Fill those calls with the vector expressions.
89
+
90
+ ```js
91
+ const left = Math.min(
92
+ 0.5 * m00 + 0.5 * m10 + m30,
93
+ -0.5 * m00 + 0.5 * m10 + m30,
94
+ -0.5 * m00 + -0.5 * m10 + m30,
95
+ 0.5 * m00 + -0.5 * m10 + m30
96
+ );
97
+ const right = Math.max(
98
+ 0.5 * m00 + 0.5 * m10 + m30,
99
+ -0.5 * m00 + 0.5 * m10 + m30,
100
+ -0.5 * m00 + -0.5 * m10 + m30,
101
+ 0.5 * m00 + -0.5 * m10 + m30
102
+ );
103
+ const top = Math.max(
104
+ 0.5 * m01 + 0.5 * m11 + m31,
105
+ -0.5 * m01 + 0.5 * m11 + m31,
106
+ -0.5 * m01 + -0.5 * m11 + m31,
107
+ 0.5 * m01 + -0.5 * m11 + m31
108
+ );
109
+ const bottom = Math.min(
110
+ 0.5 * m01 + 0.5 * m11 + m31,
111
+ -0.5 * m01 + 0.5 * m11 + m31,
112
+ -0.5 * m01 + -0.5 * m11 + m31,
113
+ 0.5 * m01 + -0.5 * m11 + m31
114
+ );
115
+ ```
116
+
117
+ Pull out the `0.5 * m??` patterns.
118
+
119
+ ```js
120
+ const x0 = 0.5 * m00;
121
+ const x1 = 0.5 * m10;
122
+ const y0 = 0.5 * m01;
123
+ const y1 = 0.5 * m11;
124
+
125
+ const left = Math.min(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30);
126
+ const right = Math.max(x0 + x1 + m30, -x0 + x1 + m30, -x0 + -x1 + m30, x0 + -x1 + m30);
127
+ const top = Math.max(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31);
128
+ const bottom = Math.min(y0 + y1 + m31, -y0 + y1 + m31, -y0 + -y1 + m31, y0 + -y1 + m31);
129
+ ```
130
+
131
+ Now each argument for the min and max calls take an expression like `(a * x0 + b * x1 + m3?)`. As each expression has the x0, x1, and m3? variables we can split the min and max calls on the addition operators. Each new call has all the coefficients of that variable.
132
+
133
+ ```js
134
+ const left = Math.min(x0, -x0) + Math.min(x1, -x1) + Math.min(m30, m30);
135
+ const right = Math.max(x0, -x0) + Math.max(x1, -x1) + Math.max(m30, m30);
136
+ const top = Math.max(y0, -y0) + Math.max(y1, -y1) + Math.max(m31, m31);
137
+ const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + Math.min(m31, m31);
138
+ ```
139
+
140
+ The min or max of two copies of the same value will just be that value.
141
+
142
+ ```js
143
+ const left = Math.min(x0, -x0) + Math.min(x1, -x1) + m30;
144
+ const right = Math.max(x0, -x0) + Math.max(x1, -x1) + m30;
145
+ const top = Math.max(y0, -y0) + Math.max(y1, -y1) + m31;
146
+ const bottom = Math.min(y0, -y0) + Math.min(y1, -y1) + m31;
147
+ ```
148
+
149
+ The max of a negative and positive variable will be the absolute value of that variable. The min of a negative and positive variable will the negated absolute value of that variable.
150
+
151
+ ```js
152
+ const left = -Math.abs(x0) + -Math.abs(x1) + m30;
153
+ const right = Math.abs(x0) + Math.abs(x1) + m30;
154
+ const top = Math.abs(y0) + Math.abs(y1) + m31;
155
+ const bottom = -Math.abs(y0) + -Math.abs(y1) + m31;
156
+ ```
157
+
158
+ Pulling out the negations of the absolute values, left and right as well as top and bottom are the positive or negative sum of the absolute value of the saled and rotated unit value.
159
+
160
+ ```js
161
+ const left = -(Math.abs(x0) + Math.abs(x1)) + m30;
162
+ const right = Math.abs(x0) + Math.abs(x1) + m30;
163
+ const top = Math.abs(y0) + Math.abs(y1) + m31;
164
+ const bottom = -(Math.abs(y0) + Math.abs(y1)) + m31;
165
+ ```
166
+
167
+ We call pull out those sums and use them twice.
168
+
169
+ ```js
170
+ const x = Math.abs(x0) + Math.abs(x1);
171
+ const y = Math.abs(y0) + Math.abs(y1);
172
+
173
+ const left = -x + m30;
174
+ const right = x + m30;
175
+ const top = y + m31;
176
+ const bottom = -y + m31;
177
+ ```
178
+
179
+ This lets us arrive at our goal. Inlining some of our variables we get this block that will initialize a Rectangle to a unit square transformed by a matrix.
180
+
181
+ ```js
182
+ const m30 = m[(3 * 4) + 0];
183
+ const m31 = m[(3 * 4) + 1];
184
+
185
+ const x = Math.abs(0.5 * m[(0 * 4) + 0]) + Math.abs(0.5 * m[(1 * 4) + 0]);
186
+ const y = Math.abs(0.5 * m[(0 * 4) + 1]) + Math.abs(0.5 * m[(1 * 4) + 1]);
187
+
188
+ const left = -x + m30;
189
+ const right = x + m30;
190
+ const top = y + m31;
191
+ const bottom = -y + m31;
192
+ ```
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@scratch/scratch-render",
3
+ "version": "11.0.0-beta.1",
4
+ "description": "WebGL Renderer for Scratch 3.0",
5
+ "author": "Massachusetts Institute of Technology",
6
+ "license": "AGPL-3.0-only",
7
+ "homepage": "https://github.com/scratchfoundation/scratch-render#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/scratchfoundation/scratch-editor.git"
11
+ },
12
+ "exports": {
13
+ "webpack": "./src/index.js",
14
+ "browser": "./dist/web/scratch-render.js",
15
+ "node": "./dist/node/scratch-render.js",
16
+ "default": "./src/index.js"
17
+ },
18
+ "scripts": {
19
+ "build": "webpack --progress",
20
+ "docs": "jsdoc -c .jsdoc.json",
21
+ "lint": "eslint .",
22
+ "prepublish": "npm run build",
23
+ "prepublish-watch": "npm run watch",
24
+ "start": "webpack-dev-server",
25
+ "tap": "tap test/unit test/integration",
26
+ "test": "npm run lint && npm run docs && npm run build && npm run tap",
27
+ "watch": "webpack --progress --watch --watch-poll"
28
+ },
29
+ "tap": {
30
+ "branches": 10,
31
+ "functions": 20,
32
+ "lines": 30,
33
+ "statements": 30
34
+ },
35
+ "browserslist": [
36
+ "Chrome >= 63",
37
+ "Edge >= 15",
38
+ "Firefox >= 57",
39
+ "Safari >= 8",
40
+ "Android >= 63",
41
+ "iOS >= 8"
42
+ ],
43
+ "dependencies": {
44
+ "@scratch/scratch-svg-renderer": "11.0.0-beta.1",
45
+ "grapheme-breaker": "^0.3.2",
46
+ "hull.js": "0.2.10",
47
+ "ify-loader": "^1.0.4",
48
+ "linebreak": "^0.3.0",
49
+ "minilog": "^3.1.0",
50
+ "raw-loader": "^0.5.1",
51
+ "twgl.js": "^4.4.0"
52
+ },
53
+ "peerDependencies": {
54
+ "scratch-render-fonts": "^1.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@babel/core": "7.25.2",
58
+ "@babel/eslint-parser": "7.25.1",
59
+ "@babel/polyfill": "7.12.1",
60
+ "@babel/preset-env": "7.25.4",
61
+ "@scratch/scratch-vm": "11.0.0-beta.1",
62
+ "babel-loader": "9.1.3",
63
+ "copy-webpack-plugin": "4.6.0",
64
+ "docdash": "0.4.0",
65
+ "eslint": "8.57.0",
66
+ "eslint-config-scratch": "9.0.8",
67
+ "gh-pages": "1.2.0",
68
+ "html-webpack-plugin": "5.6.0",
69
+ "jsdoc": "3.6.11",
70
+ "json": "9.0.6",
71
+ "playwright-chromium": "1.46.1",
72
+ "scratch-render-fonts": "1.0.93",
73
+ "scratch-semantic-release-config": "1.0.14",
74
+ "scratch-storage": "^4.0.24",
75
+ "scratch-webpack-configuration": "1.6.0",
76
+ "semantic-release": "19.0.5",
77
+ "tap": "16.3.10",
78
+ "terser-webpack-plugin": "5.3.10",
79
+ "travis-after-all": "1.4.5",
80
+ "webpack": "5.94.0",
81
+ "webpack-cli": "5.1.4",
82
+ "webpack-dev-server": "5.0.4"
83
+ }
84
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ extends: 'scratch-semantic-release-config',
3
+ branches: [
4
+ {
5
+ name: 'develop'
6
+ // default channel
7
+ }
8
+ // TODO: add hotfix config after at least one normal release
9
+ ]
10
+ };
@@ -0,0 +1,120 @@
1
+ const twgl = require('twgl.js');
2
+
3
+ const Skin = require('./Skin');
4
+
5
+ class BitmapSkin extends Skin {
6
+ /**
7
+ * Create a new Bitmap Skin.
8
+ * @extends Skin
9
+ * @param {!int} id - The ID for this Skin.
10
+ * @param {!RenderWebGL} renderer - The renderer which will use this skin.
11
+ */
12
+ constructor (id, renderer) {
13
+ super(id);
14
+
15
+ /** @type {!int} */
16
+ this._costumeResolution = 1;
17
+
18
+ /** @type {!RenderWebGL} */
19
+ this._renderer = renderer;
20
+
21
+ /** @type {Array<int>} */
22
+ this._textureSize = [0, 0];
23
+ }
24
+
25
+ /**
26
+ * Dispose of this object. Do not use it after calling this method.
27
+ */
28
+ dispose () {
29
+ if (this._texture) {
30
+ this._renderer.gl.deleteTexture(this._texture);
31
+ this._texture = null;
32
+ }
33
+ super.dispose();
34
+ }
35
+
36
+ /**
37
+ * @return {Array<number>} the "native" size, in texels, of this skin.
38
+ */
39
+ get size () {
40
+ return [this._textureSize[0] / this._costumeResolution, this._textureSize[1] / this._costumeResolution];
41
+ }
42
+
43
+ /**
44
+ * @param {Array<number>} scale - The scaling factors to be used.
45
+ * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given scale.
46
+ */
47
+ // eslint-disable-next-line no-unused-vars
48
+ getTexture (scale) {
49
+ return this._texture || super.getTexture();
50
+ }
51
+
52
+ /**
53
+ * Set the contents of this skin to a snapshot of the provided bitmap data.
54
+ * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - new contents for this skin.
55
+ * @param {int} [costumeResolution=1] - The resolution to use for this bitmap.
56
+ * @param {Array<number>} [rotationCenter] - Optional rotation center for the bitmap. If not supplied, it will be
57
+ * calculated from the bounding box
58
+ * @fires Skin.event:WasAltered
59
+ */
60
+ setBitmap (bitmapData, costumeResolution, rotationCenter) {
61
+ if (!bitmapData.width || !bitmapData.height) {
62
+ super.setEmptyImageData();
63
+ return;
64
+ }
65
+ const gl = this._renderer.gl;
66
+
67
+ // Preferably bitmapData is ImageData. ImageData speeds up updating
68
+ // Silhouette and is better handled by more browsers in regards to
69
+ // memory.
70
+ let textureData = bitmapData;
71
+ if (bitmapData instanceof HTMLCanvasElement) {
72
+ // Given a HTMLCanvasElement get the image data to pass to webgl and
73
+ // Silhouette.
74
+ const context = bitmapData.getContext('2d');
75
+ textureData = context.getImageData(0, 0, bitmapData.width, bitmapData.height);
76
+ }
77
+
78
+ if (this._texture === null) {
79
+ const textureOptions = {
80
+ auto: false,
81
+ wrap: gl.CLAMP_TO_EDGE
82
+ };
83
+
84
+ this._texture = twgl.createTexture(gl, textureOptions);
85
+ }
86
+
87
+ this._setTexture(textureData);
88
+
89
+ // Do these last in case any of the above throws an exception
90
+ this._costumeResolution = costumeResolution || 2;
91
+ this._textureSize = BitmapSkin._getBitmapSize(bitmapData);
92
+
93
+ if (typeof rotationCenter === 'undefined') rotationCenter = this.calculateRotationCenter();
94
+ this._rotationCenter[0] = rotationCenter[0];
95
+ this._rotationCenter[1] = rotationCenter[1];
96
+
97
+ this.emit(Skin.Events.WasAltered);
98
+ }
99
+
100
+ /**
101
+ * @param {ImageData|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement} bitmapData - bitmap data to inspect.
102
+ * @returns {Array<int>} the width and height of the bitmap data, in pixels.
103
+ * @private
104
+ */
105
+ static _getBitmapSize (bitmapData) {
106
+ if (bitmapData instanceof HTMLImageElement) {
107
+ return [bitmapData.naturalWidth || bitmapData.width, bitmapData.naturalHeight || bitmapData.height];
108
+ }
109
+
110
+ if (bitmapData instanceof HTMLVideoElement) {
111
+ return [bitmapData.videoWidth || bitmapData.width, bitmapData.videoHeight || bitmapData.height];
112
+ }
113
+
114
+ // ImageData or HTMLCanvasElement
115
+ return [bitmapData.width, bitmapData.height];
116
+ }
117
+
118
+ }
119
+
120
+ module.exports = BitmapSkin;