diff --git a/src/data/mazeFilters.js b/src/data/mazeFilters.js new file mode 100644 index 0000000..b230912 --- /dev/null +++ b/src/data/mazeFilters.js @@ -0,0 +1,48 @@ +class TileFilter +{ + constructor(tiles) + { + this.tiles = new Set(tiles.map( ({r,g,b})=>(r<<16) + (g<<8) + b ) ); + } + isMatch(r, g, b) + { + if(g == undefined || b == undefined) return this.tiles.has(r); + const hexCode = (r<<16) + (g<<8) + b; + return this.tiles.has(hexCode); + } +} + +const mazeFilters = { + STONE : new TileFilter([ + new Tile("Stone Wall", 73, 103, 125), + new Tile("Stone Ground", 103, 131, 151), + new Tile("Dark Stone Ground", 123, 140, 172), + new Tile("Iron Ore", 130, 155, 203), + new Tile("Stone Moss", 207, 241, 255) + ]), + CLAY : new TileFilter([ + new Tile("Clay Wall", 193, 100, 54), + new Tile("Clay Ground", 232, 139, 105), + new Tile("Tin Ore", 142, 122, 118), + new Tile("Chrysalis", 252, 166, 148), + new Tile("Clay Moss", 126, 87, 78) + ]), + WILDERNESS : new TileFilter([ + new Tile("Grass Wall", 22, 131, 27), + new Tile("Grass Ground", 61, 155, 65), + new Tile("Tilled Grass Ground", 12, 115, 43), + new Tile("Scarlet Ore", 206, 59, 59), + new Tile("Ground Poison Slime", 184, 86, 165), + new Tile("Lush Moss", 163, 206, 74) + ]), + SEA : new TileFilter([ + new Tile("Limestone Wall", 180, 147, 154), + new Tile("City Wall", 49, 77, 87), + new Tile("Beach Sand Ground", 235, 192, 190), + new Tile("City Floor", 87, 128, 132), + new Tile("Octarine Ore", 139, 82, 238), + new Tile("Sea Water", 52, 208, 255), + new Tile("Ground Slippery Slime", 47, 47, 255), + new Tile("Urban Moss", 42, 210, 62) + ]) +}; \ No newline at end of file diff --git a/src/data/pois.js b/src/data/pois.js index 93171a7..bcf5e88 100644 --- a/src/data/pois.js +++ b/src/data/pois.js @@ -110,7 +110,7 @@ function toggleTile(tile) { Alpine.store('tilecolormap').visible = Alpine.store('tilecolormap').visible.filter((e) => e.name != tile.name); tile.disabled = true; - redrawMap(); + redrawMapDirty(); tile.disabled = false; } @@ -133,6 +133,6 @@ function toggleCategoryTiles(tiles) { Alpine.store('tilecolormap').visible = Alpine.store('tilecolormap').visible.filter((e) => e.name != tile.name); } - redrawMap(); + redrawMapDirty(); tiles.forEach(tile => { tile.disabled = false; }); } \ No newline at end of file diff --git a/src/index.html b/src/index.html index fcfe5ff..f82d837 100644 --- a/src/index.html +++ b/src/index.html @@ -22,6 +22,7 @@ + diff --git a/src/js/canvastools.js b/src/js/canvastools.js index 5283b88..1b3dd83 100644 --- a/src/js/canvastools.js +++ b/src/js/canvastools.js @@ -1,14 +1,30 @@ +// include { buildHighlightSelection } from "./menubuilder.js"; + let currentZoom = 1 let MAX_ZOOM = 12; let MIN_ZOOM = 0.1; let _image_cache = undefined; -let cameraOffset = { x: 0, y: 0 } +let _image_pixelData = null; +let _decorated_pixelData = null; +let cameraOffset = { x: 0, y: 0 }; let previousCoreRelativeOffset = undefined; let _global_ctx; let coreLoc = { x: 0, y: 0 }; let pixelMap = {}; let panZoomElem; +function getCanvasPixelData() +{ + if(!_image_pixelData) + { + _image_pixelData = _global_ctx.getImageData(0, 0, canvas.width, canvas.height); + return _image_pixelData; + } + const cloneData = new Uint8ClampedArray(_image_pixelData.data); + const clone = new ImageData(cloneData, _image_pixelData.width, _image_pixelData.height); + return clone; +} + function panImage(dx, dy) { cameraOffset.x += dx; cameraOffset.y += dy; @@ -106,10 +122,11 @@ function zoomWithMouseWheel(event) { panZoomElem.zoomToPoint(targetScale, event, opts); + const mapCanvas = document.getElementById('mapcanvas'); if (panZoomElem.getScale() < 1.0) { - document.getElementById("mapcanvas").style.imageRendering = "auto"; + mapCanvas.style.imageRendering = "auto"; } else { - document.getElementById("mapcanvas").style.imageRendering = "pixelated"; + mapCanvas.style.imageRendering = "pixelated"; } storeCoreRelativeOffset(); } @@ -126,10 +143,18 @@ function setContext(ctx, width, height) { } function redrawMap() { - const canvas = document.getElementById("mapcanvas"); - setContext(_global_ctx, canvas.width, canvas.height); + const mapCanvas = document.getElementById('mapcanvas'); + setContext(_global_ctx, mapCanvas.width, mapCanvas.height); + _global_ctx.putImageData(_decorated_pixelData, 0, 0); + decorateMap(mapCanvas.width, mapCanvas.height); +} + +function redrawMapDirty() { + const mapCanvas = document.getElementById('mapcanvas'); + setContext(_global_ctx, mapCanvas.width, mapCanvas.height); _global_ctx.drawImage(_image_cache, 0, 0); - decorateMap(canvas.width, canvas.height); + highlightSelected(); + decorateMap(mapCanvas.width, mapCanvas.height); } function loop(val, min, max) { @@ -137,8 +162,6 @@ function loop(val, min, max) { } function decorateMap(width, height) { - highlightSelected(); - const showArcsCheckbox = document.getElementById("showArcs"); if (showArcsCheckbox.checked) { @@ -327,7 +350,7 @@ function drawMap(tiles) { } coreLoc.x = -minx * TILE_SIZE; coreLoc.y = (maxy + 1) * TILE_SIZE; - const canvas = document.getElementById("mapcanvas"); + const canvas = document.getElementById('mapcanvas'); canvas.width = (maxx - minx + 1) * TILE_SIZE; canvas.height = (maxy - miny + 1) * TILE_SIZE; // const ctx = canvas.getContext('2d', { willReadFrequently: true }); @@ -342,8 +365,11 @@ function drawMap(tiles) { _global_ctx.drawImage(tiles[i].image, px, py); } } + // cache original image _image_cache = new Image(); _image_cache.src = canvas.toDataURL(); + _image_pixelData = ctx.getImageData(0, 0, canvas.width, canvas.height); + _decorated_pixelData = _image_pixelData; if (previousCoreRelativeOffset) { panToPreviousCoreRelativeOffset(); @@ -354,25 +380,20 @@ function drawMap(tiles) { Alpine.store('data').mapLoaded = true; Alpine.store('data').firstTimeLoaded = true; + highlightSelected(); decorateMap(canvas.width, canvas.height); } function highlightSelected() { - let searchobj = { count: 0, boulders: {} }; - - buildHighlightSelection(searchobj); - - const canvas = document.getElementById("mapcanvas"); - const myImage = _global_ctx.getImageData(0, 0, canvas.width, canvas.height); + _decorated_pixelData = getCanvasPixelData(); - if (searchobj.count > 0) { - highlightColors(myImage, searchobj); - } + const filters = buildHighlightSelection(); + if(filters) highlightColors(_decorated_pixelData, filters); if (Alpine.store('data').showMazeHoles) { - findStone(myImage.data, canvas.width); + findHole(_decorated_pixelData.data, _decorated_pixelData.width); } - _global_ctx.putImageData(myImage, 0, 0); + _global_ctx.putImageData(_decorated_pixelData, 0, 0); } function recenterMap() { @@ -380,112 +401,48 @@ function recenterMap() { panToCore(); } -function testPixel(width, myImageData, r, g, b, x, y) { - let i = (y * width + x) * 4; - let r1 = myImageData[i], g1 = myImageData[i + 1], b1 = myImageData[i + 2]; - - return (r == r1 && g == g1 && b == b1); -} - -function highlightPixel(width, myImageData, x, y) { - let i = (y * width + x) * 4; - myImageData[i + 3] = 255; -} - -function testBoulder(width, myImageData, r, g, b, x, y, x1, y1) { - let i = (y * width + x) * 4; - let count = 0; - if (testPixel(width, myImageData, r, g, b, x1, y1)) { - count++; - } - if (testPixel(width, myImageData, r, g, b, x, y1)) { - count++; - } - if (testPixel(width, myImageData, r, g, b, x1, y)) { - count++; - } - if (count == 3) { - highlightPixel(width, myImageData, x1, y1); - highlightPixel(width, myImageData, x, y1); - highlightPixel(width, myImageData, x1, y); - highlightPixel(width, myImageData, x, y); - } else if (myImageData[i + 3] != 255) { - let alpha = Alpine.store('data').tileTransparency / 100 * 255; - myImageData[i + 3] = alpha; +function highlightColors(imageData, {normal, boulders}) +{ + const {data:pixelArray, width} = imageData; + const alpha = Alpine.store('data').tileTransparency / 100 * 255; + const length = pixelArray.length; + + for (let i = 0; i < length; i += 4) { + if (pixelArray[i + 3] === 0) continue; + const r = pixelArray[i], g = pixelArray[i+1], b = pixelArray[i+2]; + pixelArray[i + 3] = normal.isMatch(r,g,b) ? 255 : alpha; + if(boulders.isMatch(r,g,b)) highlightBoulders(imageData, i); } } -function highlightBoulder(myImage, r, g, b, x, y) { - const myImageData = myImage.data; - let count = 0; - - count = 0; - /*if (x > 0 && y > 0) { - testBoulder(myImage.width, myImageData, r, g, b, x, y, x - 1, y - 1); +function checkBoulders(imageData, x, y) +{ + const {data:pixelArray, width, height} = imageData; + function getHexCode(x, y) + { + const i = (y*width + x)*4; + return pixelArray[i] << 16 + pixelArray[i+1] << 8 + pixelArray[i+2]; } - //test bottom left - if (x > 0 && y < myImage.height) { - testBoulder(myImage.width, myImageData, r, g, b, x, y, x - 1, y + 1); - }*/ - //test bottom right - if (x < myImage.width && y < myImage.height) { - testBoulder(myImage.width, myImageData, r, g, b, x, y, x + 1, y + 1); - }/* - if (x < myImage.width && y > 0) { - testBoulder(myImage.width, myImageData, r, g, b, x, y, x + 1, y - 1); - }*/ -} -function highlightColors(myImage, search) { - const myImageData = myImage.data; - let alpha = Alpine.store('data').tileTransparency / 100 * 255; - - for (let i = 0; i < myImageData.length; i += 4) { - if (myImageData[i + 3] != 0) { //if not transparent - let r = myImageData[i], g = myImageData[i + 1], b = myImageData[i + 2]; - if (search[r] && search[r][g] && search[r][g][b]) { - myImageData[i + 3] = 255; - } else { - myImageData[i + 3] = alpha; - } - } - } - for (let i = 0, p = 0; i < myImageData.length; i += 4, ++p) { - if (myImageData[i + 3] != 0) { //if not transparent - - let x = parseInt(p % myImage.width); - let y = parseInt(p / myImage.width); - let r = myImageData[i], g = myImageData[i + 1], b = myImageData[i + 2]; - if (search.boulders[r] && search.boulders[r][g] && search.boulders[r][g][b]) { - highlightBoulder(myImage, r, g, b, x, y); - } - } - } + if(x-1 >= width || y-1 >= height) return false; + const baseHex = getHexCode(x, y); + if(getHexCode(x+1, y) !== baseHex || getHexCode(x, y+1) !== baseHex || getHexCode(x+1, y+1) !== baseHex) return false; + return true; } -function _highlightColors(myImage, search) { - const myImageData = myImage.data; - let alpha = Math.max(TileSliderInfo.transparency(), 1); - - for (let i = 0; i < myImageData.length; i += 4) { - if (myImageData[i + 3] != 0) { //if not transparent - let r = myImageData[i], g = myImageData[i + 1], b = myImageData[i + 2]; - if (search[r] && search[r][g] && search[r][g][b]) { - myImageData[i + 3] = 255; - } else { - myImageData[i + 3] = alpha; - } - } - } - for (let i = 0, p = 0; i < myImageData.length; i += 4, ++p) { - if (myImageData[i + 3] != 0) { //if not transparent - - let x = parseInt(p % myImage.width); - let y = parseInt(p / myImage.width); - let r = myImageData[i], g = myImageData[i + 1], b = myImageData[i + 2]; - if (search.boulders[r] && search.boulders[r][g] && search.boulders[r][g][b]) { - highlightBoulder(myImage, r, g, b, x, y); - } +function highlightBoulders(imageData, i) +{ + const {data:pixelArray, width, height} = imageData; + const x = (i/4) % width; + const y = Math.floor((i/4) / width); + + if(!checkBoulders(imageData, x, y)) return; + for(let dx=0; dx<2; dx++) + { + for(let dy=0; dy<2; dy++) + { + let index = ((y+dy)*width + x+dx)*4 + 3; + pixelArray[index] = 255 } } } \ No newline at end of file diff --git a/src/js/mazehunter.js b/src/js/mazehunter.js index 7de8f1d..2e519b9 100644 --- a/src/js/mazehunter.js +++ b/src/js/mazehunter.js @@ -1,11 +1,9 @@ +// import { mazeFilters } from "./maxeFilters.js"; +// import { coreLoc } from "./canvastools.js"; + const SEARCH_RADII = { min: 140, min2: 19600, max: 500, max2: 250000 }; const OUTER_SEARCH_RADII = { min: 500, max: 800 } const MAZE_HIGLIGHT = { 1: { r: 255, g: 0, b: 255 }, 2: { r: 0, g: 255, b: 255 }, 3: { r: 0, g: 255, b: 0 } }; -const STONE_FILTER = { count: 0 }; -const CLAY_FILTER = { count: 0 }; - -const WILDERNESS_FILTER = { count: 0 }; -const SUNKENSEA_FILTER = { count: 0 }; const stoneArc = { start: 0, end: 0 }; const wildernessArc = { start: 0, end: 0 }; @@ -13,103 +11,65 @@ const wildernessArc = { start: 0, end: 0 }; const arcticks = 1440.0; const deltaRadians = (Math.PI * 2.0) / arcticks; +let isStoneFound = false; let HIGHEST_STONE = 0; let HIGHEST_WILDERNESS = 0; -function averageAround(i, arr, width) { - let sval = i - width; - let eval = i + width; - let dsum = 0; - let dcount = 0; - for (let i = sval; i <= eval; i++) { - let idx = i % arr.length; - if (idx < 0) idx = arr.length + idx; - dsum += arr[idx]; - dcount++; +function countAngleTiles(angle, {minRadius, maxRadius, positiveFilter, negativeFilter,myImageData, width}) { + let prevX, prevY; + let delta = 0, count = 0; + for(let radius = minRadius; radius < maxRadius; radius++) { + let x = Math.floor(radius * Math.sin(angle)); + let y = Math.floor(radius * Math.cos(angle)); + if(x == prevX && y == prevY) continue; + + [prevX, prevY] = [x, y]; + x += coreLoc.x; + y = coreLoc.y - y; + i = (y * width + x) * 4; + let r = myImageData[i], g = myImageData[i + 1], b = myImageData[i + 2]; + + delta += +(positiveFilter.isMatch(r,g,b)); + delta -= +(negativeFilter.isMatch(r,g,b)); + count++; } - return dsum / dcount; + if(count === 0) return 0; + return delta / count; } function countStoneClay(myImageData, width) { - let prevX, prevY, x, y, i; let deltaStone = []; + const config = { + minRadius:SEARCH_RADII.min, + maxRadius:SEARCH_RADII.max, + positiveFilter:mazeFilters.STONE, + negativeFilter:mazeFilters.CLAY,myImageData, width + }; for (let rad = 0; rad < arcticks; rad++) { let angle = rad * deltaRadians; - let numStone = 0, numClay = 0; - for (let radius = SEARCH_RADII.min; radius < SEARCH_RADII.max; radius++) { - x = parseInt(radius * Math.sin(angle)); - y = parseInt(radius * Math.cos(angle)); - if (x != prevX || y != prevY) { - prevX = x; - prevY = y; - x += coreLoc.x; - y = coreLoc.y - y; - i = (y * width + x) * 4; - let r = myImageData[i], g = myImageData[i + 1], b = myImageData[i + 2]; - if (STONE_FILTER[r] && STONE_FILTER[r][g] && STONE_FILTER[r][g][b]) { - ++numStone; - } else if (CLAY_FILTER[r] && CLAY_FILTER[r][g] && CLAY_FILTER[r][g][b]) { - ++numClay; - } - } - } - deltaStone.push(numStone - numClay); + deltaStone.push( countAngleTiles(angle, config) ); } return deltaStone; } function countWildernessSunkenSea(myImageData, width) { - let prevX, prevY, x, y, i; let deltaWilderness = []; + const config = { + minRadius:OUTER_SEARCH_RADII.min, + maxRadius:OUTER_SEARCH_RADII.max, + positiveFilter:mazeFilters.WILDERNESS, + negativeFilter:mazeFilters.SEA,myImageData, width + }; for (let rad = 0; rad < arcticks; rad++) { let angle = rad * deltaRadians; - let numWilderness = 0, numSunkenSea = 0; - for (let radius = OUTER_SEARCH_RADII.min; radius < OUTER_SEARCH_RADII.max; radius++) { - x = parseInt(radius * Math.sin(angle)); - y = parseInt(radius * Math.cos(angle)); - if (x != prevX || y != prevY) { - prevX = x; - prevY = y; - x += coreLoc.x; - y = coreLoc.y - y; - i = (y * width + x) * 4; - let r = myImageData[i], g = myImageData[i + 1], b = myImageData[i + 2]; - if (WILDERNESS_FILTER[r] && WILDERNESS_FILTER[r][g] && WILDERNESS_FILTER[r][g][b]) { - ++numWilderness; - } else if (SUNKENSEA_FILTER[r] && SUNKENSEA_FILTER[r][g] && SUNKENSEA_FILTER[r][g][b]) { - ++numSunkenSea; - } - } - } - deltaWilderness.push(numWilderness - numSunkenSea); + deltaWilderness.push( countAngleTiles(angle, config) ); } return deltaWilderness; } -function recordBiomeChange(rad, deltastone) { - if (stoneArc.end === undefined && stoneArc.start === undefined) { - let test = averageAround(rad + arcticks / 4, deltastone, 18); - if (test < 0) { - stoneArc.end = rad * deltaRadians; - stoneArc.endTicks = rad; - } else { - stoneArc.start = rad * deltaRadians; - stoneArc.startTicks = rad; - } - } else if (stoneArc.end === undefined) { - stoneArc.end = rad * deltaRadians; - stoneArc.endTicks = rad; - return true; - } else { - stoneArc.start = rad * deltaRadians; - stoneArc.startTicks = rad; - return true; - } - return false; -} - function findStone(myImageData, width) { let deltaStone = countStoneClay(myImageData, width); + stoneArc.start = stoneArc.end = undefined; //change to: // for every tick, sum the half arc, push it to a new array @@ -139,7 +99,7 @@ function findStone(myImageData, width) { stoneArc.start = maxStone.index * deltaRadians; stoneArc.endTicks = maxClay.index; stoneArc.end = maxClay.index * deltaRadians; - findHole(myImageData, width); + isStoneFound = true; } function findWilderness(myImageData, width) { @@ -148,7 +108,7 @@ function findWilderness(myImageData, width) { //change to: // for every tick, sum the half arc, push it to a new array // find the max value slot in the array, that is the start angle, add half of arcticks and that is the end angle - let countWidth = arcticks / 2; + let countWidth = arcticks / 3; let maxWilderness = { index: -1, count: 0 }, maxSunkenSea = { index: -1, count: 0 }; let tally = 0; for (let rad = 0; rad < arcticks; ++rad) { @@ -178,6 +138,8 @@ function findWilderness(myImageData, width) { function findHole(myImageData, width) { + if(!isStoneFound) findStone(myImageData, width); + let visited = {}; let prevX, prevY, x, y, i; let b = 0; @@ -363,32 +325,4 @@ function isBigEnough(hole, x, y, maze) { } } return true; -} - -function buildStoneFilter() { - for (let tileType of tileColorMap) { - let filterObj = undefined; - - if (tileType.tilesetname == "Stone") { - filterObj = STONE_FILTER; - } else if (tileType.tilesetname == "Clay") { - filterObj = CLAY_FILTER; - } else if (tileType.tilesetname == "Wilderness") { - filterObj = WILDERNESS_FILTER; - } else if (tileType.tilesetname == "SunkenSea") { - filterObj = SUNKENSEA_FILTER; - } - - if (filterObj) { - filterObj.count++; - if (!filterObj[tileType.r]) { - ((filterObj[tileType.r] = {})[tileType.g] = {})[tileType.b] = true; - } else if (!filterObj[tileType.r][tileType.g]) { - (filterObj[tileType.r][tileType.g] = {})[tileType.b] = true; - } else { - filterObj[tileType.r][tileType.g][tileType.b] = true; - } - } - } -} -buildStoneFilter(); \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/menubuilder.js b/src/js/menubuilder.js index 1a42292..229b291 100644 --- a/src/js/menubuilder.js +++ b/src/js/menubuilder.js @@ -63,18 +63,12 @@ function _isBoulder(elem) { } function buildHighlightSelection(search) { - for (let tile of Alpine.store('tilecolormap').visible) { - search.count++; - filterObj = isBoulder(tile) ? search.boulders : search; - - if (!filterObj[tile.r]) { - ((filterObj[tile.r] = {})[tile.g] = {})[tile.b] = true; - } else if (!filterObj[tile.r][tile.g]) { - (filterObj[tile.r][tile.g] = {})[tile.b] = true; - } else { - filterObj[tile.r][tile.g][tile.b] = true; - } - } + const tiles = Alpine.store('tilecolormap').visible; + if(tiles.length === 0) return null; + return { + normal: new TileFilter( tiles.filter(tile=>!isBoulder(tile)) ), + boulders: new TileFilter( tiles.filter(isBoulder) ) + }; } function _buildHighlightSelection(search) { diff --git a/src/js/page.js b/src/js/page.js index 3079a19..b805d7d 100644 --- a/src/js/page.js +++ b/src/js/page.js @@ -115,6 +115,7 @@ function resetMap() { tilelist = []; HIGHEST_STONE = 0; HIGHEST_WILDERNESS = 0; + isStoneFound = false; document.getElementById('showArcs').checked = false; } @@ -156,7 +157,7 @@ function onChangeGridTransparency(event) { redrawMap(); } function onChangeTileTransparency(event) { - redrawMap(); + redrawMapDirty(); } function onChangeShowCustomRing(event) { @@ -176,7 +177,11 @@ function onChangeShowMobGrid(event) { } function onChangeShowMazeHoles(event) { - redrawDebounce(event); + event.target.setAttribute("disabled", "true"); + redrawMapDirty(); + setTimeout(() => { + event.target.removeAttribute("disabled"); + }, 10); } function onChangeShowArcs(event) { @@ -184,10 +189,9 @@ function onChangeShowArcs(event) { event.target.setAttribute("disabled", "true"); if (checked) { - const canvas = document.getElementById("mapcanvas"); - const myImage = _global_ctx.getImageData(0, 0, canvas.width, canvas.height); - findStone(myImage.data, canvas.width); - findWilderness(myImage.data, canvas.width); + const myImage = getCanvasPixelData(); + findStone(myImage.data, myImage.width); + findWilderness(myImage.data, myImage.width); } redrawMap();