212 lines
6.4 KiB
Go
212 lines
6.4 KiB
Go
|
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))
|
||
|
}
|