From 93d05750bc2b666f29b0fcca52f2db8434e944c8 Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Sat, 6 Jan 2024 00:03:16 -0500 Subject: [PATCH] Implement test region display Refactor rendering to more consistently work in "habitat-space" where the origin is at the bottom-left and y increases when moving upwards --- neohabitat.js | 84 +++++++++++++++++++++++++++++++++++ region.html | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++ render.js | 90 +++++++++++++++++++++----------------- show.js | 18 ++++---- 4 files changed, 262 insertions(+), 48 deletions(-) create mode 100644 neohabitat.js create mode 100644 region.html diff --git a/neohabitat.js b/neohabitat.js new file mode 100644 index 0000000..bf258ad --- /dev/null +++ b/neohabitat.js @@ -0,0 +1,84 @@ +// adapted from populateModels.js in neohabitat + +const replacements = [ + [/UP/g, '"|"'], + [/DOWN/g, '"}"'], + [/LEFT/g, '"~"'], + [/RIGHT/g, '"\u007f"'], + [/SPACE/g, '" "'], + [/WEST/g, '0'], + [/SOUTH/g, '1'], + [/EAST/g, '2'], + [/NORTH/g, '3'] +]; + +const joinReplacements = { + UP: '|', + DOWN: '}', + LEFT: '~', + RIGHT: '\u007f', + SPACE: ' ', + WEST: '0', + SOUTH: '1', + EAST: '2', + NORTH: '3' +}; + +const replacementJoinRegex = /((([A-Z]+\s?\+\s?)+)([A-Z]+\s?)+)/; +const stringJoinRegex = /(("([^"]|\\")*"\s*\+\s*)+"([^"]|\\")*")/g; + +function templateStringJoins(data) { + if (data.search(/\+/) != -1) { + return data.replace(/(\n)/g, '').replace(stringJoinRegex, + function(origText, offset, string) { + var replacementText = []; + var splitText = origText.split('+'); + for (var textLineId in splitText) { + var trimTextLine = splitText[textLineId].trim(); + var quotesRemoved = trimTextLine.replace(/(^")|("$)/g, ''); + replacementText.push(quotesRemoved); + } + return '"{0}"'.format(replacementText.join('')); + } + ); + } + return data; +} + +function templateConstantJoins(data) { + return data.replace(replacementJoinRegex, function(origText, offset, string) { + var replacementText = []; + var splitText = origText.split('+'); + for (var habConstId in splitText) { + var trimHabConst = splitText[habConstId].trim(); + if (trimHabConst in joinReplacements) { + replacementText.push(joinReplacements[trimHabConst]); + } + } + return '"{0}"'.format(replacementText.join('')); + }); +} + +function templateHabitatObject(data) { + var templated = templateConstantJoins(data); + for (var replacementId in replacements) { + var replacement = replacements[replacementId]; + var regex = replacement[0]; + var replacementText = replacement[1]; + templated = templated.replace(regex, replacementText); + } + return templateStringJoins(templated); +} + +export function parseHabitatObject(data) { + return JSON.parse(templateHabitatObject(data)) +} + +export function colorsFromOrientation(orientation) { + const colorVal = (orientation & 0x78) >> 3 + if (orientation & 0x80) { + return { wildcard: colorVal } + } else { + return { pattern: colorVal } + } +} diff --git a/region.html b/region.html new file mode 100644 index 0000000..ea32c97 --- /dev/null +++ b/region.html @@ -0,0 +1,118 @@ + + + + + Inhabitor - The Habitat Inspector + + + +

Inhabitor - The Habitat Inspector

