diff --git a/canvas/shatter.go b/canvas/shatter.go new file mode 100644 index 0000000..e5ab1a3 --- /dev/null +++ b/canvas/shatter.go @@ -0,0 +1,96 @@ +package canvas + +import "image" + +// Shatter takes in a bounding rectangle, and several rectangles to be +// subtracted from it. It returns a slice of rectangles that tile together to +// make up the difference between them. This is intended to be used for figuring +// out which areas of a container box's background are covered by other boxes so +// it doesn't waste CPU cycles drawing to those areas. +func Shatter ( + glass image.Rectangle, + rocks ...image.Rectangle, +) ( + tiles []image.Rectangle, +) { + // in this function, the metaphor of throwing several rocks at a sheet + // of glass is used to illustrate the concept. + + tiles = []image.Rectangle { glass } + for _, rock := range rocks { + + // check each tile to see if the rock has collided with it + tileLen := len(tiles) + for tileIndex := 0; tileIndex < tileLen; tileIndex ++ { + tile := tiles[tileIndex] + if !rock.Overlaps(tile) { continue } + newTiles, n := shatterOnce(tile, rock) + if n > 0 { + // the tile was shattered into one or more sub + // tiles + tiles[tileIndex] = newTiles[0] + tiles = append(tiles, newTiles[1:n]...) + } else { + // the tile was entirely obscured by the rock + // and must be wholly removed + tiles = remove(tiles, tileIndex) + tileIndex -- + tileLen -- + } + } + } + return +} + +func shatterOnce (glass, rock image.Rectangle) (tiles [4]image.Rectangle, n int) { + rock = rock.Intersect(glass) + + // |'''''''''''| + // | | + // |###|'''| | + // |###|___| | + // | | + // |___________| + if rock.Min.X > glass.Min.X { tiles[n] = image.Rect ( + glass.Min.X, rock.Min.Y, + rock.Min.X, rock.Max.Y, + ); n ++ } + + // |'''''''''''| + // | | + // | |'''|###| + // | |___|###| + // | | + // |___________| + if rock.Max.X < glass.Max.X { tiles[n] = image.Rect ( + rock.Max.X, rock.Min.Y, + glass.Max.X, rock.Max.Y, + ); n ++ } + + // |###########| + // |###########| + // | |'''| | + // | |___| | + // | | + // |___________| + if rock.Min.Y > glass.Min.Y { tiles[n] = image.Rect ( + glass.Min.X, glass.Min.Y, + glass.Max.X, rock.Min.Y, + ); n ++ } + + // |'''''''''''| + // | | + // | |'''| | + // | |___| | + // |###########| + // |###########| + if rock.Max.Y < glass.Max.Y { tiles[n] = image.Rect ( + glass.Min.X, rock.Max.Y, + glass.Max.X, glass.Max.Y, + ); n ++ } + return +} + +func remove[ELEMENT any] (slice []ELEMENT, s int) []ELEMENT { + return append(slice[:s], slice[s + 1:]...) +}