maybe slight performance improvement

This commit is contained in:
Zutatensuppe 2020-11-08 12:56:54 +01:00
parent 36e5f8437d
commit 1f3eb41b4e
4 changed files with 408 additions and 344 deletions

View file

@ -5,11 +5,12 @@ export default class Bitmap {
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)
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) {
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]
@ -33,7 +34,6 @@ export default class Bitmap {
imgData.data.set(this._data);
ctx.putImageData(imgData, 0, 0);
return new Promise((resolve) => {
const img = document.createElement('img')
@ -47,7 +47,7 @@ export default class Bitmap {
getPix(x, y, out) {
if (x < 0 || y < 0 || x >= this._w || y >= this._h) {
return false;
return false
}
x = Math.round(x)
y = Math.round(y)
@ -61,7 +61,7 @@ export default class Bitmap {
putPix(x, y, rgba) {
if (x < 0 || y < 0 || x >= this._w || y >= this._h) {
return;
return
}
x = Math.round(x)
y = Math.round(y)

View file

@ -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) {
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() {
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)
}
}
}

View file

@ -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,10 +45,11 @@ 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)
}
for (let rect_cap of rects_cap) {
let startX = Math.floor(rect_cap.x0)
let startY = Math.floor(rect_cap.y0)
@ -61,72 +61,24 @@ function fillBitmapCapped(bitmap, rgba, rect_cap) {
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
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))
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.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.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 (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)
}
}
}
}
}
}
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(((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 (src.getPix(src_x, src_y, tmp)) {
if (tmp[3] === 255) {
dst.putPix(x, y, tmp)
}
}
}
}
adapter_dst.apply()
}
function copy(src) {
@ -179,42 +204,35 @@ function dataToBitmap(w, h, data) {
return bitmap
}
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 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)
return canvasToBitmap(c, ctx)
}
function loadImageToBitmap(imagePath) {
return new Promise((resolve) => {
async function loadImageToBitmap(imagePath) {
const img = new Image()
img.onload= () => {
resolve(imageToBitmap(img))
}
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,54 +344,63 @@ async function createPuzzleTileBitmaps (bitmap, tiles, info) {
const bitmaps = new Array(tiles.length)
for (let tile of tiles) {
let c = createCanvas(tileDrawSize, tileDrawSize)
let ctx = c.getContext('2d')
ctx.clearRect(0, 0,tileDrawSize, tileDrawSize)
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
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 paths = {}
function pathForShape(shape) {
const key = `${shape.top}${shape.right}${shape.left}${shape.bottom}`
if (paths[key]) {
return paths[key]
}
//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);
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
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);
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
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 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) {
const srcRect = srcRectByIdx(info, tile.idx)
ctx.clip()
const path = pathForShape(info.shapes[tile.idx])
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,
@ -373,58 +412,10 @@ async function createPuzzleTileBitmaps (bitmap, tiles, info) {
tileDrawSize,
tileDrawSize,
)
ctx.closePath()
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);
}
//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)
const bitmap = canvasToBitmap(c, ctx)
bitmaps[tile.idx] = bitmap
}
@ -444,13 +435,9 @@ 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
@ -458,12 +445,11 @@ const unfinishedTileByPos = (puzzle, pos) => {
let maxZ = -1
let tileIdx = -1
for (let idx = 0; idx < puzzle.tiles.length; idx++) {
let tile = puzzle.tiles[idx]
const 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,
@ -482,23 +468,21 @@ const unfinishedTileByPos = (puzzle, pos) => {
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