-
Notifications
You must be signed in to change notification settings - Fork 12
/
main.go
151 lines (124 loc) · 3.34 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package main
import (
"log"
"math"
"github.com/unixpickle/model3d/model3d"
"github.com/unixpickle/model3d/render3d"
"github.com/unixpickle/model3d/toolbox3d"
"github.com/unixpickle/model3d/model2d"
)
const (
CrossSectionRadius = 0.5
CrossSectionJitter = 0.2
BananaLength = 3.0
BananaStemLength = 0.3
BananaStemFrac = BananaStemLength / BananaLength
BananaStemRadius = 0.3
BaseThickness = 0.15
BaseWidthFrac = 0.9
)
func main() {
banana := NewBananaSolid()
// Create a small square base at the lowest part of
// the model to make it stickable onto a wall.
log.Println("Creating base...")
minPoint := SolidMinZ(banana)
baseSize := BaseWidthFrac * (banana.Max().Y - banana.Min().Y)
baseMin := model3d.XYZ(minPoint.X-baseSize/2, -baseSize/2, minPoint.Z-BaseThickness/2)
base := &model3d.Rect{
MinVal: baseMin,
MaxVal: baseMin.Add(model3d.XYZ(baseSize, baseSize, BaseThickness)),
}
solid := model3d.JoinedSolid{
base,
banana,
}
log.Println("Creating mesh...")
mesh := model3d.MarchingCubesSearch(solid, 0.015, 8)
log.Println("Saving mesh...")
mesh.SaveGroupedSTL("banana.stl")
log.Println("Rendering...")
render3d.SaveRendering("rendering.png", mesh, model3d.XYZ(3.5, -4, 2), 500, 300, nil)
}
type BananaSolid struct {
CrossSection model2d.Solid
Radius model2d.BezierCurve
Curve *Curve
MinVal model3d.Coord3D
MaxVal model3d.Coord3D
}
func NewBananaSolid() *BananaSolid {
radiusCurve := model2d.BezierCurve{
model2d.XY(0, 0),
model2d.XY(0, 1.5),
model2d.XY(1-BananaStemFrac, 1.5),
model2d.XY(1-BananaStemFrac, 0),
}
squircle := CreateSquircle()
curve := NewCurve()
maxRadius := CurveMaxY(radiusCurve) * squircle.Max().Sub(squircle.Min()).X / 2
curveMin := curve.Min()
curveMax := curve.Max()
return &BananaSolid{
CrossSection: squircle,
Radius: radiusCurve,
Curve: curve,
MinVal: model3d.XYZ(curveMin.X-maxRadius, -maxRadius, curveMin.Y-maxRadius),
MaxVal: model3d.XYZ(curveMax.X+maxRadius, maxRadius, curveMax.Y+maxRadius),
}
}
func (b *BananaSolid) Min() model3d.Coord3D {
return b.MinVal
}
func (b *BananaSolid) Max() model3d.Coord3D {
return b.MaxVal
}
func (b *BananaSolid) Contains(c model3d.Coord3D) bool {
if !model3d.InBounds(b, c) {
return false
}
x, axis1 := b.Curve.Project(c.XZ())
c2d := model2d.XY(axis1, c.Y)
var radius float64
if x < 1-BananaStemFrac {
radius = b.Radius.EvalX(x)
}
if x > 0.5 {
// Add in a stem.
radius = math.Max(radius, BananaStemRadius)
}
if radius <= 0 {
return false
}
c2d = c2d.Scale(1 / radius)
return b.CrossSection.Contains(c2d)
}
func CreateSquircle() model2d.Solid {
var res model2d.IntersectedSolid
for i := 0; i < 4; i++ {
theta := float64(i) * math.Pi / 2
center := model2d.Coord{X: math.Cos(theta), Y: math.Sin(theta)}
res = append(res, &model2d.Circle{
Radius: CrossSectionRadius,
Center: center.Scale(CrossSectionJitter),
})
}
return res
}
func CurveMaxY(radiusCurve model2d.Curve) float64 {
ls := &toolbox3d.LineSearch{Stops: 100, Recursions: 4}
_, y := ls.Maximize(0, 1, func(t float64) float64 {
return radiusCurve.Eval(t).Y
})
return y
}
func SolidMinZ(s model3d.Solid) model3d.Coord3D {
lowRes := model3d.MarchingCubesSearch(s, 0.05, 8)
var lowest model3d.Coord3D
for i, c := range lowRes.VertexSlice() {
if i == 0 || c.Z < lowest.Z {
lowest = c
}
}
return lowest
}