Compare commits

...

6 Commits

16 changed files with 322 additions and 131 deletions

View File

@ -8,7 +8,8 @@
- [ ] Change layout when playing in portrait mode.
- [X] Fix wobble animation.
- [ ] Add more unit tests?
- [ ] Fix z-fighting of monsters.
- [ ] Add exploding animation of monsters.
- [X] Fix z-fighting of monsters.
- [X] Add exploding animation of monsters.
- [ ] Add audio settings (music & sound volume).
- [X] Hearts must be saved as well for resume.
- [X] Hearts must be saved as well for resume.
- [ ] Add demo mode.

View File

@ -1,6 +1,8 @@
package main
import (
"log"
"opslag.de/schobers/tins2021"
"opslag.de/schobers/zntg/play"
"opslag.de/schobers/zntg/ui"
@ -61,19 +63,24 @@ func (a *app) Init(ctx ui.Context) error {
})
a.context.ShowMainMenu(ctx)
err := a.context.Audio.LoadSample(
if err := a.context.Audio.LoadSample(
"level_completed.mp3",
"level_game_over.mp3",
"level_new_high_score.mp3",
"menu_interaction.mp3",
"monster_jump.mp3",
"player_collect_heart.mp3",
"player_collect_star.mp3",
"player_hurt.mp3",
); err != nil {
log.Printf("failed to load samples; %v\n", err)
}
if err := a.context.Audio.LoadSampleVolume(-1, "menu_interaction.mp3"); err != nil {
log.Printf("failed to load samples; %v\n", err)
}
if err := a.context.Audio.LoadSampleVolume(-2,
"monster_jump.mp3",
"player_move.mp3",
)
if err != nil {
return err
); err != nil {
log.Printf("failed to load samples; %v\n", err)
}
return nil

View File

@ -15,9 +15,11 @@ type appContext struct {
Score *tins2021.ScoreState
Debug bool
StarTexture tins2021.AnimatedTexture
HeartTexture tins2021.AnimatedTexture
MonsterTextures map[tins2021.MonsterType]tins2021.AnimatedTexture
StarTexture tins2021.AnimatedTexture
HeartTexture tins2021.AnimatedTexture
MonsterTextureNames map[tins2021.MonsterType]string
MonsterTextures map[tins2021.MonsterType]tins2021.AnimatedTexture
DyingMonsterTextures map[tins2021.MonsterType]tins2021.AnimatedTexture
Audio *AudioPlayer
MenuMusic *Music
@ -28,17 +30,27 @@ type appContext struct {
func newAppContext(ctx ui.Context, settings *tins2021.Settings, score *tins2021.ScoreState, setView func(ui.Control)) *appContext {
textures := textureGenerator{}
app := &appContext{
setView: setView,
Settings: settings,
Audio: NewAudioPlayer(ctx.Resources(), "resources/sounds/"),
Score: score,
StarTexture: newAnimatedTexture(ctx, "star", defaultAnimationFrames, textures.Star),
HeartTexture: newAnimatedTexture(ctx, "heart", defaultAnimationFrames, textures.Heart),
MonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{
tins2021.MonsterTypeStraight: newAnimatedTexture(ctx, "straight-walking-monster", defaultAnimationFrames, textures.StraightWalkingMonster),
tins2021.MonsterTypeRandom: newAnimatedTexture(ctx, "random-walking-monster", defaultAnimationFrames, textures.RandomWalkingMonster),
tins2021.MonsterTypeChaser: newAnimatedTexture(ctx, "chasing-monster", defaultAnimationFrames, textures.ChasingMonster),
},
setView: setView,
Settings: settings,
Audio: NewAudioPlayer(ctx.Resources(), "resources/sounds/"),
Score: score,
StarTexture: newAnimatedTexture(ctx, "star", defaultAnimationFrames, textures.Star),
HeartTexture: newAnimatedTexture(ctx, "heart", defaultAnimationFrames, textures.Heart),
MonsterTextureNames: map[tins2021.MonsterType]string{},
MonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{},
DyingMonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{},
}
monsterNames := map[tins2021.MonsterType]string{
tins2021.MonsterTypeStraight: "straight-walking",
tins2021.MonsterTypeRandom: "random-walking",
tins2021.MonsterTypeChaser: "chasing",
}
for typ, name := range monsterNames {
textureName := fmt.Sprintf("%s-monster", name)
app.MonsterTextureNames[typ] = textureName
app.MonsterTextures[typ] = newAnimatedTexture(ctx, textureName, defaultAnimationFrames, textures.Monster(typ))
app.DyingMonsterTextures[typ] = newAnimatedTexture(ctx, fmt.Sprintf("%s-dying-monster", name), defaultAnimationFrames, textures.DyingMonster(typ))
}
return app

View File

@ -9,6 +9,8 @@ import (
"github.com/faiface/beep/effects"
"github.com/faiface/beep/mp3"
"github.com/faiface/beep/speaker"
"github.com/pkg/errors"
"opslag.de/schobers/ut"
"opslag.de/schobers/zntg/ui"
)
@ -39,15 +41,23 @@ func NewAudioPlayer(resources ui.Resources, prefix string) *AudioPlayer {
}
func (p *AudioPlayer) LoadSample(name ...string) error {
for _, name := range name {
if _, err := p.openSample(name); err != nil {
return err
}
}
return nil
return p.LoadSampleVolume(0, name...)
}
func (p *AudioPlayer) openSample(name string) (Sample, error) {
func (p *AudioPlayer) LoadSampleVolume(volume float64, name ...string) error {
var all error
for _, name := range name {
if _, err := p.openSample(name, volume); err != nil {
all = ut.ErrCombine(all, errors.Wrap(err, "failed to open sample"))
}
}
if all == nil {
return nil
}
return errors.WithMessage(all, "failed to open one or more samples")
}
func (p *AudioPlayer) openSample(name string, volume float64) (Sample, error) {
sample, ok := p.Samples[name]
if ok {
return sample, nil
@ -60,17 +70,22 @@ func (p *AudioPlayer) openSample(name string) (Sample, error) {
buffer := beep.NewBuffer(format)
buffer.Append(stream)
sample = Sample{Buffer: buffer, SampleRate: format.SampleRate}
sample = Sample{Buffer: buffer, Volume: volume, SampleRate: format.SampleRate}
p.Samples[name] = sample
return sample, nil
}
func (p *AudioPlayer) PlaySample(name string) error {
sample, err := p.openSample(name)
sample, err := p.openSample(name, 0)
if err != nil {
return err
}
speaker.Play(p.resample(sample.Stream(), sample.SampleRate))
speaker.Play(&effects.Volume{
Streamer: p.resample(sample.Stream(), sample.SampleRate),
Base: 2,
Volume: float64(sample.Volume),
Silent: false,
})
return nil
}
@ -143,6 +158,7 @@ func (m Music) Finished(player *AudioPlayer) {
type Sample struct {
*beep.Buffer
Volume float64
SampleRate beep.SampleRate
}

View File

@ -36,7 +36,9 @@ func newCredits(app *appContext, ctx ui.Context) *credits {
"zntg: an abstraction for rendering (UIs)",
" - https://opslag.de/schobers/zntg", "",
"allg5: an Allegro abstraction for Go",
" - https://opslag.de/schobers/allg5",
" - https://opslag.de/schobers/allg5", "",
"ut: a utility library",
" - https://opslag.de/schobers/ut", "",
"",
"**... and also using**",
"Allegro: cross-platform development library",
@ -58,6 +60,10 @@ func newCredits(app *appContext, ctx ui.Context) *credits {
" - https://github.com/nfnt/resize", "",
"testify: a testing library for Go",
" - https://github.com/stretchr/testify", "",
"beep: a sound library",
" - https://github.com/faiface/beep", "",
"oto: a low level sound library",
" - https://https://github.com/hajimehoshi/oto", "",
"",
"# THANKS",
"",
@ -79,7 +85,9 @@ func (c *credits) Handle(ctx ui.Context, e ui.Event) bool {
s := c.content[c.hovering]
if strings.HasPrefix(s, " - https://") {
url := s[3:]
c.openBrowser(url)
if err := c.openBrowser(url); err != nil {
log.Println(err)
}
}
}
case *ui.KeyDownEvent:
@ -146,20 +154,16 @@ func (c *credits) fonts(ctx ui.Context) []ui.Font {
return fonts
}
func (c *credits) openBrowser(url string) {
var err error
func (c *credits) openBrowser(url string) error {
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
return exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
return exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
if err != nil {
log.Println(err)
return fmt.Errorf("unsupported platform")
}
}

View File

@ -44,6 +44,7 @@ func (l *infoLegend) Render(ctx ui.Context) {
}
func newInfo(app *appContext, ctx ui.Context) ui.Control {
monsterName := func(typ tins2021.MonsterType) string { return app.MonsterTextureNames[typ] }
legend := ui.BuildStackPanel(ui.OrientationVertical, func(p *ui.StackPanel) {
p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("star", infoLegendIconSize),
@ -54,15 +55,15 @@ func newInfo(app *appContext, ctx ui.Context) ui.Control {
Description: "Gives (back) a life.",
})
p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("straight-walking-monster", infoLegendIconSize),
Icon: ctx.Textures().ScaledByName(monsterName(tins2021.MonsterTypeStraight), infoLegendIconSize),
Description: "Monster that walks over a fixed diagonal.",
})
p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("random-walking-monster", infoLegendIconSize),
Icon: ctx.Textures().ScaledByName(monsterName(tins2021.MonsterTypeRandom), infoLegendIconSize),
Description: "Monster that walks randomly.",
})
p.AddChild(&infoLegend{
Icon: ctx.Textures().ScaledByName("chasing-monster", infoLegendIconSize),
Icon: ctx.Textures().ScaledByName(monsterName(tins2021.MonsterTypeChaser), infoLegendIconSize),
Description: "Monster that walks towards you.",
})
})

View File

@ -3,6 +3,7 @@ package main
import (
"fmt"
"image/color"
"log"
"math/rand"
"strconv"
"time"
@ -24,8 +25,10 @@ type levelController struct {
Inverted tins2021.NamedTexture
Animations map[string]*tins2021.Animations
IdleMonsters *tins2021.Animations
MovingMonsters *tins2021.Animations
IdleMonsters *tins2021.Animations
MovingMonsters *tins2021.Animations
DyingMonsters *tins2021.Animations
DyingMonsterTypes map[geom.Point]tins2021.MonsterType
SmallFont *tins2021.BitmapFont
@ -134,17 +137,26 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
}
}
monsterHit := func(hit *tins2021.MonsterHit) {
r.app.Audio.PlaySample("player_hurt.mp3")
if hit == nil {
log.Printf("player was hit by monster but we don't know exactly where?\n")
}
r.DyingMonsters.Frame(hit.Position)
r.DyingMonsterTypes[hit.Position] = hit.Type
}
switch e := e.(type) {
case *ui.KeyDownEvent:
dir, ok := r.Controls[e.Key]
if ok {
stars, lives := r.Level.StarsCollected, r.Level.Lives
r.Level.MovePlayer(dir)
_, hit := r.Level.MovePlayer(dir)
switch {
case r.Level.StarsCollected > stars:
r.app.Audio.PlaySample("player_collect_star.mp3")
case r.Level.Lives < lives:
r.app.Audio.PlaySample("player_hurt.mp3")
monsterHit(hit)
case r.Level.Lives > lives:
r.app.Audio.PlaySample("player_collect_heart.mp3")
}
@ -161,6 +173,7 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
}
r.IdleMonsters.Update()
r.MovingMonsters.Update()
r.DyingMonsters.Update()
var jumped []geom.Point
for pos, animation := range r.MovingMonsters.Values {
if animation.Frame < 40 { // after 40 frames the player hit is checked
@ -168,10 +181,10 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
}
target := r.Level.MonsterTargets[pos]
if target == r.Level.Player { // player is hit
monsterHit(&tins2021.MonsterHit{Position: target, Type: r.Level.Monsters[pos].Type()})
r.Level.DestroyMonster(pos)
jumped = append(jumped, pos)
r.Level.DecrementLive()
r.app.Audio.PlaySample("player_hurt.mp3")
checkGameOver()
continue
}
@ -190,7 +203,7 @@ func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
var jumping []geom.Point
for pos, animation := range r.IdleMonsters.Values {
for animation.Frame > 0 {
if rand.Intn(10) != 0 {
if rand.Intn(100) < 37 { // P = .37
monster, ok := r.Level.Monsters[pos]
if ok && monster != nil {
target, ok := monster.FindTarget(r.Level, pos)
@ -222,8 +235,10 @@ func (r *levelController) Play(level *tins2021.Level) {
"heart": tins2021.NewAnimations(80*time.Millisecond, defaultAnimationFrames, true, true),
"monster": tins2021.NewAnimations(80*time.Millisecond, defaultAnimationFrames, true, true),
}
r.IdleMonsters = tins2021.NewAnimations(200*time.Millisecond, 100, false, false)
r.IdleMonsters = tins2021.NewAnimations(40*time.Millisecond, 100, false, false)
r.MovingMonsters = tins2021.NewAnimations(16*time.Millisecond, 50, false, false)
r.DyingMonsters = tins2021.NewAnimations(16*time.Millisecond, 20, false, false)
r.DyingMonsterTypes = map[geom.Point]tins2021.MonsterType{}
for monster := range level.Monsters {
r.IdleMonsters.Frame(monster)
}
@ -272,6 +287,10 @@ func (r levelController) Render(ctx ui.Context) {
for typ, animation := range r.app.MonsterTextures {
monsterTextures[typ] = animation.Scale(scale * .4)
}
dyingMonsterTextures := map[tins2021.MonsterType]tins2021.AnimatedTexture{}
for typ, animation := range r.app.DyingMonsterTextures {
dyingMonsterTextures[typ] = animation.Scale(scale * .4)
}
propHeight := star.FrameSize(0).Y
propOffset := geom.PtF32(-.5*float32(propHeight), -.8*float32(propHeight))
@ -319,23 +338,46 @@ func (r levelController) Render(ctx ui.Context) {
renderer.DrawTexturePoint(player, playerPosition.Add(centerTopSquare))
}
for pos, monsterType := range r.Level.Monsters {
tile := r.Level.Tiles[pos]
if tile == nil {
for y := r.Level.Bounds.Min.Y; y < r.Level.Bounds.Max.Y; y++ {
for x := r.Level.Bounds.Min.X; x < r.Level.Bounds.Max.X; x++ {
pos := geom.Pt(x, y)
monsterType, ok := r.Level.Monsters[pos]
if !ok {
continue
}
tile := r.Level.Tiles[pos]
if tile == nil {
continue
}
texture := monsterTextures[monsterType.Type()]
_, platformPos := positionOfTile(pos)
if target, ok := r.Level.MonsterTargets[pos]; ok {
_, targetPlatformPos := positionOfTile(target)
dt := float32(r.MovingMonsters.Frame(pos)) / 50.
delta := targetPlatformPos.Sub(platformPos)
curve := geom.PtF32(0, .6*geom.Sin32(dt*geom.Pi)*textureWidth)
interpolatedPos := platformPos.Add(delta.Mul(dt)).Sub(curve)
texture.Draw(renderer, interpolatedPos.Add(propOffset), r.Animations["monster"].Frame(pos))
} else {
texture.Draw(renderer, platformPos.Add(propOffset), r.Animations["monster"].Frame(pos))
}
}
}
var died []geom.Point
for pos, monster := range r.DyingMonsterTypes {
frame := r.DyingMonsters.Frame(pos)
if frame == 20 {
died = append(died, pos)
continue
}
texture := monsterTextures[monsterType.Type()]
texture := dyingMonsterTextures[monster]
_, platformPos := positionOfTile(pos)
if target, ok := r.Level.MonsterTargets[pos]; ok {
_, targetPlatformPos := positionOfTile(target)
dt := float32(r.MovingMonsters.Frame(pos)) / 50.
delta := targetPlatformPos.Sub(platformPos)
curve := geom.PtF32(0, .6*geom.Sin32(dt*geom.Pi)*textureWidth)
interpolatedPos := platformPos.Add(delta.Mul(dt)).Sub(curve)
texture.Draw(renderer, interpolatedPos.Add(propOffset), r.Animations["monster"].Frame(pos))
} else {
texture.Draw(renderer, platformPos.Add(propOffset), r.Animations["monster"].Frame(pos))
}
texture.Draw(renderer, platformPos.Add(propOffset), frame)
}
for _, pos := range died {
delete(r.DyingMonsters.Values, pos)
delete(r.DyingMonsterTypes, pos)
}
textColor := ctx.Style().Palette.Text

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -16,12 +16,29 @@ func (textureGenerator) Heart() image.Image {
return tins2021.AnimatePolygon(tins2021.CreateHeart(), tins2021.Red, defaultAnimationFrames, tins2021.MeshRotateAnimation{})
}
func (textureGenerator) monster(color string) image.Image {
return tins2021.AnimatePolygon(tins2021.CreateHexagon(), color, defaultAnimationFrames, tins2021.MeshWobbleTransformation{Wobble: 30})
func (g textureGenerator) MonsterTypeColor(typ tins2021.MonsterType) string {
switch typ {
case tins2021.MonsterTypeStraight:
return tins2021.Green
case tins2021.MonsterTypeRandom:
return tins2021.Blue
case tins2021.MonsterTypeChaser:
return tins2021.Purple
default:
panic("monster does not have a color")
}
}
func (g textureGenerator) ChasingMonster() image.Image { return g.monster(tins2021.Purple) }
func (g textureGenerator) Monster(typ tins2021.MonsterType) func() image.Image {
color := g.MonsterTypeColor(typ)
return func() image.Image {
return tins2021.AnimatePolygon(tins2021.CreateHexagon(), color, defaultAnimationFrames, tins2021.MeshWobbleTransformation{Wobble: 30})
}
}
func (g textureGenerator) RandomWalkingMonster() image.Image { return g.monster(tins2021.Blue) }
func (g textureGenerator) StraightWalkingMonster() image.Image { return g.monster(tins2021.Green) }
func (g textureGenerator) DyingMonster(typ tins2021.MonsterType) func() image.Image {
color := g.MonsterTypeColor(typ)
return func() image.Image {
return tins2021.Animate(color, defaultAnimationFrames, &tins2021.ExplodingHexagonAnimation{})
}
}

View File

@ -92,10 +92,15 @@ func (l *Level) MoveMonster(target, source geom.Point) {
l.DestroyMonster(source)
}
func (l *Level) MovePlayer(dir Direction) bool {
type MonsterHit struct {
Position geom.Point
Type MonsterType
}
func (l *Level) MovePlayer(dir Direction) (bool, *MonsterHit) {
towards, allowed := l.CanPlayerMove(dir)
if !allowed {
return false
return false, nil
}
l.Player = towards
tile := l.Tiles[towards]
@ -109,13 +114,18 @@ func (l *Level) MovePlayer(dir Direction) bool {
tile.Star = false
l.Score += 25
}
var hit *MonsterHit
if l.Monsters[towards] != nil {
hit = &MonsterHit{
Position: towards,
Type: l.Monsters[towards].Type(),
}
l.DecrementLive()
l.DestroyMonster(towards)
l.Score -= 5
}
l.Score -= 1 // for every move
return true
return true, hit
}
func (l *Level) Randomize(difficulty int, stars int) {

15
math.go Normal file
View File

@ -0,0 +1,15 @@
package tins2021
import "opslag.de/schobers/geom"
func Polar(a, r float64) geom.PointF {
return geom.PtF(r*geom.Cos(a), r*geom.Sin(a))
}
func PolarUnity(a float64) geom.PointF {
return geom.PtF(geom.Cos(a), geom.Sin(a))
}
func Percentage(i, n int) float64 {
return float64(i) / float64(n)
}

View File

@ -28,19 +28,17 @@ var (
light = fauxgl.V(.5, 1, .75).Normalize() // light direction
)
func animateMesh(mesh *fauxgl.Mesh, hexColor string, frames int, transform MeshAnimationTransformer) image.Image {
func Animate(hexColor string, frames int, animator MeshAnimator) image.Image {
const scale = 4
const s = 1.1
mesh.BiUnitCube()
matrix := fauxgl.Orthographic(-s, s, -s, s, near, far).Mul(fauxgl.LookAt(eye, center, up))
animation := image.NewNRGBA(image.Rect(0, 0, TextureSize*frames, TextureSize))
threads := ints.Max(1, runtime.NumCPU())
framesC := make(chan int, threads)
wait := parallel(threads, func() {
wait := parallel(1, func() {
context := fauxgl.NewContext(TextureSize*scale, TextureSize*scale)
color := fauxgl.HexColor(hexColor)
@ -53,9 +51,8 @@ func animateMesh(mesh *fauxgl.Mesh, hexColor string, frames int, transform MeshA
shader.AmbientColor = fauxgl.MakeColor(mustHexColor(`#7F7F7F`))
context.Shader = shader
copy := mesh.Copy()
transform.transform(copy, FrameState{Current: i, TotalFrames: frames})
context.DrawMesh(copy)
mesh := animator.animate(FrameState{Current: i, TotalFrames: frames})
context.DrawMesh(mesh)
frame := resize.Resize(TextureSize, TextureSize, context.Image(), resize.Bilinear)
draw.Copy(animation, image.Pt(i*TextureSize, 0), frame, frame.Bounds(), draw.Src, nil)
@ -70,12 +67,12 @@ func animateMesh(mesh *fauxgl.Mesh, hexColor string, frames int, transform MeshA
return animation
}
func AnimatePolygon(polygon geom.PolygonF, hexColor string, frames int, transform MeshAnimationTransformer) image.Image {
mesh := generateMeshFromPolygon(polygon, .2)
return animateMesh(mesh, hexColor, frames, transform)
func AnimatePolygon(polygon geom.PolygonF, hexColor string, frames int, transformer MeshTransformer) image.Image {
animation := newMeshAnimation(generateMeshFromPolygon(polygon, .2), transformer)
return Animate(hexColor, frames, animation)
}
func AnimateSTL(resources ui.PhysicalResources, name, hexColor string, frames int, transform MeshAnimationTransformer) image.Image {
func AnimateSTL(resources ui.PhysicalResources, name, hexColor string, frames int, transformer MeshTransformer) image.Image {
path, err := resources.FetchResource(name)
if err != nil {
panic(err)
@ -84,7 +81,53 @@ func AnimateSTL(resources ui.PhysicalResources, name, hexColor string, frames in
if err != nil {
panic(err)
}
return animateMesh(mesh, hexColor, frames, transform)
return Animate(hexColor, frames, newMeshAnimation(mesh, transformer))
}
type ExplodingHexagonAnimation struct {
Biunit fauxgl.Matrix
}
func rotateMeshAround(mesh *fauxgl.Mesh, around fauxgl.Vector, angle float64) {
mesh.Transform(fauxgl.Translate(fauxgl.V(-around.X, -around.Y, -around.Z)))
mesh.Transform(fauxgl.Rotate(fauxgl.V(0, 1, 0), angle))
mesh.Transform(fauxgl.Translate(around))
}
func (a *ExplodingHexagonAnimation) animate(s FrameState) *fauxgl.Mesh {
ani := s.Animation()
mesh := fauxgl.NewEmptyMesh()
const parts = 6
const oneThird = float64(1) / 3
const twoThirds = float64(2) / 3
const da = twoThirds * geom.Pi
partHeight := geom.Sqrt(3) / 2
for part := 0; part < parts; part++ {
a := 2 * geom.Pi * float64(part) / float64(parts)
closeLength := (float64(.25) + .75*(1-ani))
center := Polar(a, twoThirds*partHeight)
aa := a + ani*geom.Pi
far := center.Add(Polar(aa, oneThird*partHeight))
farWidth := float64(.5) * (1 - ani)
right := far.Add(Polar(aa-.5*geom.Pi, farWidth))
left := far.Add(Polar(aa+.5*geom.Pi, farWidth))
close := center.Add(Polar(aa+geom.Pi, closeLength*twoThirds*partHeight))
partMesh := generateMeshFromPolygon(geom.PolF(right, left, close), .2)
rotateMeshAround(partMesh, fauxgl.V(-center.X, -center.Y, 0), ani*geom.Pi)
mesh.Add(partMesh)
}
if s.Current == 0 {
a.Biunit = mesh.BiUnitCube()
} else {
mesh.Transform(a.Biunit)
}
return mesh
}
type FrameState struct {
@ -94,7 +137,7 @@ type FrameState struct {
func (s FrameState) Animation() float64 { return float64(s.Current) / float64(s.TotalFrames) }
func generateMeshFromPolygon(polygon geom.PolygonF, thickness float64) *fauxgl.Mesh {
func generateTrianglesForPolygon(polygon geom.PolygonF, thickness float64) []*fauxgl.Triangle {
vec := func(p geom.PointF, z float64) fauxgl.Vector { return fauxgl.V(p.X, p.Y, z) }
tri := fauxgl.NewTriangleForPoints
face := func(q, r, s geom.PointF, n float64) *fauxgl.Triangle {
@ -115,7 +158,11 @@ func generateMeshFromPolygon(polygon geom.PolygonF, thickness float64) *fauxgl.M
q, r, s, t := vec(p, back), vec(next, back), vec(next, front), vec(p, front)
triangles = append(triangles, tri(q, r, s), tri(q, s, t))
}
return triangles
}
func generateMeshFromPolygon(polygon geom.PolygonF, thickness float64) *fauxgl.Mesh {
triangles := generateTrianglesForPolygon(polygon, thickness)
mesh := fauxgl.NewTriangleMesh(triangles)
return mesh
}
@ -130,6 +177,54 @@ func iterate(n int, threads int) <-chan int {
return iterator
}
type MeshAnimation struct {
MeshTransformer
mesh *fauxgl.Mesh
}
func newMeshAnimation(mesh *fauxgl.Mesh, transformer MeshTransformer) *MeshAnimation {
mesh.BiUnitCube()
return &MeshAnimation{transformer, mesh}
}
func (a *MeshAnimation) animate(s FrameState) *fauxgl.Mesh {
mesh := a.mesh.Copy()
a.MeshTransformer.transform(mesh, s)
return mesh
}
type MeshAnimator interface {
animate(FrameState) *fauxgl.Mesh
}
type MeshRotateAnimation struct{}
func (MeshRotateAnimation) transform(mesh *fauxgl.Mesh, s FrameState) {
mesh.Transform(fauxgl.Rotate(up, 2*geom.Pi*s.Animation()))
}
type MeshTransformer interface {
transform(*fauxgl.Mesh, FrameState)
}
type MeshWobbleTransformation struct {
Wobble float64
}
func (a MeshWobbleTransformation) wobble(s FrameState) float64 {
animation := float64(s.Current) / float64(s.TotalFrames)
animation += .25
if animation >= 1 {
animation -= 1
}
return geom.Abs(animation*4-2) - 1
}
func (a MeshWobbleTransformation) transform(mesh *fauxgl.Mesh, s FrameState) {
mesh.Transform(fauxgl.Rotate(up, a.wobble(s)*a.Wobble*geom.Pi/180))
}
func parallel(n int, action func()) *sync.WaitGroup {
wait := &sync.WaitGroup{}
wait.Add(n)
@ -142,33 +237,6 @@ func parallel(n int, action func()) *sync.WaitGroup {
return wait
}
type MeshAnimationTransformer interface {
transform(*fauxgl.Mesh, FrameState)
}
type MeshRotateAnimation struct{}
func (MeshRotateAnimation) transform(mesh *fauxgl.Mesh, s FrameState) {
mesh.Transform(fauxgl.Rotate(up, 2*geom.Pi*s.Animation()))
}
type MeshWobbleTransformation struct {
Wobble float64
}
func (a MeshWobbleTransformation) animate(s FrameState) float64 {
animation := float64(s.Current) / float64(s.TotalFrames)
animation += .25
if animation >= 1 {
animation -= 1
}
return geom.Abs(animation*4-2) - 1
}
func (a MeshWobbleTransformation) transform(mesh *fauxgl.Mesh, s FrameState) {
mesh.Transform(fauxgl.Rotate(up, a.animate(s)*a.Wobble*geom.Pi/180))
}
func saveMeshSTL(path, name string, mesh *fauxgl.Mesh) error {
stl, err := os.Create(path)
if err != nil {

View File

@ -4,6 +4,8 @@ import (
"opslag.de/schobers/geom"
)
var AllMonsterTypes = []MonsterType{MonsterTypeStraight, MonsterTypeRandom, MonsterTypeChaser}
type ChasingMonster struct{}
func (m ChasingMonster) Type() MonsterType { return MonsterTypeChaser }

View File

@ -13,7 +13,7 @@ func CreateHeart() geom.PolygonF {
var polygon geom.PolygonF
const segments = 100
for segment := 0; segment < 100; segment++ {
t := 2 * geom.Pi * float64(segment) / segments
t := 2 * geom.Pi * Percentage(segment, segments)
st := geom.Sin(t)
polygon.Points = append(polygon.Points, geom.PtF(
16*st*st*st,
@ -23,15 +23,15 @@ func CreateHeart() geom.PolygonF {
}
func CreateHexagon() geom.PolygonF {
var polygon geom.PolygonF
pt := func(rotation float64) geom.PointF {
a := .5*geom.Pi + 2*geom.Pi*rotation
return geom.PtF(geom.Cos(a), geom.Sin(a))
}
const sides = 6
for side := 0; side < 6; side++ {
return CreateRegularPolygon(6)
}
func CreateRegularPolygon(sides int) geom.PolygonF {
var polygon geom.PolygonF
for side := 0; side < sides; side++ {
polygon.Points = append(polygon.Points,
pt(float64(side)/float64(sides)),
PolarUnity(float64(side)/float64(sides)),
)
}
return polygon
@ -39,14 +39,10 @@ func CreateHexagon() geom.PolygonF {
func CreateStar(sides int) geom.PolygonF {
var polygon geom.PolygonF
pt := func(rotation float64) geom.PointF {
a := .5*geom.Pi + 2*geom.Pi*rotation
return geom.PtF(geom.Cos(a), geom.Sin(a))
}
for side := 0; side < sides; side++ {
polygon.Points = append(polygon.Points,
pt(float64(side)/float64(sides)),
pt((float64(side)+0.5)/float64(sides)).Mul(.5),
PolarUnity(float64(side)/float64(sides)),
PolarUnity((float64(side)+0.5)/float64(sides)).Mul(.5),
)
}
return polygon