Skip to content

Instantly share code, notes, and snippets.

@unixpickle
Last active May 8, 2020 23:33
Show Gist options
  • Select an option

  • Save unixpickle/1132b6aae131452384b315b1cb55005f to your computer and use it in GitHub Desktop.

Select an option

Save unixpickle/1132b6aae131452384b315b1cb55005f to your computer and use it in GitHub Desktop.
Deformation into sphere
module gists/deformation
go 1.14
require (
github.com/unixpickle/essentials v1.1.0
github.com/unixpickle/model3d v0.2.1
github.com/unixpickle/polish v0.0.0-20200508163631-1844661b331d
)
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/unixpickle/essentials v1.0.1/go.mod h1:dQ1idvqrgrDgub3mfckQm7osVPzT3u9rB6NK/LEhmtQ=
github.com/unixpickle/essentials v1.1.0 h1:kJ/mU3MfmmSfuU8zyplwkup60lKV9+ucqZC+hR1GgVU=
github.com/unixpickle/essentials v1.1.0/go.mod h1:dQ1idvqrgrDgub3mfckQm7osVPzT3u9rB6NK/LEhmtQ=
github.com/unixpickle/model3d v0.2.1 h1:zivAhQEXGRXiXjgzoSwR5XabUBoh+2icTi9aKNR0i60=
github.com/unixpickle/model3d v0.2.1/go.mod h1:/jD5uEOZVtoeyF0K1t9rCFsturMfh/gdj3fX/DVV360=
github.com/unixpickle/polish v0.0.0-20200508163631-1844661b331d h1:rGYhWppLAJHhJutmdh+rp041ppAm8m1G2nrep+gm8UU=
github.com/unixpickle/polish v0.0.0-20200508163631-1844661b331d/go.mod h1:chqUO0H3b+eCUmr/ZNyEklARKZQZXbrxGC5PZbThTrY=
package main
import (
"fmt"
"image/png"
"log"
"math"
"os"
"github.com/unixpickle/essentials"
"github.com/unixpickle/model3d/model3d"
"github.com/unixpickle/model3d/render3d"
"github.com/unixpickle/polish/polish"
)
const (
RenderSize = 400
RoomWidth = 10
RoomHeight = 10
RoomDepth = 20
LightWidth = 4
LightDepth = 2
LightHeight = 0.2
LightIntensity = 5
LightCenterX = RoomWidth / 2
LightCenterY = RoomDepth * 0.6
LightCenterZ = RoomHeight - LightHeight/2
PickleCenterX = RoomWidth / 2
PickleCenterY = RoomDepth * 0.7
PickleCenterZ = RoomHeight / 2
PickleRescaleHeight = RoomHeight * 0.7
)
func main() {
mesh, colors := CreatePickle()
var i int
DeformIntoSphere(mesh, 48, func() {
log.Println("Creating frame", i, "...")
img := CreateFrame(mesh, colors)
PolishAndSave(fmt.Sprintf("frame_%03d.png", i), img)
i++
})
}
func DeformIntoSphere(mesh *model3d.Mesh, iters int, cb func()) {
// Center the mesh before operating on it.
min, max := mesh.Min(), mesh.Max()
offset := min.Mid(max).Scale(-1)
mesh.Iterate(func(t *model3d.Triangle) {
mesh.Remove(t)
for i, c := range t {
t[i] = c.Add(offset)
}
mesh.Add(t)
})
// Turn the mesh into indexable coordinates for faster
// access.
coords := mesh.VertexSlice()
coordToIdx := map[model3d.Coord3D]int{}
for i, c := range coords {
coordToIdx[c] = i
}
triIdxs := map[*model3d.Triangle][3]int{}
mesh.Iterate(func(t *model3d.Triangle) {
var idxs [3]int
for i, c := range t {
idxs[i] = coordToIdx[c]
}
triIdxs[t] = idxs
})
// Compute the geo-coordinates that each vertex on the
// mesh will eventually end up at.
geoCoords := make([]model3d.GeoCoord, len(coords))
yToCenter := map[float64]model3d.Coord3D{}
collider := model3d.MeshToCollider(mesh)
for i, c := range coords {
center, ok := yToCenter[c.Y]
if !ok {
sliceMin, sliceMax := max, min
cutter := &model3d.Triangle{
model3d.Coord3D{X: -1000, Y: c.Y, Z: -1000},
model3d.Coord3D{X: 1000, Y: c.Y, Z: -1000},
model3d.Coord3D{X: 0, Y: c.Y, Z: 1000},
}
for _, seg := range collider.TriangleCollisions(cutter) {
sliceMin = sliceMin.Min(seg[0].Min(seg[1]))
sliceMax = sliceMax.Max(seg[0].Max(seg[1]))
}
center = sliceMin.Mid(sliceMax)
yToCenter[c.Y] = center
}
center.Y = 0
geoCoords[i] = c.Sub(center).Geo()
}
// Interpolate between original and final mesh.
for i := 0; i < iters; i++ {
frac := float64(i) / float64(iters-1)
newCoords := make([]model3d.Coord3D, len(coords))
for i, c := range coords {
geoC := geoCoords[i].Coord3D()
newCoords[i] = c.Scale(1 - frac).Add(geoC.Scale(frac))
}
mesh.Iterate(func(t *model3d.Triangle) {
mesh.Remove(t)
for i, cIdx := range triIdxs[t] {
t[i] = newCoords[cIdx]
}
mesh.Add(t)
})
cb()
}
}
func CreateFrame(mesh *model3d.Mesh, colors map[*model3d.Triangle]render3d.Color) *render3d.Image {
light := CreateLight()
scene := render3d.JoinedObject{
CreatePickleObject(mesh, colors),
CreateRoom(),
light,
}
bidir := &render3d.BidirPathTracer{
Camera: render3d.NewCameraAt(
model3d.Coord3D{X: RoomWidth / 2, Y: RoomDepth * 0.01, Z: RoomHeight * 0.5},
model3d.Coord3D{X: RoomWidth / 2, Y: RoomDepth, Z: RoomHeight * 0.5},
math.Pi/3.6,
),
Light: light,
MaxDepth: 15,
MinDepth: 3,
NumSamples: 100,
RouletteDelta: 0.1,
Cutoff: 1e-4,
Antialias: 1.0,
LogFunc: func(frac, rayPerPix float64) {
if math.Abs(frac*10-math.Floor(frac*10)) < 1e-5 {
log.Printf("Render at %d%% (rpp %d)", int(100*frac), int(rayPerPix))
}
},
}
img := render3d.NewImage(RenderSize, RenderSize)
bidir.Render(img, scene)
return img
}
func PolishAndSave(path string, frame *render3d.Image) {
img := polish.PolishImage(polish.ModelTypeDeep, frame.RGBA())
w, err := os.Create(path)
essentials.Must(err)
defer w.Close()
essentials.Must(png.Encode(w, img))
}
func CreatePickleObject(mesh *model3d.Mesh,
colors map[*model3d.Triangle]render3d.Color) render3d.Object {
var pickle render3d.Object
pickle = &PickleObject{
Object: &render3d.ColliderObject{
Collider: model3d.MeshToCollider(mesh),
},
Colors: colors,
}
// Orient pickle to face the camera.
pickle = render3d.MatrixMultiply(pickle, model3d.NewMatrix3Rotation(
model3d.Coord3D{X: 1},
math.Pi/2,
))
// Scale to be correct height.
min, max := pickle.Min(), pickle.Max()
heightScale := PickleRescaleHeight / (max.Z - min.Z)
pickle = render3d.MatrixMultiply(pickle, &model3d.Matrix3{
heightScale, 0, 0,
0, heightScale, 0,
0, 0, heightScale,
})
// Place in the center position.
min, max = pickle.Min(), pickle.Max()
center := model3d.Coord3D{X: PickleCenterX, Y: PickleCenterY, Z: PickleCenterZ}
offset := center.Sub(max.Mid(min))
pickle = render3d.Translate(pickle, offset)
return pickle
}
func CreateRoom() render3d.Object {
return &render3d.ColliderObject{
Collider: model3d.MeshToCollider(
model3d.NewMeshRect(
model3d.Coord3D{Z: -RoomHeight},
model3d.Coord3D{X: RoomWidth, Y: RoomDepth},
).MapCoords(model3d.Coord3D{X: 1, Y: 1, Z: -1}.Mul),
),
Material: &render3d.LambertMaterial{
DiffuseColor: render3d.NewColor(0.8),
},
}
}
func CreateLight() render3d.AreaLight {
mesh := model3d.NewMeshRect(
model3d.Coord3D{X: LightCenterX - LightWidth/2, Y: LightCenterY - LightDepth/2,
Z: LightCenterZ - LightHeight/2},
model3d.Coord3D{X: LightCenterX + LightWidth/2, Y: LightCenterY + LightDepth/2,
Z: LightCenterZ + LightHeight/2},
)
// Remove top of light since it touches the ceiling.
mesh.Iterate(func(t *model3d.Triangle) {
if t.Normal().Z > 0.5 {
mesh.Remove(t)
}
})
return render3d.NewMeshAreaLight(mesh, render3d.NewColor(LightIntensity))
}
type PickleObject struct {
render3d.Object
Colors map[*model3d.Triangle]render3d.Color
}
func (p *PickleObject) Cast(r *model3d.Ray) (model3d.RayCollision, render3d.Material, bool) {
rc, mat, ok := p.Object.Cast(r)
if !ok {
return rc, mat, ok
}
mat = &render3d.PhongMaterial{
Alpha: 20.0,
SpecularColor: render3d.NewColor(0.1),
DiffuseColor: p.Colors[rc.Extra.(*model3d.TriangleCollision).Triangle].Scale(0.9),
}
return rc, mat, ok
}
package main
// Stolen from https://github.com/unixpickle/model3d/blob/d744cfb11dd09540f732abe672c3432bfa9e4454/examples/decoration/pickle/main.go
import (
"sync"
"github.com/unixpickle/model3d/model2d"
"github.com/unixpickle/model3d/model3d"
"github.com/unixpickle/model3d/render3d"
)
var Green = [3]float64{
float64(0x1b) / 255.0,
float64(0xad) / 255.0,
float64(0x64) / 255.0,
}
const (
PickleLength = 2.0
PickleWidth = PickleLength / 2
)
func CreatePickle() (*model3d.Mesh, map[*model3d.Triangle]render3d.Color) {
var solid model3d.Solid
solid = &PickleSolid{F: NewPickleFunction()}
inscription := NewInscription()
mesh := model3d.MarchingCubesSearch(solid, 0.006, 8)
colorFunc := model3d.VertexColorsToTriangle(inscription.ColorAt)
colors := map[*model3d.Triangle]render3d.Color{}
mesh.Iterate(func(t *model3d.Triangle) {
rgb := colorFunc(t)
colors[t] = render3d.NewColorRGB(rgb[0], rgb[1], rgb[2])
})
return mesh, colors
}
type PickleSolid struct {
F *PickleFunction
}
func (p *PickleSolid) Min() model3d.Coord3D {
min := p.F.Collider.Min()
return model3d.Coord3D{X: min.X, Y: min.Y, Z: -PickleWidth}
}
func (p *PickleSolid) Max() model3d.Coord3D {
max := p.F.Collider.Max()
return model3d.Coord3D{X: max.X, Y: max.Y, Z: PickleWidth}
}
func (p *PickleSolid) Contains(c model3d.Coord3D) bool {
radius := p.F.RadiusAt(c.Y)
center := p.F.CenterAt(c.Y)
dist := c.Dist(model3d.Coord3D{X: center, Y: c.Y})
return dist < radius
}
type PickleFunction struct {
Collider model2d.Collider
// A synchronized map[float64][2]float64{}.
cache sync.Map
}
func NewPickleFunction() *PickleFunction {
bmp := model2d.MustReadBitmap("pickle.png", nil).FlipY()
mesh := bmp.Mesh().SmoothSq(100)
mesh = mesh.MapCoords(func(c model2d.Coord) model2d.Coord {
return c.Scale(PickleLength / float64(bmp.Height))
})
collider := model2d.MeshToCollider(mesh)
return &PickleFunction{Collider: collider}
}
func (p *PickleFunction) RadiusAt(y float64) float64 {
min, max := p.minMaxAt(y)
return (max - min) / 2
}
func (p *PickleFunction) CenterAt(y float64) float64 {
min, max := p.minMaxAt(y)
return (min + max) / 2
}
func (p *PickleFunction) minMaxAt(y float64) (float64, float64) {
if val, ok := p.cache.Load(y); ok {
val := val.([2]float64)
return val[0], val[1]
}
r := &model2d.Ray{
Origin: model2d.Coord{Y: y},
Direction: model2d.Coord{X: 1},
}
min := 0.0
max := 0.0
p.Collider.RayCollisions(r, func(rc model2d.RayCollision) {
if rc.Scale < min || min == 0 {
min = rc.Scale
}
if rc.Scale > max {
max = rc.Scale
}
})
p.cache.Store(y, [2]float64{min, max})
return min, max
}
type Inscription struct {
Solid model2d.Solid
}
func NewInscription() *Inscription {
bmp := model2d.MustReadBitmap("inscription.png", nil).FlipY()
mesh := bmp.Mesh().SmoothSq(20)
collider := model2d.MeshToCollider(mesh)
scale := PickleLength / float64(bmp.Height)
return &Inscription{
Solid: model2d.ScaleSolid(model2d.NewColliderSolid(collider), scale),
}
}
func (i *Inscription) Min() model3d.Coord3D {
min := i.Solid.Min()
return model3d.Coord3D{X: min.X, Y: min.Y, Z: -PickleWidth}
}
func (i *Inscription) Max() model3d.Coord3D {
max := i.Solid.Max()
return model3d.Coord3D{X: max.X, Y: max.Y, Z: PickleWidth}
}
func (i *Inscription) Contains(c model3d.Coord3D) bool {
if !model3d.InBounds(i, c) {
return false
}
return i.Solid.Contains(c.Coord2D())
}
func (i *Inscription) ColorAt(c model3d.Coord3D) [3]float64 {
if c.Z < 0 {
return Green
}
if i.Solid.Contains(c.Coord2D()) {
return [3]float64{1, 1, 1}
} else {
return Green
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment