Added bounding box.
Renamed things based on golint. Fixed movement of the paddle.main
parent
4343af651f
commit
eec2d2c3d1
30
main.go
30
main.go
|
@ -8,8 +8,8 @@ import (
|
|||
"runtime/pprof"
|
||||
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/breakout"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/game_window"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_objects"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gamewindow"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects"
|
||||
gl "github.com/go-gl/gl/v3.1/gles2"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
|
@ -17,8 +17,8 @@ import (
|
|||
|
||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
|
||||
const GameTitle = "Carpy Breakout"
|
||||
const LogFile = "output.log"
|
||||
const gameTitle = "Carpy Breakout"
|
||||
const logFile = "output.log"
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
@ -54,7 +54,7 @@ func runOpenGL() {
|
|||
|
||||
sdlSettings()
|
||||
|
||||
gameWindow, err := game_window.NewGameWindow(GameTitle)
|
||||
gameWindow, err := gamewindow.NewGameWindow(gameTitle)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -74,15 +74,15 @@ func runOpenGL() {
|
|||
// Then we can setup the OpenGL specific stuff in the game window
|
||||
gameWindow.GLInit()
|
||||
|
||||
sunLight := gl_objects.NewPointLight(mgl32.Vec3{-10, 10, 30})
|
||||
sunLight := globjects.NewPointLight(mgl32.Vec3{-10, 10, 30})
|
||||
sunLight.GLInit(gameWindow.GLProgram)
|
||||
|
||||
camera := gl_objects.NewCamera(gameWindow.GLProgram)
|
||||
camera := globjects.NewCamera(gameWindow.GLProgram)
|
||||
camera.Position = mgl32.Vec3{0, 0, 30}
|
||||
gameWindow.SetCamera(camera)
|
||||
gameWindow.AddObject(camera)
|
||||
|
||||
// textureId, err := gl_helpers.NewTexture("square.png")
|
||||
// textureId, err := glhelpers.NewTexture("square.png")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
@ -99,22 +99,24 @@ func runOpenGL() {
|
|||
gameWindow.NegativeBoundary = negativeBoundary
|
||||
|
||||
// Bounding Box
|
||||
boundingBoxMaterial := gl_objects.NewMaterial()
|
||||
boundingBoxMaterial := globjects.NewMaterial()
|
||||
boundingBoxMaterial.Color = mgl32.Vec4{0.3, 0.3, 0.3, 1}
|
||||
boundingBox := breakout.NewBoundingBox(negativeBoundary, positiveBoundary, boundingBoxMaterial)
|
||||
boundingBox.GLInit(gameWindow.GLProgram)
|
||||
gameWindow.AddObject(boundingBox)
|
||||
|
||||
// Paddle
|
||||
paddleMaterial := gl_objects.NewMaterial()
|
||||
paddleMaterial := globjects.NewMaterial()
|
||||
paddleMaterial.Color = mgl32.Vec4{0, 1, 0, 1}
|
||||
paddle := gl_objects.NewBox(6.0, 1.0, 4.0, mgl32.Vec3{0, -6, 0}, paddleMaterial)
|
||||
paddle := globjects.NewBox(6.0, 1.0, 4.0, mgl32.Vec3{0, -6, 0}, paddleMaterial)
|
||||
paddle.GLInit(gameWindow.GLProgram)
|
||||
gameWindow.AddObject(paddle)
|
||||
gameWindow.SetPaddle(paddle)
|
||||
|
||||
// Ball
|
||||
ballMaterial := gl_objects.NewMaterial()
|
||||
ballMaterial := globjects.NewMaterial()
|
||||
ballMaterial.Color = mgl32.Vec4{1, 1, 1, 1}
|
||||
ball := gl_objects.NewBox(1, 1, 1, mgl32.Vec3{-8, 0, 0}, ballMaterial)
|
||||
ball := globjects.NewBox(1, 1, 1, mgl32.Vec3{-8, 0, 0}, ballMaterial)
|
||||
ball.GLInit(gameWindow.GLProgram)
|
||||
gameWindow.AddObject(ball)
|
||||
|
||||
|
@ -133,7 +135,7 @@ func runOpenGL() {
|
|||
}
|
||||
|
||||
func setupLogging() {
|
||||
// file, err := os.OpenFile(LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
// file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
|
|
@ -1,64 +1,37 @@
|
|||
package breakout
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_objects"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
type BoundingBox struct {
|
||||
Boxes []*gl_objects.Box
|
||||
Boxes []*globjects.Box
|
||||
}
|
||||
|
||||
func NewBoundingBox(lowerLeft, topRight mgl32.Vec3, material gl_objects.Material) *BoundingBox {
|
||||
func NewBoundingBox(lowerLeft, topRight mgl32.Vec3, material *globjects.Material) *BoundingBox {
|
||||
|
||||
var brickWidth float32 = 4.0
|
||||
var brickHeight float32 = 1.0
|
||||
var brickDepth float32 = 4.0
|
||||
var depth float32 = 6.0
|
||||
var height float32 = 4.0
|
||||
var offset float32 = height / 2.0
|
||||
|
||||
var widthOffset float32 = 0.5
|
||||
var heightOffset float32 = 1
|
||||
boxes := make([]*globjects.Box, 4)
|
||||
|
||||
bw := brickWidth + widthOffset
|
||||
bh := brickHeight + heightOffset
|
||||
// Top
|
||||
topPos := mgl32.Vec3{0, topRight.Y() + offset, topRight.Z()}
|
||||
boxes[0] = globjects.NewBox(topRight.X()-lowerLeft.X()+height*2, height, depth, topPos, material)
|
||||
|
||||
lowerLeftPos := mgl32.Vec3{
|
||||
bw/2 - float32(numColumns)*(bw)/2,
|
||||
bh/2 - float32(numRows)*(bh)/2,
|
||||
0,
|
||||
}
|
||||
lowerLeftPos = lowerLeftPos.Add(adjustPos)
|
||||
// Bottom
|
||||
bottomPos := mgl32.Vec3{0, lowerLeft.Y() - offset, lowerLeft.Z()}
|
||||
boxes[1] = globjects.NewBox(topRight.X()-lowerLeft.X()+height*2, height, depth, bottomPos, material)
|
||||
|
||||
topRightPos := mgl32.Vec3{
|
||||
bw/2 + float32(numColumns)*(bw)/2,
|
||||
bh/2 + float32(numRows)*(bh)/2,
|
||||
0,
|
||||
}
|
||||
topRightPos = topRightPos.Add(adjustPos)
|
||||
// Left
|
||||
leftPos := mgl32.Vec3{lowerLeft.X() - offset, 0, lowerLeft.Z()}
|
||||
boxes[2] = globjects.NewBox(height, topRight.Y()-lowerLeft.Y(), depth, leftPos, material)
|
||||
|
||||
targets.TopRight = topRightPos
|
||||
targets.LowerLeft = lowerLeftPos
|
||||
|
||||
materials := make([]*gl_objects.Material, numColumns)
|
||||
|
||||
for i := 0; i < numColumns; i++ {
|
||||
red := 0 + float32(i)/float32(numColumns)
|
||||
blue := 1 - float32(i)/float32(numColumns)
|
||||
m := gl_objects.NewMaterial()
|
||||
m.Color = mgl32.Vec4{red, 0, blue, 1}
|
||||
materials[i] = m
|
||||
}
|
||||
|
||||
n := 0
|
||||
for i := 0; i < numRows; i++ {
|
||||
for j := 0; j < numColumns; j++ {
|
||||
position := lowerLeftPos.Add(mgl32.Vec3{
|
||||
float32(j) * bw,
|
||||
float32(i) * bh,
|
||||
0})
|
||||
targets.Bricks[n] = NewBrick(brickWidth, brickHeight, brickDepth, position, materials[i])
|
||||
n++
|
||||
}
|
||||
}
|
||||
// Right
|
||||
rightPos := mgl32.Vec3{topRight.X() + offset, 0, topRight.Z()}
|
||||
boxes[3] = globjects.NewBox(height, topRight.Y()-lowerLeft.Y(), depth, rightPos, material)
|
||||
|
||||
boundingBox := BoundingBox{
|
||||
Boxes: boxes,
|
||||
|
@ -66,14 +39,26 @@ func NewBoundingBox(lowerLeft, topRight mgl32.Vec3, material gl_objects.Material
|
|||
return &boundingBox
|
||||
}
|
||||
|
||||
func (t *BoundingBox) GLInit(glProgram uint32) {
|
||||
for _, brick := range t.Bricks {
|
||||
brick.GLInit(glProgram)
|
||||
func (b *BoundingBox) GLInit(glProgram uint32) {
|
||||
for _, box := range b.Boxes {
|
||||
box.GLInit(glProgram)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *BoundingBox) GLDraw() {
|
||||
for _, brick := range t.Bricks {
|
||||
brick.GLDraw()
|
||||
func (b *BoundingBox) GLDraw() {
|
||||
for _, box := range b.Boxes {
|
||||
box.GLDraw()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BoundingBox) ToggleWireframe() {
|
||||
for _, box := range b.Boxes {
|
||||
box.ToggleWireframe()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BoundingBox) Update() {
|
||||
for _, box := range b.Boxes {
|
||||
box.Update()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package breakout
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_objects"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
type Brick struct {
|
||||
Box *gl_objects.Box
|
||||
Box *globjects.Box
|
||||
broken bool
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,10 @@ func NewBrick(
|
|||
brickHeight float32,
|
||||
brickDepth float32,
|
||||
position mgl32.Vec3,
|
||||
material *gl_objects.Material) *Brick {
|
||||
material *globjects.Material) *Brick {
|
||||
|
||||
brick := Brick{
|
||||
Box: gl_objects.NewBox(brickWidth, brickHeight, brickDepth, position, material),
|
||||
Box: globjects.NewBox(brickWidth, brickHeight, brickDepth, position, material),
|
||||
broken: false,
|
||||
}
|
||||
return &brick
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package breakout
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_objects"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
|
@ -47,12 +47,12 @@ func NewTargets(numRows, numColumns int, adjustPos mgl32.Vec3) *Targets {
|
|||
targets.TopRight = topRightPos
|
||||
targets.LowerLeft = lowerLeftPos
|
||||
|
||||
materials := make([]*gl_objects.Material, numColumns)
|
||||
materials := make([]*globjects.Material, numColumns)
|
||||
|
||||
for i := 0; i < numColumns; i++ {
|
||||
red := 0 + float32(i)/float32(numColumns)
|
||||
blue := 1 - float32(i)/float32(numColumns)
|
||||
m := gl_objects.NewMaterial()
|
||||
m := globjects.NewMaterial()
|
||||
m.Color = mgl32.Vec4{red, 0, blue, 1}
|
||||
materials[i] = m
|
||||
}
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
package game_window
|
||||
package gamewindow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_helpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_objects"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/globjects"
|
||||
gl "github.com/go-gl/gl/v3.1/gles2"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
const DefaultWindowWidth int32 = 800
|
||||
const DefaultWindowHeight int32 = 600
|
||||
const DefaultWindowFlags uint32 = sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE | sdl.WINDOW_OPENGL
|
||||
const FullscreenWindowFlags uint32 = DefaultWindowFlags | sdl.WINDOW_FULLSCREEN_DESKTOP
|
||||
const defaultWindowWidth int32 = 800
|
||||
const defaultWindowHeight int32 = 600
|
||||
const defaultWindowFlags uint32 = sdl.WINDOW_SHOWN | sdl.WINDOW_RESIZABLE | sdl.WINDOW_OPENGL
|
||||
const fullscreenWindowFlags uint32 = defaultWindowFlags | sdl.WINDOW_FULLSCREEN_DESKTOP
|
||||
|
||||
type GameWindow struct {
|
||||
SDLWindow *sdl.Window
|
||||
GLContext *sdl.GLContext
|
||||
GLProgram uint32
|
||||
GLObjects []gl_objects.GLObject
|
||||
Camera *gl_objects.Camera
|
||||
Paddle *gl_objects.Box
|
||||
GLObjects []globjects.GLObject
|
||||
Camera *globjects.Camera
|
||||
Paddle *globjects.Box
|
||||
PositiveBoundary mgl32.Vec3
|
||||
NegativeBoundary mgl32.Vec3
|
||||
|
||||
keystates map[sdl.Keycode]bool
|
||||
mouseMoved bool
|
||||
running bool
|
||||
fullscreen bool
|
||||
wireframe bool
|
||||
|
@ -38,15 +40,16 @@ func NewGameWindow(title string) (*GameWindow, error) {
|
|||
fullscreen: false,
|
||||
wireframe: false,
|
||||
freelook: false,
|
||||
keystates: make(map[sdl.Keycode]bool),
|
||||
}
|
||||
|
||||
window, err := sdl.CreateWindow(
|
||||
title,
|
||||
sdl.WINDOWPOS_UNDEFINED,
|
||||
sdl.WINDOWPOS_UNDEFINED,
|
||||
DefaultWindowWidth,
|
||||
DefaultWindowHeight,
|
||||
DefaultWindowFlags)
|
||||
defaultWindowWidth,
|
||||
defaultWindowHeight,
|
||||
defaultWindowFlags)
|
||||
if err != nil {
|
||||
log.Println("Failed creating SDL window")
|
||||
return nil, err
|
||||
|
@ -64,7 +67,7 @@ func NewGameWindow(title string) (*GameWindow, error) {
|
|||
}
|
||||
|
||||
func (w *GameWindow) GLInit() error {
|
||||
program, err := gl_helpers.NewDefaultProgram()
|
||||
program, err := glhelpers.NewDefaultProgram()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -76,7 +79,7 @@ func (w *GameWindow) GLInit() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (w *GameWindow) AddObject(object gl_objects.GLObject) {
|
||||
func (w *GameWindow) AddObject(object globjects.GLObject) {
|
||||
w.GLObjects = append(w.GLObjects, object)
|
||||
}
|
||||
|
||||
|
@ -87,9 +90,9 @@ func (w *GameWindow) Destroy() {
|
|||
|
||||
func (w *GameWindow) ToggleFullscreen() {
|
||||
if w.fullscreen {
|
||||
w.SDLWindow.SetFullscreen(DefaultWindowFlags)
|
||||
w.SDLWindow.SetFullscreen(defaultWindowFlags)
|
||||
} else {
|
||||
w.SDLWindow.SetFullscreen(FullscreenWindowFlags)
|
||||
w.SDLWindow.SetFullscreen(fullscreenWindowFlags)
|
||||
}
|
||||
w.fullscreen = !w.fullscreen
|
||||
}
|
||||
|
@ -113,16 +116,16 @@ func (w *GameWindow) StopRunning() {
|
|||
func (w *GameWindow) WindowProjection() {
|
||||
width, height := w.SDLWindow.GLGetDrawableSize()
|
||||
projection := mgl32.Perspective(mgl32.DegToRad(45.0), float32(width)/float32(height), 0.01, 1000.0)
|
||||
gl_helpers.SetUniformMatrix4f(w.GLProgram, "projection", &projection)
|
||||
glhelpers.SetUniformMatrix4f(w.GLProgram, "projection", &projection)
|
||||
|
||||
gl.Viewport(0, 0, width, height)
|
||||
}
|
||||
|
||||
func (w *GameWindow) SetPaddle(paddle *gl_objects.Box) {
|
||||
func (w *GameWindow) SetPaddle(paddle *globjects.Box) {
|
||||
w.Paddle = paddle
|
||||
}
|
||||
|
||||
func (w *GameWindow) SetCamera(camera *gl_objects.Camera) {
|
||||
func (w *GameWindow) SetCamera(camera *globjects.Camera) {
|
||||
w.Camera = camera
|
||||
}
|
||||
|
||||
|
@ -148,7 +151,7 @@ func (w *GameWindow) GLDraw() {
|
|||
model := mgl32.Ident4()
|
||||
// Probably can't do concurrent drawing due to OpenGL
|
||||
for _, o := range w.GLObjects {
|
||||
gl_helpers.SetUniformMatrix4f(w.GLProgram, "model", &model)
|
||||
glhelpers.SetUniformMatrix4f(w.GLProgram, "model", &model)
|
||||
o.GLDraw()
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +168,15 @@ func (w *GameWindow) Update() {
|
|||
// }
|
||||
// wg.Wait()
|
||||
|
||||
if w.IsFreelookOn() {
|
||||
w.handleFreelookMovement()
|
||||
} else {
|
||||
if !w.mouseMoved {
|
||||
w.handleGameKeyboardMovement()
|
||||
}
|
||||
}
|
||||
w.mouseMoved = false
|
||||
|
||||
for _, o := range w.GLObjects {
|
||||
o.Update()
|
||||
}
|
||||
|
@ -181,7 +193,7 @@ func (w *GameWindow) HandleEvents() {
|
|||
log.Println("Quiting")
|
||||
w.StopRunning()
|
||||
case *sdl.MouseMotionEvent:
|
||||
w.HandleMouseMovementEvent(e)
|
||||
w.handleMouseMovementEvent(e)
|
||||
case *sdl.MouseButtonEvent:
|
||||
fmt.Printf("[%d ms] MouseButton\ttype:%d\tid:%d\tx:%d\ty:%d\tbutton:%d\tstate:%d\n",
|
||||
e.Timestamp, e.Type, e.Which, e.X, e.Y, e.Button, e.State)
|
||||
|
@ -195,6 +207,7 @@ func (w *GameWindow) HandleEvents() {
|
|||
}
|
||||
|
||||
func (w *GameWindow) HandleKeyboardEvent(e *sdl.KeyboardEvent) {
|
||||
// Key toggles (directly use the keyboard events to something)
|
||||
if e.Keysym.Sym == sdl.K_ESCAPE {
|
||||
log.Println("Esc quitting")
|
||||
w.StopRunning()
|
||||
|
@ -215,118 +228,89 @@ func (w *GameWindow) HandleKeyboardEvent(e *sdl.KeyboardEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
// Movement
|
||||
if w.IsFreelookOn() {
|
||||
w.handleFreelookMovement(e)
|
||||
// Key states (just set a boolean whether the key is actively being pressed)
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.keystates[e.Keysym.Sym] = true
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.keystates[e.Keysym.Sym] = false
|
||||
}
|
||||
}
|
||||
|
||||
func (w *GameWindow) handleFreelookMovement() {
|
||||
// Forward/Back
|
||||
if w.keystates[sdl.K_w] {
|
||||
w.Camera.SetZVelocity(1)
|
||||
} else if w.keystates[sdl.K_s] {
|
||||
w.Camera.SetZVelocity(-1)
|
||||
} else {
|
||||
w.handleGameKeyboardMovement(e)
|
||||
w.Camera.SetZVelocity(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *GameWindow) handleFreelookMovement(e *sdl.KeyboardEvent) {
|
||||
if e.Keysym.Sym == sdl.K_w {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetZVelocity(1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetZVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_a {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetXVelocity(-1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetXVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_s {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetZVelocity(-1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetZVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_d {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetXVelocity(1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetXVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_q {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetYVelocity(-1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetYVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_e {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetYVelocity(1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetYVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_z {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetRotationVelocity(-1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetRotationVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_x {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetRotationVelocity(1)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetRotationVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_LSHIFT {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Camera.SetFastMoveVelocity(2)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Camera.SetFastMoveVelocity(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *GameWindow) HandleMouseMovementEvent(e *sdl.MouseMotionEvent) {
|
||||
if w.IsFreelookOn() {
|
||||
if e.XRel != 0 || e.YRel != 0 {
|
||||
w.Camera.Rotate(e.XRel, e.YRel, 0)
|
||||
}
|
||||
// Left/Right
|
||||
if w.keystates[sdl.K_d] {
|
||||
w.Camera.SetXVelocity(1)
|
||||
} else if w.keystates[sdl.K_a] {
|
||||
w.Camera.SetXVelocity(-1)
|
||||
} else {
|
||||
w.handleGameMouseMovement(e)
|
||||
w.Camera.SetXVelocity(0)
|
||||
}
|
||||
|
||||
// Up/Down
|
||||
if w.keystates[sdl.K_e] {
|
||||
w.Camera.SetYVelocity(1)
|
||||
} else if w.keystates[sdl.K_q] {
|
||||
w.Camera.SetYVelocity(-1)
|
||||
} else {
|
||||
w.Camera.SetYVelocity(0)
|
||||
}
|
||||
|
||||
// Spin
|
||||
if w.keystates[sdl.K_x] {
|
||||
w.Camera.SetRotationVelocity(1)
|
||||
} else if w.keystates[sdl.K_z] {
|
||||
w.Camera.SetRotationVelocity(-1)
|
||||
} else {
|
||||
w.Camera.SetRotationVelocity(0)
|
||||
}
|
||||
|
||||
// Faster
|
||||
if w.keystates[sdl.K_LSHIFT] {
|
||||
w.Camera.SetFastMoveVelocity(2)
|
||||
} else {
|
||||
w.Camera.SetFastMoveVelocity(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *GameWindow) handleGameKeyboardMovement(e *sdl.KeyboardEvent) {
|
||||
if e.Keysym.Sym == sdl.K_a {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Paddle.SetXVelocity(-0.5)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Paddle.SetXVelocity(0)
|
||||
}
|
||||
}
|
||||
if e.Keysym.Sym == sdl.K_d {
|
||||
if e.Type == sdl.KEYDOWN {
|
||||
w.Paddle.SetXVelocity(0.5)
|
||||
} else if e.Type == sdl.KEYUP {
|
||||
w.Paddle.SetXVelocity(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *GameWindow) handleGameMouseMovement(e *sdl.MouseMotionEvent) {
|
||||
func (w *GameWindow) handleMouseMovementEvent(e *sdl.MouseMotionEvent) {
|
||||
if e.XRel != 0 || e.YRel != 0 {
|
||||
v := float32(e.XRel) / 50.0
|
||||
w.Paddle.SetXVelocity(v)
|
||||
w.mouseMoved = true
|
||||
if w.IsFreelookOn() {
|
||||
w.Camera.Rotate(e.XRel, e.YRel, 0)
|
||||
} else {
|
||||
// Paddle movement
|
||||
v := float32(e.XRel) / 50.0
|
||||
w.Paddle.SetXVelocity(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *GameWindow) handleGameKeyboardMovement() {
|
||||
if w.keystates[sdl.K_d] {
|
||||
w.Paddle.SetXVelocity(0.5)
|
||||
} else if w.keystates[sdl.K_a] {
|
||||
w.Paddle.SetXVelocity(-0.5)
|
||||
} else {
|
||||
w.Paddle.SetXVelocity(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *GameWindow) ensurePaddleBoundary() {
|
||||
if w.Paddle.Translation.X() < w.NegativeBoundary.X() {
|
||||
w.Paddle.Translation[0] = w.NegativeBoundary.X()
|
||||
} else if w.Paddle.Translation.X() > w.PositiveBoundary.X() {
|
||||
w.Paddle.Translation[0] = w.PositiveBoundary.X()
|
||||
offset := w.Paddle.Width / 2.0
|
||||
furthestLeft := w.NegativeBoundary.X() + offset
|
||||
furthestRight := w.PositiveBoundary.X() - offset
|
||||
if w.Paddle.Translation.X() < furthestLeft {
|
||||
w.Paddle.Translation[0] = furthestLeft
|
||||
} else if w.Paddle.Translation.X() > furthestRight {
|
||||
w.Paddle.Translation[0] = furthestRight
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package gl_objects
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_helpers"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
type Material struct {
|
||||
TextureDiffuse int32
|
||||
TextureSpecular int32
|
||||
Shininess float32
|
||||
Color mgl32.Vec4
|
||||
TextureOn bool
|
||||
TextureId uint32
|
||||
}
|
||||
|
||||
func NewMaterial() *Material {
|
||||
m := Material{
|
||||
Shininess: 128.0,
|
||||
Color: mgl32.Vec4{1, 1, 1, 1},
|
||||
TextureOn: false,
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *Material) GLDraw(glProgram uint32) {
|
||||
gl_helpers.SetUniformInt(glProgram, "material.textureDiffuse", m.TextureDiffuse)
|
||||
gl_helpers.SetUniformInt(glProgram, "material.textureSpecular", m.TextureSpecular)
|
||||
|
||||
gl_helpers.SetUniformFloat(glProgram, "material.shininess", m.Shininess)
|
||||
gl_helpers.SetUniformVec4f(glProgram, "material.color", m.Color)
|
||||
|
||||
var tOn int32 = 0
|
||||
if m.TextureOn {
|
||||
tOn = 1
|
||||
}
|
||||
gl_helpers.SetUniformInt(glProgram, "material.textureOn", tOn)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package gl_objects
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_helpers"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
// Point Light
|
||||
type PointLight struct {
|
||||
Position mgl32.Vec3
|
||||
AmbientColor mgl32.Vec4
|
||||
DiffuseColor mgl32.Vec4
|
||||
SpecularColor mgl32.Vec4
|
||||
}
|
||||
|
||||
func NewPointLight(position mgl32.Vec3) *PointLight {
|
||||
d := PointLight{
|
||||
Position: position,
|
||||
AmbientColor: mgl32.Vec4{0.2, 0.2, 0.2, 1},
|
||||
DiffuseColor: mgl32.Vec4{1, 1, 1, 1},
|
||||
SpecularColor: mgl32.Vec4{1, 1, 1, 1},
|
||||
}
|
||||
return &d
|
||||
}
|
||||
|
||||
func (l *PointLight) GLInit(glProgram uint32) {
|
||||
gl_helpers.SetUniformVec3f(glProgram, "light.position", l.Position)
|
||||
gl_helpers.SetUniformVec4f(glProgram, "light.ambient", l.AmbientColor)
|
||||
gl_helpers.SetUniformVec4f(glProgram, "light.diffuse", l.DiffuseColor)
|
||||
gl_helpers.SetUniformVec4f(glProgram, "light.specular", l.SpecularColor)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package gl_helpers
|
||||
package glhelpers
|
||||
|
||||
var FragmentShaderSource string = `
|
||||
#version 330
|
|
@ -1,4 +1,4 @@
|
|||
package gl_helpers
|
||||
package glhelpers
|
||||
|
||||
// https://github.com/go-gl/example
|
||||
|
||||
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"image"
|
||||
"image/draw"
|
||||
_ "image/png"
|
||||
"os"
|
||||
"strings"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package gl_helpers
|
||||
package glhelpers
|
||||
|
||||
// Adapted from the OpenGL Superbible
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
package gl_objects
|
||||
package globjects
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
gl "github.com/go-gl/gl/v3.1/gles2"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_helpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers"
|
||||
)
|
||||
|
||||
type Box struct {
|
||||
|
@ -22,8 +20,8 @@ type Box struct {
|
|||
VertexArray []float32
|
||||
Material *Material
|
||||
GLProgram uint32
|
||||
GLVertexArrayId uint32
|
||||
GLVertexBufferId uint32
|
||||
GLVertexArrayID uint32
|
||||
GLVertexBufferID uint32
|
||||
Wireframe bool
|
||||
}
|
||||
|
||||
|
@ -78,11 +76,11 @@ func NewBox(
|
|||
|
||||
func (b *Box) GLInit(glProgram uint32) {
|
||||
b.GLProgram = glProgram
|
||||
gl.GenVertexArrays(1, &b.GLVertexArrayId)
|
||||
gl.BindVertexArray(b.GLVertexArrayId)
|
||||
gl.GenVertexArrays(1, &b.GLVertexArrayID)
|
||||
gl.BindVertexArray(b.GLVertexArrayID)
|
||||
|
||||
gl.GenBuffers(1, &b.GLVertexBufferId)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.GLVertexBufferId)
|
||||
gl.GenBuffers(1, &b.GLVertexBufferID)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.GLVertexBufferID)
|
||||
sizeOfFloat := 4
|
||||
gl.BufferData(gl.ARRAY_BUFFER, len(b.VertexArray)*sizeOfFloat, gl.Ptr(b.VertexArray), gl.STATIC_DRAW)
|
||||
|
||||
|
@ -109,7 +107,7 @@ func (b *Box) GLDraw() {
|
|||
|
||||
model := translate.Mul4(rotate)
|
||||
|
||||
gl_helpers.SetUniformMatrix4f(b.GLProgram, "model", &model)
|
||||
glhelpers.SetUniformMatrix4f(b.GLProgram, "model", &model)
|
||||
|
||||
b.Material.GLDraw(b.GLProgram)
|
||||
|
||||
|
@ -121,22 +119,22 @@ func (b *Box) GLDraw() {
|
|||
}
|
||||
|
||||
func (b *Box) glDrawTriangles() {
|
||||
gl_helpers.SetUniformInt(b.GLProgram, "lightsOn", 1)
|
||||
glhelpers.SetUniformInt(b.GLProgram, "lightsOn", 1)
|
||||
|
||||
gl.BindVertexArray(b.GLVertexArrayId)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.GLVertexBufferId)
|
||||
gl.BindVertexArray(b.GLVertexArrayID)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.GLVertexBufferID)
|
||||
|
||||
if b.Material.TextureOn {
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, b.Material.TextureId)
|
||||
gl.BindTexture(gl.TEXTURE_2D, b.Material.TextureID)
|
||||
}
|
||||
|
||||
gl_helpers.UpdateVertexAttribs(b.GLProgram, VertexSize)
|
||||
glhelpers.UpdateVertexAttribs(b.GLProgram, VertexSize)
|
||||
gl.DrawArrays(gl.TRIANGLES, 0, 6*2*3) // 6 sides, 2 triangles, 3 points each
|
||||
}
|
||||
|
||||
func (b *Box) glDrawLineLoop() {
|
||||
gl_helpers.SetUniformInt(b.GLProgram, "lightsOn", 0)
|
||||
glhelpers.SetUniformInt(b.GLProgram, "lightsOn", 0)
|
||||
for _, face := range b.Faces {
|
||||
face.GLDrawLineLoop(b.GLProgram)
|
||||
}
|
||||
|
@ -164,7 +162,6 @@ func (b *Box) GetVertexArray() []float32 {
|
|||
}
|
||||
|
||||
func (b *Box) ToggleWireframe() {
|
||||
log.Println("Wireframe toggled: ", b.Wireframe)
|
||||
b.Wireframe = !b.Wireframe
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package gl_objects
|
||||
package globjects
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_helpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
|
@ -47,8 +47,8 @@ func (c *Camera) GetCenter() mgl32.Vec3 {
|
|||
|
||||
func (c *Camera) GLDraw() {
|
||||
cameraMatrix := mgl32.LookAtV(c.Position, c.GetCenter(), c.Up)
|
||||
gl_helpers.SetUniformMatrix4f(c.GLProgram, "camera", &cameraMatrix)
|
||||
gl_helpers.SetUniformVec3f(c.GLProgram, "cameraPos", c.Position)
|
||||
glhelpers.SetUniformMatrix4f(c.GLProgram, "camera", &cameraMatrix)
|
||||
glhelpers.SetUniformVec3f(c.GLProgram, "cameraPos", c.Position)
|
||||
}
|
||||
|
||||
func (c *Camera) Update() {
|
|
@ -1,12 +1,12 @@
|
|||
package gl_objects
|
||||
package globjects
|
||||
|
||||
import (
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_helpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers"
|
||||
)
|
||||
|
||||
// Directional Light
|
||||
// DirectionalLight implements effectively an infinitely far away point light.
|
||||
type DirectionalLight struct {
|
||||
Direction mgl32.Vec3 // the direction the light is travelling from the sun
|
||||
AmbientColor mgl32.Vec4
|
||||
|
@ -25,8 +25,8 @@ func NewDirectionalLight(direction mgl32.Vec3) *DirectionalLight {
|
|||
}
|
||||
|
||||
func (l *DirectionalLight) GLInit(glProgram uint32) {
|
||||
gl_helpers.SetUniformVec3f(glProgram, "dirLight.direction", l.Direction)
|
||||
gl_helpers.SetUniformVec4f(glProgram, "dirLight.ambient", l.AmbientColor)
|
||||
gl_helpers.SetUniformVec4f(glProgram, "dirLight.diffuse", l.DiffuseColor)
|
||||
gl_helpers.SetUniformVec4f(glProgram, "dirLight.specular", l.SpecularColor)
|
||||
glhelpers.SetUniformVec3f(glProgram, "dirLight.direction", l.Direction)
|
||||
glhelpers.SetUniformVec4f(glProgram, "dirLight.ambient", l.AmbientColor)
|
||||
glhelpers.SetUniformVec4f(glProgram, "dirLight.diffuse", l.DiffuseColor)
|
||||
glhelpers.SetUniformVec4f(glProgram, "dirLight.specular", l.SpecularColor)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package gl_objects
|
||||
package globjects
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
@ -6,15 +6,15 @@ import (
|
|||
gl "github.com/go-gl/gl/v3.1/gles2"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/gl_helpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/math_helpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers"
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/mathhelpers"
|
||||
)
|
||||
|
||||
type Face struct {
|
||||
Triangles []*Triangle
|
||||
LineLoopVertexArray []float32
|
||||
GLLineLoopVertexArrayId uint32
|
||||
GLLineLoopVertexBufferId uint32
|
||||
GLLineLoopVertexArrayID uint32
|
||||
GLLineLoopVertexBufferID uint32
|
||||
Material *Material
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ func newFaceVertex(
|
|||
v := mgl32.Vec3{xoffset, 0, zoffset}
|
||||
|
||||
up := mgl32.Vec3{0, 1, 0}
|
||||
angle, err := math_helpers.AngleBetweenVectors(up, normal)
|
||||
angle, err := mathhelpers.AngleBetweenVectors(up, normal)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
|
@ -133,21 +133,21 @@ func (f *Face) GetLineLoopVertexArray() []float32 {
|
|||
}
|
||||
|
||||
func (f *Face) GLDrawLineLoop(glProgram uint32) {
|
||||
gl.BindVertexArray(f.GLLineLoopVertexArrayId)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, f.GLLineLoopVertexBufferId)
|
||||
gl.BindVertexArray(f.GLLineLoopVertexArrayID)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, f.GLLineLoopVertexBufferID)
|
||||
|
||||
gl_helpers.UpdateVertexAttribs(glProgram, VertexSize)
|
||||
glhelpers.UpdateVertexAttribs(glProgram, VertexSize)
|
||||
gl.DrawArrays(gl.LINE_LOOP, 0, 4)
|
||||
}
|
||||
|
||||
func (f *Face) GLInit(glProgram uint32) {
|
||||
gl_helpers.UpdateVertexAttribs(glProgram, VertexSize)
|
||||
glhelpers.UpdateVertexAttribs(glProgram, VertexSize)
|
||||
// LineLoop setup
|
||||
gl.GenVertexArrays(1, &f.GLLineLoopVertexArrayId)
|
||||
gl.BindVertexArray(f.GLLineLoopVertexArrayId)
|
||||
gl.GenVertexArrays(1, &f.GLLineLoopVertexArrayID)
|
||||
gl.BindVertexArray(f.GLLineLoopVertexArrayID)
|
||||
|
||||
gl.GenBuffers(1, &f.GLLineLoopVertexBufferId)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, f.GLLineLoopVertexBufferId)
|
||||
gl.GenBuffers(1, &f.GLLineLoopVertexBufferID)
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, f.GLLineLoopVertexBufferID)
|
||||
sizeOfFloat := 4
|
||||
gl.BufferData(gl.ARRAY_BUFFER, len(f.LineLoopVertexArray)*sizeOfFloat, gl.Ptr(f.LineLoopVertexArray), gl.STATIC_DRAW)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package gl_objects
|
||||
package globjects
|
||||
|
||||
type GLObject interface {
|
||||
Update()
|
|
@ -0,0 +1,39 @@
|
|||
package globjects
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
type Material struct {
|
||||
TextureDiffuse int32
|
||||
TextureSpecular int32
|
||||
Shininess float32
|
||||
Color mgl32.Vec4
|
||||
TextureOn bool
|
||||
TextureID uint32
|
||||
}
|
||||
|
||||
func NewMaterial() *Material {
|
||||
m := Material{
|
||||
Shininess: 128.0,
|
||||
Color: mgl32.Vec4{1, 1, 1, 1},
|
||||
TextureOn: false,
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (m *Material) GLDraw(glProgram uint32) {
|
||||
glhelpers.SetUniformInt(glProgram, "material.textureDiffuse", m.TextureDiffuse)
|
||||
glhelpers.SetUniformInt(glProgram, "material.textureSpecular", m.TextureSpecular)
|
||||
|
||||
glhelpers.SetUniformFloat(glProgram, "material.shininess", m.Shininess)
|
||||
glhelpers.SetUniformVec4f(glProgram, "material.color", m.Color)
|
||||
|
||||
var tOn int32 = 0
|
||||
if m.TextureOn {
|
||||
tOn = 1
|
||||
}
|
||||
glhelpers.SetUniformInt(glProgram, "material.textureOn", tOn)
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package globjects
|
||||
|
||||
import (
|
||||
"gitea.wisellama.rocks/Wisellama/carpy-breakout/pkg/glhelpers"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
// PointLight implements a specifically position light source where
|
||||
// light radiates out in all directions and fades away the further
|
||||
// away you get.
|
||||
type PointLight struct {
|
||||
Position mgl32.Vec3
|
||||
AmbientColor mgl32.Vec4
|
||||
DiffuseColor mgl32.Vec4
|
||||
SpecularColor mgl32.Vec4
|
||||
}
|
||||
|
||||
func NewPointLight(position mgl32.Vec3) *PointLight {
|
||||
d := PointLight{
|
||||
Position: position,
|
||||
AmbientColor: mgl32.Vec4{0.2, 0.2, 0.2, 1},
|
||||
DiffuseColor: mgl32.Vec4{1, 1, 1, 1},
|
||||
SpecularColor: mgl32.Vec4{1, 1, 1, 1},
|
||||
}
|
||||
return &d
|
||||
}
|
||||
|
||||
func (l *PointLight) GLInit(glProgram uint32) {
|
||||
glhelpers.SetUniformVec3f(glProgram, "light.position", l.Position)
|
||||
glhelpers.SetUniformVec4f(glProgram, "light.ambient", l.AmbientColor)
|
||||
glhelpers.SetUniformVec4f(glProgram, "light.diffuse", l.DiffuseColor)
|
||||
glhelpers.SetUniformVec4f(glProgram, "light.specular", l.SpecularColor)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package gl_objects
|
||||
package globjects
|
||||
|
||||
type Triangle struct {
|
||||
Vertices []*Vertex
|
|
@ -1,4 +1,4 @@
|
|||
package gl_objects
|
||||
package globjects
|
||||
|
||||
import (
|
||||
"github.com/go-gl/mathgl/mgl32"
|
|
@ -1,4 +1,4 @@
|
|||
package math_helpers
|
||||
package mathhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
Loading…
Reference in New Issue