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