From 67241b9b37985a158ae47d6ae72e7981928cc13d Mon Sep 17 00:00:00 2001 From: iegod Date: Wed, 13 Sep 2023 08:45:18 -0400 Subject: [PATCH] Additional visualizations for trig. relationship. Added sinusoidal expansion of additional circles, dependent on tick rate. --- examples/splashmenu/scenes/rays.go | 267 +++++++++++++++++++++++++++-- 1 file changed, 256 insertions(+), 11 deletions(-) diff --git a/examples/splashmenu/scenes/rays.go b/examples/splashmenu/scenes/rays.go index 9ef7822..593565a 100644 --- a/examples/splashmenu/scenes/rays.go +++ b/examples/splashmenu/scenes/rays.go @@ -2,42 +2,144 @@ package splashmenu import ( "cosmos/diego/groovy" + splashmenu "cosmos/diego/groovy/examples/splashmenu/fonts" + "fmt" "image/color" + "math" + "strconv" "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "github.com/hajimehoshi/ebiten/v2/text" + "github.com/hajimehoshi/ebiten/v2/vector" ) 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 { events map[groovy.SceneEvent]func() increment int Dimensions groovy.Area - bgcolor color.RGBA + + coordinates Coords + selection Coords + interacting bool + + 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 { - return Rays{ - events: make(map[groovy.SceneEvent]func()), - increment: 0, - bgcolor: color.RGBA{ - R: BGCOLOR >> 0x10 & 0xff, - G: BGCOLOR >> 0x08 & 0xff, - B: BGCOLOR >> 0x00 & 0xff, - A: 0xff, - }, + r := Rays{ + events: make(map[groovy.SceneEvent]func()), + increment: 0, + bgcolor: hexToRGBA(BGCOLOR), + coordinates: Coords{X: 0, Y: 0}, + selection: Coords{X: 0, Y: 0}, } + + 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 { 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 } func (r *Rays) Draw(screen *ebiten.Image) { 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) { @@ -47,3 +149,146 @@ func (r *Rays) SetDimensions(a groovy.Area) { func (r *Rays) SetEventHandler(event groovy.SceneEvent, f func()) { 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) + + } +}