tins2021/meshanimation.go
Sander Schobers 7053e7b9f2 Reverted back to go:embed.
Parallelized generation of textures.
Refactored animation rendering.
2021-08-10 19:33:30 +02:00

198 lines
5.5 KiB
Go

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)
}