Skip to content

Commit e4f37db

Browse files
committed
level browse betterness
1 parent 19cc646 commit e4f37db

1 file changed

Lines changed: 241 additions & 39 deletions

File tree

frontend/javascript/level-browse.js

Lines changed: 241 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -251,56 +251,258 @@ function calculateAdjacency(tileIdx, tileId, tiles, tileset, w, h) {
251251
const tilesetMap = new Map()
252252
const imgMap = new Map()
253253

254-
async function loadTileset(tilesetPath) {
255-
console.log(tilesetPath)
256-
if (tilesetMap.has(tilesetPath)) return tilesetMap.get(tilesetPath)
257-
258-
const fetchPromise = (async () => {
259-
260-
const res = await fetch(tilesetPath)
261-
const rawJson = await res.json()
262-
const tilesetJson = rawJson.tiles
263-
const tileset = {}
264-
const path = rawJson.path
265-
266-
const promises = tilesetJson.map(async (def) => {
267-
const img = new Image()
268-
img.src = path + def.file
269-
await new Promise(resolve => {
270-
img.onload = resolve
271-
img.onerror = resolve
272-
})
254+
async function loadSpriteSheetTileset(manifest) {
255+
const tileset = []
256+
const raw = await fetch(manifest.spritesheetPath)
257+
const blob = await raw.blob()
258+
259+
const spriteSheet = await new Promise((resolve, reject) => {
260+
const img = new Image()
261+
img.onload = () => resolve(img)
262+
img.onerror = reject
263+
img.src = manifest.spritesheetPath
264+
})
265+
266+
const tileWidth = manifest.tileWidth
267+
const width = spriteSheet.naturalWidth / tileWidth
268+
console.log(`width: ${width}`)
269+
for (const tile of manifest.tiles) {
270+
const dpr = window.devicePixelRatio
271+
const canvas = document.createElement("canvas")
272+
const ctx = canvas.getContext('2d')
273+
canvas.width = width
274+
canvas.height = width
275+
ctx.imageSmoothingEnabled = false
276+
canvas.style.imageRendering = 'pixelated'
277+
278+
ctx.drawImage(spriteSheet, tile.x * width, tile.y * width, width, width, 0, 0, width, width)
279+
280+
const images = []
281+
282+
if (tile.type === "adjacency") {
283+
for (let i = 0; i < 16; i++) {
284+
const subCanvas = document.createElement("canvas")
285+
const subCtx = subCanvas.getContext("2d")
286+
subCtx.setTransform(dpr, 0, 0, dpr, 0, 0)
287+
subCanvas.width = width
288+
subCanvas.height = width
289+
subCtx.imageSmoothingEnabled = false
290+
subCanvas.style.imageRendering = 'pixelated'
291+
292+
let x = tile.x + i
293+
let y = tile.y
294+
295+
if (x >= tileWidth) {
296+
y = Math.floor(x / tileWidth) + y
297+
x = x % tileWidth
298+
}
299+
subCtx.drawImage(spriteSheet, x * width, y * width, width, width, 0, 0, width, width)
300+
images.push(subCanvas)
301+
}
302+
} else if (tile.type === "rotation") {
303+
for (let i = 0; i < 4; i++) {
304+
const subCanvas = document.createElement("canvas")
305+
const subCtx = subCanvas.getContext("2d")
306+
subCtx.setTransform(dpr, 0, 0, dpr, 0, 0)
307+
subCanvas.width = width
308+
subCanvas.height = width
309+
subCtx.imageSmoothingEnabled = false
310+
subCanvas.style.imageRendering = 'pixelated'
311+
312+
let x = tile.x + i
313+
let y = tile.y
314+
315+
if (x >= tileWidth) {
316+
y = Math.floor(x / tileWidth) + y
317+
x = x % tileWidth
318+
}
319+
subCtx.drawImage(spriteSheet, x * width, y * width, width, width, 0, 0, width, width)
320+
images.push(subCanvas)
321+
}
322+
}
323+
324+
let minimapColor = 'rgba(0, 0, 0, 0)'
325+
try {
326+
const imgData = ctx.getImageData(0, 0, width, width).data
327+
const colorCounts = {}
328+
let maxCount = 0
329+
for (let i = 0; i < imgData.length; i += 4) {
330+
const r = imgData[i]
331+
const g = imgData[i + 1]
332+
const b = imgData[i + 2]
333+
const a = imgData[i + 3]
334+
335+
if (a < 128) continue
336+
const rgb = `rgb(${r}, ${g}, ${b})`
337+
colorCounts[rgb] = (colorCounts[rgb] || 0) + 1
338+
339+
if (colorCounts[rgb] > maxCount) {
340+
maxCount = colorCounts[rgb]
341+
minimapColor = rgb
342+
}
343+
}
344+
} catch (e) {
345+
console.warn("could not calculate minimap color", e)
346+
}
273347

274-
tileset[def.id] = { ...def, triggerAdjacency: def.triggerAdjacency, image: img, images: [] }
348+
const tileObject = {
349+
...tile,
350+
minimapColor: minimapColor,
351+
image: canvas,
352+
}
353+
if (images.length > 0) {
354+
tileObject.images = images
355+
}
356+
tileset.push(tileObject)
357+
}
358+
console.log(tileset)
359+
return tileset
360+
}
275361

276-
if (def.type == "adjacency" || def.type == "rotation") {
277-
const w = img.naturalHeight
278-
if (w > 0) {
279-
const count = Math.floor(img.naturalWidth / w)
280-
for (let i = 0; i < count; i++) {
362+
export function splitStripImages(tileset) {
363+
// split strip images
364+
const newTileset = []
365+
tileset.forEach(tile => {
366+
if (!tile) return
367+
if (tile.images) {
368+
newTileset[tile.id] = tile
369+
return
370+
}
371+
if (tile.type === 'adjacency' && tile.image) {
372+
// split the strip into different pieces here
373+
const h = tile.image.naturalHeight
374+
const w = tile.image.naturalWidth
375+
const sublist = []
376+
for (let i = 0; i < 16; i++) {
377+
const c = document.createElement('canvas')
378+
c.width = h
379+
c.height = h
380+
const ctx = c.getContext('2d')
381+
ctx.drawImage(tile.image, i * h, 0, h, h, 0, 0, h, h)
382+
383+
sublist.push(c)
384+
}
385+
newTileset[tile.id] = { ...tile, images: sublist }
386+
} else if (tile.type == 'rotation') {
387+
const h = tile.image.naturalHeight
388+
const w = tile.image.naturalWidth
389+
const sublist = []
390+
if (w == h * 4) {
391+
for (let i = 0; i < 4; i++) {
392+
const c = document.createElement('canvas')
393+
c.width = h
394+
c.height = h
395+
const ctx = c.getContext('2d')
396+
ctx.drawImage(tile.image, i * h, 0, h, h, 0, 0, h, h)
397+
sublist.push(c)
398+
}
399+
newTileset[tile.id] = { ...tile, images: sublist }
400+
} else if (w == h * 8) {
401+
for (let i = 0; i < 8; i++) {
402+
const c = document.createElement('canvas')
403+
c.width = h
404+
c.height = h
405+
const ctx = c.getContext('2d')
406+
ctx.drawImage(tile.image, i * h, 0, h, h, 0, 0, h, h)
407+
sublist.push(c)
408+
}
409+
newTileset[tile.id] = { ...tile, images: sublist }
410+
}
411+
} else {
412+
newTileset[tile.id] = tile
413+
}
414+
})
415+
return newTileset
416+
}
417+
418+
export async function loadTileset(manifestPath) {
419+
if (manifestPath === "/assets/medium-spritesheet.json") manifestPath = "/assets/medium.json"
420+
if (tilesetMap.has(manifestPath)) {
421+
const tileset = tilesetMap.get(manifestPath)
422+
return tileset
423+
}
424+
425+
const fetchPromise = fetch(manifestPath)
426+
.then(response => response.json())
427+
.then(async (manifest) => {
428+
if (manifest.type == "spritesheet") {
429+
const tileset = await loadSpriteSheetTileset(manifest)
430+
431+
const characterImage = await new Promise((resolve) => {
432+
const img = new Image()
433+
const prefix = manifest.path + "/"
434+
img.onload = () => resolve(img)
435+
img.onerror = (e) => {
436+
console.error(`failed to load character image from: ${srcPath}`, e)
437+
resolve(null)
438+
}
439+
img.src = prefix + manifest.characterFile
440+
})
441+
442+
tilesetMap.set(manifestPath, tileset)
443+
console.log(characterImage)
444+
return tileset
445+
}
446+
447+
let loadedCount = 0
448+
const totalCount = manifest.tiles.length + 1
449+
450+
function updateProgress() {
451+
loadedCount++
452+
window.dispatchEvent(new CustomEvent('loading:progress', {
453+
detail: { loaded: loadedCount, total: totalCount }
454+
}))
455+
}
456+
457+
const promises = manifest.tiles.map(tileData => {
458+
459+
if (!tileData.file) {
460+
updateProgress()
461+
return Promise.resolve(tileData)
462+
}
463+
return new Promise((resolve, reject) => {
464+
const img = new Image()
465+
img.src = manifest.path + tileData.file
466+
img.onload = () => {
281467
const canvas = document.createElement('canvas')
282-
canvas.width = w
283-
canvas.height = w
284468
const ctx = canvas.getContext('2d')
285-
ctx.drawImage(img, i * w, 0, w, w, 0, 0, w, w)
469+
canvas.width = img.height || 1
470+
canvas.height = img.height || 1
471+
ctx.drawImage(img, 0, 0)
286472

287-
const sliceImg = new Image()
288-
sliceImg.src = canvas.toDataURL()
289-
tileset[def.id].images[i] = sliceImg
473+
updateProgress()
474+
resolve({ ...tileData, image: img })
290475
}
291-
}
292-
}
476+
img.onerror = (e) => {
477+
updateProgress()
478+
reject(e)
479+
}
480+
})
481+
})
482+
483+
return Promise.all(promises)
484+
.then((items) => {
485+
const tileset = []
486+
items.forEach(item => {
487+
tileset[item.id] = item
488+
})
489+
490+
tilesetMap.set(manifestPath, tileset)
491+
return tileset
492+
})
493+
293494
})
294-
await Promise.all(promises)
295-
return tileset
296-
})();
297-
tilesetMap.set(tilesetPath, fetchPromise)
298-
console.log(tilesetMap.has(tilesetPath))
495+
496+
tilesetMap.set(manifestPath, fetchPromise)
497+
299498
return fetchPromise
499+
300500
}
301501

502+
302503
export async function renderLevelPreview(canvas, levelData) {
303504
let tileset = await loadTileset(levelData.data.tilesetPath)
505+
tileset = splitStripImages(tileset)
304506
tileset = Object.values(tileset)
305507
if (!canvas || !levelData) return
306508

@@ -324,7 +526,7 @@ export async function renderLevelPreview(canvas, levelData) {
324526

325527
const rotationData = decodeRLE(levelData.data.layers[1] ? levelData.data.layers[1].data : [])
326528

327-
const spawnId = tileset.find(f => f.mechanics && f.mechanics.includes("spawn")).id
529+
const spawnId = tileset.find(f => f.mechanics && f.mechanics.includes("spawn"))?.id
328530
const spawnIdx = decoded.findIndex(f => f == spawnId)
329531

330532
let spawnX = 0

0 commit comments

Comments
 (0)