-
Notifications
You must be signed in to change notification settings - Fork 1
/
misc.go
141 lines (124 loc) · 4.7 KB
/
misc.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
package geo
import (
"math"
"math/rand"
"sort"
"time"
)
// Clamp returns n if it is within [min(a, b), max(a, b)] otherwise it returns the closer
// of a and b.
func Clamp(n, a, b float64) float64 {
if a > b {
a, b = b, a
}
if n < a {
return a
}
if n > b {
return b
}
return n
}
// RandIndex takes a list of weights and returns an index with a probability corresponding
// to the relative weight of each index. Behavior is undefined if weights is the empty list.
// A weight of 0 will never be selected unless all are 0, in which case all indices have
// equal probability. Negative weights are treated as 0.
func RandIndex(weights []float64) int {
cumWeights := make([]float64, len(weights))
cumWeights[0] = weights[0]
for i, w := range weights {
if i > 0 {
cumWeights[i] = cumWeights[i-1] + math.Max(w, 0)
}
}
if cumWeights[len(weights)-1] == 0.0 {
return rand.Intn(len(weights))
}
rnd := rand.Float64() * cumWeights[len(weights)-1]
return sort.SearchFloat64s(cumWeights, rnd)
}
// Map takes a number n in the range [a1, b1] and remaps it be in the range [a2, b2], with
// its relative position within the range preserved. E.g if n is half way between a1 and b1
// then the value returned will be half way between a2 and b2. If a1 == b1 then +Inf is returned.
func Map(n, a1, b1, a2, b2 float64) float64 {
range1 := b1 - a1
range2 := b2 - a2
offset := n - a1
percent := offset / range1
newOffset := percent * range2
return a2 + newOffset
}
// Mod is the modulus operator. Unlike the Mod in the math package this wraps negative
// numbers around to the positive axis. If b is 0 then this function returns 0.
// e.g. if b is 3
// a -5 -4 -3 -2 -1 0 1 2 3 4 5
// return 1 2 0 1 2 0 1 2 0 1 2
func Mod(a, b float64) float64 {
if b == 0 {
return 0
}
return math.Mod(math.Mod(a, b)+b, b)
}
// I2F2 takes 2 ints and converts them to float64. e.g.
// rect.SetTopLeft(I2F2(functionThatReturns2Ints()))
func I2F2(i1, i2 int) (f1, f2 float64) {
return float64(i1), float64(i2)
}
// Shaker wraps the arguments to the shake functions for convenience and reusability.
type Shaker struct {
// Seed can be used to change up the shaking behaviour, because all of the shake functions
// are deterministic and often one wants it look different while keeping the other parameters
// the same. Though if different values for t are always being used then changing the Seed
// may not be necessary.
Seed float64
// StartTime is the time that the shaking starts. Reactivating a Shaker that has ended
// should usually be as simple as updating the StartTime.
StartTime time.Time
// Duration is how long the shaking takes place.
Duration time.Duration
// Amplitude is the maximum length of the offset.
Amplitude float64
// Frequency controls how quickly the shaking happens.
Frequency float64
// Falloff modifies the amplitude over time, using StartTime and Duration. This makes
// it easy to fade out the shaking.
Falloff EaseFn
}
// EndTime returns the time that the shaking would end, if using the non-Const Shake functions.
func (s *Shaker) EndTime() time.Time {
return s.StartTime.Add(s.Duration)
}
// Shake takes a current time t and returns an offset. The time t will be clamped between
// s.StartTime and s.StartTime + s.Duration. This function makes use of StartTime, Duration,
// and Falloff to change the amplitude of the offset over time. The length of the Vec returned
// is never greater than the amplitude.
func (s *Shaker) Shake(t time.Time) Vec {
return s.shakeConst(t, s.amp(t))
}
// ShakeConst takes a current time t and returns an offset. The max amplitude of the offset
// is not varied over time so StartTime, Duration, and Falloff are not used.
func (s *Shaker) ShakeConst(t time.Time) Vec {
return s.shakeConst(t, s.Amplitude)
}
// Shake1 is the same as Shake function but works in 1 dimension.
func (s *Shaker) Shake1(t time.Time) float64 {
return s.shakeConst1(t, s.amp(t))
}
// ShakeConst1 is the same as the ShakeConst but works in 1 dimension.
func (s *Shaker) ShakeConst1(t time.Time) float64 {
return s.shakeConst1(t, s.Amplitude)
}
func (s *Shaker) amp(t time.Time) float64 {
dt := Clamp(t.Sub(s.StartTime).Seconds(), 0, s.Duration.Seconds()) / s.Duration.Seconds()
return s.Amplitude * (1 - s.Falloff(dt))
}
func (s *Shaker) shakeConst(t time.Time, amplitude float64) Vec {
dt := time.Duration(t.UnixNano()).Seconds()
len := Map(Perlin(dt*s.Frequency, s.Seed, s.Seed), 0, 1, -1, 1) * amplitude
angle := Map(Perlin(s.Seed, dt*s.Frequency, s.Seed), 0, 1, -math.Pi, math.Pi)
return VecLA(len, angle)
}
func (s *Shaker) shakeConst1(t time.Time, amplitude float64) float64 {
dt := time.Duration(t.UnixNano()).Seconds()
return Map(Perlin(dt*s.Frequency, s.Seed, s.Seed), 0, 1, -1, 1) * amplitude
}