You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

255 lines
5.2 KiB

package main
import (
"embed"
_ "image/png"
"log"
"math"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2"
"github.com/kvartborg/vector"
"github.com/solarlune/tetra3d"
)
const (
X = iota
Y
Z
)
const (
defaultW = 320
defaultH = 240
)
var boxBrown = tetra3d.NewColor(66/255.0, 40/255.0, 1/255.0, 255/255.0)
//go:embed asset
var assetFS embed.FS
type Game struct {
GameScene *tetra3d.Scene
Camera *tetra3d.Camera
CameraTilt float64
CameraRotate float64
NativeResolution bool
boxes []*tetra3d.Model
boxVelocities []vector.Vector
PrevMousePosition vector.Vector
w, h int
}
func NewGame() *Game {
g := &Game{}
data, err := assetFS.ReadFile("asset/shapes.gltf")
if err != nil {
panic(err)
}
library, err := tetra3d.LoadGLTFData(data, nil)
if err != nil {
panic(err)
}
g.GameScene = library.FindScene("Scene")
//g.GameScene = tetra3d.NewScene("Scene")
g.GameScene.FogMode = tetra3d.FogOff
log.Println(g.GameScene.Root.WorldPosition())
// Tetra uses OpenGL's coordinate system (+X = Right, +Y = Up, +Z = Back),
// in comparison to Blender's coordinate system (+X = Right, +Y = Forward,
// +Z = Up).
g.SetSize(defaultW, defaultH)
const mapSize = 100
for y := 0.0; y < mapSize; y++ {
for x := 0.0; x < mapSize; x++ {
plane := tetra3d.NewCube()
plane.ApplyMatrix(tetra3d.NewMatrix4Scale(5, 0.5, 5))
plane.SetVertexColor(tetra3d.Color{0, 0, 255, 255})
planeModel := tetra3d.NewModel(plane, "plane")
g.GameScene.Root.AddChildren(planeModel)
//planeModel.Rotate(0, 1, 0, math.Pi)
planeModel.SetLocalPosition(vector.Vector{x * 10, -1.5, y * 10})
}
}
return g
}
func (g *Game) updateCamera() {
pos := vector.Vector{0, 2, 2}
if g.Camera == nil {
g.Camera = tetra3d.NewCamera(g.w, g.h)
} else {
pos = g.Camera.LocalPosition()
g.Camera.Resize(g.w, g.h)
}
g.Camera.SetLocalPosition(pos)
}
func (g *Game) Update() error {
pos := g.Camera.LocalPosition()
pos[X] += 0.005
g.Camera.SetLocalPosition(pos)
/*x, y := ebiten.CursorPosition()
if x < 0 {
x = 0
} else if x > g.w {
x = g.w
}
if y < 0 {
y = 0
} else if y > g.h {
y = g.h
}
pctX := float64(x) / float64(g.w)
pctY := float64(y) / float64(g.h)
if pctX < 0 {
pctX = 0
} else if pctX > 1 {
pctX = 1
}
if pctY < 0 {
pctY = 0
} else if pctY > 1 {
pctY = 1
}*/
// Rotating the camera with the mouse
// Rotate and tilt the camera according to mouse movements
mx, my := ebiten.CursorPosition()
mv := vector.Vector{float64(mx), float64(my)}
diff := mv.Sub(g.PrevMousePosition)
g.CameraTilt -= diff[1] * 0.005
g.CameraRotate -= diff[0] * 0.005
g.CameraTilt = math.Max(math.Min(g.CameraTilt, math.Pi/2-0.1), -math.Pi/2+0.1)
tilt := tetra3d.NewMatrix4Rotate(1, 0, 0, g.CameraTilt)
rotate := tetra3d.NewMatrix4Rotate(0, 1, 0, g.CameraRotate)
// Order of this is important - tilt * rotate works, rotate * tilt does not, lol
g.Camera.SetLocalRotation(tilt.Mult(rotate))
g.PrevMousePosition = mv.Clone()
//localRot := tetra3d.NewMatrix4Rotate(1, 1, 1, math.Pi/4-(math.Pi*pctX))
//g.Camera.SetLocalRotation(localRot)
//g.Camera.Rotate(1, 0, 0, math.Pi/4-(math.Pi*pctY))
//localRot = localRot.Rotated(0, 1, 0, math.Pi/4-(math.Pi*pctX))
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
g.fireBox()
}
for i, box := range g.boxes {
position := box.LocalPosition()
//log.Printf("%d %+v", i, position)
if position[Y] <= -0.5 {
// Hit ground.
continue
}
pos := box.LocalPosition()
box.SetLocalPosition(pos.Add(g.boxVelocities[i]))
if g.boxVelocities[i][Y] > -0.1 {
g.boxVelocities[i][Y] -= 0.001
}
}
// TODO rotate Y
//_ = pctY
//log.Println(x, y, g.w, g.h, pctX, pctY)
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
g.Camera.Clear()
g.Camera.RenderNodes(g.GameScene, g.GameScene.Root)
screen.DrawImage(g.Camera.ColorTexture, nil)
//g.Camera.DrawDebugWireframe(screen, g.GameScene.Root, color.Black)
g.Camera.DrawDebugText(screen, 1)
}
func (g *Game) Layout(w, h int) (int, int) {
if g.NativeResolution && (w != g.w || h != g.h) {
g.SetSize(w, h)
}
return g.w, g.h
}
func (g *Game) SetSize(w, h int) {
g.w, g.h = w, h
g.updateCamera()
}
func (g *Game) fireBox() {
mesh := tetra3d.NewCube()
mesh.SetVertexColor(*boxBrown)
mesh.ApplyMatrix(tetra3d.NewMatrix4Scale(0.5, 0.5, 0.5))
box := tetra3d.NewModel(mesh, "Cube")
box.SetLocalPosition(g.Camera.LocalPosition())
//cameraPos := g.Camera.LocalPosition()
cameraRotation := g.Camera.LocalRotation()
scale := 0.2
velocity := vector.Vector{
-cameraRotation[2][0] * scale,
-cameraRotation[2][1] * scale,
-cameraRotation[2][2] * scale,
}
velocity[Y] += 0.1
//log.Printf("%+v", velocity)
g.boxes = append(g.boxes, box)
g.boxVelocities = append(g.boxVelocities, velocity)
g.GameScene.Root.AddChildren(box)
}
func main() {
ebiten.SetWindowTitle("Brown Box Earth Simulator")
ebiten.SetWindowResizable(true)
ebiten.SetMaxTPS(144)
ebiten.SetRunnableOnUnfocused(true) // Note - this currently does nothing in ebiten
ebiten.SetWindowClosingHandled(false) // TODO
ebiten.SetFPSMode(ebiten.FPSModeVsyncOn)
ebiten.SetCursorMode(ebiten.CursorModeCaptured)
g := NewGame()
parseFlags(g)
if err := ebiten.RunGame(g); err != nil {
panic(err)
}
}