-
Notifications
You must be signed in to change notification settings - Fork 12
/
main.go
147 lines (125 loc) · 3.89 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
package main
import (
"flag"
"log"
"math"
"github.com/unixpickle/model3d/render3d"
"github.com/unixpickle/essentials"
"github.com/unixpickle/model3d/model3d"
)
func main() {
var radius float64
var depth float64
var ripple float64
var pointRadius float64
var pointDepth float64
var pointSpacing float64
var extraPoints bool
flag.Float64Var(&radius, "radius", 2.5, "radius of the sun ball")
flag.Float64Var(&depth, "depth", 1.5, "depth of the sun ball (no larger than radius)")
flag.Float64Var(&ripple, "ripple", 0.05, "relative height of sphere ripples")
flag.Float64Var(&pointRadius, "point-radius", 0.7, "outward radius of pointed edges")
flag.Float64Var(&pointDepth, "point-depth", 0.1, "depth of pointed edges")
flag.Float64Var(&pointSpacing, "point-spacing", 1.0, "space between points (in point size)")
flag.BoolVar(&extraPoints, "extra-points", false, "add an extra layer of points")
flag.Parse()
if depth > radius {
essentials.Die("depth must not exceed radius")
}
solid := model3d.JoinedSolid{
NewSunBall(radius, depth, ripple),
NewPointedEdges(radius, pointRadius, pointDepth, pointSpacing, false),
}
if extraPoints {
solid = append(solid, NewPointedEdges(radius, pointRadius, pointDepth*1.5, pointSpacing,
true))
}
log.Println("Creating mesh...")
mesh := model3d.MarchingCubesSearch(solid, 0.02, 8)
mesh = mesh.EliminateCoplanar(1e-5)
log.Println("Saving mesh...")
mesh.SaveGroupedSTL("sun.stl")
log.Println("Rendering...")
render3d.SaveRandomGrid("rendering.png", mesh, 3, 3, 300, nil)
}
type SunBall struct {
Radius float64
Depth float64
SphereCenter model3d.Coord3D
SphereRadius float64
RippleHeight float64
}
func NewSunBall(radius, depth, ripple float64) *SunBall {
// radius^2 + (sphereRadius - depth)^2 = sphereRadius^2
// radius^2 + sphereRadius^2 - 2*sphereRadius*depth + depth^2 = sphereRadius^2
// radius^2 + depth^2 = 2*sphereRadius*depth
// (radius^2 + depth^2)/(2*depth) = sphereRadius
sphereRadius := (radius*radius + depth*depth) / (2 * depth)
return &SunBall{
Radius: radius,
Depth: depth,
SphereCenter: model3d.Z(depth - sphereRadius),
SphereRadius: sphereRadius,
RippleHeight: ripple * sphereRadius,
}
}
func (s *SunBall) Min() model3d.Coord3D {
return model3d.XY(-s.Radius, -s.Radius)
}
func (s *SunBall) Max() model3d.Coord3D {
return model3d.XYZ(s.Radius, s.Radius, s.Depth)
}
func (s *SunBall) Contains(c model3d.Coord3D) bool {
if !model3d.InBounds(s, c) {
return false
}
geo := c.Sub(s.SphereCenter).Geo()
r := c.Dist(s.SphereCenter)
rippleInset := math.Pow(math.Cos(geo.Lat*20+math.Sin(geo.Lon*5)), 2)
rippleInset *= s.RippleHeight
rippleInset *= c.Z / s.Depth // smaller ripples at sides
return r < s.SphereRadius-rippleInset
}
type PointedEdges struct {
MinRadius float64
MaxRadius float64
Depth float64
Phase float64
Frequency float64
Spacing float64
}
func NewPointedEdges(baseRadius, extraRadius, depth, spacing float64, phase bool) *PointedEdges {
circum := math.Pi * 2 * (baseRadius + extraRadius/2)
sideLength := 2 * extraRadius / math.Sqrt(3)
freq := math.Floor(circum / (sideLength * spacing))
res := &PointedEdges{
MinRadius: baseRadius,
MaxRadius: baseRadius + extraRadius,
Depth: depth,
Frequency: freq,
Spacing: spacing,
}
if phase {
res.Phase += 0.5 / res.Frequency
}
return res
}
func (p *PointedEdges) Min() model3d.Coord3D {
return model3d.XY(-p.MaxRadius, -p.MaxRadius)
}
func (p *PointedEdges) Max() model3d.Coord3D {
return model3d.XYZ(p.MaxRadius, p.MaxRadius, p.Depth)
}
func (p *PointedEdges) Contains(c model3d.Coord3D) bool {
if !model3d.InBounds(p, c) {
return false
}
r := c.XY().Norm()
if r < p.MinRadius {
return true
}
theta := math.Atan2(c.Y, c.X) + 2*math.Pi
modulo := math.Mod(theta/(2*math.Pi)+p.Phase, 1/p.Frequency) * p.Frequency
radiusFrac := (p.MaxRadius - r) / (p.MaxRadius - p.MinRadius)
return math.Abs(modulo-0.5)*2*p.Spacing < radiusFrac
}