Reverted back to go:embed.
Parallelized generation of textures. Refactored animation rendering.
46
animatedtexture.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package tins2021
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnimatedTexture struct {
|
||||||
|
texture NamedTexture
|
||||||
|
animation ui.Texture
|
||||||
|
frames []geom.RectangleF32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnimatedTexture(texture NamedTexture, animation ui.Texture, n int) AnimatedTexture {
|
||||||
|
frames := make([]geom.RectangleF32, 0, n)
|
||||||
|
height := float32(animation.Height())
|
||||||
|
width := float32(animation.Width())
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
left := width * float32(i) / float32(n)
|
||||||
|
right := width * float32(i+1) / float32(n)
|
||||||
|
frames = append(frames, geom.RectF32(left, 0, right, height))
|
||||||
|
}
|
||||||
|
return AnimatedTexture{texture: texture, animation: animation, frames: frames}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnimatedTexture(texture NamedTexture, n int) AnimatedTexture {
|
||||||
|
return newAnimatedTexture(texture, texture.Texture(), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FitAnimatedTexture(texture NamedTexture, scale float32, n int) AnimatedTexture {
|
||||||
|
height := float32(texture.Texture().Height())
|
||||||
|
scale = geom.Round32(height*scale) / height // clip scale to integer width/height
|
||||||
|
return newAnimatedTexture(texture, texture.Scaled(scale), n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t AnimatedTexture) Draw(renderer ui.Renderer, pos geom.PointF32, frame int) {
|
||||||
|
renderer.DrawTexturePointOptions(t.animation, pos, ui.DrawOptions{Source: &t.frames[frame]})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t AnimatedTexture) FrameSize(i int) geom.PointF32 { return t.frames[i].Size() }
|
||||||
|
|
||||||
|
func (t AnimatedTexture) Frames() int { return len(t.frames) }
|
||||||
|
|
||||||
|
func (t AnimatedTexture) Scale(scale float32) AnimatedTexture {
|
||||||
|
return FitAnimatedTexture(t.texture, scale, t.Frames())
|
||||||
|
}
|
39
animation.go
@ -5,47 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/zntg/ui"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AnimatedTexture struct {
|
|
||||||
Texture ui.Texture
|
|
||||||
Frames []geom.RectangleF32
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAnimatedTexture(texture ui.Texture, n int) AnimatedTexture {
|
|
||||||
frames := make([]geom.RectangleF32, 0, n)
|
|
||||||
height := float32(texture.Height())
|
|
||||||
width := float32(texture.Width())
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
left := width * float32(i) / float32(n)
|
|
||||||
right := width * float32(i+1) / float32(n)
|
|
||||||
frames = append(frames, geom.RectF32(left, 0, right, height))
|
|
||||||
}
|
|
||||||
return AnimatedTexture{Texture: texture, Frames: frames}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FitAnimatedTexture(textures *ui.Textures, name string, scale float32, n int) AnimatedTexture {
|
|
||||||
height := float32(textures.Texture(name).Height())
|
|
||||||
scale = geom.Round32(height*scale) / height // clip scale to integer width/height
|
|
||||||
return NewAnimatedTexture(textures.ScaledByName(name, scale), n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t AnimatedTexture) Scale(scale float32) AnimatedTexture {
|
|
||||||
frames := make([]geom.RectangleF32, 0, len(t.Frames))
|
|
||||||
for _, frame := range t.Frames {
|
|
||||||
frames = append(frames, geom.RectangleF32{Min: frame.Min.Mul(scale), Max: frame.Max.Mul(scale)})
|
|
||||||
}
|
|
||||||
return AnimatedTexture{
|
|
||||||
Texture: t.Texture,
|
|
||||||
Frames: frames,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t AnimatedTexture) Draw(renderer ui.Renderer, pos geom.PointF32, frame int) {
|
|
||||||
renderer.DrawTexturePointOptions(t.Texture, pos, ui.DrawOptions{Source: &t.Frames[frame]})
|
|
||||||
}
|
|
||||||
|
|
||||||
type Animation struct {
|
type Animation struct {
|
||||||
LastUpdate time.Time
|
LastUpdate time.Time
|
||||||
Frame int
|
Frame int
|
||||||
|
@ -35,18 +35,18 @@ const fpsOverlayName = `fps`
|
|||||||
|
|
||||||
func (a *app) Init(ctx ui.Context) error {
|
func (a *app) Init(ctx ui.Context) error {
|
||||||
if err := a.loadFonts(ctx,
|
if err := a.loadFonts(ctx,
|
||||||
fontDescriptor{"debug", "fonts/FiraMono-Regular.ttf", 12},
|
fontDescriptor{"debug", "resources/fonts/FiraMono-Regular.ttf", 12},
|
||||||
fontDescriptor{"default", "fonts/escheresk.ttf", 48},
|
fontDescriptor{"default", "resources/fonts/escheresk.ttf", 48},
|
||||||
fontDescriptor{"small", "fonts/escheresk.ttf", 16},
|
fontDescriptor{"small", "resources/fonts/escheresk.ttf", 16},
|
||||||
fontDescriptor{"score", "fonts/FiraMono-Regular.ttf", 24},
|
fontDescriptor{"score", "resources/fonts/FiraMono-Regular.ttf", 24},
|
||||||
fontDescriptor{"title", "fonts/escher.ttf", 80},
|
fontDescriptor{"title", "resources/fonts/escher.ttf", 80},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
textureLoader := tins2021.NewResourceLoader()
|
textureLoader := tins2021.NewResourceLoader()
|
||||||
textures := ctx.Textures()
|
textures := ctx.Textures()
|
||||||
if err := textureLoader.LoadFromFile(ctx.Resources(), "textures.txt", func(name, content string) error {
|
if err := textureLoader.LoadFromFile(ctx.Resources(), "resources/textures.txt", func(name, content string) error {
|
||||||
_, err := textures.CreateTexturePath(name, content, true)
|
_, err := textures.CreateTexturePath(name, content, true)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
|
||||||
|
|
||||||
"opslag.de/schobers/tins2021"
|
"opslag.de/schobers/tins2021"
|
||||||
"opslag.de/schobers/zntg/ui"
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
@ -12,34 +10,25 @@ type appContext struct {
|
|||||||
|
|
||||||
Debug bool
|
Debug bool
|
||||||
|
|
||||||
MonsterTextureNames map[tins2021.MonsterType]string
|
StarTexture tins2021.AnimatedTexture
|
||||||
|
HeartTexture tins2021.AnimatedTexture
|
||||||
|
|
||||||
|
MonsterTextures map[tins2021.MonsterType]tins2021.AnimatedTexture
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAppContext(ctx ui.Context, setView func(ui.Control)) *appContext {
|
func newAppContext(ctx ui.Context, setView func(ui.Control)) *appContext {
|
||||||
newAnimatedTexture(ctx, "star", "images/star.png", defaultAnimationFrames, func() image.Image {
|
textures := textureGenerator{}
|
||||||
return tins2021.AnimatePolygon(tins2021.CreateStar(5), tins2021.Yellow, tins2021.NewRotateAnimation(defaultAnimationFrames))
|
|
||||||
})
|
|
||||||
newAnimatedTexture(ctx, "heart", "images/heart.png", defaultAnimationFrames, func() image.Image {
|
|
||||||
return tins2021.AnimatePolygon(tins2021.CreateHeart(), tins2021.Red, tins2021.NewRotateAnimation(defaultAnimationFrames))
|
|
||||||
})
|
|
||||||
|
|
||||||
app := &appContext{
|
app := &appContext{
|
||||||
setView: setView,
|
setView: setView,
|
||||||
MonsterTextureNames: map[tins2021.MonsterType]string{
|
StarTexture: newAnimatedTexture(ctx, "star", defaultAnimationFrames, textures.Star),
|
||||||
tins2021.MonsterTypeStraight: "straight-walking-monster",
|
HeartTexture: newAnimatedTexture(ctx, "heart", defaultAnimationFrames, textures.Heart),
|
||||||
tins2021.MonsterTypeRandom: "random-walking-monster",
|
MonsterTextures: map[tins2021.MonsterType]tins2021.AnimatedTexture{
|
||||||
tins2021.MonsterTypeChaser: "chasing-monster",
|
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),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
newAnimatedTexture(ctx, app.MonsterTextureNames[tins2021.MonsterTypeStraight], "images/monster-straight.png", defaultAnimationFrames, func() image.Image {
|
|
||||||
return tins2021.AnimatePolygon(tins2021.CreateHexagon(), tins2021.Green, tins2021.NewWobbleAnimation(defaultAnimationFrames, 30))
|
|
||||||
})
|
|
||||||
newAnimatedTexture(ctx, app.MonsterTextureNames[tins2021.MonsterTypeRandom], "images/monster-random.png", defaultAnimationFrames, func() image.Image {
|
|
||||||
return tins2021.AnimatePolygon(tins2021.CreateHexagon(), tins2021.Blue, tins2021.NewWobbleAnimation(defaultAnimationFrames, 30))
|
|
||||||
})
|
|
||||||
newAnimatedTexture(ctx, app.MonsterTextureNames[tins2021.MonsterTypeChaser], "images/monster-chaser.png", defaultAnimationFrames, func() image.Image {
|
|
||||||
return tins2021.AnimatePolygon(tins2021.CreateHexagon(), tins2021.Purple, tins2021.NewWobbleAnimation(defaultAnimationFrames, 30))
|
|
||||||
})
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func copyFile(path string, file fs.File) error {
|
func copyFile(path string, file fs.File) error {
|
||||||
@ -21,19 +20,19 @@ func copyFile(path string, file fs.File) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyBoxToDisk() error {
|
func copyBoxToDisk(resources embed.FS) error {
|
||||||
box := rice.MustFindBox(`resources`)
|
return fs.WalkDir(resources, "", func(path string, entry fs.DirEntry, err error) error {
|
||||||
return box.Walk("", func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
if entry.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
src, err := box.Open(path)
|
src, err := resources.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return copyFile(filepath.Join(box.Name(), path), src)
|
defer src.Close()
|
||||||
|
return copyFile(filepath.Join(`resources`, path), src)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,8 @@ type levelController struct {
|
|||||||
|
|
||||||
Level *tins2021.Level
|
Level *tins2021.Level
|
||||||
|
|
||||||
Cubes cubeTexture
|
Cube tins2021.NamedTexture
|
||||||
|
Inverted tins2021.NamedTexture
|
||||||
Animations map[string]*tins2021.Animations
|
Animations map[string]*tins2021.Animations
|
||||||
|
|
||||||
IdleMonsters *tins2021.Animations
|
IdleMonsters *tins2021.Animations
|
||||||
@ -32,7 +33,8 @@ type levelController struct {
|
|||||||
func newLevelControl(app *appContext, ctx ui.Context, level *tins2021.Level) *levelController {
|
func newLevelControl(app *appContext, ctx ui.Context, level *tins2021.Level) *levelController {
|
||||||
control := &levelController{app: app}
|
control := &levelController{app: app}
|
||||||
textures := ctx.Textures()
|
textures := ctx.Textures()
|
||||||
control.Cubes = newCubeTexture(textures, tins2021.Orange)
|
control.Cube = tins2021.MustCreateNamedTextureImage(textures, "cube", tins2021.GenerateCube(tins2021.Orange))
|
||||||
|
control.Inverted = tins2021.MustCreateNamedTextureImage(textures, "cube_inverted", tins2021.GenerateHole(tins2021.Orange))
|
||||||
|
|
||||||
small, err := tins2021.NewBitmapFont(ctx.Renderer(), ctx.Fonts().Font("small"), tins2021.NumericCharacters...)
|
small, err := tins2021.NewBitmapFont(ctx.Renderer(), ctx.Fonts().Font("small"), tins2021.NumericCharacters...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,17 +148,15 @@ func (r *levelController) Play(level *tins2021.Level) {
|
|||||||
r.Level = level
|
r.Level = level
|
||||||
|
|
||||||
r.Animations = map[string]*tins2021.Animations{
|
r.Animations = map[string]*tins2021.Animations{
|
||||||
"star": tins2021.NewAnimations(50*time.Millisecond, defaultAnimationFrames, true, true),
|
"star": tins2021.NewAnimations(50*time.Millisecond, defaultAnimationFrames, true, true),
|
||||||
"heart": tins2021.NewAnimations(80*time.Millisecond, defaultAnimationFrames, true, true),
|
"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(200*time.Millisecond, 100, false, false)
|
||||||
r.MovingMonsters = tins2021.NewAnimations(16*time.Millisecond, 50, false, false)
|
r.MovingMonsters = tins2021.NewAnimations(16*time.Millisecond, 50, false, false)
|
||||||
for monster := range level.Monsters {
|
for monster := range level.Monsters {
|
||||||
r.IdleMonsters.Frame(monster)
|
r.IdleMonsters.Frame(monster)
|
||||||
}
|
}
|
||||||
for _, monster := range r.app.MonsterTextureNames {
|
|
||||||
r.Animations[monster] = tins2021.NewAnimations(80*time.Millisecond, defaultAnimationFrames, true, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultAnimationFrames = 20
|
const defaultAnimationFrames = 20
|
||||||
@ -189,20 +189,21 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderer := ctx.Renderer()
|
renderer := ctx.Renderer()
|
||||||
textures := ctx.Textures()
|
cube := r.Cube.Scaled(scale)
|
||||||
cubes := r.Cubes.Scaled(textures, scale)
|
inverted := r.Inverted.Scaled(scale)
|
||||||
|
|
||||||
cubeWidth := float32(cubes.Normal.Width())
|
cubeWidth := float32(cube.Width())
|
||||||
cubeHeight := float32(cubes.Normal.Height())
|
cubeHeight := float32(cube.Height())
|
||||||
|
|
||||||
player := ctx.Textures().ScaledByName("dwarf", scale*.6)
|
player := ctx.Textures().ScaledByName("dwarf", scale*.6)
|
||||||
star := tins2021.FitAnimatedTexture(textures, "star", scale*.4, defaultAnimationFrames)
|
star := r.app.StarTexture.Scale(scale * .4)
|
||||||
heart := tins2021.FitAnimatedTexture(textures, "heart", scale*.4, defaultAnimationFrames)
|
heart := r.app.HeartTexture.Scale(scale * .4)
|
||||||
monsterTextures := map[tins2021.MonsterType]tins2021.AnimatedTexture{}
|
monsterTextures := map[tins2021.MonsterType]tins2021.AnimatedTexture{}
|
||||||
for typ, name := range r.app.MonsterTextureNames {
|
for typ, animation := range r.app.MonsterTextures {
|
||||||
monsterTextures[typ] = tins2021.FitAnimatedTexture(textures, name, scale*.4, defaultAnimationFrames)
|
monsterTextures[typ] = animation.Scale(scale * .4)
|
||||||
}
|
}
|
||||||
propOffset := geom.PtF32(-.5*float32(star.Texture.Height()), -.8*float32(star.Texture.Height()))
|
propHeight := star.FrameSize(0).Y
|
||||||
|
propOffset := geom.PtF32(-.5*float32(propHeight), -.8*float32(propHeight))
|
||||||
|
|
||||||
distances := r.Level.Tiles.Distances(r.Level.Player)
|
distances := r.Level.Tiles.Distances(r.Level.Player)
|
||||||
|
|
||||||
@ -222,9 +223,9 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
screenPos, platformPos := positionOfTile(pos)
|
screenPos, platformPos := positionOfTile(pos)
|
||||||
tileTexture := cubes.Normal.Texture
|
tileTexture := cube
|
||||||
if tile.Inversed {
|
if tile.Inversed {
|
||||||
tileTexture = cubes.Inversed.Texture
|
tileTexture = inverted
|
||||||
}
|
}
|
||||||
renderer.DrawTexturePoint(tileTexture, screenPos)
|
renderer.DrawTexturePoint(tileTexture, screenPos)
|
||||||
if r.app.Debug {
|
if r.app.Debug {
|
||||||
@ -253,7 +254,6 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
if tile == nil {
|
if tile == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name := r.app.MonsterTextureNames[monsterType.Type()]
|
|
||||||
texture := monsterTextures[monsterType.Type()]
|
texture := monsterTextures[monsterType.Type()]
|
||||||
_, platformPos := positionOfTile(pos)
|
_, platformPos := positionOfTile(pos)
|
||||||
if target, ok := r.Level.MonsterTargets[pos]; ok {
|
if target, ok := r.Level.MonsterTargets[pos]; ok {
|
||||||
@ -262,15 +262,15 @@ func (r levelController) Render(ctx ui.Context) {
|
|||||||
delta := targetPlatformPos.Sub(platformPos)
|
delta := targetPlatformPos.Sub(platformPos)
|
||||||
curve := geom.PtF32(0, .6*geom.Sin32(dt*geom.Pi)*textureWidth)
|
curve := geom.PtF32(0, .6*geom.Sin32(dt*geom.Pi)*textureWidth)
|
||||||
interpolatedPos := platformPos.Add(delta.Mul(dt)).Sub(curve)
|
interpolatedPos := platformPos.Add(delta.Mul(dt)).Sub(curve)
|
||||||
texture.Draw(renderer, interpolatedPos.Add(propOffset), r.Animations[name].Frame(pos))
|
texture.Draw(renderer, interpolatedPos.Add(propOffset), r.Animations["monster"].Frame(pos))
|
||||||
} else {
|
} else {
|
||||||
texture.Draw(renderer, platformPos.Add(propOffset), r.Animations[name].Frame(pos))
|
texture.Draw(renderer, platformPos.Add(propOffset), r.Animations["monster"].Frame(pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textColor := ctx.Style().Palette.Text
|
textColor := ctx.Style().Palette.Text
|
||||||
scoreFont := ctx.Fonts().Font("score")
|
scoreFont := ctx.Fonts().Font("score")
|
||||||
fontOffsetY := .5 * (float32(star.Texture.Height()) - scoreFont.Height())
|
fontOffsetY := .5 * (float32(propHeight) - scoreFont.Height())
|
||||||
|
|
||||||
// stars & hearts
|
// stars & hearts
|
||||||
scoreTopLeft := scoreView.Min
|
scoreTopLeft := scoreView.Min
|
||||||
|
@ -1 +1 @@
|
|||||||
dwarf: images/dwarf.png
|
dwarf: resources/textures/gnome.png
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
27
cmd/tins2021/texturegenerator.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"opslag.de/schobers/tins2021"
|
||||||
|
)
|
||||||
|
|
||||||
|
type textureGenerator struct{}
|
||||||
|
|
||||||
|
func (textureGenerator) Star() image.Image {
|
||||||
|
return tins2021.AnimatePolygon(tins2021.CreateStar(5), tins2021.Yellow, defaultAnimationFrames, tins2021.MeshRotateAnimation{})
|
||||||
|
}
|
||||||
|
|
||||||
|
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) ChasingMonster() image.Image { return g.monster(tins2021.Purple) }
|
||||||
|
|
||||||
|
func (g textureGenerator) RandomWalkingMonster() image.Image { return g.monster(tins2021.Blue) }
|
||||||
|
|
||||||
|
func (g textureGenerator) StraightWalkingMonster() image.Image { return g.monster(tins2021.Green) }
|
@ -1,48 +1,28 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"opslag.de/schobers/tins2021"
|
"opslag.de/schobers/tins2021"
|
||||||
"opslag.de/schobers/zntg/ui"
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type namedTexture struct {
|
func chrono(action func()) time.Duration {
|
||||||
ui.Texture
|
start := time.Now()
|
||||||
|
action()
|
||||||
Name string
|
return time.Now().Sub(start)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNamedTexture(textures *ui.Textures, name string, im image.Image) namedTexture {
|
func chronoErr(action func() error) (time.Duration, error) {
|
||||||
texture, err := textures.CreateTextureGo(name, im, true)
|
start := time.Now()
|
||||||
if err != nil {
|
err := action()
|
||||||
panic(err)
|
return time.Now().Sub(start), err
|
||||||
}
|
|
||||||
return namedTexture{texture, name}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t namedTexture) Scaled(textures *ui.Textures, scale float32) namedTexture {
|
|
||||||
return namedTexture{textures.ScaledByName(t.Name, scale), t.Name}
|
|
||||||
}
|
|
||||||
|
|
||||||
type cubeTexture struct {
|
|
||||||
Normal, Inversed namedTexture
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCubeTexture(textures *ui.Textures, color string) cubeTexture {
|
|
||||||
return cubeTexture{
|
|
||||||
Normal: newNamedTexture(textures, "cube_"+color, tins2021.GenerateCube(color)),
|
|
||||||
Inversed: newNamedTexture(textures, "cube_"+color+"_inversed", tins2021.GenerateHole(color)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t cubeTexture) Scaled(textures *ui.Textures, scale float32) cubeTexture {
|
|
||||||
return cubeTexture{
|
|
||||||
Normal: t.Normal.Scaled(textures, scale),
|
|
||||||
Inversed: t.Inversed.Scaled(textures, scale),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTextureImage(ctx ui.Context, resource string) image.Image {
|
func loadTextureImage(ctx ui.Context, resource string) image.Image {
|
||||||
@ -58,26 +38,29 @@ func loadTextureImage(ctx ui.Context, resource string) image.Image {
|
|||||||
return im
|
return im
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveTextureImage(resource string, im image.Image) {
|
func newAnimatedTexture(ctx ui.Context, name string, frames int, render func() image.Image) tins2021.AnimatedTexture {
|
||||||
out, err := os.Create(resource)
|
resourceName := fmt.Sprintf("resources/textures/%s.png", name)
|
||||||
if err != nil {
|
raw := loadTextureImage(ctx, resourceName)
|
||||||
return
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
png.Encode(out, im)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAnimatedTexture(ctx ui.Context, name, resource string, frames int, render animatedTextureRenderFn) tins2021.AnimatedTexture {
|
|
||||||
raw := loadTextureImage(ctx, resource)
|
|
||||||
if raw == nil {
|
if raw == nil {
|
||||||
raw = render()
|
renderTime := chrono(func() {
|
||||||
saveTextureImage(resource, raw)
|
raw = render()
|
||||||
}
|
})
|
||||||
texture, err := ctx.Textures().CreateTextureGo(name, raw, true)
|
if err := saveTextureImage(resourceName, raw); err != nil {
|
||||||
if err != nil {
|
log.Printf("failed to write animated texture \"%s\" to disk (%s); error: %v\n", name, resourceName, err)
|
||||||
panic(err)
|
} else {
|
||||||
|
log.Printf("generated animated texture \"%s\" in %v (and saved to disk \"%s\")\n", name, renderTime, resourceName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
texture := tins2021.MustCreateNamedTextureImage(ctx.Textures(), name, raw)
|
||||||
return tins2021.NewAnimatedTexture(texture, frames)
|
return tins2021.NewAnimatedTexture(texture, frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
type animatedTextureRenderFn func() image.Image
|
func saveTextureImage(resource string, im image.Image) error {
|
||||||
|
os.MkdirAll(filepath.Dir(resource), 0744)
|
||||||
|
out, err := os.Create(resource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
return png.Encode(out, im)
|
||||||
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"flag"
|
"flag"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
rice "github.com/GeertJohan/go.rice"
|
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/tins2021"
|
"opslag.de/schobers/tins2021"
|
||||||
"opslag.de/schobers/zntg"
|
"opslag.de/schobers/zntg"
|
||||||
"opslag.de/schobers/zntg/addons/riceres"
|
"opslag.de/schobers/zntg/addons/embedres"
|
||||||
"opslag.de/schobers/zntg/ui"
|
"opslag.de/schobers/zntg/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// #cgo windows LDFLAGS: -Wl,-subsystem,windows
|
// #cgo windows LDFLAGS: -Wl,-subsystem,windows
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
//go:generate go get -u github.com/GeertJohan/go.rice/rice
|
//go:embed resources
|
||||||
//go:generate rice embed
|
var resources embed.FS
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := run()
|
err := run()
|
||||||
@ -27,9 +27,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openResources() ui.Resources {
|
func openResources() ui.Resources {
|
||||||
box := rice.MustFindBox(`resources`)
|
embedded := embedres.New(resources)
|
||||||
embedded := riceres.New(box)
|
return ui.NewFallbackResources(ui.NewPathResources(nil, `resources`), embedded)
|
||||||
return ui.NewFallbackResources(ui.NewPathResources(nil, box.Name()), embedded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func run() error {
|
||||||
@ -38,7 +37,7 @@ func run() error {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if extract {
|
if extract {
|
||||||
return copyBoxToDisk()
|
return copyBoxToDisk(resources)
|
||||||
}
|
}
|
||||||
res := openResources()
|
res := openResources()
|
||||||
|
|
||||||
|
197
meshanimation.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package tins2021
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fogleman/fauxgl"
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
"golang.org/x/image/draw"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/geom/ints"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fovy = 40 // vertical field of view in degrees
|
||||||
|
near = 1 // near clipping plane
|
||||||
|
far = 10 // far clipping plane
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
eye = fauxgl.V(0, 0, 4) // camera position
|
||||||
|
center = fauxgl.V(0, 0, 0) // view center position
|
||||||
|
up = fauxgl.V(0, 1, 0) // up vector
|
||||||
|
light = fauxgl.V(.5, 1, .75).Normalize() // light direction
|
||||||
|
)
|
||||||
|
|
||||||
|
func animateMesh(mesh *fauxgl.Mesh, hexColor string, frames int, transform MeshAnimationTransformer) 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() {
|
||||||
|
context := fauxgl.NewContext(TextureSize*scale, TextureSize*scale)
|
||||||
|
color := fauxgl.HexColor(hexColor)
|
||||||
|
|
||||||
|
for i := range framesC {
|
||||||
|
context.ClearDepthBuffer()
|
||||||
|
context.ClearColorBufferWith(fauxgl.Transparent)
|
||||||
|
|
||||||
|
shader := fauxgl.NewPhongShader(matrix, light, eye)
|
||||||
|
shader.ObjectColor = color
|
||||||
|
shader.AmbientColor = fauxgl.MakeColor(mustHexColor(`#7F7F7F`))
|
||||||
|
context.Shader = shader
|
||||||
|
|
||||||
|
copy := mesh.Copy()
|
||||||
|
transform.transform(copy, FrameState{Current: i, TotalFrames: frames})
|
||||||
|
context.DrawMesh(copy)
|
||||||
|
|
||||||
|
frame := resize.Resize(TextureSize, TextureSize, context.Image(), resize.Bilinear)
|
||||||
|
draw.Copy(animation, image.Pt(i*TextureSize, 0), frame, frame.Bounds(), draw.Src, nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for f := 0; f < frames; f++ {
|
||||||
|
framesC <- f
|
||||||
|
}
|
||||||
|
close(framesC)
|
||||||
|
wait.Wait()
|
||||||
|
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 AnimateSTL(resources ui.PhysicalResources, name, hexColor string, frames int, transform MeshAnimationTransformer) image.Image {
|
||||||
|
path, err := resources.FetchResource(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
mesh, err := fauxgl.LoadSTL(path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return animateMesh(mesh, hexColor, frames, transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FrameState struct {
|
||||||
|
Current int
|
||||||
|
TotalFrames int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s FrameState) Animation() float64 { return float64(s.Current) / float64(s.TotalFrames) }
|
||||||
|
|
||||||
|
func generateMeshFromPolygon(polygon geom.PolygonF, thickness float64) *fauxgl.Mesh {
|
||||||
|
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 {
|
||||||
|
return tri(vec(q, n*thickness), vec(r, n*thickness), vec(s, n*thickness))
|
||||||
|
}
|
||||||
|
var triangles []*fauxgl.Triangle
|
||||||
|
// generate front & back
|
||||||
|
for _, t := range polygon.Triangulate() {
|
||||||
|
triangles = append(triangles,
|
||||||
|
face(t.Points[0], t.Points[1], t.Points[2], 1), // front
|
||||||
|
face(t.Points[2], t.Points[1], t.Points[0], -1), // back
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// generate side
|
||||||
|
back, front := -thickness, thickness
|
||||||
|
for i, p := range polygon.Points {
|
||||||
|
next := polygon.Points[(i+1)%len(polygon.Points)]
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh := fauxgl.NewTriangleMesh(triangles)
|
||||||
|
return mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterate(n int, threads int) <-chan int {
|
||||||
|
iterator := make(chan int, threads)
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
iterator <- i
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallel(n int, action func()) *sync.WaitGroup {
|
||||||
|
wait := &sync.WaitGroup{}
|
||||||
|
wait.Add(n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
go func() {
|
||||||
|
action()
|
||||||
|
wait.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stl.Close()
|
||||||
|
|
||||||
|
fmt.Fprintf(stl, "solid %s\n", name)
|
||||||
|
for _, triangle := range mesh.Triangles {
|
||||||
|
normal := triangle.Normal()
|
||||||
|
fmt.Fprintf(stl, " facet normal %f, %f, %f\n", normal.X, normal.Y, normal.Z)
|
||||||
|
fmt.Fprintf(stl, " outer loop\n")
|
||||||
|
fmt.Fprintf(stl, " vertex %f, %f, %f\n", triangle.V1.Position.X, triangle.V1.Position.Y, triangle.V1.Position.Z)
|
||||||
|
fmt.Fprintf(stl, " vertex %f, %f, %f\n", triangle.V2.Position.X, triangle.V2.Position.Y, triangle.V2.Position.Z)
|
||||||
|
fmt.Fprintf(stl, " vertex %f, %f, %f\n", triangle.V3.Position.X, triangle.V3.Position.Y, triangle.V3.Position.Z)
|
||||||
|
fmt.Fprintf(stl, " endloop\n")
|
||||||
|
fmt.Fprintf(stl, " endfacet\n")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(stl, "endsolid %s\n", name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveSTLFromPolygon(path, name string, polygon geom.PolygonF, thickness float64) {
|
||||||
|
mesh := generateMeshFromPolygon(polygon, thickness)
|
||||||
|
saveMeshSTL(path, name, mesh)
|
||||||
|
}
|
39
namedtexture.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package tins2021
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamedTexture struct {
|
||||||
|
textures *ui.Textures
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNamedTexture(textures *ui.Textures, name string) NamedTexture {
|
||||||
|
return NamedTexture{
|
||||||
|
textures: textures,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateNamedTextureImage(textures *ui.Textures, name string, im image.Image) (NamedTexture, error) {
|
||||||
|
_, err := textures.CreateTextureGo(name, im, true)
|
||||||
|
if err != nil {
|
||||||
|
return NamedTexture{}, err
|
||||||
|
}
|
||||||
|
return NewNamedTexture(textures, name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustCreateNamedTextureImage(textures *ui.Textures, name string, im image.Image) NamedTexture {
|
||||||
|
texture, err := CreateNamedTextureImage(textures, name, im)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return texture
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t NamedTexture) Scaled(scale float32) ui.Texture { return t.textures.ScaledByName(t.name, scale) }
|
||||||
|
|
||||||
|
func (t NamedTexture) Texture() ui.Texture { return t.textures.Texture(t.name) }
|
@ -1,211 +0,0 @@
|
|||||||
package tins2021
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/fogleman/fauxgl"
|
|
||||||
"github.com/nfnt/resize"
|
|
||||||
"golang.org/x/image/draw"
|
|
||||||
"opslag.de/schobers/geom"
|
|
||||||
"opslag.de/schobers/zntg/ui"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
fovy = 40 // vertical field of view in degrees
|
|
||||||
near = 1 // near clipping plane
|
|
||||||
far = 10 // far clipping plane
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
eye = fauxgl.V(0, 0, 4) // camera position
|
|
||||||
center = fauxgl.V(0, 0, 0) // view center position
|
|
||||||
up = fauxgl.V(0, 1, 0) // up vector
|
|
||||||
light = fauxgl.V(.5, 1, .75).Normalize() // light direction
|
|
||||||
)
|
|
||||||
|
|
||||||
func AnimatePolygon(polygon geom.PolygonF, hexColor string, renderer AnimationRenderer) image.Image {
|
|
||||||
mesh := generateMeshFromPolygon(polygon, .2)
|
|
||||||
renderer.setup(mesh)
|
|
||||||
return renderMeshAnimation(hexColor, renderer.frames(), renderer.render)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AnimateSTL(resources ui.PhysicalResources, name, hexColor string, renderer AnimationRenderer) image.Image {
|
|
||||||
path, err := resources.FetchResource(name)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
mesh, err := fauxgl.LoadSTL(path)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
renderer.setup(mesh)
|
|
||||||
return renderMeshAnimation(hexColor, renderer.frames(), renderer.render)
|
|
||||||
}
|
|
||||||
|
|
||||||
type animationRendererBase struct {
|
|
||||||
Frames int
|
|
||||||
Mesh *fauxgl.Mesh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r animationRendererBase) frames() int { return r.Frames }
|
|
||||||
|
|
||||||
func (r *animationRendererBase) setup(mesh *fauxgl.Mesh) {
|
|
||||||
r.Mesh = mesh
|
|
||||||
mesh.BiUnitCube()
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ AnimationRenderer = &RotateAnimationRenderer{}
|
|
||||||
var _ AnimationRenderer = &WobbleAnimationRenderer{}
|
|
||||||
|
|
||||||
type AnimationRenderer interface {
|
|
||||||
frames() int
|
|
||||||
setup(*fauxgl.Mesh)
|
|
||||||
render(*fauxgl.Context, int, float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMeshFromPolygon(polygon geom.PolygonF, thickness float64) *fauxgl.Mesh {
|
|
||||||
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 {
|
|
||||||
return tri(vec(q, n*thickness), vec(r, n*thickness), vec(s, n*thickness))
|
|
||||||
}
|
|
||||||
var triangles []*fauxgl.Triangle
|
|
||||||
// generate front & back
|
|
||||||
for _, t := range polygon.Triangulate() {
|
|
||||||
triangles = append(triangles,
|
|
||||||
face(t.Points[0], t.Points[1], t.Points[2], 1), // front
|
|
||||||
face(t.Points[2], t.Points[1], t.Points[0], -1), // back
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// generate side
|
|
||||||
back, front := -thickness, thickness
|
|
||||||
for i, p := range polygon.Points {
|
|
||||||
next := polygon.Points[(i+1)%len(polygon.Points)]
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
mesh := fauxgl.NewTriangleMesh(triangles)
|
|
||||||
return mesh
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderMeshAnimation(hexColor string, frames int, render func(*fauxgl.Context, int, float64)) image.Image {
|
|
||||||
const scale = 4
|
|
||||||
context := fauxgl.NewContext(TextureSize*scale, TextureSize*scale)
|
|
||||||
|
|
||||||
// matrix := fauxgl.LookAt(eye, center, up).Perspective(fovy, 1, near, far)
|
|
||||||
|
|
||||||
const s = 1.1
|
|
||||||
// rot3 := func(m fauxgl.Matrix) fauxgl.Matrix {
|
|
||||||
// return fauxgl.Matrix{
|
|
||||||
// X00: m.X20, X01: m.X10, X02: m.X00, X03: m.X03,
|
|
||||||
// X10: m.X21, X11: m.X11, X12: m.X01, X13: m.X13,
|
|
||||||
// X20: m.X22, X21: m.X12, X22: m.X02, X23: m.X23,
|
|
||||||
// X30: m.X30, X31: m.X31, X32: m.X32, X33: m.X33,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// sqrt_6_1 := 1 / geom.Sqrt(6)
|
|
||||||
// iso := fauxgl.Matrix{
|
|
||||||
// X00: sqrt_6_1 * geom.Sqrt(3), X01: 0, X02: -sqrt_6_1 * geom.Sqrt(3), X03: 0,
|
|
||||||
// X10: sqrt_6_1, X11: 2 * sqrt_6_1, X12: sqrt_6_1, X13: 0,
|
|
||||||
// X20: sqrt_6_1 * geom.Sqrt(2), X21: -sqrt_6_1 * geom.Sqrt(2), X22: sqrt_6_1 * geom.Sqrt(2), X23: 0,
|
|
||||||
// X30: 0, X31: 0, X32: 0, X33: 1}
|
|
||||||
|
|
||||||
matrix := fauxgl.Orthographic(-s, s, -s, s, near, far).Mul(fauxgl.LookAt(eye, center, up))
|
|
||||||
|
|
||||||
color := fauxgl.HexColor(hexColor)
|
|
||||||
|
|
||||||
animation := image.NewNRGBA(image.Rect(0, 0, TextureSize*frames, TextureSize))
|
|
||||||
for i := 0; i < frames; i++ {
|
|
||||||
context.ClearDepthBuffer()
|
|
||||||
context.ClearColorBufferWith(fauxgl.Transparent)
|
|
||||||
|
|
||||||
shader := fauxgl.NewPhongShader(matrix, light, eye)
|
|
||||||
shader.ObjectColor = color
|
|
||||||
shader.AmbientColor = fauxgl.MakeColor(mustHexColor(`#7F7F7F`))
|
|
||||||
context.Shader = shader
|
|
||||||
|
|
||||||
render(context, i, float64(i)/float64(frames))
|
|
||||||
|
|
||||||
frame := resize.Resize(TextureSize, TextureSize, context.Image(), resize.Bilinear)
|
|
||||||
draw.Copy(animation, image.Pt(i*TextureSize, 0), frame, frame.Bounds(), draw.Src, nil)
|
|
||||||
}
|
|
||||||
return animation
|
|
||||||
}
|
|
||||||
|
|
||||||
type RotateAnimationRenderer struct {
|
|
||||||
animationRendererBase
|
|
||||||
|
|
||||||
Rotation float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRotateAnimation(frames int) AnimationRenderer {
|
|
||||||
return &RotateAnimationRenderer{
|
|
||||||
animationRendererBase: animationRendererBase{Frames: frames},
|
|
||||||
Rotation: 2 * geom.Pi / float64(frames),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a RotateAnimationRenderer) render(context *fauxgl.Context, _ int, _ float64) {
|
|
||||||
context.DrawMesh(a.Mesh)
|
|
||||||
a.Mesh.Transform(fauxgl.Rotate(up, a.Rotation))
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveMeshSTL(path, name string, mesh *fauxgl.Mesh) error {
|
|
||||||
stl, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer stl.Close()
|
|
||||||
|
|
||||||
fmt.Fprintf(stl, "solid %s\n", name)
|
|
||||||
for _, triangle := range mesh.Triangles {
|
|
||||||
normal := triangle.Normal()
|
|
||||||
fmt.Fprintf(stl, " facet normal %f, %f, %f\n", normal.X, normal.Y, normal.Z)
|
|
||||||
fmt.Fprintf(stl, " outer loop\n")
|
|
||||||
fmt.Fprintf(stl, " vertex %f, %f, %f\n", triangle.V1.Position.X, triangle.V1.Position.Y, triangle.V1.Position.Z)
|
|
||||||
fmt.Fprintf(stl, " vertex %f, %f, %f\n", triangle.V2.Position.X, triangle.V2.Position.Y, triangle.V2.Position.Z)
|
|
||||||
fmt.Fprintf(stl, " vertex %f, %f, %f\n", triangle.V3.Position.X, triangle.V3.Position.Y, triangle.V3.Position.Z)
|
|
||||||
fmt.Fprintf(stl, " endloop\n")
|
|
||||||
fmt.Fprintf(stl, " endfacet\n")
|
|
||||||
}
|
|
||||||
fmt.Fprintf(stl, "endsolid %s\n", name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveSTLFromPolygon(path, name string, polygon geom.PolygonF, thickness float64) {
|
|
||||||
mesh := generateMeshFromPolygon(polygon, thickness)
|
|
||||||
saveMeshSTL(path, name, mesh)
|
|
||||||
}
|
|
||||||
|
|
||||||
type WobbleAnimationRenderer struct {
|
|
||||||
animationRendererBase
|
|
||||||
|
|
||||||
Wobble float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWobbleAnimation(frames int, wobble float64) AnimationRenderer {
|
|
||||||
return &WobbleAnimationRenderer{
|
|
||||||
animationRendererBase: animationRendererBase{Frames: frames},
|
|
||||||
Wobble: wobble,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a WobbleAnimationRenderer) animate(frame float64) float64 {
|
|
||||||
frame += .25
|
|
||||||
if frame >= 1 {
|
|
||||||
frame -= 1
|
|
||||||
}
|
|
||||||
// return geom.Cos(float64(frame) * 2 * geom.Pi / float64(a.Frames))
|
|
||||||
return geom.Abs(frame*4-2) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a WobbleAnimationRenderer) render(context *fauxgl.Context, frame int, animation float64) {
|
|
||||||
context.DrawMesh(a.Mesh)
|
|
||||||
curr := a.animate(animation)
|
|
||||||
next := a.animate(float64(frame+1) / float64(a.Frames))
|
|
||||||
a.Mesh.Transform(fauxgl.Rotate(up, (next-curr)*a.Wobble*geom.Pi/180))
|
|
||||||
}
|
|