+
+
+
+
+ + + + \ No newline at end of file diff --git a/render.js b/render.js index daa992a..b2990e3 100644 --- a/render.js +++ b/render.js @@ -30,9 +30,14 @@ const makeCanvas = (w, h) => { const canvas = document.createElement("canvas") canvas.width = w canvas.height = h + canvas.style.imageRendering = "pixelated" + canvas.style.width = `${w * 3}px` + canvas.style.height = `${h * 3}px` return canvas } +export const canvasForSpace = ({ minX, maxX, minY, maxY }) => makeCanvas((maxX - minX) * 8, maxY - minY) + const defaultColors = { wildcard: 6, skin: 10, @@ -99,28 +104,50 @@ export const celsFromMask = (prop, celMask) => { return cels } +// canvas coordinate spaces have the top-left corner at 0,0, x increasing to the right, y increasing down. +// habitat coordinate spaces have the object origin at 0,0, x increasing to the right, y increasing _up_. +// In addition, 1 unit horizontally in habitat coordinate space corresponds to 8 pixels horizontally in canvas space. +export const translateSpace = ({ minX, maxX, minY, maxY }, dx, dy) => { + return { minX: minX + dx, maxX: maxX + dx, minY: minY + dy, maxY: maxY + dy } +} + +export const compositeSpaces = (spaces) => { + return { minX: Math.min(...spaces.map((f) => f ? f.minX : Math.min())), + maxX: Math.max(...spaces.map((f) => f ? f.maxX : Math.max())), + minY: Math.min(...spaces.map((f) => f ? f.minY : Math.min())), + maxY: Math.max(...spaces.map((f) => f ? f.maxY : Math.max())) } +} + +export const topLeftCanvasOffset = (outerSpace, innerSpace) => { + return [(innerSpace.minX - outerSpace.minX) * 8, outerSpace.maxY - innerSpace.maxY] +} + +export const drawInSpace = (ctx, canvas, ctxSpace, canvasSpace) => { + const [x, y] = topLeftCanvasOffset(ctxSpace, canvasSpace) + ctx.drawImage(canvas, x, y) +} + +// Habitat's coordinate space consistently has y=0 for the bottom, and increasing y means going up export const frameFromCels = (cels, celColors = null, paintOrder = null) => { if (cels.length == 0) { return null } - let minX = Number.POSITIVE_INFINITY - let minY = Number.POSITIVE_INFINITY - let maxX = Number.NEGATIVE_INFINITY - let maxY = Number.NEGATIVE_INFINITY let xRel = 0 let yRel = 0 + let xOrigin = null + let yOrigin = null let layers = [] for (const [icel, cel] of cels.entries()) { if (cel) { + if (xOrigin == null) { + xOrigin = cel.xOffset + yOrigin = cel.yOffset - cel.height + } const x = cel.xOffset + xRel - const y = -(cel.yOffset + yRel) - minX = Math.min(minX, x) - minY = Math.min(minY, y) - maxX = Math.max(maxX, cel.width + x) - maxY = Math.max(maxY, cel.height + y) + const y = cel.yOffset + yRel if (cel.bitmap) { const colors = (Array.isArray(celColors) ? celColors[icel] : celColors) ?? {} - layers.push({ canvas: canvasFromBitmap(cel.bitmap, colors), x, y }) + layers.push({ canvas: canvasFromBitmap(cel.bitmap, colors), minX: x, minY: y - cel.height, maxX: x + cel.width, maxY: y }) } else { layers.push(null) } @@ -139,17 +166,16 @@ export const frameFromCels = (cels, celColors = null, paintOrder = null) => { layers = reordered } - const w = (maxX - minX) * 8 - const h = maxY - minY + const space = compositeSpaces(layers) - const canvas = makeCanvas(w, h) + const canvas = canvasForSpace(space) const ctx = canvas.getContext("2d") for (const layer of layers) { if (layer && layer.canvas) { - ctx.drawImage(layer.canvas, (layer.x - minX) * 8, layer.y - minY) + drawInSpace(ctx, layer.canvas, space, layer) } } - return { canvas: canvas, xOffset: minX * 8, yOffset: minY, w: w, h: h } + return {...translateSpace(space, -xOrigin, -yOrigin), canvas: canvas } } const framesFromAnimation = (animation, frameFromState) => { @@ -252,37 +278,23 @@ export const imageFromCanvas = (canvas) => { } export const animate = (frames) => { - if (frames.length == 0) { - return textNode("") - } else if (frames.length == 1) { - return imageFromCanvas(frames[0].canvas) - } - let minX = Number.POSITIVE_INFINITY - let minY = Number.POSITIVE_INFINITY - let maxX = Number.NEGATIVE_INFINITY - let maxY = Number.NEGATIVE_INFINITY - for (const frame of frames) { - minX = Math.min(minX, frame.xOffset) - minY = Math.min(minY, frame.yOffset) - maxX = Math.max(maxX, frame.xOffset + frame.w) - maxY = Math.max(maxY, frame.yOffset + frame.h) - } + const space = compositeSpaces(frames) - const w = maxX - minX - const h = maxY - minY - const canvas = makeCanvas(w, h) - canvas.style.imageRendering = "pixelated" - canvas.style.width = `${w * 3}px` - canvas.style.height = `${h * 3}px` + if (frames.length == 0) { + return { ...space, element: textNode("") } + } else if (frames.length == 1) { + return { ...space, element: imageFromCanvas(frames[0].canvas) } + } + const canvas = canvasForSpace(space) let iframe = 0 const ctx = canvas.getContext("2d") const nextFrame = () => { const frame = frames[iframe] - ctx.clearRect(0, 0, w, h) - ctx.drawImage(frame.canvas, frame.xOffset - minX, frame.yOffset - minY) + ctx.clearRect(0, 0, canvas.width, canvas.height) + drawInSpace(ctx, frame.canvas, space, frame) iframe = (iframe + 1) % frames.length } nextFrame() setInterval(nextFrame, 250) - return canvas + return { ...space, element: canvas } } diff --git a/show.js b/show.js index cb404d8..72c0553 100644 --- a/show.js +++ b/show.js @@ -43,18 +43,18 @@ export const docBuilder = ({ detailHref, errorContainer }) => { return { linkDetail, showError } } +const showRender = (doc, container, filename, render) => { + if (Array.isArray(render)) { + render.forEach((r) => showRender(doc, container, filename, r)) + } else if (render) { + container.appendChild(doc.linkDetail(render.element, filename)) + } +} + export const showAll = (doc, container, filename, values, f) => { for (const value of values) { try { - let elements = f(value) - if (elements && !Array.isArray(elements)) { - elements = [elements] - } - if (elements) { - for (const element of elements) { - container.appendChild(doc.linkDetail(element, filename)) - } - } + showRender(doc, container, filename, f(value)) } catch (e) { doc.showError(e, filename) }