mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
examples/raycasting: Refactoring
This commit is contained in:
parent
d724e17032
commit
6906ea1c4f
@ -36,6 +36,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
screenWidth = 240
|
screenWidth = 240
|
||||||
screenHeight = 240
|
screenHeight = 240
|
||||||
|
padding = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -64,16 +65,31 @@ func init() {
|
|||||||
triangleImage.Fill(color.White)
|
triangleImage.Fill(color.White)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Line struct {
|
type line struct {
|
||||||
X1, Y1, X2, Y2 float64
|
X1, Y1, X2, Y2 float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Line) angle() float64 {
|
func (l *line) angle() float64 {
|
||||||
return math.Atan2(l.Y2-l.Y1, l.X2-l.X1)
|
return math.Atan2(l.Y2-l.Y1, l.X2-l.X1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRay(x, y, length, angle float64) Line {
|
type object struct {
|
||||||
return Line{
|
walls []line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o object) points() [][2]float64 {
|
||||||
|
// Get one of the endpoints for all segments,
|
||||||
|
// + the startpoint of the first one, for non-closed paths
|
||||||
|
var points [][2]float64
|
||||||
|
for _, wall := range o.walls {
|
||||||
|
points = append(points, [2]float64{wall.X2, wall.Y2})
|
||||||
|
}
|
||||||
|
points = append(points, [2]float64{o.walls[0].X1, o.walls[0].Y1})
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRay(x, y, length, angle float64) line {
|
||||||
|
return line{
|
||||||
X1: x,
|
X1: x,
|
||||||
Y1: y,
|
Y1: y,
|
||||||
X2: x + length*math.Cos(angle),
|
X2: x + length*math.Cos(angle),
|
||||||
@ -82,49 +98,40 @@ func newRay(x, y, length, angle float64) Line {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// intersection calculates the intersection of given two lines.
|
// intersection calculates the intersection of given two lines.
|
||||||
func intersection(l1, l2 Line) (float64, float64, error) {
|
func intersection(l1, l2 line) (float64, float64, bool) {
|
||||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
|
||||||
denom := (l1.X1-l1.X2)*(l2.Y1-l2.Y2) - (l1.Y1-l1.Y2)*(l2.X1-l2.X2)
|
denom := (l1.X1-l1.X2)*(l2.Y1-l2.Y2) - (l1.Y1-l1.Y2)*(l2.X1-l2.X2)
|
||||||
tNum := (l1.X1-l2.X1)*(l2.Y1-l2.Y2) - (l1.Y1-l2.Y1)*(l2.X1-l2.X2)
|
tNum := (l1.X1-l2.X1)*(l2.Y1-l2.Y2) - (l1.Y1-l2.Y1)*(l2.X1-l2.X2)
|
||||||
uNum := -((l1.X1-l1.X2)*(l1.Y1-l2.Y1) - (l1.Y1-l1.Y2)*(l1.X1-l2.X1))
|
uNum := -((l1.X1-l1.X2)*(l1.Y1-l2.Y1) - (l1.Y1-l1.Y2)*(l1.X1-l2.X1))
|
||||||
|
|
||||||
if denom == 0 {
|
if denom == 0 {
|
||||||
return 0, 0, errors.New("lines parallel or coincident")
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
t := tNum / denom
|
t := tNum / denom
|
||||||
if t > 1 || t < 0 {
|
if t > 1 || t < 0 {
|
||||||
return 0, 0, errors.New("lines intersect, segments do not")
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
u := uNum / denom
|
u := uNum / denom
|
||||||
if u > 1 || u < 0 {
|
if u > 1 || u < 0 {
|
||||||
return 0, 0, errors.New("lines intersect, segments do not")
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
x := l1.X1 + t*(l1.X2-l1.X1)
|
x := l1.X1 + t*(l1.X2-l1.X1)
|
||||||
y := l1.Y1 + t*(l1.Y2-l1.Y1)
|
y := l1.Y1 + t*(l1.Y2-l1.Y1)
|
||||||
return x, y, nil
|
return x, y, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// rayCasting returns a slice of Line originating from point cx, cy and intersecting with objects
|
// rayCasting returns a slice of line originating from point cx, cy and intersecting with objects
|
||||||
func rayCasting(cx, cy float64, objects [][]Line) []Line {
|
func rayCasting(cx, cy float64, objects []object) []line {
|
||||||
var rays []Line
|
|
||||||
|
|
||||||
const rayLength = 1000 // something large enough to reach all objects
|
const rayLength = 1000 // something large enough to reach all objects
|
||||||
|
|
||||||
|
var rays []line
|
||||||
for _, obj := range objects {
|
for _, obj := range objects {
|
||||||
|
|
||||||
// Get one of the endpoints for all segments,
|
|
||||||
// + the startpoint of the first one, for non-closed paths
|
|
||||||
var objPoints [][2]float64
|
|
||||||
for _, wall := range obj {
|
|
||||||
objPoints = append(objPoints, [2]float64{wall.X2, wall.Y2})
|
|
||||||
}
|
|
||||||
objPoints = append(objPoints, [2]float64{obj[0].X1, obj[0].Y1})
|
|
||||||
|
|
||||||
// Cast two rays per point
|
// Cast two rays per point
|
||||||
for _, p := range objPoints {
|
for _, p := range obj.points() {
|
||||||
l := Line{cx, cy, p[0], p[1]}
|
l := line{cx, cy, p[0], p[1]}
|
||||||
angle := l.angle()
|
angle := l.angle()
|
||||||
|
|
||||||
for _, offset := range []float64{-0.005, 0.005} {
|
for _, offset := range []float64{-0.005, 0.005} {
|
||||||
@ -133,8 +140,8 @@ func rayCasting(cx, cy float64, objects [][]Line) []Line {
|
|||||||
|
|
||||||
// Unpack all objects
|
// Unpack all objects
|
||||||
for _, o := range objects {
|
for _, o := range objects {
|
||||||
for _, wall := range o {
|
for _, wall := range o.walls {
|
||||||
if px, py, err := intersection(ray, wall); err == nil {
|
if px, py, ok := intersection(ray, wall); ok {
|
||||||
points = append(points, [2]float64{px, py})
|
points = append(points, [2]float64{px, py})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,7 +149,7 @@ func rayCasting(cx, cy float64, objects [][]Line) []Line {
|
|||||||
|
|
||||||
// Find the point closest to start of ray
|
// Find the point closest to start of ray
|
||||||
min := math.Inf(1)
|
min := math.Inf(1)
|
||||||
var minI = -1
|
minI := -1
|
||||||
for i, p := range points {
|
for i, p := range points {
|
||||||
d2 := (cx-p[0])*(cx-p[0]) + (cy-p[1])*(cy-p[1])
|
d2 := (cx-p[0])*(cx-p[0]) + (cy-p[1])*(cy-p[1])
|
||||||
if d2 < min {
|
if d2 < min {
|
||||||
@ -150,7 +157,7 @@ func rayCasting(cx, cy float64, objects [][]Line) []Line {
|
|||||||
minI = i
|
minI = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rays = append(rays, Line{cx, cy, points[minI][0], points[minI][1]})
|
rays = append(rays, line{cx, cy, points[minI][0], points[minI][1]})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,23 +169,6 @@ func rayCasting(cx, cy float64, objects [][]Line) []Line {
|
|||||||
return rays
|
return rays
|
||||||
}
|
}
|
||||||
|
|
||||||
func vertices(x1, y1, x2, y2, x3, y3 float64) []ebiten.Vertex {
|
|
||||||
return []ebiten.Vertex{
|
|
||||||
{float32(x1), float32(y1), 0, 0, 1, 1, 1, 1},
|
|
||||||
{float32(x2), float32(y2), 0, 0, 1, 1, 1, 1},
|
|
||||||
{float32(x3), float32(y3), 0, 0, 1, 1, 1, 1},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rect(x, y, w, h float64) []Line {
|
|
||||||
return []Line{
|
|
||||||
{x, y, x, y + h},
|
|
||||||
{x, y + h, x + w, y + h},
|
|
||||||
{x + w, y + h, x + w, y},
|
|
||||||
{x + w, y, x, y},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMovement() {
|
func handleMovement() {
|
||||||
if ebiten.IsKeyPressed(ebiten.KeyD) || ebiten.IsKeyPressed(ebiten.KeyRight) {
|
if ebiten.IsKeyPressed(ebiten.KeyD) || ebiten.IsKeyPressed(ebiten.KeyRight) {
|
||||||
px += 4
|
px += 4
|
||||||
@ -214,6 +204,14 @@ func handleMovement() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rayVertices(x1, y1, x2, y2, x3, y3 float64) []ebiten.Vertex {
|
||||||
|
return []ebiten.Vertex{
|
||||||
|
{float32(x1), float32(y1), 0, 0, 1, 1, 1, 1},
|
||||||
|
{float32(x2), float32(y2), 0, 0, 1, 1, 1, 1},
|
||||||
|
{float32(x3), float32(y3), 0, 0, 1, 1, 1, 1},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(screen *ebiten.Image) error {
|
func update(screen *ebiten.Image) error {
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||||
return errors.New("game ended by player")
|
return errors.New("game ended by player")
|
||||||
@ -231,23 +229,22 @@ func update(screen *ebiten.Image) error {
|
|||||||
|
|
||||||
// Reset the shadowImage
|
// Reset the shadowImage
|
||||||
shadowImage.Fill(color.Black)
|
shadowImage.Fill(color.Black)
|
||||||
rays := rayCasting(px, py, objects)
|
rays := rayCasting(float64(px), float64(py), objects)
|
||||||
|
|
||||||
// Subtract ray triangles from shadow
|
// Subtract ray triangles from shadow
|
||||||
opt := &ebiten.DrawTrianglesOptions{}
|
opt := &ebiten.DrawTrianglesOptions{}
|
||||||
opt.Address = ebiten.AddressRepeat
|
opt.Address = ebiten.AddressRepeat
|
||||||
opt.CompositeMode = ebiten.CompositeModeSourceOut
|
opt.CompositeMode = ebiten.CompositeModeSourceOut
|
||||||
|
|
||||||
for i, line := range rays {
|
for i, line := range rays {
|
||||||
nextLine := rays[(i+1)%len(rays)]
|
nextLine := rays[(i+1)%len(rays)]
|
||||||
|
|
||||||
// Draw triangle of area between rays
|
// Draw triangle of area between rays
|
||||||
v := vertices(px, py, nextLine.X2, nextLine.Y2, line.X2, line.Y2)
|
v := rayVertices(float64(px), float64(py), nextLine.X2, nextLine.Y2, line.X2, line.Y2)
|
||||||
shadowImage.DrawTriangles(v, []uint16{0, 1, 2}, triangleImage, opt)
|
shadowImage.DrawTriangles(v, []uint16{0, 1, 2}, triangleImage, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
screen.DrawImage(bgImage, &ebiten.DrawImageOptions{})
|
screen.DrawImage(bgImage, nil)
|
||||||
|
|
||||||
if showRays {
|
if showRays {
|
||||||
// Draw rays
|
// Draw rays
|
||||||
@ -262,15 +259,15 @@ func update(screen *ebiten.Image) error {
|
|||||||
screen.DrawImage(shadowImage, op)
|
screen.DrawImage(shadowImage, op)
|
||||||
|
|
||||||
// Draw walls
|
// Draw walls
|
||||||
for _, wall := range objects {
|
for _, obj := range objects {
|
||||||
for _, w := range wall {
|
for _, w := range obj.walls {
|
||||||
ebitenutil.DrawLine(screen, w.X1, w.Y1, w.X2, w.Y2, color.RGBA{255, 0, 0, 255})
|
ebitenutil.DrawLine(screen, w.X1, w.Y1, w.X2, w.Y2, color.RGBA{255, 0, 0, 255})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw player as a rect
|
// Draw player as a rect
|
||||||
ebitenutil.DrawRect(screen, px-2, py-2, 4, 4, color.Black)
|
ebitenutil.DrawRect(screen, float64(px)-2, float64(py)-2, 4, 4, color.Black)
|
||||||
ebitenutil.DrawRect(screen, px-1, py-1, 2, 2, color.RGBA{255, 100, 100, 255})
|
ebitenutil.DrawRect(screen, float64(px)-1, float64(py)-1, 2, 2, color.RGBA{255, 100, 100, 255})
|
||||||
|
|
||||||
if showRays {
|
if showRays {
|
||||||
ebitenutil.DebugPrintAt(screen, "R: hide rays", padding, 0)
|
ebitenutil.DebugPrintAt(screen, "R: hide rays", padding, 0)
|
||||||
@ -285,25 +282,32 @@ func update(screen *ebiten.Image) error {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
showRays bool
|
showRays bool
|
||||||
px, py float64
|
px, py int
|
||||||
objects [][]Line
|
objects []object
|
||||||
)
|
)
|
||||||
|
|
||||||
const padding = 20
|
func rect(x, y, w, h float64) []line {
|
||||||
|
return []line{
|
||||||
|
{x, y, x, y + h},
|
||||||
|
{x, y + h, x + w, y + h},
|
||||||
|
{x + w, y + h, x + w, y},
|
||||||
|
{x + w, y, x, y},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
px = screenWidth / 2
|
px = screenWidth / 2
|
||||||
py = screenHeight / 2
|
py = screenHeight / 2
|
||||||
|
|
||||||
// Add outer walls
|
// Add outer walls
|
||||||
objects = append(objects, rect(padding, padding, screenWidth-2*padding, screenHeight-2*padding))
|
objects = append(objects, object{rect(padding, padding, screenWidth-2*padding, screenHeight-2*padding)})
|
||||||
|
|
||||||
// Angled wall
|
// Angled wall
|
||||||
objects = append(objects, []Line{{50, 110, 100, 150}})
|
objects = append(objects, object{[]line{{50, 110, 100, 150}}})
|
||||||
|
|
||||||
// Rectangles
|
// Rectangles
|
||||||
objects = append(objects, rect(45, 50, 70, 20))
|
objects = append(objects, object{rect(45, 50, 70, 20)})
|
||||||
objects = append(objects, rect(150, 50, 30, 60))
|
objects = append(objects, object{rect(150, 50, 30, 60)})
|
||||||
|
|
||||||
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Ray casting and shadows (Ebiten demo)"); err != nil {
|
if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Ray casting and shadows (Ebiten demo)"); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user