From 924fb4174199c08cb6091666873a61f2ae2c1231 Mon Sep 17 00:00:00 2001 From: HF Date: Thu, 22 Jun 2023 20:17:41 +0200 Subject: [PATCH] updating drawOcean.js script and instructions --- utils/ocean-tiles/README.md | 13 +-- utils/ocean-tiles/createOceanTiles.js | 81 ------------- utils/ocean-tiles/drawOcean.js | 162 ++++++++++++++++++++++---- 3 files changed, 145 insertions(+), 111 deletions(-) delete mode 100644 utils/ocean-tiles/createOceanTiles.js diff --git a/utils/ocean-tiles/README.md b/utils/ocean-tiles/README.md index c8236ff2..c00a1182 100644 --- a/utils/ocean-tiles/README.md +++ b/utils/ocean-tiles/README.md @@ -1,6 +1,7 @@ # ocean tiles -In order to have the ocean and land on the canvas, or any other background pic, we have to create tiles that we can later upload to the canvas with drawOcean.js. -Those are the commands to create tiles in subfolders: +In order to have the ocean and land on the canvas, we have to scale up the ocean.png, create tiles and then upload them to the canvas with drawOcean.js. + +Execute those shell commands in this folder (utils/ocean-tiles). Imagemagick needs to be installed. - create folder for tiles: @@ -33,12 +34,8 @@ for i in {0..31}; do mkdir $i; done ``` for file in ./ocean_tiles*.png; do NUM=`echo $file | sed -e 's/.*ocean_tiles//' -e 's/.png//'`; Y=$(expr $NUM / 32); X=$(expr $NUM % 32); newfile="$X/$Y.png"; mv $file $newfile; done ``` - -- to remove the subfolders again +- Draws the 2048x2048 tiles from the ./ocean directory on the canvas. Uses localhost:6379 as redis url, if you need a different one, edit it in the drawOcean.js file. ``` -for i in {0..31}; do rm -r $i; done +npm run babel-node ./utils/ocean-tiles/drawOcean.js ``` - -# createOceanTiles.js -createOceanTiles is splitting the generated ocean tiles into 256x256 tiles, skipping the ones that are empty (aka all ocean). diff --git a/utils/ocean-tiles/createOceanTiles.js b/utils/ocean-tiles/createOceanTiles.js deleted file mode 100644 index 458b978e..00000000 --- a/utils/ocean-tiles/createOceanTiles.js +++ /dev/null @@ -1,81 +0,0 @@ -/* @flow - * this script takes black/withe tiles and sets their colors on the canvas - * its used to set the land area on the planet. - * - * The tiles it uses are explained in 3dmodels/ocean-tiles - * - * run this script with --expose-gc or you run out of RAM - */ - - -import sharp from 'sharp'; -import fs from 'fs'; - -const CANVAS_SIZE = 256 * 256; -const CANVAS_MIN_XY = -(CANVAS_SIZE / 2); -const CANVAS_MAX_XY = (CANVAS_SIZE / 2) - 1; - -const TILEFOLDER = './ocean'; -const TILE_SIZE = 2048; -const SMALL_TILE_SIZE = 256; - - -async function createTiles() { - const targetfolder = './tmpfolder'; - if (!fs.existsSync(targetfolder)) { - fs.mkdirSync(targetfolder); - } - - for (let dx = 0; dx < CANVAS_SIZE / SMALL_TILE_SIZE; dx += 1) { - const dir = `${targetfolder}/${dx}`; - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - } - - for (let ty = 0; ty < CANVAS_SIZE / TILE_SIZE; ty += 1) { - for (let tx = 0; tx < CANVAS_SIZE / TILE_SIZE; tx += 1) { - const [ x, y ] = [tx, ty].map(z => z * TILE_SIZE / SMALL_TILE_SIZE); - const filename = `${TILEFOLDER}/${tx}/${ty}.png`; - console.log(`Checking tile ${filename}`); - if (!fs.existsSync(filename)) continue; - let tile = await sharp(filename).ensureAlpha().raw().toBuffer(); - tile = new Uint32Array(tile.buffer); - for (let dxc = 0; dxc < TILE_SIZE / SMALL_TILE_SIZE; dxc += 1) { - for (let dyc = 0; dyc < TILE_SIZE / SMALL_TILE_SIZE; dyc += 1) { - const buffer = new Uint32Array(SMALL_TILE_SIZE * SMALL_TILE_SIZE); - let boff = (dxc + dyc * TILE_SIZE) * SMALL_TILE_SIZE; - let soff = 0; - let exists = false; - for (let column = 0; column < SMALL_TILE_SIZE; column += 1) { - for (let row = 0; row < SMALL_TILE_SIZE; row += 1) { - const mask = tile[boff++] & 0x00FFFFFF; - if (!mask && !exists) { - exists = true; - } - buffer[soff++] = (mask) ? 0xFFFFE3CA : 0xFFFFFFFF; - } - boff += TILE_SIZE - SMALL_TILE_SIZE; - } - if (exists) { - const filename = `${targetfolder}/${tx * TILE_SIZE / SMALL_TILE_SIZE + dxc}/${ty * TILE_SIZE / SMALL_TILE_SIZE + dyc}.png` - await sharp( - Buffer.from( - buffer.buffer, - ), { - raw: { - width: SMALL_TILE_SIZE, - height: SMALL_TILE_SIZE, - channels: 4, - }, - }, - ).toFile(filename); - } - } - } - tile = null; - } - } -} - -createTiles(); diff --git a/utils/ocean-tiles/drawOcean.js b/utils/ocean-tiles/drawOcean.js index be248bfc..ead21af4 100644 --- a/utils/ocean-tiles/drawOcean.js +++ b/utils/ocean-tiles/drawOcean.js @@ -1,39 +1,157 @@ -/* @flow - * this script takes black/withe tiles and sets their colors on the canvas +/* + * this script takes black/whtie tiles and sets their colors on the canvas * its used to set the land area on the planet. - * - * The tiles it uses are explained in 3dmodels/ocean-tiles - * - * run this script with --expose-gc or you run out of RAM */ -import { imagemask2Canvas } from '../../src/core/Image'; +import { createClient, commandOptions } from 'redis'; import sharp from 'sharp'; import fs from 'fs'; +import canvases from '../../src/canvases.json'; +import { getChunkOfPixel } from '../../src/core/utils'; + +// ----------------------------------------------------------------------------- const CANVAS_SIZE = 256 * 256; +const TILE_SIZE = 256; +const TILEFOLDER = './utils/ocean-tiles/ocean'; +const IMG_TILE_SIZE = 2048; +// change redis URL here if you need a different one +const redis = createClient({ url: "redis://localhost:6379" }); + +// ----------------------------------------------------------------------------- + const CANVAS_MIN_XY = -(CANVAS_SIZE / 2); const CANVAS_MAX_XY = (CANVAS_SIZE / 2) - 1; -const TILEFOLDER = './ocean'; -const TILE_SIZE = 2048; +/** + * get Chunk from redis, pad if neccessary + */ +async function getChunk( + canvasId, + i, + j, + padding = null, +) { + const key = `ch:${canvasId}:${i}:${j}`; + let chunk = await redis.get( + commandOptions({ returnBuffers: true }), + key, + ); + if (padding > 0 && chunk && chunk.length < padding) { + const pad = Buffer.alloc(padding - chunk.length); + chunk = Buffer.concat([chunk, pad]); + } + return chunk; +} +/** + * Load imagemask from ABGR buffer and execute filter function + * for each black pixel + */ +async function imagemask2Canvas( + canvasId, + x, + y, + data, + width, + height, + filter, +) { + console.info( + `Loading mask with size ${width} / ${height} to ${x} / ${y} to the canvas`, + ); + const expectedLength = TILE_SIZE ** 2; + const canvas = canvases[canvasId]; + const canvasMinXY = -(canvas.size / 2); -async function applyMasks() { - for (let ty = 0; ty < CANVAS_SIZE / TILE_SIZE; ty += 1) { - for (let tx = 0; tx < CANVAS_SIZE / TILE_SIZE; tx += 1) { - const [ x, y ] = [tx, ty].map(z => z * TILE_SIZE + CANVAS_MIN_XY); - const filename = `${TILEFOLDER}/${tx}/${ty}.png`; - console.log(`Checking tile ${filename}`); - if (!fs.existsSync(filename)) continue; - let tile = await sharp(filename).removeAlpha().raw().toBuffer(); - await imagemask2Canvas(0, x, y, tile, TILE_SIZE, TILE_SIZE, (clr) => { - return 1; //set color to index 1 -> land - }); - tile = null; + const imageData = new Uint8Array(data.buffer); + + const [ucx, ucy] = getChunkOfPixel(canvas.size, x, y); + const [lcx, lcy] = getChunkOfPixel(canvas.size, x + width, y + height); + + console.info(`Loading to chunks from ${ucx} / ${ucy} to ${lcx} / ${lcy} ...`); + for (let cx = ucx; cx <= lcx; cx += 1) { + for (let cy = ucy; cy <= lcy; cy += 1) { + let chunk = null; + try { + chunk = await getChunk(canvasId, cx, cy, expectedLength); + } catch (error) { + console.error( + // eslint-disable-next-line max-len + `Could not load chunk ch:${canvasId}:${cx}:${cy} for imagemask-load: ${error.message}`, + ); + } + chunk = (chunk && chunk.length) + ? new Uint8Array(chunk) + : new Uint8Array(TILE_SIZE * TILE_SIZE); + // offset of chunk in image + const cOffX = cx * TILE_SIZE + canvasMinXY - x; + const cOffY = cy * TILE_SIZE + canvasMinXY - y; + let cOff = 0; + let pxlCnt = 0; + for (let py = 0; py < TILE_SIZE; py += 1) { + for (let px = 0; px < TILE_SIZE; px += 1) { + const clrX = cOffX + px; + const clrY = cOffY + py; + if (clrX >= 0 && clrY >= 0 && clrX < width && clrY < height) { + let offset = (clrX + clrY * width) * 3; + if (!imageData[offset++] + && !imageData[offset++] + && !imageData[offset] + ) { + chunk[cOff] = filter(); + pxlCnt += 1; + } + } + cOff += 1; + } + } + if (pxlCnt) { + const key = `ch:${canvasId}:${cx}:${cy}`; + const ret = await redis.set(key, Buffer.from(chunk.buffer)); + if (ret) { + console.info(`Loaded ${pxlCnt} pixels into chunk ${cx}, ${cy}.`); + } + } + chunk = null; } - global.gc(); + } + console.info('Imagemask loading done.'); +} + +/** + * Load black/white tiles from TILEFOLDER onto canvas + */ +async function applyMasks() { + await redis.connect(); + try { + for (let ty = 0; ty < CANVAS_SIZE / IMG_TILE_SIZE; ty += 1) { + for (let tx = 0; tx < CANVAS_SIZE / IMG_TILE_SIZE; tx += 1) { + const [ x, y ] = [tx, ty].map(z => z * IMG_TILE_SIZE + CANVAS_MIN_XY); + const filename = `${TILEFOLDER}/${tx}/${ty}.png`; + console.log(`Checking tile ${filename}`); + if (!fs.existsSync(filename)) { + console.log(`Not Found ${filename}`); + continue; + } + let tile = await sharp(filename).removeAlpha().raw().toBuffer(); + // eslint-disable-next-line max-len + await imagemask2Canvas(0, x, y, tile, IMG_TILE_SIZE, IMG_TILE_SIZE, () => { + return 1; //set color to index 1 -> land + }); + tile = null; + } + if (global?.gc) { + global.gc(); + } + } + await redis.quit(); + process.exit(0); + } catch (err) { + console.error(err); + await redis.quit(); + process.exit(1); } }