Implement overlay sprite rendering and update API endpoints for overlays
This commit is contained in:
parent
d8940a1b1d
commit
06e67d8341
6 changed files with 145 additions and 82 deletions
|
|
@ -29,6 +29,7 @@ function ensureDir(dirPath) {
|
|||
}
|
||||
|
||||
const DOWNLOAD_CACHE_ROOT = path.join(TILE_CACHE_ROOT, "downloads");
|
||||
const RENDER_CACHE_VERSION = "v2-overlays-as-sprites";
|
||||
sharp.cache(false);
|
||||
|
||||
function normalizeBuildOptions(options = {}) {
|
||||
|
|
@ -39,7 +40,7 @@ function normalizeBuildOptions(options = {}) {
|
|||
}
|
||||
|
||||
function buildOptionSuffix(options) {
|
||||
return `editor-${options.includeEditor ? "on" : "off"}_roofs-${options.includeRoofs ? "on" : "off"}`;
|
||||
return `${RENDER_CACHE_VERSION}_editor-${options.includeEditor ? "on" : "off"}_roofs-${options.includeRoofs ? "on" : "off"}`;
|
||||
}
|
||||
|
||||
function toHex(value, width = 4) {
|
||||
|
|
@ -189,6 +190,7 @@ function serializeOverlayItem(node, minLeft, minTop, index) {
|
|||
|
||||
return {
|
||||
id: `${index}:${item.source}:${item.shape}:${item.frame}:${item.x}:${item.y}:${item.z}`,
|
||||
index,
|
||||
kind,
|
||||
label: overlayLabel(kind),
|
||||
family: info.family,
|
||||
|
|
@ -238,6 +240,9 @@ function serializeOverlayItem(node, minLeft, minTop, index) {
|
|||
invitem: info.isInvitem,
|
||||
animType: info.animType
|
||||
},
|
||||
presentation: {
|
||||
opacity: kind === "helper" ? 0.5 : info.isTranslucent ? 0.7 : 1
|
||||
},
|
||||
notes: overlayNotes(item, info)
|
||||
};
|
||||
}
|
||||
|
|
@ -410,6 +415,8 @@ export class BuildManager {
|
|||
assets,
|
||||
prepared: [],
|
||||
overlays: [],
|
||||
overlayNodes: [],
|
||||
overlayNodeById: new Map(),
|
||||
minLeft: 0,
|
||||
minTop: 0,
|
||||
width: TILE_SIZE,
|
||||
|
|
@ -462,6 +469,8 @@ export class BuildManager {
|
|||
assets,
|
||||
prepared: [],
|
||||
overlays: [],
|
||||
overlayNodes: [],
|
||||
overlayNodeById: new Map(),
|
||||
minLeft: 0,
|
||||
minTop: 0,
|
||||
width: TILE_SIZE,
|
||||
|
|
@ -503,6 +512,10 @@ export class BuildManager {
|
|||
const overlays = overlayProjection.projected.map((node, index) =>
|
||||
serializeOverlayItem(node, bounds.minLeft, bounds.minTop, index)
|
||||
);
|
||||
const overlayNodeById = new Map();
|
||||
for (let index = 0; index < overlays.length; index += 1) {
|
||||
overlayNodeById.set(overlays[index].id, overlayProjection.projected[index]);
|
||||
}
|
||||
const invalidItemCount = sorted.invalidItemCount + overlayProjection.invalidItemCount;
|
||||
const invalidItems = [...sorted.invalidItems, ...overlayProjection.invalidItems].slice(0, 20);
|
||||
|
||||
|
|
@ -551,6 +564,8 @@ export class BuildManager {
|
|||
assets,
|
||||
prepared: sorted.prepared,
|
||||
overlays,
|
||||
overlayNodes: overlayProjection.projected,
|
||||
overlayNodeById,
|
||||
minLeft: bounds.minLeft,
|
||||
minTop: bounds.minTop,
|
||||
width: bounds.width,
|
||||
|
|
@ -623,6 +638,63 @@ export class BuildManager {
|
|||
};
|
||||
}
|
||||
|
||||
async renderOverlaySprite(jobId, gameId, mapId, overlayId, format = "webp") {
|
||||
const job = this.requireReadyJob(jobId, gameId, mapId);
|
||||
const overlay = job.build.overlays.find((item) => item.id === overlayId);
|
||||
const node = job.build.overlayNodeById.get(overlayId);
|
||||
if (!overlay || !node) {
|
||||
throw new Error("Unknown overlay id");
|
||||
}
|
||||
|
||||
const extension = format === "png" ? "png" : "webp";
|
||||
const spritePath = path.join(
|
||||
TILE_CACHE_ROOT,
|
||||
gameId,
|
||||
`map-${mapId}`,
|
||||
buildOptionSuffix(job.options),
|
||||
"overlays",
|
||||
`${overlay.index}.${extension}`
|
||||
);
|
||||
if (fs.existsSync(spritePath)) {
|
||||
return fs.readFileSync(spritePath);
|
||||
}
|
||||
|
||||
const spriteWidth = Math.max(1, overlay.screen.width);
|
||||
const spriteHeight = Math.max(1, overlay.screen.height);
|
||||
const buffer = rgbaBuffer(spriteWidth, spriteHeight, [0, 0, 0, 0]);
|
||||
blitFrame(
|
||||
buffer,
|
||||
spriteWidth,
|
||||
spriteHeight,
|
||||
0,
|
||||
0,
|
||||
node.frame,
|
||||
node.pixels,
|
||||
job.build.assets.palette,
|
||||
Boolean(node.item.flags & FLAG_FLIPPED)
|
||||
);
|
||||
|
||||
let output;
|
||||
if (format === "png") {
|
||||
output = encodePng(spriteWidth, spriteHeight, buffer);
|
||||
} else {
|
||||
output = await sharp(buffer, {
|
||||
raw: {
|
||||
width: spriteWidth,
|
||||
height: spriteHeight,
|
||||
channels: 4
|
||||
},
|
||||
limitInputPixels: false
|
||||
})
|
||||
.webp({ lossless: true, effort: 4 })
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
ensureDir(path.dirname(spritePath));
|
||||
fs.writeFileSync(spritePath, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
async renderFullMap(jobId, gameId, mapId) {
|
||||
const job = this.requireReadyJob(jobId, gameId, mapId);
|
||||
const outputPath = path.join(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue