Skip to content
This repository was archived by the owner on Aug 1, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
*.d.ts
*.css
lib
node_modules
lib
52 changes: 30 additions & 22 deletions src/components/TileMap/TileMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ export class TileMap extends React.PureComponent<Props, State> {
padding: 4,
isDraggable: true,
layers: [],
renderMap: renderMap
renderMap: renderMap,
}

private oldState: State
private canvas: HTMLCanvasElement | null
private strikeCanvas: HTMLCanvasElement
private mounted: boolean
private hover: Coord | null
private popupTimeout: number | null
Expand All @@ -61,17 +62,18 @@ export class TileMap extends React.PureComponent<Props, State> {
pan: { x: panX, y: panY },
center: {
x: x == null ? initialX : x,
y: y == null ? initialY : y
y: y == null ? initialY : y,
},
size: zoom * size,
zoom,
popup: null
popup: null,
}
this.state = this.generateState(props, initialState)
this.oldState = this.state
this.hover = null
this.mounted = false
this.canvas = null
this.strikeCanvas = document.createElement('canvas')
this.popupTimeout = null
}

Expand All @@ -86,12 +88,12 @@ export class TileMap extends React.PureComponent<Props, State> {
...nextState,
center: {
x: nextProps.x,
y: nextProps.y
y: nextProps.y,
},
pan: {
x: 0,
y: 0
}
y: 0,
},
}
}

Expand Down Expand Up @@ -122,7 +124,7 @@ export class TileMap extends React.PureComponent<Props, State> {
if (newZoom !== this.props.zoom && newZoom !== this.state.zoom) {
this.setState({
zoom: newZoom,
size: this.props.size * newZoom
size: this.props.size * newZoom,
})
}
}
Expand Down Expand Up @@ -174,7 +176,7 @@ export class TileMap extends React.PureComponent<Props, State> {
center,
pan,
size,
padding
padding,
})
return { ...viewport, pan, zoom, center, size }
}
Expand Down Expand Up @@ -208,18 +210,18 @@ export class TileMap extends React.PureComponent<Props, State> {

const boundaries = {
nw: { x: minX - halfWidth, y: maxY + halfHeight },
se: { x: maxX + halfWidth, y: minY - halfHeight }
se: { x: maxX + halfWidth, y: minY - halfHeight },
}

const viewport = {
nw: {
x: this.state.center.x - halfWidth,
y: this.state.center.y + halfHeight
y: this.state.center.y + halfHeight,
},
se: {
x: this.state.center.x + halfWidth,
y: this.state.center.y - halfHeight
}
y: this.state.center.y - halfHeight,
},
}

if (viewport.nw.x + newPan.x / newSize < boundaries.nw.x) {
Expand All @@ -238,7 +240,7 @@ export class TileMap extends React.PureComponent<Props, State> {
this.setState({
pan: newPan,
zoom: newZoom,
size: newSize
size: newSize,
})
this.renderMap()
this.debouncedUpdateCenter()
Expand All @@ -252,7 +254,7 @@ export class TileMap extends React.PureComponent<Props, State> {

const viewportOffset = {
x: (width - padding - 0.5) / 2 - center.x,
y: (height - padding) / 2 + center.y
y: (height - padding) / 2 + center.y,
}

const coordX = Math.round(panOffset.x - viewportOffset.x)
Expand Down Expand Up @@ -326,7 +328,7 @@ export class TileMap extends React.PureComponent<Props, State> {
if (this.mounted) {
this.setState(
{
popup: { x, y, top, left, visible: true }
popup: { x, y, top, left, visible: true },
},
() => onPopup(this.state.popup!)
)
Expand All @@ -352,8 +354,8 @@ export class TileMap extends React.PureComponent<Props, State> {
{
popup: {
...this.state.popup,
visible: false
}
visible: false,
},
},
() => {
onPopup(this.state.popup!)
Expand All @@ -371,34 +373,40 @@ export class TileMap extends React.PureComponent<Props, State> {
const newPan = { x: panX, y: panY }
const newCenter = {
x: center.x + Math.floor((pan.x - panX) / size),
y: center.y - Math.floor((pan.y - panY) / size)
y: center.y - Math.floor((pan.y - panY) / size),
}

this.setState({
pan: newPan,
center: newCenter
center: newCenter,
})
}

renderMap() {
if (!this.canvas) {
const strikeContext = this.strikeCanvas.getContext('2d')

if (!this.canvas || !strikeContext) {
return
}

const { width, height, layers, renderMap } = this.props
const { nw, se, pan, size, center } = this.state
const { nw, se, pan, size, center, zoom } = this.state
this.strikeCanvas.width = width
this.strikeCanvas.height = height
const ctx = this.canvas.getContext('2d')!

renderMap({
ctx,
strikeCanvasCtx: strikeContext,
width,
height,
size,
pan,
nw,
se,
center,
layers
layers,
zoom,
})
}

Expand Down
2 changes: 2 additions & 0 deletions src/components/TileMap/TileMap.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export type Popup = {

export type MapRenderer = (args: {
ctx: CanvasRenderingContext2D
strikeCanvasCtx: CanvasRenderingContext2D
width: number
height: number
size: number
Expand All @@ -82,4 +83,5 @@ export type MapRenderer = (args: {
se: Coord
center: Coord
layers: Layer[]
zoom: number
}) => void
1 change: 1 addition & 0 deletions src/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export type Tile = {
left?: boolean
topLeft?: boolean
scale?: number
strikethrough?: boolean
}
61 changes: 56 additions & 5 deletions src/render/map.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { renderTile } from './tile'
import {
addClippingContextForTileStrike,
buildStrikethroughPattern,
finishClippingForTileStrike,
initializeContextForTileStrike,
renderTile,
} from './tile'
import { Coord, Layer } from '../lib/common'

export function renderMap(args: {
ctx: CanvasRenderingContext2D
strikeCanvasCtx: CanvasRenderingContext2D
width: number
height: number
size: number
Expand All @@ -11,15 +18,23 @@ export function renderMap(args: {
se: Coord
center: Coord
layers: Layer[]
zoom: number
}) {
const { ctx, width, height, size, pan, nw, se, center, layers } = args

const { ctx, strikeCanvasCtx, width, height, size, pan, nw, se, center, layers } = args
// Creates the strikethrough pattern that will be used to draw the strikethrough in the tiles.
// The scale is set to 1 as scaling the pattern makes it very difficult to be used into a repeatable pattern.
const strikethroughPattern = buildStrikethroughPattern(ctx, 1)
// Sets up the clipping region to be used in the strike process.
let clippingRegion: Path2D
ctx.clearRect(0, 0, width, height)

const halfWidth = width / 2
const halfHeight = height / 2

for (const layer of layers) {
// Initializes the strike canvas and its context
clippingRegion = initializeContextForTileStrike(strikeCanvasCtx, width, height)
let hasStrikeTile = false
for (let x = nw.x; x < se.x; x++) {
for (let y = se.y; y < nw.y; y++) {
const offsetX = (center.x - x) * size + (pan ? pan.x : 0)
Expand All @@ -29,7 +44,7 @@ export function renderMap(args: {
if (!tile) {
continue
}
const { color, top, left, topLeft, scale } = tile
const { color, top, left, topLeft, scale, strikethrough } = tile

const halfSize = scale ? (size * scale) / 2 : size / 2

Expand All @@ -44,9 +59,45 @@ export function renderMap(args: {
left,
top,
topLeft,
scale
scale,
})

if (strikethrough) {
hasStrikeTile = true
// Adds the rectangles figures to the clipping region to later clip everything outside of those regions
addClippingContextForTileStrike({
ctx: strikeCanvasCtx,
region: clippingRegion,
x: halfWidth - offsetX + halfSize,
y: halfHeight - offsetY + halfSize,
size,
padding: size < 7 ? 0.5 : size < 12 ? 1 : size < 18 ? 1.5 : 2,
offset: 1,
left,
top,
topLeft,
scale,
})
}
}
}
if (strikethroughPattern && hasStrikeTile) {
// Applies the pattern and clips the regions to produce the strike pattern over the tiles.
finishClippingForTileStrike(
strikeCanvasCtx,
clippingRegion,
strikethroughPattern,
width,
height
)
}
// Draw the strike pattern
if (
strikeCanvasCtx.canvas.width > 0 &&
strikeCanvasCtx.canvas.height > 0 &&
hasStrikeTile
) {
ctx.drawImage(strikeCanvasCtx.canvas, 0, 0)
}
}
}
Loading