Created
February 16, 2018 07:27
-
-
Save quillaja/7ffbf31c6ca639451d28e3a9c1e366b5 to your computer and use it in GitHub Desktop.
Camera for Pixel graphics library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"math" | |
"github.com/faiface/pixel" | |
"github.com/faiface/pixel/pixelgl" | |
) | |
// An interal type to store 2 related numbers, a low and high bound. | |
// pixel.Vec could have been used instead, but this is creates a more | |
// self-documenting interface for the user. | |
type clamp struct { | |
Low, High float64 | |
} | |
// Camera provides a simple but relatively feature rich 2D camera for use with | |
// the Pixel graphics library. | |
// | |
// Position (panning) is controlled via the Position and PanSpeed members. | |
// By default, the Position is controled by the user via the Up, Down, Left, and | |
// Right keys. However, this can be customized by setting the UpButton, | |
// DownButton, LeftButton, and RightButton members to a pixelgl.Button value. | |
// | |
// Zoom is controlled by the Zoom and ZoomSpeed members. Zooming is based | |
// on multication, so a "zero" zoom is Zoom == 1. The ZoomSpeed is based on | |
// expotentiation where ZoomSpeed is the base which is raised to some "zoom | |
// level" power (ie ZoomSpeed**zoomlvl). This zoom level is usually the mouse's | |
// Y-scroll position, but can be customized by providing setting ZoomLevel to | |
// a function with the signature `func() float64`. | |
// | |
// Limits on panning and zooming are provided by XExtents, YExtents, and | |
// ZExtents which provide "Low" and "High" limits to which Position.X, | |
// Position.Y, and Position.Z are clamped to, respectively. | |
// | |
// The Reset() series of methods allow the camera position and/or zoom to be | |
// reset to their original values. If NewCamera() was used to instantiate, then | |
// those defaults are Pos(0,0) and Zoom(1). If NewCameraParams() was used, the | |
// initial values for position and zoom provided as parameters are saved within | |
// the camera and then used when Reset() is called. | |
// | |
// Example usage within the context of Pixel: | |
// | |
// // ... prior initialization | |
// cam := pixel.NewCamera() // using defaults | |
// // alternatively: NewCameraParams(width/2, height/2, 200, 1.1) for window center | |
// loopStart := time.Now() | |
// for !win.Closed() { | |
// elapsed := time.Since(loopStart) | |
// loopStart = time.Now() | |
// | |
// // Reset() should be called before Update() and before | |
// // setting the window's matrix (via SetMatrix()) | |
// if win.JustPressed(pixelgl.KeyHome) { | |
// cam.Reset() // reset Position and Zoom to (0,0) and 1 | |
// } | |
// | |
// cam.Update(win, elapsed.Seconds()) // camera updates position, zoom, etc | |
// win.SetMatrix(cam.GetMatrix()) // provide transformation matrix to window | |
// win.Clear() | |
// //... redraw window, update state, etc | |
// | |
type Camera struct { | |
Position pixel.Vec | |
PanSpeed float64 | |
Zoom float64 | |
ZoomSpeed float64 | |
XExtents clamp | |
YExtents clamp | |
ZExtents clamp | |
UpButton, DownButton pixelgl.Button | |
LeftButton, RightButton pixelgl.Button | |
ZoomLevel func() float64 | |
prevWinBounds pixel.Rect | |
origPosition pixel.Vec | |
origZoom float64 | |
} | |
// NewCamera creates a new camera with sane defaults. | |
// | |
// Default values are: | |
// Position: pixel.ZV // X=0, Y=0 | |
// PanSpeed: 200 // pixels/sec | |
// Zoom: 1 | |
// ZoomSpeed: 1.1 | |
// XExtents.Low = -5000, XExtents.High = 5000 | |
// YExtents.Low = -5000, YExtents.High = 5000 | |
// ZExtents.Low = -50, ZExtents.High = 50 | |
// | |
// Keyboard Up, Down, Left, and Right control panning, and the mouse wheel | |
// controls zoom. | |
func NewCamera() *Camera { | |
return &Camera{ | |
pixel.ZV, | |
200, | |
1, | |
1.1, | |
clamp{-5000, 5000}, | |
clamp{-5000, 5000}, | |
clamp{-50, 50}, | |
pixelgl.KeyUp, pixelgl.KeyDown, | |
pixelgl.KeyLeft, pixelgl.KeyRight, | |
nil, | |
pixel.Rect{}, | |
pixel.ZV, | |
1} | |
} | |
// NewCameraParams creates a new camera with the given parameters. "origPosition" | |
// and "origZoom" are stored and used for calls to Reset(). Other camera parameters | |
// are set according to the defaults (see NewCamera()) but can be changed. | |
func NewCameraParams(origPosition pixel.Vec, origZoom, panSpeed, zoomSpeed float64) *Camera { | |
c := NewCamera() | |
c.Position = origPosition | |
c.origPosition = origPosition | |
c.Zoom = origZoom | |
c.origZoom = origZoom | |
c.PanSpeed = panSpeed | |
c.ZoomSpeed = zoomSpeed | |
return c | |
} | |
// Update recalculates the camera position and zoom based, and is generally called | |
// each frame before setting the window's matrx. Update checks the keyboard for | |
// the status of the defined panning and zooming controls (see `Camera`). | |
// Position.X, Position.Y, and Zoom are clamped to XExtents, YExtents, and ZExtents | |
// respectively. | |
func (cam *Camera) Update(win *pixelgl.Window, timeElapsed float64) { | |
// save window bounds (used in GetMatrix()) only when changed | |
if cam.prevWinBounds != win.Bounds() { | |
cam.prevWinBounds = win.Bounds() | |
} | |
// update pan | |
if win.Pressed(cam.LeftButton) { | |
cam.Position.X -= cam.PanSpeed * timeElapsed | |
} | |
if win.Pressed(cam.RightButton) { | |
cam.Position.X += cam.PanSpeed * timeElapsed | |
} | |
if win.Pressed(cam.DownButton) { | |
cam.Position.Y -= cam.PanSpeed * timeElapsed | |
} | |
if win.Pressed(cam.UpButton) { | |
cam.Position.Y += cam.PanSpeed * timeElapsed | |
} | |
// update zoom based on either the user-defined "ZoomLevel()" | |
// or the mouse wheel's Y position | |
var zlvl float64 | |
if cam.ZoomLevel != nil { | |
zlvl = cam.ZoomLevel() | |
} else { | |
zlvl = win.MouseScroll().Y | |
} | |
cam.Zoom *= math.Pow(cam.ZoomSpeed, zlvl) | |
// clamp to extents | |
cam.Position.X = pixel.Clamp(cam.Position.X, cam.XExtents.Low, cam.XExtents.High) | |
cam.Position.Y = pixel.Clamp(cam.Position.Y, cam.YExtents.Low, cam.YExtents.High) | |
cam.Zoom = pixel.Clamp(cam.Zoom, cam.ZExtents.Low, cam.ZExtents.High) | |
} | |
// GetMatrix gets the transformation matrix to apply the camera's settings to | |
// a window. | |
// | |
// win.SetMatrix(cam.GetMatrix()) | |
func (cam *Camera) GetMatrix() pixel.Matrix { | |
return pixel.IM.Scaled(cam.Position, cam.Zoom). | |
Moved(cam.prevWinBounds.Center().Sub(cam.Position)) | |
} | |
// Unproject will translate a point to its apparent position in the camera's | |
// view. This method is identical to: | |
// | |
// m := cam.GetMatrix() | |
// m.Unproject(point) | |
func (cam *Camera) Unproject(point pixel.Vec) pixel.Vec { | |
return cam.GetMatrix().Unproject(point) | |
} | |
// Reset restores the camera's Position and Zoom to its initial settings. | |
func (cam *Camera) Reset() { | |
cam.ResetPan() | |
cam.ResetZoom() | |
} | |
// ResetPan restores the camera's Position to its initial settings. | |
func (cam *Camera) ResetPan() { | |
cam.ResetXPan() | |
cam.ResetYPan() | |
} | |
// ResetXPan restores the camera's Position.X (horizontal pan) to its initial setting. | |
func (cam *Camera) ResetXPan() { cam.Position.X = cam.origPosition.X } | |
// ResetYPan restores the camera's Position.Y (vertical pan) to its initial setting. | |
func (cam *Camera) ResetYPan() { cam.Position.Y = cam.origPosition.Y } | |
// ResetZoom restores the camera's Zoom to its initial setting. | |
func (cam *Camera) ResetZoom() { cam.Zoom = cam.origZoom } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment