2021-08-10 17:33:30 +00:00
|
|
|
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
|
|
|
|
)
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
func Animate(hexColor string, frames int, animator MeshAnimator) image.Image {
|
2021-08-10 17:33:30 +00:00
|
|
|
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)
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
wait := parallel(1, func() {
|
2021-08-10 17:33:30 +00:00
|
|
|
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
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
mesh := animator.animate(FrameState{Current: i, TotalFrames: frames})
|
|
|
|
context.DrawMesh(mesh)
|
2021-08-10 17:33:30 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
func AnimatePolygon(polygon geom.PolygonF, hexColor string, frames int, transformer MeshTransformer) image.Image {
|
|
|
|
animation := newMeshAnimation(generateMeshFromPolygon(polygon, .2), transformer)
|
|
|
|
return Animate(hexColor, frames, animation)
|
2021-08-10 17:33:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
func AnimateSTL(resources ui.PhysicalResources, name, hexColor string, frames int, transformer MeshTransformer) image.Image {
|
2021-08-10 17:33:30 +00:00
|
|
|
path, err := resources.FetchResource(name)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
mesh, err := fauxgl.LoadSTL(path)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2021-08-14 07:22:40 +00:00
|
|
|
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
|
2021-08-10 17:33:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type FrameState struct {
|
|
|
|
Current int
|
|
|
|
TotalFrames int
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s FrameState) Animation() float64 { return float64(s.Current) / float64(s.TotalFrames) }
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
func generateTrianglesForPolygon(polygon geom.PolygonF, thickness float64) []*fauxgl.Triangle {
|
2021-08-10 17:33:30 +00:00
|
|
|
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))
|
|
|
|
}
|
2021-08-14 07:22:40 +00:00
|
|
|
return triangles
|
|
|
|
}
|
2021-08-10 17:33:30 +00:00
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
func generateMeshFromPolygon(polygon geom.PolygonF, thickness float64) *fauxgl.Mesh {
|
|
|
|
triangles := generateTrianglesForPolygon(polygon, thickness)
|
2021-08-10 17:33:30 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
type MeshAnimation struct {
|
|
|
|
MeshTransformer
|
|
|
|
|
|
|
|
mesh *fauxgl.Mesh
|
2021-08-10 17:33:30 +00:00
|
|
|
}
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
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
|
2021-08-10 17:33:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type MeshRotateAnimation struct{}
|
|
|
|
|
|
|
|
func (MeshRotateAnimation) transform(mesh *fauxgl.Mesh, s FrameState) {
|
|
|
|
mesh.Transform(fauxgl.Rotate(up, 2*geom.Pi*s.Animation()))
|
|
|
|
}
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
type MeshTransformer interface {
|
|
|
|
transform(*fauxgl.Mesh, FrameState)
|
|
|
|
}
|
|
|
|
|
2021-08-10 17:33:30 +00:00
|
|
|
type MeshWobbleTransformation struct {
|
|
|
|
Wobble float64
|
|
|
|
}
|
|
|
|
|
2021-08-14 07:22:40 +00:00
|
|
|
func (a MeshWobbleTransformation) wobble(s FrameState) float64 {
|
2021-08-10 17:33:30 +00:00
|
|
|
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) {
|
2021-08-14 07:22:40 +00:00
|
|
|
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
|
2021-08-10 17:33:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|