Skip to content

Commit

Permalink
Add filled triangles and 3D stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Devine committed Feb 2, 2022
1 parent ae8ff70 commit edf8a61
Show file tree
Hide file tree
Showing 5 changed files with 591 additions and 3 deletions.
61 changes: 59 additions & 2 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"image/color"
"image/png"
"os"
"sort"
"strings"

palette "github.com/pdevine/go-asciisprite/palette"
Expand Down Expand Up @@ -315,6 +316,15 @@ func (s Surface) Blit(t Surface, x, y int) error {

// Draw a line between two points on a Surface
func (s Surface) Line(x0, y0, x1, y1 int, ch rune) error {
points := findPointsInLine(x0, y0, x1, y1)
for _, p := range points {
s.Point(p.X, p.Y, ch)
}
return nil
}

func findPointsInLine(x0, y0, x1, y1 int) []Point {
var points []Point
dx := abs(x1 - x0)
dy := -abs(y1 - y0)
err := dx + dy
Expand All @@ -327,7 +337,7 @@ func (s Surface) Line(x0, y0, x1, y1 int, ch rune) error {
sy = -1
}
for {
s.Point(x0, y0, ch)
points = append(points, Point{x0, y0})
if x0 == x1 && y0 == y1 {
break
}
Expand All @@ -341,7 +351,7 @@ func (s Surface) Line(x0, y0, x1, y1 int, ch rune) error {
y0 += sy
}
}
return nil
return points
}

// Draw a rectangle on a Surface
Expand Down Expand Up @@ -370,6 +380,53 @@ func (s Surface) Point(x, y int, ch rune) {
}
}

// Draw a triangle on a Surface
func (s Surface) Triangle(x0, y0, x1, y1, x2, y2 int, ch rune, fill bool) error {
if fill {
points := []Point{
Point{x0, y0},
Point{x1, y1},
Point{x2, y2},
}

sort.Slice(points, func(i, j int) bool {
return points[i].Y < points[j].Y
})

pMin := points[0]
pMid := points[1]
pMax := points[2]
pl := findPointsInLine(pMin.X, pMin.Y, pMax.X, pMax.Y)

var opl []Point

// don't put in horizontal lines
if pMin.Y == pMid.Y {
opl = append(opl, pMid)
} else {
opl = append(opl, findPointsInLine(pMin.X, pMin.Y, pMid.X, pMid.Y)...)
}
if pMax.Y == pMid.Y {
opl = append(opl, pMid)
} else {
opl = append(opl, findPointsInLine(pMax.X, pMax.Y, pMid.X, pMid.Y)...)
}

for _, p := range pl {
for _, op := range opl {
if p.Y == op.Y {
s.Line(p.X, p.Y, op.X, op.Y, ch)
}
}
}
} else {
s.Line(x0, y0, x1, y1, ch)
s.Line(x1, y1, x2, y2, ch)
s.Line(x2, y2, x0, y0, ch)
}
return nil
}

