Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}