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 Animate(hexColor string, frames int, animator MeshAnimator) image.Image { const scale = 4 const s = 1.1 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(1, 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 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) } }) for f := 0; f < frames; f++ { framesC <- f } close(framesC) wait.Wait() return animation } 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, transformer MeshTransformer) image.Image { path, err := resources.FetchResource(name) if err != nil { panic(err) } mesh, err := fauxgl.LoadSTL(path) if err != nil { panic(err) } 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 { Current int TotalFrames int } func (s FrameState) Animation() float64 { return float64(s.Current) / float64(s.TotalFrames) } 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 { 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)) } return triangles } func generateMeshFromPolygon(polygon geom.PolygonF, thickness float64) *fauxgl.Mesh { triangles := generateTrianglesForPolygon(polygon, thickness) 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 } 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) for i := 0; i < n; i++ { go func() { action() wait.Done() }() } return wait } 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) }