// Draw a circle on a Surface
func (s Surface) Circle(xc, yc, r int, ch rune, fill bool) error {
x := 0
Expand Down
188 changes: 188 additions & 0 deletions test/test-lines3d.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package main

import (
"math"
"math/rand"
"time"

sprite "github.com/pdevine/go-asciisprite"
tm "github.com/pdevine/go-asciisprite/termbox"
)

var allSprites sprite.SpriteGroup
var Width int
var Height int
var Rand *rand.Rand

type Point3D struct {
X float64
Y float64
Z float64
cX float64
cY float64
cZ float64
fl float64
vpX float64
vpY float64
scale float64
}

func NewPoint3D(x, y, z float64) *Point3D {
p := &Point3D{
fl: 250.0,
scale: 1.0,
X: x,
Y: y,
Z: z,
}
return p
}

func (p *Point3D) SetVanishingPoint(vpX, vpY int) {
p.vpX = float64(vpX)
p.vpY = float64(vpY)
}

func (p *Point3D) SetCenter(cX, cY, cZ float64) {
p.cX = cX
p.cY = cY
p.cZ = cZ
}

func (p *Point3D) ScreenX() int {
p.scale = p.fl / (p.fl + p.Z + p.cZ)
return int(math.Round(p.vpX + (p.cX+p.X)*p.scale))
}

func (p *Point3D) ScreenY() int {
p.scale = p.fl / (p.fl + p.Z + p.cZ)
return int(math.Round(p.vpY + (p.cY+p.Y)*p.scale))
}

func (p *Point3D) RotateX(angleX float64) {
cosX := math.Cos(angleX)
sinX := math.Sin(angleX)

p.Y = (p.Y * cosX) - (p.Z * sinX)
p.Z = (p.Z * cosX) + (p.Y * sinX)
}

func (p *Point3D) RotateY(angleY float64) {
cosY := math.Cos(angleY)
sinY := math.Sin(angleY)

p.X = (p.X * cosY) - (p.Z * sinY)
p.Z = (p.Z * cosY) + (p.X * sinY)
}

func (p *Point3D) RotateZ(angleZ float64) {
cosZ := math.Cos(angleZ)
sinZ := math.Sin(angleZ)

p.X = (p.X * cosZ) - (p.Y * sinZ)
p.Y = (p.Y * cosZ) + (p.X * sinZ)
}

type Square3D struct {
sprite.BaseSprite
points []*Point3D
}

func NewSquare3D() *Square3D {
s := &Square3D{BaseSprite: sprite.BaseSprite{
Visible: true},
}

s.points = []*Point3D{
NewPoint3D(-10, -10, 50),
NewPoint3D(10, -10, 50),
NewPoint3D(10, 10, 50),
NewPoint3D(-10, 10, 50),
}

for _, p := range s.points {
p.SetVanishingPoint(Width/2, Height/2)
}

surf := sprite.NewSurface(Width, Height, false)
s.BlockCostumes = append(s.BlockCostumes, &surf)

return s
}

func (s *Square3D) Update() {
angleX := 0.01
angleY := 0.05

for _, p := range s.points {
p.RotateX(angleX)
p.RotateY(angleY)
}

surf := sprite.NewSurface(Width, Height, false)
for cnt := 0; cnt < len(s.points); cnt++ {
if cnt == len(s.points)-1 {
c := s.points[cnt]
n := s.points[0]
surf.Line(c.ScreenX(), c.ScreenY(), n.ScreenX(), n.ScreenY(), 'X')
} else {
c := s.points[cnt]
n := s.points[cnt+1]
surf.Line(c.ScreenX(), c.ScreenY(), n.ScreenX(), n.ScreenY(), 'X')
}
}

s.BlockCostumes[0] = &surf
}

func main() {
// XXX - Wait a bit until the terminal is properly initialized
time.Sleep(500 * time.Millisecond)

err := tm.Init()
if err != nil {
panic(err)
}
defer tm.Close()

w, h := tm.Size()
Width = w * 2
Height = h * 2
Rand = rand.New(rand.NewSource(time.Now().UnixNano()))

event_queue := make(chan tm.Event)
go func() {
for {
event_queue <- tm.PollEvent()
}
}()

i := NewSquare3D()

allSprites.Init(Width, Height, true)
allSprites.BlockMode = true
allSprites.Background = tm.Attribute(178)
allSprites.Sprites = append(allSprites.Sprites, i)

mainloop:
for {
tm.Clear(tm.Attribute(178), tm.Attribute(178))

select {
case ev := <-event_queue:
if ev.Type == tm.EventKey {
if ev.Key == tm.KeyEsc {
break mainloop
}
} else if ev.Type == tm.EventResize {
Width = ev.Width * 2
Height = ev.Height * 2
allSprites.Resize(Width, Height)
}
default:
allSprites.Update()
allSprites.Render()
time.Sleep(50 * time.Millisecond)
}
}
}
1 change: 0 additions & 1 deletion test/test-png.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ func main() {
}

}

Loading

0 comments on commit edf8a61

Please sign in to comment.