diff --git a/README.md b/README.md index 6e64348..6dc213f 100644 --- a/README.md +++ b/README.md @@ -342,8 +342,7 @@ Like `MX` and `MY`, Litecanvas also declares these other variables: - `H`: the height of the game canvas - `T`: the amount of seconds since the game started - `PI`: approximately 3.14 radians (or 180 degrees) -- `TWO_PI`: approximately 6.28 radians (or 360 degrees) -- `HALF_PI`: approximately 1.57 radians (or 90 degrees) +- `TAU`: approximately 6.28 radians (or 360 degrees) ### And much more! diff --git a/bun.lock b/bun.lock index 2a87dbc..097e613 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,7 @@ "@happy-dom/global-registrator": "^20.9.0", "@size-limit/preset-small-lib": "^12.1.0", "@swc/core": "^1.15.40", - "ava": "8", + "ava": "^8.0.1", "esbuild": "^0.28.0", "genversion": "^3.2.0", "gzip-size": "^7.0.0", diff --git a/package.json b/package.json index 5ab66a7..aa7a9b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "litecanvas", - "version": "0.209.0", + "version": "0.300.0", "description": "Lightweight HTML5 canvas 2D game engine suitable for small projects and creative coding. Inspired by PICO-8 and p5.js/Processing.", "license": "MIT", "author": "Luiz Bills ", diff --git a/samples/bench/bench.js b/samples/bench/bench.js index 97a651a..fbcd7e4 100644 --- a/samples/bench/bench.js +++ b/samples/bench/bench.js @@ -9,7 +9,7 @@ litecanvas({ width: state.width, height: state.height, canvas: '#c', - autoscale: false, + // autoscale: false, }) use(pluginFrameRateMeter) diff --git a/samples/clip/clip.js b/samples/clip/clip.js index 8980440..847bcdc 100644 --- a/samples/clip/clip.js +++ b/samples/clip/clip.js @@ -46,7 +46,7 @@ function draw() { } function clipcirc(x, y, radius) { - clip((_ctx) => _ctx.arc(x, y, radius, 0, TWO_PI)) + clip((_ctx) => _ctx.arc(x, y, radius, 0, TAU)) } function cliprect(x, y, width, height) { diff --git a/samples/clock/clock.js b/samples/clock/clock.js index 8102347..4c323fc 100644 --- a/samples/clock/clock.js +++ b/samples/clock/clock.js @@ -28,9 +28,9 @@ function draw() { const minutes = date.getMinutes() const hour = date.getHours() - const s = map(seconds, 0, 60, 0, TWO_PI) - HALF_PI - const m = map(minutes + norm(seconds, 0, 60), 0, 60, 0, TWO_PI) - HALF_PI - const h = map(hour + norm(minutes, 0, 60), 0, 24, 0, TWO_PI * 2) - HALF_PI + const s = map(seconds, 0, 60, 0, TAU) - PI / 2 + const m = map(minutes + norm(seconds, 0, 60), 0, 60, 0, TAU) - PI / 2 + const h = map(hour + norm(minutes, 0, 60), 0, 24, 0, TAU * 2) - PI / 2 // Draw the hands of the clock linewidth(1) diff --git a/samples/raycast/raycast.js b/samples/raycast/raycast.js index 0f06ebc..eeabb24 100644 --- a/samples/raycast/raycast.js +++ b/samples/raycast/raycast.js @@ -10,7 +10,8 @@ litecanvas() // const 3 = 3 // const 2 = 2 -const ONE_AND_HALF_PI = (3 * Math.PI) / 2 +const ONE_AND_HALF_PI = (3 * PI) / 2 +const HALF_PI = PI / 2 const MIDPOINT = 600 @@ -46,21 +47,21 @@ const player = { }, init: function () { - this.heading = Math.PI / 2 // player angle + this.heading = HALF_PI this.position = { x: 100, y: 100 } this.velocity = { x: 0, y: 0 } }, } function rayLength(ax, ay, bx, by) { - return Math.sqrt((bx - ax) * (bx - ax) + (by - ay) * (by - ay)) + return sqrt((bx - ax) * (bx - ax) + (by - ay) * (by - ay)) } function rayNormalize(angle) { - let normalizedAngle = angle % TWO_PI + let normalizedAngle = angle % TAU if (normalizedAngle < 0) { - normalizedAngle += TWO_PI + normalizedAngle += TAU } return normalizedAngle @@ -111,30 +112,30 @@ function draw() { hx = player.position.x, hy = player.position.y - let rayAngle = player.heading - (Math.PI / 180) * 30 + let rayAngle = player.heading - (PI / 180) * 30 for (let r = 0; r < 60; r++) { // check horizontal lines let depthOfFieldX = 0 - const atan = -1 / Math.tan(rayAngle) + const atan = -1 / tan(rayAngle) // looking down - if (rayAngle > Math.PI) { + if (rayAngle > PI) { rayY = ((player.position.y >> 6) << 6) - 0.0001 rayX = (player.position.y - rayY) * atan + player.position.x offsetY = -64 offsetX = -offsetY * atan } // looking up - if (rayAngle < Math.PI) { + if (rayAngle < PI) { rayY = ((player.position.y >> 6) << 6) + 64 rayX = (player.position.y - rayY) * atan + player.position.x offsetY = +64 offsetX = -offsetY * atan } // looking sideways - if (rayAngle === 0 || rayAngle === Math.PI) { + if (rayAngle === 0 || rayAngle === PI) { rayX = player.position.x rayY = player.position.y depthOfFieldX = 8 @@ -165,7 +166,7 @@ function draw() { vx = player.position.x, vy = player.position.y let depthOfFieldV = 0 - const ntan = -Math.tan(rayAngle) + const ntan = -tan(rayAngle) // looking left if (rayAngle > HALF_PI && rayAngle < ONE_AND_HALF_PI) { @@ -182,7 +183,7 @@ function draw() { offsetY = -offsetX * ntan } // looking up or down - if (rayAngle === 0 || rayAngle === Math.PI) { + if (rayAngle === 0 || rayAngle === PI) { rayX = player.position.x rayY = player.position.y depthOfFieldV = 8 @@ -230,7 +231,7 @@ function draw() { // ray wall const fishAngle = rayNormalize(player.heading - rayAngle) - const dist = Math.min(distH, distV) * Math.cos(fishAngle) + const dist = min(distH, distV) * cos(fishAngle) let lineHeight = (map.size * 512) / dist if (lineHeight > 512) { @@ -245,7 +246,7 @@ function draw() { linewidth(8) line(MIDPOINT + r * 8, 0, MIDPOINT + r * 8, lineHeight + lineOffset, wallColor) - rayAngle = rayNormalize(rayAngle + Math.PI / 180) + rayAngle = rayNormalize(rayAngle + PI / 180) alpha(1) } } @@ -259,12 +260,12 @@ function draw() { { if (iskeydown('a') || iskeydown('ArrowLeft')) { player.heading -= 0.05 - player.velocity.x = Math.cos(player.heading) * 5 - player.velocity.y = Math.sin(player.heading) * 5 + player.velocity.x = cos(player.heading) * 5 + player.velocity.y = sin(player.heading) * 5 } else if (iskeydown('d') || iskeydown('ArrowRight')) { player.heading += 0.05 - player.velocity.x = Math.cos(player.heading) * 5 - player.velocity.y = Math.sin(player.heading) * 5 + player.velocity.x = cos(player.heading) * 5 + player.velocity.y = sin(player.heading) * 5 } if (iskeydown('w') || iskeydown('ArrowUp')) { diff --git a/samples/starfield/starfield.js b/samples/starfield/starfield.js index 2897ec0..2ba7b7f 100644 --- a/samples/starfield/starfield.js +++ b/samples/starfield/starfield.js @@ -26,7 +26,7 @@ function update() { if (stars.length < numberOfStars) { for (let i = 0; i < numberOfStars - stars.length; i += 1) { - let angle = rand() * TWO_PI + let angle = rand() * TAU let radius = Math.sqrt(rand()) * (W / 2) let x = radius * Math.cos(angle) @@ -47,7 +47,7 @@ function update() { stars[i][3] = size if (z < 1) { - let angle = rand() * TWO_PI + let angle = rand() * TAU let radius = Math.sqrt(rand()) * (W / 2) stars[i][0] = radius * Math.cos(angle) stars[i][1] = radius * Math.sin(angle) diff --git a/src/index.js b/src/index.js index 551d7f5..d34e19d 100644 --- a/src/index.js +++ b/src/index.js @@ -14,7 +14,7 @@ export default function litecanvas(settings = {}) { const root = window, math = Math, perf = performance, - TWO_PI = math.PI * 2, + TAU = math.PI * 2, raf = requestAnimationFrame, isNumber = Number.isFinite, /** @type {Function[]} */ @@ -117,20 +117,9 @@ export default function litecanvas(settings = {}) { * Twice the value of the mathematical constant PI (π). * Approximately 6.28318 * - * Note: TWO_PI radians equals 360°, PI radians equals 180°, - * HALF_PI radians equals 90°, and HALF_PI/2 radians equals 45°. - * * @type {number} */ - TWO_PI, - - /** - * Half the value of the mathematical constant PI (π). - * Approximately 1.57079 - * - * @type {number} - */ - HALF_PI: TWO_PI / 4, + TAU, /** * Calculates a linear (interpolation) value over t%. @@ -181,9 +170,6 @@ export default function litecanvas(settings = {}) { * @param {number} a dividend * @param {number} b divisor * @returns {number} the remainder - * @example - * mod(-1, 5) // => 4 - * -1 % 5 // => -1 */ mod(a, b) { DEV: assert(isNumber(a), 'mod() 1st parameter must be a number') @@ -499,7 +485,7 @@ export default function litecanvas(settings = {}) { beginPath(_ctx) - _ctx.ellipse(~~x, ~~y, ~~radiusX, ~~radiusY, 0, 0, TWO_PI) + _ctx.ellipse(~~x, ~~y, ~~radiusX, ~~radiusY, 0, 0, TAU) instance.stroke(color) }, @@ -530,7 +516,7 @@ export default function litecanvas(settings = {}) { beginPath(_ctx) - _ctx.ellipse(~~x, ~~y, ~~radiusX, ~~radiusY, 0, 0, TWO_PI) + _ctx.ellipse(~~x, ~~y, ~~radiusX, ~~radiusY, 0, 0, TAU) instance.fill(color) }, @@ -1336,6 +1322,15 @@ export default function litecanvas(settings = {}) { return internals[index] }, + /** + * Returns `true` if the engine loop is paused. + * + * @returns {boolean} + */ + ispaused() { + return _paused + }, + /** * Pauses the engine loop (update & draw). */ @@ -1363,15 +1358,6 @@ export default function litecanvas(settings = {}) { } }, - /** - * Returns `true` if the engine loop is paused. - * - * @returns {boolean} - */ - ispaused() { - return _paused - }, - /** * Shutdown the litecanvas instance and remove all event listeners. */ @@ -1421,6 +1407,8 @@ export default function litecanvas(settings = {}) { } function init() { + resizeCanvas() + // listen window resize event when "autoscale" is enabled if (settings.autoscale) { on(root, 'resize', resizeCanvas) @@ -1748,16 +1736,14 @@ export default function litecanvas(settings = {}) { on(_canvas, 'click', () => focus()) - resizeCanvas() - if (!_canvas.parentNode) { d.body.appendChild(_canvas) } - _canvas.style.imageRendering = 'pixelated' - // disable default browser's right click in canvas _canvas.oncontextmenu = () => false + + resizeCanvas() } function resizeCanvas() { @@ -1774,6 +1760,11 @@ export default function litecanvas(settings = {}) { 'litecanvas() option "width" is required when the option "height" is defined' ) + DEV: assert( + 'boolean' === typeof settings.autoscale || + (isNumber(settings.autoscale) && settings.autoscale > 1), + 'litecanvas() option "autoscale" must be boolean or a number > 1' + ) const width = settings.width > 0 ? settings.width : innerWidth, height = settings.width > 0 ? settings.height || settings.width : innerHeight @@ -1784,6 +1775,9 @@ export default function litecanvas(settings = {}) { _canvas.width = width _canvas.height = height + /** @ts-ignore */ + _canvas.style = 'image-rendering:pixelated' + if (settings.autoscale) { let maxScale = +settings.autoscale if (!_canvas.style.display) { diff --git a/src/version.js b/src/version.js index f6cbcd7..5537057 100644 --- a/src/version.js +++ b/src/version.js @@ -1,2 +1,2 @@ // Generated by genversion. -export const version = '0.209.0' +export const version = '0.300.0' diff --git a/tests/math.js b/tests/math.js index cf1a36a..d22c9d6 100644 --- a/tests/math.js +++ b/tests/math.js @@ -23,12 +23,8 @@ test('PI', async (t) => { t.is(local.PI, Math.PI) }) -test('TWO_PI', async (t) => { - t.is(local.TWO_PI, Math.PI * 2) -}) - -test('HALF_PI', async (t) => { - t.is(local.HALF_PI, Math.PI / 2) +test('TAU', async (t) => { + t.is(local.TAU, Math.PI * 2) }) test('lerp', async (t) => { @@ -40,12 +36,12 @@ test('lerp', async (t) => { test('deg2rad', async (t) => { t.is(local.deg2rad(180), local.PI) - t.is(local.deg2rad(360), local.TWO_PI) + t.is(local.deg2rad(360), local.TAU) }) test('rad2deg', async (t) => { t.is(local.rad2deg(local.PI), 180) - t.is(local.rad2deg(local.HALF_PI), 90) + t.is(local.rad2deg(local.TAU), 360) }) test('clamp', async (t) => { @@ -99,3 +95,11 @@ test('dist', async (t) => { const actual = local.dist(0, 0, 0, 100) t.is(actual, expected) }) + +test('mod', async (t) => { + { + const expected = 4 + const actual = local.mod(-1, 5) + t.is(actual, expected) + } +}) diff --git a/tests/stat.js b/tests/stat.js index 0ca541d..a8755bd 100644 --- a/tests/stat.js +++ b/tests/stat.js @@ -58,7 +58,6 @@ test('stat(2) returns the current delta time (dt) value', async (t) => { test('stat(3) returns current canvas element scale factor', async (t) => { { - const expected = 2 const scaled = litecanvas({ global: false, @@ -66,24 +65,31 @@ test('stat(3) returns current canvas element scale factor', async (t) => { height: window.innerHeight / 2, autoscale: true, }) - const actual = scaled.stat(3) + await onLitecanvas(scaled, 'init', () => { + const expected = 2 - t.is(actual, expected) - scaled.quit() + const actual = scaled.stat(3) + + t.is(actual, expected) + scaled.quit() + }) } { - const expected = 1 const notScaled = litecanvas({ global: false, width: 320, autoscale: false, }) - const actual = notScaled.stat(3) + await onLitecanvas(notScaled, 'init', () => { + const expected = 1 - t.is(actual, expected) - notScaled.quit() + const actual = notScaled.stat(3) + + t.is(actual, expected) + notScaled.quit() + }) } }) diff --git a/types/global.d.ts b/types/global.d.ts index f6281cb..3ecdba4 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -25,16 +25,8 @@ declare global { /** * Twice the value of the mathematical constant PI (π). * Approximately 6.28318 - * - * Note: TWO_PI radians equals 360°, PI radians equals 180°, - * HALF_PI radians equals 90°, and HALF_PI/2 radians equals 45°. - */ - var TWO_PI: number - /** - * Half the value of the mathematical constant PI (π). - * Approximately 1.57079 */ - var HALF_PI: number + var TAU: number /** * Calculates a linear (interpolation) value over t%. * @@ -68,8 +60,8 @@ declare global { * @param b divisor * @returns the remainder * @example - * mod(-1, 5) // => 4 - * -1 % 5 // => -1 + * mod(-1, 5) // => 4 + * -1 % 5 // => -1 */ function mod(a: number, b: number): number /** diff --git a/types/types.d.ts b/types/types.d.ts index 045dfbf..05c4fa9 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -19,16 +19,8 @@ type LitecanvasInstance = { /** * Twice the value of the mathematical constant PI (π). * Approximately 6.28318 - * - * Note: TWO_PI radians equals 360°, PI radians equals 180°, - * HALF_PI radians equals 90°, and HALF_PI/2 radians equals 45°. - */ - TWO_PI: number - /** - * Half the value of the mathematical constant PI (π). - * Approximately 1.57079 */ - HALF_PI: number + TAU: number /** * Calculates a linear (interpolation) value over t%. * @@ -62,8 +54,8 @@ type LitecanvasInstance = { * @param b divisor * @returns the remainder * @example - * mod(-1, 5) // => 4 - * -1 % 5 // => -1 + * mod(-1, 5) // => 4 + * -1 % 5 // => -1 */ mod(a: number, b: number): number /**