125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
import { createTileGrid, type TileItem } from '../../utils/tile'
|
|
import { getTileSizePx, type CameraState } from '../camera/camera'
|
|
import { type MapScene } from '../renderer/mapRenderer'
|
|
import { type TileStore } from '../tile/tileStore'
|
|
import { type MapLayer, type LayerRenderContext } from './mapLayer'
|
|
|
|
function buildGridKey(scene: MapScene, tileSize: number): string {
|
|
return [
|
|
scene.tileSource,
|
|
scene.zoom,
|
|
scene.centerTileX,
|
|
scene.centerTileY,
|
|
scene.viewportWidth,
|
|
scene.viewportHeight,
|
|
tileSize,
|
|
scene.overdraw,
|
|
].join('|')
|
|
}
|
|
|
|
export class TileLayer implements MapLayer {
|
|
lastVisibleTileCount: number
|
|
lastReadyTileCount: number
|
|
cachedGridKey: string
|
|
cachedTiles: TileItem[]
|
|
|
|
constructor() {
|
|
this.lastVisibleTileCount = 0
|
|
this.lastReadyTileCount = 0
|
|
this.cachedGridKey = ''
|
|
this.cachedTiles = []
|
|
}
|
|
|
|
prepareTiles(scene: MapScene, camera: CameraState, tileStore: TileStore): TileItem[] {
|
|
const tileSize = getTileSizePx(camera)
|
|
if (!tileSize) {
|
|
this.lastVisibleTileCount = 0
|
|
this.lastReadyTileCount = 0
|
|
this.cachedGridKey = ''
|
|
this.cachedTiles = []
|
|
return []
|
|
}
|
|
|
|
const gridKey = buildGridKey(scene, tileSize)
|
|
if (gridKey !== this.cachedGridKey) {
|
|
this.cachedGridKey = gridKey
|
|
this.cachedTiles = createTileGrid({
|
|
urlTemplate: scene.tileSource,
|
|
zoom: scene.zoom,
|
|
centerTileX: scene.centerTileX,
|
|
centerTileY: scene.centerTileY,
|
|
viewportWidth: scene.viewportWidth,
|
|
viewportHeight: scene.viewportHeight,
|
|
tileSize,
|
|
overdraw: scene.overdraw,
|
|
})
|
|
}
|
|
|
|
tileStore.queueVisibleTiles(this.cachedTiles, scene, gridKey)
|
|
this.lastVisibleTileCount = this.cachedTiles.length
|
|
this.lastReadyTileCount = 0
|
|
return this.cachedTiles
|
|
}
|
|
|
|
draw(context: LayerRenderContext): void {
|
|
const { ctx, scene, camera, tileStore } = context
|
|
const tiles = this.prepareTiles(scene, camera, tileStore)
|
|
|
|
for (const tile of tiles) {
|
|
const entry = tileStore.getEntry(tile.url)
|
|
const drawLeft = tile.leftPx + scene.translateX
|
|
const drawTop = tile.topPx + scene.translateY
|
|
|
|
if (entry && entry.status === 'ready' && entry.image) {
|
|
ctx.drawImage(entry.image, drawLeft, drawTop, tile.sizePx, tile.sizePx)
|
|
this.lastReadyTileCount += 1
|
|
continue
|
|
}
|
|
|
|
const parentFallback = tileStore.getParentFallbackSlice(tile, scene)
|
|
let drewFallback = false
|
|
if (parentFallback) {
|
|
ctx.drawImage(
|
|
parentFallback.entry.image,
|
|
parentFallback.sourceX,
|
|
parentFallback.sourceY,
|
|
parentFallback.sourceWidth,
|
|
parentFallback.sourceHeight,
|
|
drawLeft,
|
|
drawTop,
|
|
tile.sizePx,
|
|
tile.sizePx,
|
|
)
|
|
drewFallback = true
|
|
}
|
|
|
|
const childFallback = tileStore.getChildFallback(tile, scene)
|
|
if (childFallback) {
|
|
const cellWidth = tile.sizePx / childFallback.division
|
|
const cellHeight = tile.sizePx / childFallback.division
|
|
for (const child of childFallback.children) {
|
|
const childImageWidth = child.entry.image.width || 256
|
|
const childImageHeight = child.entry.image.height || 256
|
|
ctx.drawImage(
|
|
child.entry.image,
|
|
0,
|
|
0,
|
|
childImageWidth,
|
|
childImageHeight,
|
|
drawLeft + child.offsetX * cellWidth,
|
|
drawTop + child.offsetY * cellHeight,
|
|
cellWidth,
|
|
cellHeight,
|
|
)
|
|
}
|
|
drewFallback = true
|
|
}
|
|
|
|
if (!drewFallback) {
|
|
ctx.fillStyle = entry && entry.status === 'error' ? '#d9b2b2' : '#dbeed4'
|
|
ctx.fillRect(drawLeft, drawTop, tile.sizePx, tile.sizePx)
|
|
}
|
|
}
|
|
}
|
|
}
|