Additional visualizations for trig. relationship. Added sinusoidal expansion of additional circles, dependent on tick rate.
This commit is contained in:
@@ -2,42 +2,144 @@ package splashmenu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cosmos/diego/groovy"
|
"cosmos/diego/groovy"
|
||||||
|
splashmenu "cosmos/diego/groovy/examples/splashmenu/fonts"
|
||||||
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/text"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BGCOLOR = 0xCCCCCC
|
BGCOLOR = 0xCCCCCC
|
||||||
|
LINECOLOR = 0x8a8a8a
|
||||||
|
SQUARECOLOR = 0x199a19
|
||||||
|
RADIUSCOLOR = 0xFF0000
|
||||||
|
AXISCOLOR = 0xf100c1
|
||||||
|
SEMIAXISCOLOR = 0x178fc5 //0x1e1e1e //0xdccdFF
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ColorData struct {
|
||||||
|
selectionColor color.RGBA
|
||||||
|
squareColor color.RGBA
|
||||||
|
radiusColor color.RGBA
|
||||||
|
axisColor color.RGBA
|
||||||
|
semiAxisColor color.RGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
type Coords struct {
|
||||||
|
X float32
|
||||||
|
Y float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Coords) DistanceFrom(pos Coords) float32 {
|
||||||
|
return float32(math.Sqrt(float64((pos.X-c.X)*(pos.X-c.X) + (pos.Y-c.Y)*(pos.Y-c.Y))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Coords) SignedDistanceFrom(pos Coords) Coords {
|
||||||
|
return Coords{X: pos.X - c.X, Y: pos.Y - c.Y}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Coords) Magnitude() float32 {
|
||||||
|
return float32(math.Sqrt(float64(c.X*c.X + c.Y*c.Y)))
|
||||||
|
}
|
||||||
|
|
||||||
type Rays struct {
|
type Rays struct {
|
||||||
events map[groovy.SceneEvent]func()
|
events map[groovy.SceneEvent]func()
|
||||||
increment int
|
increment int
|
||||||
Dimensions groovy.Area
|
Dimensions groovy.Area
|
||||||
|
|
||||||
|
coordinates Coords
|
||||||
|
selection Coords
|
||||||
|
interacting bool
|
||||||
|
|
||||||
bgcolor color.RGBA
|
bgcolor color.RGBA
|
||||||
|
colorData ColorData
|
||||||
|
}
|
||||||
|
|
||||||
|
func hexToRGBA(hexcolor int) color.RGBA {
|
||||||
|
return color.RGBA{
|
||||||
|
R: uint8(hexcolor >> 0x10 & 0xff),
|
||||||
|
G: uint8(hexcolor >> 0x08 & 0xff),
|
||||||
|
B: uint8(hexcolor >> 0x00 & 0xff),
|
||||||
|
A: 0xff,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRays() Rays {
|
func NewRays() Rays {
|
||||||
return Rays{
|
r := Rays{
|
||||||
events: make(map[groovy.SceneEvent]func()),
|
events: make(map[groovy.SceneEvent]func()),
|
||||||
increment: 0,
|
increment: 0,
|
||||||
bgcolor: color.RGBA{
|
bgcolor: hexToRGBA(BGCOLOR),
|
||||||
R: BGCOLOR >> 0x10 & 0xff,
|
coordinates: Coords{X: 0, Y: 0},
|
||||||
G: BGCOLOR >> 0x08 & 0xff,
|
selection: Coords{X: 0, Y: 0},
|
||||||
B: BGCOLOR >> 0x00 & 0xff,
|
|
||||||
A: 0xff,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.colorData.selectionColor = hexToRGBA(LINECOLOR)
|
||||||
|
r.colorData.squareColor = hexToRGBA(SQUARECOLOR)
|
||||||
|
r.colorData.radiusColor = hexToRGBA(RADIUSCOLOR)
|
||||||
|
r.colorData.axisColor = hexToRGBA(AXISCOLOR)
|
||||||
|
r.colorData.semiAxisColor = hexToRGBA(SEMIAXISCOLOR)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func gimmeFloatCursor() (float32, float32) {
|
||||||
|
x, y := ebiten.CursorPosition()
|
||||||
|
|
||||||
|
return float32(x), float32(y)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Rays) Update() error {
|
func (r *Rays) Update() error {
|
||||||
r.increment++
|
r.increment++
|
||||||
|
|
||||||
|
x, y := gimmeFloatCursor()
|
||||||
|
mouseCoords := Coords{X: x, Y: y}
|
||||||
|
newRad := mouseCoords.DistanceFrom(r.selection)
|
||||||
|
|
||||||
|
xMin := r.selection.X - newRad
|
||||||
|
xMax := r.selection.X + newRad
|
||||||
|
yMin := r.selection.Y - newRad
|
||||||
|
yMax := r.selection.Y + newRad
|
||||||
|
|
||||||
|
if 0 <= xMin && xMax < float32(r.Dimensions.Width) &&
|
||||||
|
0 <= yMin && yMax < float32(r.Dimensions.Height) {
|
||||||
|
r.coordinates = mouseCoords
|
||||||
|
}
|
||||||
|
|
||||||
|
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||||
|
r.selection = mouseCoords
|
||||||
|
r.interacting = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
|
||||||
|
//r.selection = r.coordinates
|
||||||
|
r.interacting = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.interacting {
|
||||||
|
r.selection = r.coordinates
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Rays) Draw(screen *ebiten.Image) {
|
func (r *Rays) Draw(screen *ebiten.Image) {
|
||||||
screen.Fill(r.bgcolor)
|
screen.Fill(r.bgcolor)
|
||||||
|
r.DrawTheAngleBits(screen)
|
||||||
|
|
||||||
|
x, y := gimmeFloatCursor()
|
||||||
|
|
||||||
|
text.Draw(screen, fmt.Sprintf("Mouse x, y: (%.1f, %.1f)", x, y), splashmenu.SplashFont.Menu, 20, 20, color.White)
|
||||||
|
text.Draw(screen, fmt.Sprintf("Selection x, y: (%.1f, %.1f)", r.selection.X, r.selection.Y), splashmenu.SplashFont.Menu, 20, 40, color.White)
|
||||||
|
text.Draw(screen, fmt.Sprintf("Radius: %.1f", r.GetPrimaryRadius()), splashmenu.SplashFont.Menu, 20, 60, color.White)
|
||||||
|
text.Draw(screen, fmt.Sprintf("Interaction: %s", strconv.FormatBool(r.interacting)), splashmenu.SplashFont.Menu, 20, 80, color.White)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Rays) SetDimensions(a groovy.Area) {
|
func (r *Rays) SetDimensions(a groovy.Area) {
|
||||||
@@ -47,3 +149,146 @@ func (r *Rays) SetDimensions(a groovy.Area) {
|
|||||||
func (r *Rays) SetEventHandler(event groovy.SceneEvent, f func()) {
|
func (r *Rays) SetEventHandler(event groovy.SceneEvent, f func()) {
|
||||||
r.events[event] = f
|
r.events[event] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Rays) DrawTheAngleBits(screen *ebiten.Image) {
|
||||||
|
|
||||||
|
if r.interacting {
|
||||||
|
|
||||||
|
r.DrawTrigElementAxis(screen, r.selection, r.coordinates, r.colorData)
|
||||||
|
r.DrawTrigElement(screen, r.selection, r.coordinates, r.colorData)
|
||||||
|
r.DrawCircleElement(screen, r.selection, r.coordinates, r.colorData)
|
||||||
|
|
||||||
|
dist := r.coordinates.SignedDistanceFrom(r.selection)
|
||||||
|
angle := math.Atan2(float64(dist.Y), float64(dist.X))
|
||||||
|
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
var xSpot Coords
|
||||||
|
xSpot.X = r.selection.X + float32(math.Cos(angle))*dist.Magnitude()*float32(i)
|
||||||
|
xSpot.Y = r.selection.Y + float32(math.Sin(angle))*dist.Magnitude()*float32(i)
|
||||||
|
|
||||||
|
var nSpot Coords
|
||||||
|
|
||||||
|
//our limit is pi/2, but we want to stretch this out over 30 frames
|
||||||
|
u := (math.Pi / 2) * 30
|
||||||
|
|
||||||
|
v := float64(r.increment)
|
||||||
|
|
||||||
|
//our angular frequency is simply the remainder of our current frame count / our limit
|
||||||
|
w := (v/u - math.Floor(v/u))
|
||||||
|
|
||||||
|
//now scale the circles
|
||||||
|
nSpot.X = xSpot.X + float32(math.Cos(angle))*dist.Magnitude()*float32(math.Sin(w))
|
||||||
|
nSpot.Y = xSpot.Y + float32(math.Sin(angle))*dist.Magnitude()*float32(math.Sin(w))
|
||||||
|
r.DrawCircleElement(screen, xSpot, nSpot, r.colorData)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the primary circle radius value of the current scene
|
||||||
|
func (r *Rays) GetPrimaryRadius() float32 {
|
||||||
|
return r.coordinates.DistanceFrom(r.selection)
|
||||||
|
}
|
||||||
|
|
||||||
|
// draws circle
|
||||||
|
func (r *Rays) DrawCircleElement(img *ebiten.Image, origin Coords, dest Coords, colorData ColorData) {
|
||||||
|
dist := dest.SignedDistanceFrom(origin)
|
||||||
|
vector.StrokeCircle(img, origin.X, origin.Y, dist.Magnitude(), 2, colorData.selectionColor, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// draws circle trig relationships with the x/y axis of the circles origin and a radius indicator
|
||||||
|
func (r *Rays) DrawTrigElement(img *ebiten.Image, origin Coords, dest Coords, colorData ColorData) {
|
||||||
|
dist := dest.SignedDistanceFrom(origin)
|
||||||
|
|
||||||
|
//circle and radius marker
|
||||||
|
vector.StrokeCircle(img, origin.X, origin.Y, dist.Magnitude(), 2, colorData.selectionColor, true)
|
||||||
|
vector.StrokeLine(img, origin.X, origin.Y, dest.X, dest.Y, 1, colorData.radiusColor, true)
|
||||||
|
|
||||||
|
//draw rectangle from origin to dest
|
||||||
|
vector.StrokeRect(img, origin.X, origin.Y, dest.X-origin.X, dest.Y-origin.Y, 1, colorData.squareColor, true)
|
||||||
|
|
||||||
|
//compute directionality (1)
|
||||||
|
dirX := dist.X / float32(math.Abs(float64(dist.X)))
|
||||||
|
dirY := dist.Y / float32(math.Abs(float64(dist.Y)))
|
||||||
|
|
||||||
|
//semi-axis within circle
|
||||||
|
vector.StrokeLine(img, origin.X, origin.Y, origin.X-dirX*dist.Magnitude(), origin.Y, 1, colorData.axisColor, true)
|
||||||
|
vector.StrokeLine(img, origin.X, origin.Y, origin.X, origin.Y-dirY*dist.Magnitude(), 1, colorData.axisColor, true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rays) DrawTrigElementAxis(img *ebiten.Image, origin Coords, dest Coords, colorData ColorData) {
|
||||||
|
dist := dest.SignedDistanceFrom(origin)
|
||||||
|
|
||||||
|
//origin to BORDER
|
||||||
|
// case 1. origin.x = destination.x, means intercept should be vertical line
|
||||||
|
// case 2. we have a slope
|
||||||
|
if dist.X == 0 {
|
||||||
|
vector.StrokeLine(img, origin.X, 0, origin.X, float32(r.Dimensions.Height), 1, colorData.semiAxisColor, true)
|
||||||
|
} else {
|
||||||
|
m := dist.Y / dist.X
|
||||||
|
|
||||||
|
/*
|
||||||
|
y - y1 = m(x - x1)
|
||||||
|
y = m(x - x1) + y1
|
||||||
|
x = (y - y1)/m + x1
|
||||||
|
find the intercepts:
|
||||||
|
x = Dimensions.Width
|
||||||
|
x = 0
|
||||||
|
|
||||||
|
y = 0
|
||||||
|
y = Dimensions.Height
|
||||||
|
*/
|
||||||
|
|
||||||
|
//find our y intercept for the start of our segment
|
||||||
|
var xZero Coords
|
||||||
|
xZero.X = 0
|
||||||
|
xZero.Y = m*(xZero.X-origin.X) + origin.Y
|
||||||
|
|
||||||
|
//in the even our point lies outside the visible screen boundaries,
|
||||||
|
//we can save some pixel operations by restricting to the bounds
|
||||||
|
if xZero.Y > float32(r.Dimensions.Height) {
|
||||||
|
xZero.Y = float32(r.Dimensions.Height)
|
||||||
|
xZero.X = (xZero.Y-origin.Y)/m + origin.X
|
||||||
|
}
|
||||||
|
|
||||||
|
if xZero.Y < 0 {
|
||||||
|
xZero.Y = 0
|
||||||
|
xZero.X = (xZero.Y-origin.Y)/m + origin.X
|
||||||
|
}
|
||||||
|
|
||||||
|
var xMax Coords
|
||||||
|
xMax.X = float32(r.Dimensions.Width)
|
||||||
|
xMax.Y = m*(xMax.X-origin.X) + origin.Y
|
||||||
|
|
||||||
|
if xMax.Y > float32(r.Dimensions.Height) {
|
||||||
|
xMax.Y = float32(r.Dimensions.Height)
|
||||||
|
xMax.X = (xMax.Y-origin.Y)/m + origin.X
|
||||||
|
}
|
||||||
|
|
||||||
|
if xMax.Y < 0 {
|
||||||
|
xMax.Y = 0
|
||||||
|
xMax.X = (xMax.Y-origin.Y)/m + origin.X
|
||||||
|
}
|
||||||
|
|
||||||
|
var slashLineOrigin Coords
|
||||||
|
var slashLineDest Coords
|
||||||
|
|
||||||
|
slashLineOrigin = xZero
|
||||||
|
slashLineDest = xMax
|
||||||
|
|
||||||
|
var xSpot Coords
|
||||||
|
angle := math.Atan2(float64(dist.Y), float64(dist.X))
|
||||||
|
xSpot.X = origin.X + float32(math.Cos(angle))*dist.Magnitude()
|
||||||
|
xSpot.Y = origin.Y + float32(math.Sin(angle))*dist.Magnitude()
|
||||||
|
|
||||||
|
vector.StrokeLine(img, slashLineOrigin.X, slashLineOrigin.Y, slashLineDest.X, slashLineDest.Y, 1, colorData.semiAxisColor, true)
|
||||||
|
text.Draw(img, fmt.Sprintf("%0.1f : (%0.1f, %0.1f) (%0.1f, %0.1f)", m, slashLineOrigin.X, slashLineOrigin.Y, slashLineDest.X, slashLineDest.Y), splashmenu.SplashFont.Menu, 20, 100, color.White)
|
||||||
|
|
||||||
|
//text.Draw(img, "x", splashmenu.SplashFont.Title, int(xSpot.X), int(xSpot.Y), color.Black)
|
||||||
|
vector.DrawFilledCircle(img, xSpot.X, xSpot.Y, 5, color.Black, true)
|
||||||
|
vector.DrawFilledCircle(img, origin.X, origin.Y, 5, colorData.radiusColor, true)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user