105 lines
4.5 KiB
Go
105 lines
4.5 KiB
Go
package plates
|
|
|
|
import "math"
|
|
|
|
// Belt is how faulted a margin's surroundings are: the deformation zone around a boundary, and how densely
|
|
// it is broken.
|
|
//
|
|
// It lives beside the plates rather than beside the faults because it describes a *boundary*, not a fault.
|
|
// How wide the ground is that a margin deforms is a property of what that margin is doing - a continental
|
|
// collision takes up its convergence across a belt a thousand kilometres wide and a mid-ocean ridge across an
|
|
// axis a few tens wide - and the traces are a consequence of that width, not the other way round. What
|
|
// internal/uplift's belt_faults.go does with these numbers, and the fault map they were read off, is
|
|
// documented there.
|
|
type Belt struct {
|
|
// ZoneKm is the deformation half-width of a *collision* margin closing at ReferenceCmYr, in kilometres.
|
|
// Every other kind of margin is a fraction of it, and every margin scales with its own rate.
|
|
ZoneKm float64 `json:"zone_km"`
|
|
|
|
// ReferenceCmYr is the rate ZoneKm is quoted at. Earth's big collisions run 2 to 5 cm/yr.
|
|
ReferenceCmYr float64 `json:"reference_cm_yr"`
|
|
|
|
// Per1000Km2 is the trace density over the *zone*, not over the planet: a belt is as faulted as a belt is
|
|
// wherever it happens to run, and the ground away from one is not lightly faulted, it is unfaulted.
|
|
Per1000Km2 float64 `json:"per_1000km2"`
|
|
|
|
// ThrowM is the total displacement over the whole run, low to high, before the closing rate scales it -
|
|
// the height of the scarp the fault would build if nothing eroded it.
|
|
ThrowM [2]float64 `json:"throw_m"`
|
|
|
|
// LengthKm is how long a trace is, low to high, before the local zone width scales it. A wide belt
|
|
// carries long faults and a narrow one cannot.
|
|
LengthKm [2]float64 `json:"length_km"`
|
|
|
|
// StrikeSpreadDeg is how far a trace may wander off the belt's local tangent. Small on purpose: a swarm
|
|
// being sub-parallel is the thing that makes it read as a swarm.
|
|
//
|
|
// ConjugateFraction is the share of traces drawn on the second, crossing direction, and ConjugateDeg is
|
|
// the angle between the two sets. One direction alone reads as corduroy.
|
|
//
|
|
// The first two are pointers for the reason Config.SpinFraction is: JSON cannot tell an absent number
|
|
// from a zero one, and both of these have a real meaning at zero - perfectly parallel traces, and no
|
|
// second set. Read as the same thing, a block that simply did not mention them silently turned them off,
|
|
// which is how the first painted planet came out with no conjugate set at all. Absent takes the default;
|
|
// an explicit 0 means none.
|
|
StrikeSpreadDeg *float64 `json:"strike_spread_deg"`
|
|
ConjugateFraction *float64 `json:"conjugate_fraction"`
|
|
ConjugateDeg float64 `json:"conjugate_deg"`
|
|
}
|
|
|
|
// DefaultStrikeSpreadDeg and DefaultConjugateFraction are what a belt that does not mention them gets.
|
|
const (
|
|
DefaultStrikeSpreadDeg = 11.0
|
|
DefaultConjugateFraction = 0.22
|
|
)
|
|
|
|
// Spread is the configured strike spread, or the default when the block said nothing.
|
|
func (b Belt) Spread() float64 {
|
|
if b.StrikeSpreadDeg == nil {
|
|
return DefaultStrikeSpreadDeg
|
|
}
|
|
return math.Max(0, *b.StrikeSpreadDeg)
|
|
}
|
|
|
|
// Conjugate is the configured share of crossing traces, or the default when the block said nothing.
|
|
func (b Belt) Conjugate() float64 {
|
|
if b.ConjugateFraction == nil {
|
|
return DefaultConjugateFraction
|
|
}
|
|
return math.Min(1, math.Max(0, *b.ConjugateFraction))
|
|
}
|
|
|
|
// DefaultBelt is what a planet that asks for belt faults but says nothing else gets.
|
|
func DefaultBelt() Belt {
|
|
return Belt{
|
|
ZoneKm: 6,
|
|
ReferenceCmYr: 4,
|
|
Per1000Km2: 90,
|
|
ThrowM: [2]float64{80, 420},
|
|
LengthKm: [2]float64{4, 16},
|
|
ConjugateDeg: 32,
|
|
// StrikeSpreadDeg and ConjugateFraction stay nil: their defaults live in Spread and Conjugate, so
|
|
// that an explicit zero can mean none.
|
|
}
|
|
}
|
|
|
|
// Wanted reports whether this asks for anything. A zero Belt is a planet whose margins are not faulted, which
|
|
// is what every painted planet had before this existed.
|
|
func (b Belt) Wanted() bool {
|
|
return b.ZoneKm > 0 && b.Per1000Km2 > 0 && b.LengthKm[1] > 0 && b.ThrowM[1] > 0
|
|
}
|
|
|
|
// WithDefaults fills in the fields that have a sensible value when left out. ZoneKm, Per1000Km2, ThrowM and
|
|
// LengthKm are deliberately not among them: those four are the feature, and defaulting them would turn
|
|
// leaving the block out into switching the feature on.
|
|
func (b Belt) WithDefaults() Belt {
|
|
d := DefaultBelt()
|
|
if b.ReferenceCmYr <= 0 {
|
|
b.ReferenceCmYr = d.ReferenceCmYr
|
|
}
|
|
if b.ConjugateDeg <= 0 {
|
|
b.ConjugateDeg = d.ConjugateDeg
|
|
}
|
|
return b
|
|
}
|