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