Last active
May 8, 2020 23:33
-
-
Save unixpickle/1132b6aae131452384b315b1cb55005f to your computer and use it in GitHub Desktop.
Deformation into sphere
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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

