From 1f3eb41b4ed46ef88a27b666029ddfa47296efbd Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 8 Nov 2020 12:56:54 +0100 Subject: [PATCH] maybe slight performance improvement --- game/Bitmap.js | 126 ++++----- game/CanvasAdapter.js | 47 +++- game/index.js | 577 ++++++++++++++++++++++-------------------- game/util.js | 2 +- 4 files changed, 408 insertions(+), 344 deletions(-) diff --git a/game/Bitmap.js b/game/Bitmap.js index 7bca65d..6556195 100644 --- a/game/Bitmap.js +++ b/game/Bitmap.js @@ -1,78 +1,78 @@ import BoundingRectangle from './BoundingRectangle.js' export default class Bitmap { - constructor(width, height, rgba = null) { - this._w = width - this._h = height - this._com = 4 // number of components per pixel (RGBA) - this._boundingRect = new BoundingRectangle(0, this._w - 1, 0, this._h -1) - const len = this._w * this._h * this._com - this._data = new Uint8ClampedArray(len) - if (rgba) { - for (let i = 0; i < len; i+=4) { - this._data[i] = rgba[0] - this._data[i + 1] = rgba[1] - this._data[i + 2] = rgba[2] - this._data[i + 3] = rgba[3] - } - } + constructor(width, height, rgba = null) { + this._w = width + this._h = height + this._com = 4 // number of components per pixel (RGBA) + this._boundingRect = new BoundingRectangle(0, this._w - 1, 0, this._h - 1) + const len = this._w * this._h * this._com + this._data = new Uint8ClampedArray(len) - // public - this.width = this._w - this.height = this._h + if (rgba) { + for (let i = 0; i < len; i += 4) { + this._data[i] = rgba[0] + this._data[i + 1] = rgba[1] + this._data[i + 2] = rgba[2] + this._data[i + 3] = rgba[3] + } } - toImage() { - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); + // public + this.width = this._w + this.height = this._h + } - canvas.width = this._w; - canvas.height = this._h; + toImage() { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); - const imgData = ctx.createImageData(canvas.width, canvas.height); - imgData.data.set(this._data); - ctx.putImageData(imgData, 0, 0); + canvas.width = this._w; + canvas.height = this._h; + const imgData = ctx.createImageData(canvas.width, canvas.height); + imgData.data.set(this._data); + ctx.putImageData(imgData, 0, 0); - return new Promise((resolve) => { + return new Promise((resolve) => { - const img = document.createElement('img') - img.onload = () => { - resolve(img) - } - img.src = canvas.toDataURL() - return img - }) - } - - getPix(x, y, out) { - if (x < 0 || y < 0 || x >= this._w || y >= this._h) { - return false; - } - x = Math.round(x) - y = Math.round(y) - const idx = (y * 4 * this._w) + (x * 4) - out[0] = this._data[idx] - out[1] = this._data[idx + 1] - out[2] = this._data[idx + 2] - out[3] = this._data[idx + 3] - return true + const img = document.createElement('img') + img.onload = () => { + resolve(img) + } + img.src = canvas.toDataURL() + return img + }) + } + + getPix(x, y, out) { + if (x < 0 || y < 0 || x >= this._w || y >= this._h) { + return false } + x = Math.round(x) + y = Math.round(y) + const idx = (y * 4 * this._w) + (x * 4) + out[0] = this._data[idx] + out[1] = this._data[idx + 1] + out[2] = this._data[idx + 2] + out[3] = this._data[idx + 3] + return true + } - putPix(x, y, rgba) { - if (x < 0 || y < 0 || x >= this._w || y >= this._h) { - return; - } - x = Math.round(x) - y = Math.round(y) - const idx = (y * this._com * this._w) + (x * this._com) - this._data[idx] = rgba[0] - this._data[idx + 1] = rgba[1] - this._data[idx + 2] = rgba[2] - this._data[idx + 3] = rgba[3] + putPix(x, y, rgba) { + if (x < 0 || y < 0 || x >= this._w || y >= this._h) { + return } + x = Math.round(x) + y = Math.round(y) + const idx = (y * this._com * this._w) + (x * this._com) + this._data[idx] = rgba[0] + this._data[idx + 1] = rgba[1] + this._data[idx + 2] = rgba[2] + this._data[idx + 3] = rgba[3] + } - getBoundingRect() { - return this._boundingRect - } -} \ No newline at end of file + getBoundingRect() { + return this._boundingRect + } +} diff --git a/game/CanvasAdapter.js b/game/CanvasAdapter.js index da72f7a..5fc73c8 100644 --- a/game/CanvasAdapter.js +++ b/game/CanvasAdapter.js @@ -3,6 +3,7 @@ import BoundingRectangle from './BoundingRectangle.js' export default class CanvasAdapter { constructor(canvas) { this._canvas = canvas + /** @type {CanvasRenderingContext2D} */ this._ctx = this._canvas.getContext('2d') this._w = this._canvas.width this._h = this._canvas.height @@ -11,6 +12,8 @@ export default class CanvasAdapter { this._imageData = this._ctx.createImageData(this._w, this._h) this._data = this._imageData.data + this._dirty = false + this._dirtyRect = {x0: 0, x1: 0, y0: 0, y1: 0} this.width = this._w this.height = this._h } @@ -18,15 +21,16 @@ export default class CanvasAdapter { clear() { this._imageData = this._ctx.createImageData(this._w, this._h) this._data = this._imageData.data - this.apply() + this._dirty = false } - clearRect(rect) { - for (let x = rect.x0; x< rect.x1; x++) { - for (let y = rect.y0; y< rect.y1; y++) { - this.putPix(x, y, [0,0,0,0]) + clearRect(rects) { + for (let rect of rects) { + for (let x = rect.x0; x< rect.x1; x++) { + for (let y = rect.y0; y< rect.y1; y++) { + this.putPix(x, y, [0,0,0,0]) + } } } - this.apply() } getPix(x, y, out) { @@ -47,6 +51,7 @@ export default class CanvasAdapter { if (x < 0 || y < 0 || x >= this._w || y >= this._h) { return null; } + x = Math.round(x) y = Math.round(y) const idx = (y * 4 * this._w) + (x * 4) @@ -54,6 +59,21 @@ export default class CanvasAdapter { this._data[idx + 1] = rgba[1] this._data[idx + 2] = rgba[2] this._data[idx + 3] = rgba[3] + + if (this._dirty) { + // merge + this._dirtyRect.x0 = Math.min(this._dirtyRect.x0, x) + this._dirtyRect.x1 = Math.max(this._dirtyRect.x1, x) + this._dirtyRect.y0 = Math.min(this._dirtyRect.y0, y) + this._dirtyRect.y1 = Math.max(this._dirtyRect.y1, y) + } else { + // set + this._dirty = true + this._dirtyRect.x0 = x + this._dirtyRect.x1 = x + this._dirtyRect.y0 = y + this._dirtyRect.y1 = y + } } getBoundingRect() { @@ -61,6 +81,19 @@ export default class CanvasAdapter { } apply() { - this._ctx.putImageData(this._imageData, 0, 0) + if (this._dirty) { + this._ctx.putImageData( + this._imageData, + 0, + 0, + this._dirtyRect.x0, + this._dirtyRect.y0, + this._dirtyRect.x1 - this._dirtyRect.x0, + this._dirtyRect.y1 - this._dirtyRect.y0 + ) + this._dirty = null + } else { + this._ctx.putImageData(this._imageData, 0, 0) + } } } diff --git a/game/index.js b/game/index.js index 781c057..cb69b33 100644 --- a/game/index.js +++ b/game/index.js @@ -4,7 +4,6 @@ import BoundingRectangle from './BoundingRectangle.js' import Bitmap from './Bitmap.js' import {run} from './gameloop.js' import Camera from './Camera.js' -import Point from './Point.js' import EventAdapter from './EventAdapter.js' import { choice } from './util.js' import WsClient from './WsClient.js' @@ -46,87 +45,40 @@ function fillBitmap (bitmap, rgba) { } } -function fillBitmapCapped(bitmap, rgba, rect_cap) { - if (!rect_cap) { +function fillBitmapCapped(bitmap, rgba, rects_cap) { + if (!rects_cap) { return fillBitmap(bitmap, rgba) } - let startX = Math.floor(rect_cap.x0) - let startY = Math.floor(rect_cap.y0) + for (let rect_cap of rects_cap) { + let startX = Math.floor(rect_cap.x0) + let startY = Math.floor(rect_cap.y0) - let endX = Math.ceil(rect_cap.x1) - let endY = Math.ceil(rect_cap.y1) + let endX = Math.ceil(rect_cap.x1) + let endY = Math.ceil(rect_cap.y1) - for (let x = startX; x < endX; x++) { - for (let y = startY; y < endY; y++) { - bitmap.putPix(x, y, rgba) + for (let x = startX; x < endX; x++) { + for (let y = startY; y < endY; y++) { + bitmap.putPix(x, y, rgba) + } } } } -function mapBitmapToBitmap (bitmap_src, rect_src, bitmap_dst, rect_dst) { +function mapBitmapToBitmap ( + /** @type {Bitmap} */src, + /** @type {BoundingRectangle} */ rect_src, + /** @type {Bitmap} */ dst, + /** @type {BoundingRectangle} */ rect_dst +) { const tmp = new Uint8ClampedArray(4) - const w_f = (rect_src.width) / (rect_dst.width) - const h_f = (rect_src.height) / (rect_dst.height) + const w_f = rect_src.width / rect_dst.width + const h_f = rect_src.height / rect_dst.height let startX = Math.max(rect_dst.x0, Math.floor((-rect_src.x0 / w_f) + rect_dst.x0)) let startY = Math.max(rect_dst.y0, Math.floor((-rect_src.y0 / h_f) + rect_dst.y0)) - let endX = Math.min(rect_dst.x1, Math.ceil(((bitmap_src._w - rect_src.x0) / w_f) + rect_dst.x0)) - let endY = Math.min(rect_dst.y1, Math.ceil(((bitmap_src._h - rect_src.y0) / h_f) + rect_dst.y0)) - - for (let x = startX; x < endX; x++) { - for (let y = startY; y < endY; y++) { - const src_x = rect_src.x0 + Math.floor((x - rect_dst.x0) * w_f) - const src_y = rect_src.y0 + Math.floor((y - rect_dst.y0) * h_f) - if (bitmap_src.getPix(src_x, src_y, tmp)) { - if (tmp[3] === 255) { - bitmap_dst.putPix(x, y, tmp) - } - } - } - } -} - -function mapBitmapToBitmapCapped (bitmap_src, rect_src, bitmap_dst, rect_dst, rect_cap) { - if (!rect_cap) { - return mapBitmapToBitmap(bitmap_src, rect_src, bitmap_dst, rect_dst) - } - const tmp = new Uint8ClampedArray(4) - const w_f = (rect_src.width) / (rect_dst.width) - const h_f = (rect_src.height) / (rect_dst.height) - - let startX = Math.floor(Math.max(rect_cap.x0, rect_dst.x0, (-rect_src.x0 / w_f) + rect_dst.x0)) - let startY = Math.floor(Math.max(rect_cap.y0, rect_dst.y0, (-rect_src.y0 / h_f) + rect_dst.y0)) - - let endX = Math.ceil(Math.min(rect_cap.x1, rect_dst.x1, ((bitmap_src._w - rect_src.x0) / w_f) + rect_dst.x0)) - let endY = Math.ceil(Math.min(rect_cap.y1, rect_dst.y1, ((bitmap_src._h - rect_src.y0) / h_f) + rect_dst.y0)) - - for (let x = startX; x < endX; x++) { - for (let y = startY; y < endY; y++) { - const src_x = rect_src.x0 + Math.floor((x - rect_dst.x0) * w_f) - const src_y = rect_src.y0 + Math.floor((y - rect_dst.y0) * h_f) - if (bitmap_src.getPix(src_x, src_y, tmp)) { - if (tmp[3] === 255) { - bitmap_dst.putPix(x, y, tmp) - } - } - } - } -} - -function mapBitmapToAdapterCapped (src, rect_src, dst, rect_dst, rect_cap) { - if (!rect_cap) { - return mapBitmapToAdapter(src, rect_src, dst, rect_dst) - } - const tmp = new Uint8ClampedArray(4) - const w_f = (rect_src.width) / (rect_dst.width) - const h_f = (rect_src.height) / (rect_dst.height) - - let startX = Math.floor(Math.max(rect_cap.x0, rect_dst.x0, (-rect_src.x0 / w_f) + rect_dst.x0)) - let startY = Math.floor(Math.max(rect_cap.y0, rect_dst.y0, (-rect_src.y0 / h_f) + rect_dst.y0)) - - let endX = Math.ceil(Math.min(rect_cap.x1, rect_dst.x1, ((src._w - rect_src.x0) / w_f) + rect_dst.x0)) - let endY = Math.ceil(Math.min(rect_cap.y1, rect_dst.y1, ((src._h - rect_src.y0) / h_f) + rect_dst.y0)) + let endX = Math.min(rect_dst.x1, Math.ceil(((src.width - rect_src.x0) / w_f) + rect_dst.x0)) + let endY = Math.min(rect_dst.y1, Math.ceil(((src.height - rect_src.y0) / h_f) + rect_dst.y0)) for (let x = startX; x < endX; x++) { for (let y = startY; y < endY; y++) { @@ -139,32 +91,105 @@ function mapBitmapToAdapterCapped (src, rect_src, dst, rect_dst, rect_cap) { } } } - dst.apply() } -function mapBitmapToAdapter (bitmap_src, rect_src, adapter_dst, rect_dst) { +function mapBitmapToBitmapCapped ( + /** @type {Bitmap} */ src, + /** @type {BoundingRectangle} */ rect_src, + /** @type {Bitmap} */ dst, + /** @type {BoundingRectangle} */ rect_dst, + rects_cap +) { + if (!rects_cap) { + return mapBitmapToBitmap(src, rect_src, dst, rect_dst) + } const tmp = new Uint8ClampedArray(4) - const w_f = (rect_src.x1 - rect_src.x0) / (rect_dst.x1 - rect_dst.x0) - const h_f = (rect_src.y1 - rect_src.y0) / (rect_dst.y1 - rect_dst.y0) + const w_f = rect_src.width / rect_dst.width + const h_f = rect_src.height / rect_dst.height + + for (let rect_cap of rects_cap) { + let startX = Math.floor(Math.max(rect_cap.x0, rect_dst.x0, (-rect_src.x0 / w_f) + rect_dst.x0)) + let startY = Math.floor(Math.max(rect_cap.y0, rect_dst.y0, (-rect_src.y0 / h_f) + rect_dst.y0)) + + let endX = Math.ceil(Math.min(rect_cap.x1, rect_dst.x1, ((src.width - rect_src.x0) / w_f) + rect_dst.x0)) + let endY = Math.ceil(Math.min(rect_cap.y1, rect_dst.y1, ((src.height - rect_src.y0) / h_f) + rect_dst.y0)) + + for (let x = startX; x < endX; x++) { + for (let y = startY; y < endY; y++) { + const src_x = rect_src.x0 + Math.floor((x - rect_dst.x0) * w_f) + const src_y = rect_src.y0 + Math.floor((y - rect_dst.y0) * h_f) + if (src.getPix(src_x, src_y, tmp)) { + if (tmp[3] === 255) { + dst.putPix(x, y, tmp) + } + } + } + } + } +} + +function mapBitmapToAdapterCapped ( + /** @type {Bitmap} */ src, + /** @type {BoundingRectangle} */ rect_src, + /** @type {CanvasAdapter} */ dst, + /** @type {BoundingRectangle} */ rect_dst, + rects_cap +) { + if (!rects_cap) { + return mapBitmapToAdapter(src, rect_src, dst, rect_dst) + } + const tmp = new Uint8ClampedArray(4) + const w_f = rect_src.width / rect_dst.width + const h_f = rect_src.height / rect_dst.height + + for (let rect_cap of rects_cap) { + let startX = Math.floor(Math.max(rect_cap.x0, rect_dst.x0, (-rect_src.x0 / w_f) + rect_dst.x0)) + let startY = Math.floor(Math.max(rect_cap.y0, rect_dst.y0, (-rect_src.y0 / h_f) + rect_dst.y0)) + + let endX = Math.ceil(Math.min(rect_cap.x1, rect_dst.x1, ((src.width - rect_src.x0) / w_f) + rect_dst.x0)) + let endY = Math.ceil(Math.min(rect_cap.y1, rect_dst.y1, ((src.height - rect_src.y0) / h_f) + rect_dst.y0)) + + for (let x = startX; x < endX; x++) { + for (let y = startY; y < endY; y++) { + const src_x = rect_src.x0 + Math.floor((x - rect_dst.x0) * w_f) + const src_y = rect_src.y0 + Math.floor((y - rect_dst.y0) * h_f) + if (src.getPix(src_x, src_y, tmp)) { + if (tmp[3] === 255) { + dst.putPix(x, y, tmp) + } + } + } + } + } +} + +function mapBitmapToAdapter ( + /** @type {Bitmap} */ src, + /** @type {BoundingRectangle} */ rect_src, + /** @type {CanvasAdapter} */ dst, + /** @type {BoundingRectangle} */ rect_dst +) { + const tmp = new Uint8ClampedArray(4) + const w_f = rect_src.width / rect_dst.width + const h_f = rect_src.height / rect_dst.height let startX = Math.max(rect_dst.x0, Math.floor((-rect_src.x0 / w_f) + rect_dst.x0)) let startY = Math.max(rect_dst.y0, Math.floor((-rect_src.y0 / h_f) + rect_dst.y0)) - let endX = Math.min(rect_dst.x1, Math.ceil(((bitmap_src._w - rect_src.x0) / w_f) + rect_dst.x0)) - let endY = Math.min(rect_dst.y1, Math.ceil(((bitmap_src._h - rect_src.y0) / h_f) + rect_dst.y0)) + let endX = Math.min(rect_dst.x1, Math.ceil(((src.width - rect_src.x0) / w_f) + rect_dst.x0)) + let endY = Math.min(rect_dst.y1, Math.ceil(((src.height - rect_src.y0) / h_f) + rect_dst.y0)) for (let x = startX; x < endX; x++) { for (let y = startY; y < endY; y++) { const src_x = rect_src.x0 + Math.floor((x - rect_dst.x0) * w_f) const src_y = rect_src.y0 + Math.floor((y - rect_dst.y0) * h_f) - if (bitmap_src.getPix(src_x, src_y, tmp)) { + if (src.getPix(src_x, src_y, tmp)) { if (tmp[3] === 255) { - adapter_dst.putPix(x, y, tmp) + dst.putPix(x, y, tmp) } } } } - adapter_dst.apply() } function copy(src) { @@ -179,42 +204,35 @@ function dataToBitmap(w, h, data) { return bitmap } -function imageToBitmap(img) { - const c = createCanvas(img.width, img.height) - const ctx = c.getContext('2d') - ctx.drawImage(img, 0, 0) - const data = ctx.getImageData(0, 0, c.width, c.height).data - - return dataToBitmap(c.width, c.height, data) +function canvasToBitmap( + /** @type {HTMLCanvasElement} */ c, + /** @type {CanvasRenderingContext2D} */ ctx +) { + const data = ctx.getImageData(0, 0, c.width, c.height).data + return dataToBitmap(c.width, c.height, data) } -function loadImageToBitmap(imagePath) { - return new Promise((resolve) => { - const img = new Image() - img.onload= () => { - resolve(imageToBitmap(img)) - } - img.src = imagePath - }) +function imageToBitmap(img) { + const c = createCanvas(img.width, img.height) + const ctx = c.getContext('2d') + ctx.drawImage(img, 0, 0) + return canvasToBitmap(c, ctx) +} + +async function loadImageToBitmap(imagePath) { + const img = new Image() + await new Promise((resolve) => { + img.onload = resolve + img.src = imagePath + }); + return imageToBitmap(img) } function pointInBounds(pt, rect) { - return pt.x >= rect.x0 && pt.x <= rect.x1 && pt.y >= rect.y0 && pt.y <= rect.y1 -} - -const tilesFit = (w, h, size) => { - return Math.floor(w / size) * Math.floor(h / size) -} - -const coordsByNum = (puzzleInfo) => { - const w_tiles = puzzleInfo.width / puzzleInfo.tileSize - const coords = new Array(puzzleInfo.tiles) - for (let i = 0; i < puzzleInfo.tiles; i++) { - const y = Math.floor(i / w_tiles) - const x = i % w_tiles - coords[i] = {x, y} - } - return coords + return pt.x >= rect.x0 + && pt.x <= rect.x1 + && pt.y >= rect.y0 + && pt.y <= rect.y1 } const determinePuzzleInfo = (w, h, targetTiles) => { @@ -227,20 +245,20 @@ const determinePuzzleInfo = (w, h, targetTiles) => { tileSize-- tiles = tilesFit(w, h, tileSize) - let tiles_x = Math.round(w / tileSize) - let tiles_y = Math.round(h / tileSize) + const tiles_x = Math.round(w / tileSize) + const tiles_y = Math.round(h / tileSize) tiles = tiles_x * tiles_y // then resize to final TILE_SIZE (which is always the same) tileSize = TILE_SIZE - let width = tiles_x * tileSize - let height = tiles_y * tileSize - let coords = coordsByNum({width, height, tileSize, tiles}) + const width = tiles_x * tileSize + const height = tiles_y * tileSize + const coords = coordsByNum({width, height, tileSize, tiles}) - var tileMarginWidth = tileSize * .5; - var tileDrawSize = Math.round(tileSize + tileMarginWidth*2) + const tileMarginWidth = tileSize * .5; + const tileDrawSize = Math.round(tileSize + tileMarginWidth*2) - const info = { + return { width, height, tileSize, @@ -251,7 +269,19 @@ const determinePuzzleInfo = (w, h, targetTiles) => { tiles_y, coords, } - return info +} + +const tilesFit = (w, h, size) => Math.floor(w / size) * Math.floor(h / size) + +const coordsByNum = (puzzleInfo) => { + const w_tiles = puzzleInfo.width / puzzleInfo.tileSize + const coords = new Array(puzzleInfo.tiles) + for (let i = 0; i < puzzleInfo.tiles; i++) { + const y = Math.floor(i / w_tiles) + const x = i % w_tiles + coords[i] = { x, y } + } + return coords } const resizeBitmap = (bitmap, width, height) => { @@ -314,119 +344,80 @@ async function createPuzzleTileBitmaps (bitmap, tiles, info) { const bitmaps = new Array(tiles.length) + const paths = {} + function pathForShape(shape) { + const key = `${shape.top}${shape.right}${shape.left}${shape.bottom}` + if (paths[key]) { + return paths[key] + } + + const path = new Path2D() + const topLeftEdge = { x: tileMarginWidth, y: tileMarginWidth } + path.moveTo(topLeftEdge.x, topLeftEdge.y) + for (let i = 0; i < curvyCoords.length / 6; i++) { + const p1 = pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.top * curvyCoords[i * 6 + 1] * tileRatio }) + const p2 = pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.top * curvyCoords[i * 6 + 3] * tileRatio }) + const p3 = pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.top * curvyCoords[i * 6 + 5] * tileRatio }) + path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + } + const topRightEdge = pointAdd(topLeftEdge, { x: tileSize, y: 0 }) + for (let i = 0; i < curvyCoords.length / 6; i++) { + const p1 = pointAdd(topRightEdge, { x: -shape.right * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio }) + const p2 = pointAdd(topRightEdge, { x: -shape.right * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio }) + const p3 = pointAdd(topRightEdge, { x: -shape.right * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio }) + path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + } + //Bottom + const bottomRightEdge = pointAdd(topRightEdge, { x: 0, y: tileSize }) + for (let i = 0; i < curvyCoords.length / 6; i++) { + let p1 = pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio }) + let p2 = pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio }) + let p3 = pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 5] * tileRatio }) + path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + } + //Left + const bottomLeftEdge = pointSub(bottomRightEdge, { x: tileSize, y: 0 }) + for (let i = 0; i < curvyCoords.length / 6; i++) { + let p1 = pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio }) + let p2 = pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio }) + let p3 = pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio }) + path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); + } + paths[key] = path + return path + } + for (let tile of tiles) { - let c = createCanvas(tileDrawSize, tileDrawSize) - let ctx = c.getContext('2d') - ctx.clearRect(0, 0,tileDrawSize, tileDrawSize) + const srcRect = srcRectByIdx(info, tile.idx) + const path = pathForShape(info.shapes[tile.idx]) - var topTab = info.shapes[tile.idx].top - var rightTab = info.shapes[tile.idx].right - var leftTab = info.shapes[tile.idx].left - var bottomTab = info.shapes[tile.idx].bottom + const c = createCanvas(tileDrawSize, tileDrawSize) + const ctx = c.getContext('2d') + // ----------------------------------------------------------- + // ----------------------------------------------------------- + ctx.lineWidth = 2 + ctx.stroke(path) + // ----------------------------------------------------------- + // ----------------------------------------------------------- + ctx.save(); + ctx.clip(path) + ctx.drawImage( + img, + srcRect.x0 - tileMarginWidth, + srcRect.y0 - tileMarginWidth, + tileDrawSize, + tileDrawSize, + 0, + 0, + tileDrawSize, + tileDrawSize, + ) + ctx.stroke(path) + ctx.restore(); - var topLeftEdge = new Point(tileMarginWidth, tileMarginWidth); - ctx.save(); - ctx.beginPath() - ctx.moveTo(topLeftEdge.x, topLeftEdge.y) - for (let i = 0; i < curvyCoords.length / 6; i++) { - var p1 = topLeftEdge.add(new Point( curvyCoords[i * 6 + 0] * tileRatio, topTab * curvyCoords[i * 6 + 1] * tileRatio) ); - var p2 = topLeftEdge.add(new Point( curvyCoords[i * 6 + 2] * tileRatio, topTab * curvyCoords[i * 6 + 3] * tileRatio) ); - var p3 = topLeftEdge.add(new Point( curvyCoords[i * 6 + 4] * tileRatio, topTab * curvyCoords[i * 6 + 5] * tileRatio) ); - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } + const bitmap = canvasToBitmap(c, ctx) - //Right - var topRightEdge = topLeftEdge.add(new Point(tileSize, 0)); - for (var i = 0; i < curvyCoords.length / 6; i++) { - var p1 = topRightEdge.add(new Point(-rightTab * curvyCoords[i * 6 + 1] * tileRatio, curvyCoords[i * 6 + 0] * tileRatio)) - var p2 = topRightEdge.add(new Point(-rightTab * curvyCoords[i * 6 + 3] * tileRatio, curvyCoords[i * 6 + 2] * tileRatio)) - var p3 = topRightEdge.add(new Point(-rightTab * curvyCoords[i * 6 + 5] * tileRatio, curvyCoords[i * 6 + 4] * tileRatio)) - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } - //Bottom - var bottomRightEdge = topRightEdge.add(new Point(0, tileSize)) - for (var i = 0; i < curvyCoords.length / 6; i++) { - var p1 = bottomRightEdge.sub(new Point(curvyCoords[i * 6 + 0] * tileRatio, bottomTab * curvyCoords[i * 6 + 1] * tileRatio)) - var p2 = bottomRightEdge.sub(new Point(curvyCoords[i * 6 + 2] * tileRatio, bottomTab * curvyCoords[i * 6 + 3] * tileRatio)) - var p3 = bottomRightEdge.sub(new Point(curvyCoords[i * 6 + 4] * tileRatio, bottomTab * curvyCoords[i * 6 + 5] * tileRatio)) - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } - //Left - var bottomLeftEdge = bottomRightEdge.sub(new Point(tileSize, 0)); - for (var i = 0; i < curvyCoords.length / 6; i++) { - var p1 = bottomLeftEdge.sub(new Point(-leftTab * curvyCoords[i * 6 + 1] * tileRatio, curvyCoords[i * 6 + 0] * tileRatio)) - var p2 = bottomLeftEdge.sub(new Point(-leftTab * curvyCoords[i * 6 + 3] * tileRatio, curvyCoords[i * 6 + 2] * tileRatio)) - var p3 = bottomLeftEdge.sub(new Point(-leftTab * curvyCoords[i * 6 + 5] * tileRatio, curvyCoords[i * 6 + 4] * tileRatio)) - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } - - const srcRect = srcRectByIdx(info, tile.idx) - ctx.clip() - ctx.drawImage( - img, - srcRect.x0 - tileMarginWidth, - srcRect.y0 - tileMarginWidth, - tileDrawSize, - tileDrawSize, - 0, - 0, - tileDrawSize, - tileDrawSize, - ) - ctx.closePath() - ctx.restore(); - - - // ----------------------------------------------------------- - // ----------------------------------------------------------- - var topLeftEdge = new Point(tileMarginWidth, tileMarginWidth); - ctx.save() - ctx.beginPath() - ctx.moveTo(topLeftEdge.x, topLeftEdge.y) - for (let i = 0; i < curvyCoords.length / 6; i++) { - var p1 = topLeftEdge.add(new Point( curvyCoords[i * 6 + 0] * tileRatio, topTab * curvyCoords[i * 6 + 1] * tileRatio) ); - var p2 = topLeftEdge.add(new Point( curvyCoords[i * 6 + 2] * tileRatio, topTab * curvyCoords[i * 6 + 3] * tileRatio) ); - var p3 = topLeftEdge.add(new Point( curvyCoords[i * 6 + 4] * tileRatio, topTab * curvyCoords[i * 6 + 5] * tileRatio) ); - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } - - //Right - var topRightEdge = topLeftEdge.add(new Point(tileSize, 0)); - for (var i = 0; i < curvyCoords.length / 6; i++) { - var p1 = topRightEdge.add(new Point(-rightTab * curvyCoords[i * 6 + 1] * tileRatio, curvyCoords[i * 6 + 0] * tileRatio)) - var p2 = topRightEdge.add(new Point(-rightTab * curvyCoords[i * 6 + 3] * tileRatio, curvyCoords[i * 6 + 2] * tileRatio)) - var p3 = topRightEdge.add(new Point(-rightTab * curvyCoords[i * 6 + 5] * tileRatio, curvyCoords[i * 6 + 4] * tileRatio)) - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } - //Bottom - var bottomRightEdge = topRightEdge.add(new Point(0, tileSize)) - for (var i = 0; i < curvyCoords.length / 6; i++) { - var p1 = bottomRightEdge.sub(new Point(curvyCoords[i * 6 + 0] * tileRatio, bottomTab * curvyCoords[i * 6 + 1] * tileRatio)) - var p2 = bottomRightEdge.sub(new Point(curvyCoords[i * 6 + 2] * tileRatio, bottomTab * curvyCoords[i * 6 + 3] * tileRatio)) - var p3 = bottomRightEdge.sub(new Point(curvyCoords[i * 6 + 4] * tileRatio, bottomTab * curvyCoords[i * 6 + 5] * tileRatio)) - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } - //Left - var bottomLeftEdge = bottomRightEdge.sub(new Point(tileSize, 0)); - for (var i = 0; i < curvyCoords.length / 6; i++) { - var p1 = bottomLeftEdge.sub(new Point(-leftTab * curvyCoords[i * 6 + 1] * tileRatio, curvyCoords[i * 6 + 0] * tileRatio)) - var p2 = bottomLeftEdge.sub(new Point(-leftTab * curvyCoords[i * 6 + 3] * tileRatio, curvyCoords[i * 6 + 2] * tileRatio)) - var p3 = bottomLeftEdge.sub(new Point(-leftTab * curvyCoords[i * 6 + 5] * tileRatio, curvyCoords[i * 6 + 4] * tileRatio)) - ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - } - - ctx.lineWidth = 2 - ctx.stroke() - ctx.closePath() - ctx.restore() - // ----------------------------------------------------------- - // ----------------------------------------------------------- - - - const data = ctx.getImageData(0, 0, tileDrawSize, tileDrawSize).data - const bitmap = dataToBitmap(tileDrawSize, tileDrawSize, data) - - bitmaps[tile.idx] = bitmap + bitmaps[tile.idx] = bitmap } return bitmaps @@ -444,61 +435,54 @@ function srcRectByIdx (puzzleInfo, idx) { ) } -function pointSub (a, b) { - return {x: a.x - b.x, y: a.y - b.y} -} +const pointSub = (a, b) => ({x: a.x - b.x, y: a.y - b.y}) -function pointAdd (a, b) { - return {x: a.x + b.x, y: a.y + b.y} -} +const pointAdd = (a, b) => ({x: a.x + b.x, y: a.y + b.y}) // Returns the index of the puzzle tile with the highest z index // that is not finished yet and that matches the position const unfinishedTileByPos = (puzzle, pos) => { - let maxZ = -1 - let tileIdx = -1 - for (let idx = 0; idx < puzzle.tiles.length; idx++) { - let tile = puzzle.tiles[idx] - if (tile.owner === -1) { - continue - } - - // TODO: store collision boxes on the tiles - const collisionRect = new BoundingRectangle( - tile.pos.x, - tile.pos.x + puzzle.info.tileSize - 1, - tile.pos.y, - tile.pos.y + puzzle.info.tileSize - 1, - ) - if (pointInBounds(pos, collisionRect)) { - if (maxZ === -1 || tile.z > maxZ) { - maxZ = tile.z - tileIdx = idx - } - } + let maxZ = -1 + let tileIdx = -1 + for (let idx = 0; idx < puzzle.tiles.length; idx++) { + const tile = puzzle.tiles[idx] + if (tile.owner === -1) { + continue } - return tileIdx + + const collisionRect = new BoundingRectangle( + tile.pos.x, + tile.pos.x + puzzle.info.tileSize - 1, + tile.pos.y, + tile.pos.y + puzzle.info.tileSize - 1, + ) + if (pointInBounds(pos, collisionRect)) { + if (maxZ === -1 || tile.z > maxZ) { + maxZ = tile.z + tileIdx = idx + } + } + } + return tileIdx } async function loadPuzzleBitmaps(puzzle) { // load bitmap, to determine the original size of the image - let bitmpTmp = await loadImageToBitmap(puzzle.info.imageUrl) + const bmp = await loadImageToBitmap(puzzle.info.imageUrl) // creation of tile bitmaps // then create the final puzzle bitmap // NOTE: this can decrease OR increase in size! - const bitmap = resizeBitmap(bitmpTmp, puzzle.info.width, puzzle.info.height) - const bitmaps = await createPuzzleTileBitmaps(bitmap, puzzle.tiles, puzzle.info) - // tile bitmaps - return bitmaps + const bmpResized = resizeBitmap(bmp, puzzle.info.width, puzzle.info.height) + return await createPuzzleTileBitmaps(bmpResized, puzzle.tiles, puzzle.info) } async function createPuzzle(targetTiles, imageUrl) { // load bitmap, to determine the original size of the image - let bitmpTmp = await loadImageToBitmap(imageUrl) + let bmp = await loadImageToBitmap(imageUrl) // determine puzzle information from the bitmap - let info = determinePuzzleInfo(bitmpTmp.width, bitmpTmp.height, targetTiles) + let info = determinePuzzleInfo(bmp.width, bmp.height, targetTiles) let tiles = new Array(info.tiles) for (let i = 0; i < tiles.length; i++) { @@ -640,19 +624,24 @@ async function main () { this.reset() } get () { - return this.x0 === null ? null : this + return this.x0 === null ? null : [ + {x0: this.x0, x1: this.x1, y0: this.y0, y1: this.y1} + ] + // return this._rects.length === 0 ? null : this._rects } add (pos, offset) { - let x0 = pos.x - offset - let x1 = pos.x + offset - let y0 = pos.y - offset - let y1 = pos.y + offset + const x0 = pos.x - offset + const x1 = pos.x + offset + const y0 = pos.y - offset + const y1 = pos.y + offset this.x0 = this.x0 === null ? x0 : Math.min(this.x0, x0) this.x1 = this.x1 === null ? x1 : Math.max(this.x1, x1) this.y0 = this.y0 === null ? y0 : Math.min(this.y0, y0) this.y1 = this.y1 === null ? y1 : Math.max(this.y1, y1) + // this._rects.push({ x0, x1, y0, y1 }) } reset () { + // this._rects = [] this.x0 = null this.x1 = null this.y0 = null @@ -968,10 +957,10 @@ async function main () { if (srcRect.centerDistance(dst) < puzzle.info.snapDistance) { // Snap the tile to the final destination console.log('ok! !!!') - moveGroupedTiles(tile, new Point( - srcRect.x0 + boardPos.x, - srcRect.y0 + boardPos.y - )) + moveGroupedTiles(tile, { + x: srcRect.x0 + boardPos.x, + y: srcRect.y0 + boardPos.y, + }) finishGroupedTiles(tile) let tp = cam.translateMouse(mouse) @@ -1056,7 +1045,7 @@ async function main () { let t = puzzle.tiles[grabbingTileIdx] moveGroupedTilesDiff(t, diffX, diffY) - // todo: dont +- tileDrawSize, we can work with less + // todo: dont +- tileDrawSize, we can work with less? rectTable.add(tp, puzzle.info.tileDrawSize) rectTable.add(tp_last, puzzle.info.tileDrawSize) } else { @@ -1087,16 +1076,44 @@ async function main () { } } + + // helper for measuring performance + let _pt = 0 + let _mindiff = 0 + const checkpoint_start = (mindiff) => { + _pt = performance.now() + _mindiff = mindiff + } + const checkpoint = (n) => { + const now = performance.now(); + const diff = now - _pt + if (diff > _mindiff) { + console.log(n + ': ' + (diff)); + } + _pt = now; + } + + // TODO: + // try out layered rendering and see + // if it improves performance: + // 1. background + // 2. tiles + // 3. players + // (currently, if a player moves, everthing needs to be + // rerendered at that position manually, maybe it is faster + // when using layers) const onRender = () => { if (!rerenderTable && !rerenderPlayer && !rerender) { return } - console.log('rendering') + checkpoint_start(20) // draw the puzzle table if (rerenderTable) { + fillBitmapCapped(puzzleTable, puzzleTableColor, rectTable.get()) + checkpoint('after fill') // draw the puzzle board on the table mapBitmapToBitmapCapped(board, board.getBoundingRect(), puzzleTable, new BoundingRectangle( @@ -1105,6 +1122,7 @@ async function main () { boardPos.y, boardPos.y + board.height - 1, ), rectTable.get()) + checkpoint('imgtoimg') // draw all the tiles on the table @@ -1124,6 +1142,7 @@ async function main () { rectTable.get() ) } + checkpoint('tiles') } if (rerenderTable || rerender) { @@ -1131,20 +1150,28 @@ async function main () { // only part of the table may be visible, depending on the // camera adapter.clear() + adapter.apply() + checkpoint('afterclear_1') + + // TODO: improve the rendering + // atm it is pretty slow (~40-50ms) mapBitmapToAdapter(puzzleTable, new BoundingRectangle( - cam.x, - cam.x + (cam.width / cam.zoom), - cam.y, - cam.y + (cam.height / cam.zoom), ), adapter, adapter.getBoundingRect()) + checkpoint('to_adapter_1') } else if (rerenderPlayer) { adapter.clearRect(rectPlayer.get()) + checkpoint('afterclear_2') mapBitmapToAdapterCapped(puzzleTable, new BoundingRectangle( - cam.x, - cam.x + (cam.width / cam.zoom), - cam.y, - cam.y + (cam.height / cam.zoom), ), adapter, adapter.getBoundingRect(), rectPlayer.get()) + checkpoint('to_adapter_2') } if (rerenderPlayer) { @@ -1164,8 +1191,12 @@ async function main () { ) ) } + checkpoint('after_players') } + adapter.apply() + checkpoint('finals') + rerenderTable = false rerenderPlayer = false rerender = false diff --git a/game/util.js b/game/util.js index a6215c6..bf46cd3 100644 --- a/game/util.js +++ b/game/util.js @@ -22,4 +22,4 @@ export default { randomInt, choice, shuffle, -} \ No newline at end of file +}