field.gno
4.80 Kb · 148 lines
1package physics
2
3// Wall is a straight barrier. Bounce overrides the field's restitution (0
4// keeps it); Mark and Skin are for whoever draws it, except that only a
5// bumper's skin kicks (Kicks).
6type Wall struct {
7 Seg Segment
8 Bounce float64 // 0 = use the field default
9 Mark rune
10 Skin string
11 // Every > 0 times the wall within a stroke: it stands in substep i only
12 // when (i+Tick+Phase) mod Every is below On.
13 Every, On, Phase int
14}
15
16// There is the timing rule walls, zones and course pulses share (sign-safe
17// mod, so a negative phase works): on at i when (i+phase) mod every is below
18// on; every <= 0 is always there.
19func There(i, every, on, phase int) bool {
20 return every <= 0 || ((i+phase)%every+every)%every < on
21}
22
23// WithTick is the field with its clock moved on by tick substeps. It may
24// return f itself (tick 0): do not mutate what it returns.
25func WithTick(f *Field, tick int) *Field {
26 if tick == 0 {
27 return f
28 }
29 g := *f
30 g.Tick = f.Tick + tick
31 return &g
32}
33
34// Post is a round obstacle. Bounce is the contact's restitution, played at
35// MaxBounce at most; above 1 with a bumper's skin (Kicks) it kicks the ball
36// back faster than it came (at most MaxKick times).
37type Post struct {
38 Circle
39 Bounce float64 // 0 = use the field default; played at MaxBounce at most; above 1 a bumper, if Kicks(Skin)
40 Mark rune
41 Skin string
42}
43
44// ZoneKind is what a zone does to a ball inside it.
45type ZoneKind int
46
47// The zone kinds, each named in its own line.
48const (
49 Surface ZoneKind = iota // Scale sets the rolling resistance (Rolling): sand holds, ice frees
50 Slope // a hill: Vec, gravity along it, is added to velocity each substep
51 Tunnel // the ball is moved to Vec, keeping its velocity
52 Hazard // the ball goes back where the stroke was played from: water, a pit, the void
53 // Loop is a tube's mouth (Min..Max): a ball heading in within 25° at
54 // Scale or faster comes out at Vec, else it falls back or off the side.
55 // The mouth must be at least MaxMove deep, or a fast ball steps over it.
56 Loop
57)
58
59// LoopKeep is the share of its speed a ball keeps once round a loop.
60const LoopKeep = 0.8
61
62// Zone is an area of the board that changes how the ball moves: the rectangle
63// Min..Max, or the ellipse inside it (Round), or a polygon (Poly), or all of it
64// but a polygon (Outside). Vec and Scale mean what the kind says.
65type Zone struct {
66 Kind ZoneKind
67 Min, Max Vec2
68 Vec Vec2 // Slope: acceleration (G·sin θ downhill). Tunnel: destination. Loop: where it comes down. Hazard: unused.
69 Scale float64 // Surface: its rolling resistance (Rolling). Loop: the speed to go round.
70 Mark rune
71 Skin string
72 // Round makes the zone the ellipse inscribed in Min..Max.
73 Round bool
74 // Poly (3 points or more) makes the zone that polygon, within Min..Max;
75 // Outside makes it everything in Min..Max but the polygon.
76 Poly []Vec2
77 Outside bool
78 // Every, On, Phase time the zone within a stroke, as for a Wall.
79 Every, On, Phase int
80 // Air makes a Slope moving air: it pushes the ball but is no hill, and
81 // hides none under it. Capped air never speeds the ball up.
82 Air, Capped bool
83}
84
85// Contains says whether p is in the zone, by its shape.
86func (z Zone) Contains(p Vec2) bool { return z.contains(p) }
87
88// contains is Contains through a pointer: a Zone is too big to copy per move.
89func (z *Zone) contains(p Vec2) bool {
90 if p.X < z.Min.X || p.X >= z.Max.X || p.Y < z.Min.Y || p.Y >= z.Max.Y {
91 return false
92 }
93 if len(z.Poly) >= 3 {
94 return inPoly(z.Poly, p) != z.Outside
95 }
96 if !z.Round {
97 return true
98 }
99 hx, hy := (z.Max.X-z.Min.X)/2, (z.Max.Y-z.Min.Y)/2
100 dx, dy := (p.X-z.Min.X-hx)/hx, (p.Y-z.Min.Y-hy)/hy
101 return dx*dx+dy*dy < 1
102}
103
104// Field is everything a ball rolls through: walls, posts and zones.
105type Field struct {
106 Walls []Wall
107 Posts []Post
108 Zones []Zone
109 Friction float64 // the green's rolling resistance, as Rolling maps it: 0.87 is Crr 0.224
110 Bounce float64 // default restitution of barriers
111 // Radius of the ball (0 plays it as a point).
112 Radius float64
113 // Tick is where the stroke starts on the clock of timed walls and zones.
114 Tick int
115 // Cap, above 0 and under MaxWork, replaces MaxWork for this stroke.
116 Cap int
117 // prep is the walls' offsets, worked out once (Prepare).
118 prep []float64
119}
120
121// String is the kind's name as clients read it: "surface", "slope", ...
122func (k ZoneKind) String() string {
123 switch k {
124 case Surface:
125 return "surface"
126 case Slope:
127 return "slope"
128 case Tunnel:
129 return "tunnel"
130 case Hazard:
131 return "hazard"
132 case Loop:
133 return "loop"
134 }
135 return "unknown"
136}
137
138// inPoly is the even-odd point-in-polygon test.
139func inPoly(pts []Vec2, p Vec2) bool {
140 in := false
141 for i := range pts {
142 a, b := pts[i], pts[(i+1)%len(pts)]
143 if (a.Y > p.Y) != (b.Y > p.Y) && p.X < a.X+(p.Y-a.Y)*(b.X-a.X)/(b.Y-a.Y) {
144 in = !in
145 }
146 }
147 return in
148}