mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] support processing of (many) more media types (#3090)
* initial work replacing our media decoding / encoding pipeline with ffprobe + ffmpeg * specify the video codec to use when generating static image from emoji * update go-storage library (fixes incompatibility after updating go-iotools) * maintain image aspect ratio when generating a thumbnail for it * update readme to show go-ffmpreg * fix a bunch of media tests, move filesize checking to callers of media manager for more flexibility * remove extra debug from error message * fix up incorrect function signatures * update PutFile to just use regular file copy, as changes are file is on separate partition * fix remaining tests, remove some unneeded tests now we're working with ffmpeg/ffprobe * update more tests, add more code comments * add utilities to generate processed emoji / media outputs * fix remaining tests * add test for opus media file, add license header to utility cmds * limit the number of concurrently available ffmpeg / ffprobe instances * reduce number of instances * further reduce number of instances * fix envparsing test with configuration variables * update docs and configuration with new media-{local,remote}-max-size variables
This commit is contained in:
120
vendor/github.com/golang/geo/s1/angle.go
generated
vendored
120
vendor/github.com/golang/geo/s1/angle.go
generated
vendored
@ -1,120 +0,0 @@
|
||||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package s1
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Angle represents a 1D angle. The internal representation is a double precision
|
||||
// value in radians, so conversion to and from radians is exact.
|
||||
// Conversions between E5, E6, E7, and Degrees are not always
|
||||
// exact. For example, Degrees(3.1) is different from E6(3100000) or E7(31000000).
|
||||
//
|
||||
// The following conversions between degrees and radians are exact:
|
||||
//
|
||||
// Degree*180 == Radian*math.Pi
|
||||
// Degree*(180/n) == Radian*(math.Pi/n) for n == 0..8
|
||||
//
|
||||
// These identities hold when the arguments are scaled up or down by any power
|
||||
// of 2. Some similar identities are also true, for example,
|
||||
//
|
||||
// Degree*60 == Radian*(math.Pi/3)
|
||||
//
|
||||
// But be aware that this type of identity does not hold in general. For example,
|
||||
//
|
||||
// Degree*3 != Radian*(math.Pi/60)
|
||||
//
|
||||
// Similarly, the conversion to radians means that (Angle(x)*Degree).Degrees()
|
||||
// does not always equal x. For example,
|
||||
//
|
||||
// (Angle(45*n)*Degree).Degrees() == 45*n for n == 0..8
|
||||
//
|
||||
// but
|
||||
//
|
||||
// (60*Degree).Degrees() != 60
|
||||
//
|
||||
// When testing for equality, you should allow for numerical errors (ApproxEqual)
|
||||
// or convert to discrete E5/E6/E7 values first.
|
||||
type Angle float64
|
||||
|
||||
// Angle units.
|
||||
const (
|
||||
Radian Angle = 1
|
||||
Degree = (math.Pi / 180) * Radian
|
||||
|
||||
E5 = 1e-5 * Degree
|
||||
E6 = 1e-6 * Degree
|
||||
E7 = 1e-7 * Degree
|
||||
)
|
||||
|
||||
// Radians returns the angle in radians.
|
||||
func (a Angle) Radians() float64 { return float64(a) }
|
||||
|
||||
// Degrees returns the angle in degrees.
|
||||
func (a Angle) Degrees() float64 { return float64(a / Degree) }
|
||||
|
||||
// round returns the value rounded to nearest as an int32.
|
||||
// This does not match C++ exactly for the case of x.5.
|
||||
func round(val float64) int32 {
|
||||
if val < 0 {
|
||||
return int32(val - 0.5)
|
||||
}
|
||||
return int32(val + 0.5)
|
||||
}
|
||||
|
||||
// InfAngle returns an angle larger than any finite angle.
|
||||
func InfAngle() Angle {
|
||||
return Angle(math.Inf(1))
|
||||
}
|
||||
|
||||
// isInf reports whether this Angle is infinite.
|
||||
func (a Angle) isInf() bool {
|
||||
return math.IsInf(float64(a), 0)
|
||||
}
|
||||
|
||||
// E5 returns the angle in hundred thousandths of degrees.
|
||||
func (a Angle) E5() int32 { return round(a.Degrees() * 1e5) }
|
||||
|
||||
// E6 returns the angle in millionths of degrees.
|
||||
func (a Angle) E6() int32 { return round(a.Degrees() * 1e6) }
|
||||
|
||||
// E7 returns the angle in ten millionths of degrees.
|
||||
func (a Angle) E7() int32 { return round(a.Degrees() * 1e7) }
|
||||
|
||||
// Abs returns the absolute value of the angle.
|
||||
func (a Angle) Abs() Angle { return Angle(math.Abs(float64(a))) }
|
||||
|
||||
// Normalized returns an equivalent angle in (-π, π].
|
||||
func (a Angle) Normalized() Angle {
|
||||
rad := math.Remainder(float64(a), 2*math.Pi)
|
||||
if rad <= -math.Pi {
|
||||
rad = math.Pi
|
||||
}
|
||||
return Angle(rad)
|
||||
}
|
||||
|
||||
func (a Angle) String() string {
|
||||
return strconv.FormatFloat(a.Degrees(), 'f', 7, 64) // like "%.7f"
|
||||
}
|
||||
|
||||
// ApproxEqual reports whether the two angles are the same up to a small tolerance.
|
||||
func (a Angle) ApproxEqual(other Angle) bool {
|
||||
return math.Abs(float64(a)-float64(other)) <= epsilon
|
||||
}
|
||||
|
||||
// BUG(dsymonds): The major differences from the C++ version are:
|
||||
// - no unsigned E5/E6/E7 methods
|
320
vendor/github.com/golang/geo/s1/chordangle.go
generated
vendored
320
vendor/github.com/golang/geo/s1/chordangle.go
generated
vendored
@ -1,320 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package s1
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// ChordAngle represents the angle subtended by a chord (i.e., the straight
|
||||
// line segment connecting two points on the sphere). Its representation
|
||||
// makes it very efficient for computing and comparing distances, but unlike
|
||||
// Angle it is only capable of representing angles between 0 and π radians.
|
||||
// Generally, ChordAngle should only be used in loops where many angles need
|
||||
// to be calculated and compared. Otherwise it is simpler to use Angle.
|
||||
//
|
||||
// ChordAngle loses some accuracy as the angle approaches π radians.
|
||||
// There are several different ways to measure this error, including the
|
||||
// representational error (i.e., how accurately ChordAngle can represent
|
||||
// angles near π radians), the conversion error (i.e., how much precision is
|
||||
// lost when an Angle is converted to an ChordAngle), and the measurement
|
||||
// error (i.e., how accurate the ChordAngle(a, b) constructor is when the
|
||||
// points A and B are separated by angles close to π radians). All of these
|
||||
// errors differ by a small constant factor.
|
||||
//
|
||||
// For the measurement error (which is the largest of these errors and also
|
||||
// the most important in practice), let the angle between A and B be (π - x)
|
||||
// radians, i.e. A and B are within "x" radians of being antipodal. The
|
||||
// corresponding chord length is
|
||||
//
|
||||
// r = 2 * sin((π - x) / 2) = 2 * cos(x / 2)
|
||||
//
|
||||
// For values of x not close to π the relative error in the squared chord
|
||||
// length is at most 4.5 * dblEpsilon (see MaxPointError below).
|
||||
// The relative error in "r" is thus at most 2.25 * dblEpsilon ~= 5e-16. To
|
||||
// convert this error into an equivalent angle, we have
|
||||
//
|
||||
// |dr / dx| = sin(x / 2)
|
||||
//
|
||||
// and therefore
|
||||
//
|
||||
// |dx| = dr / sin(x / 2)
|
||||
// = 5e-16 * (2 * cos(x / 2)) / sin(x / 2)
|
||||
// = 1e-15 / tan(x / 2)
|
||||
//
|
||||
// The maximum error is attained when
|
||||
//
|
||||
// x = |dx|
|
||||
// = 1e-15 / tan(x / 2)
|
||||
// ~= 1e-15 / (x / 2)
|
||||
// ~= sqrt(2e-15)
|
||||
//
|
||||
// In summary, the measurement error for an angle (π - x) is at most
|
||||
//
|
||||
// dx = min(1e-15 / tan(x / 2), sqrt(2e-15))
|
||||
// (~= min(2e-15 / x, sqrt(2e-15)) when x is small)
|
||||
//
|
||||
// On the Earth's surface (assuming a radius of 6371km), this corresponds to
|
||||
// the following worst-case measurement errors:
|
||||
//
|
||||
// Accuracy: Unless antipodal to within:
|
||||
// --------- ---------------------------
|
||||
// 6.4 nanometers 10,000 km (90 degrees)
|
||||
// 1 micrometer 81.2 kilometers
|
||||
// 1 millimeter 81.2 meters
|
||||
// 1 centimeter 8.12 meters
|
||||
// 28.5 centimeters 28.5 centimeters
|
||||
//
|
||||
// The representational and conversion errors referred to earlier are somewhat
|
||||
// smaller than this. For example, maximum distance between adjacent
|
||||
// representable ChordAngle values is only 13.5 cm rather than 28.5 cm. To
|
||||
// see this, observe that the closest representable value to r^2 = 4 is
|
||||
// r^2 = 4 * (1 - dblEpsilon / 2). Thus r = 2 * (1 - dblEpsilon / 4) and
|
||||
// the angle between these two representable values is
|
||||
//
|
||||
// x = 2 * acos(r / 2)
|
||||
// = 2 * acos(1 - dblEpsilon / 4)
|
||||
// ~= 2 * asin(sqrt(dblEpsilon / 2)
|
||||
// ~= sqrt(2 * dblEpsilon)
|
||||
// ~= 2.1e-8
|
||||
//
|
||||
// which is 13.5 cm on the Earth's surface.
|
||||
//
|
||||
// The worst case rounding error occurs when the value halfway between these
|
||||
// two representable values is rounded up to 4. This halfway value is
|
||||
// r^2 = (4 * (1 - dblEpsilon / 4)), thus r = 2 * (1 - dblEpsilon / 8) and
|
||||
// the worst case rounding error is
|
||||
//
|
||||
// x = 2 * acos(r / 2)
|
||||
// = 2 * acos(1 - dblEpsilon / 8)
|
||||
// ~= 2 * asin(sqrt(dblEpsilon / 4)
|
||||
// ~= sqrt(dblEpsilon)
|
||||
// ~= 1.5e-8
|
||||
//
|
||||
// which is 9.5 cm on the Earth's surface.
|
||||
type ChordAngle float64
|
||||
|
||||
const (
|
||||
// NegativeChordAngle represents a chord angle smaller than the zero angle.
|
||||
// The only valid operations on a NegativeChordAngle are comparisons,
|
||||
// Angle conversions, and Successor/Predecessor.
|
||||
NegativeChordAngle = ChordAngle(-1)
|
||||
|
||||
// RightChordAngle represents a chord angle of 90 degrees (a "right angle").
|
||||
RightChordAngle = ChordAngle(2)
|
||||
|
||||
// StraightChordAngle represents a chord angle of 180 degrees (a "straight angle").
|
||||
// This is the maximum finite chord angle.
|
||||
StraightChordAngle = ChordAngle(4)
|
||||
|
||||
// maxLength2 is the square of the maximum length allowed in a ChordAngle.
|
||||
maxLength2 = 4.0
|
||||
)
|
||||
|
||||
// ChordAngleFromAngle returns a ChordAngle from the given Angle.
|
||||
func ChordAngleFromAngle(a Angle) ChordAngle {
|
||||
if a < 0 {
|
||||
return NegativeChordAngle
|
||||
}
|
||||
if a.isInf() {
|
||||
return InfChordAngle()
|
||||
}
|
||||
l := 2 * math.Sin(0.5*math.Min(math.Pi, a.Radians()))
|
||||
return ChordAngle(l * l)
|
||||
}
|
||||
|
||||
// ChordAngleFromSquaredLength returns a ChordAngle from the squared chord length.
|
||||
// Note that the argument is automatically clamped to a maximum of 4 to
|
||||
// handle possible roundoff errors. The argument must be non-negative.
|
||||
func ChordAngleFromSquaredLength(length2 float64) ChordAngle {
|
||||
if length2 > maxLength2 {
|
||||
return StraightChordAngle
|
||||
}
|
||||
return ChordAngle(length2)
|
||||
}
|
||||
|
||||
// Expanded returns a new ChordAngle that has been adjusted by the given error
|
||||
// bound (which can be positive or negative). Error should be the value
|
||||
// returned by either MaxPointError or MaxAngleError. For example:
|
||||
// a := ChordAngleFromPoints(x, y)
|
||||
// a1 := a.Expanded(a.MaxPointError())
|
||||
func (c ChordAngle) Expanded(e float64) ChordAngle {
|
||||
// If the angle is special, don't change it. Otherwise clamp it to the valid range.
|
||||
if c.isSpecial() {
|
||||
return c
|
||||
}
|
||||
return ChordAngle(math.Max(0.0, math.Min(maxLength2, float64(c)+e)))
|
||||
}
|
||||
|
||||
// Angle converts this ChordAngle to an Angle.
|
||||
func (c ChordAngle) Angle() Angle {
|
||||
if c < 0 {
|
||||
return -1 * Radian
|
||||
}
|
||||
if c.isInf() {
|
||||
return InfAngle()
|
||||
}
|
||||
return Angle(2 * math.Asin(0.5*math.Sqrt(float64(c))))
|
||||
}
|
||||
|
||||
// InfChordAngle returns a chord angle larger than any finite chord angle.
|
||||
// The only valid operations on an InfChordAngle are comparisons, Angle
|
||||
// conversions, and Successor/Predecessor.
|
||||
func InfChordAngle() ChordAngle {
|
||||
return ChordAngle(math.Inf(1))
|
||||
}
|
||||
|
||||
// isInf reports whether this ChordAngle is infinite.
|
||||
func (c ChordAngle) isInf() bool {
|
||||
return math.IsInf(float64(c), 1)
|
||||
}
|
||||
|
||||
// isSpecial reports whether this ChordAngle is one of the special cases.
|
||||
func (c ChordAngle) isSpecial() bool {
|
||||
return c < 0 || c.isInf()
|
||||
}
|
||||
|
||||
// isValid reports whether this ChordAngle is valid or not.
|
||||
func (c ChordAngle) isValid() bool {
|
||||
return (c >= 0 && c <= maxLength2) || c.isSpecial()
|
||||
}
|
||||
|
||||
// Successor returns the smallest representable ChordAngle larger than this one.
|
||||
// This can be used to convert a "<" comparison to a "<=" comparison.
|
||||
//
|
||||
// Note the following special cases:
|
||||
// NegativeChordAngle.Successor == 0
|
||||
// StraightChordAngle.Successor == InfChordAngle
|
||||
// InfChordAngle.Successor == InfChordAngle
|
||||
func (c ChordAngle) Successor() ChordAngle {
|
||||
if c >= maxLength2 {
|
||||
return InfChordAngle()
|
||||
}
|
||||
if c < 0 {
|
||||
return 0
|
||||
}
|
||||
return ChordAngle(math.Nextafter(float64(c), 10.0))
|
||||
}
|
||||
|
||||
// Predecessor returns the largest representable ChordAngle less than this one.
|
||||
//
|
||||
// Note the following special cases:
|
||||
// InfChordAngle.Predecessor == StraightChordAngle
|
||||
// ChordAngle(0).Predecessor == NegativeChordAngle
|
||||
// NegativeChordAngle.Predecessor == NegativeChordAngle
|
||||
func (c ChordAngle) Predecessor() ChordAngle {
|
||||
if c <= 0 {
|
||||
return NegativeChordAngle
|
||||
}
|
||||
if c > maxLength2 {
|
||||
return StraightChordAngle
|
||||
}
|
||||
|
||||
return ChordAngle(math.Nextafter(float64(c), -10.0))
|
||||
}
|
||||
|
||||
// MaxPointError returns the maximum error size for a ChordAngle constructed
|
||||
// from 2 Points x and y, assuming that x and y are normalized to within the
|
||||
// bounds guaranteed by s2.Point.Normalize. The error is defined with respect to
|
||||
// the true distance after the points are projected to lie exactly on the sphere.
|
||||
func (c ChordAngle) MaxPointError() float64 {
|
||||
// There is a relative error of (2.5*dblEpsilon) when computing the squared
|
||||
// distance, plus a relative error of 2 * dblEpsilon, plus an absolute error
|
||||
// of (16 * dblEpsilon**2) because the lengths of the input points may differ
|
||||
// from 1 by up to (2*dblEpsilon) each. (This is the maximum error in Normalize).
|
||||
return 4.5*dblEpsilon*float64(c) + 16*dblEpsilon*dblEpsilon
|
||||
}
|
||||
|
||||
// MaxAngleError returns the maximum error for a ChordAngle constructed
|
||||
// as an Angle distance.
|
||||
func (c ChordAngle) MaxAngleError() float64 {
|
||||
return dblEpsilon * float64(c)
|
||||
}
|
||||
|
||||
// Add adds the other ChordAngle to this one and returns the resulting value.
|
||||
// This method assumes the ChordAngles are not special.
|
||||
func (c ChordAngle) Add(other ChordAngle) ChordAngle {
|
||||
// Note that this method (and Sub) is much more efficient than converting
|
||||
// the ChordAngle to an Angle and adding those and converting back. It
|
||||
// requires only one square root plus a few additions and multiplications.
|
||||
|
||||
// Optimization for the common case where b is an error tolerance
|
||||
// parameter that happens to be set to zero.
|
||||
if other == 0 {
|
||||
return c
|
||||
}
|
||||
|
||||
// Clamp the angle sum to at most 180 degrees.
|
||||
if c+other >= maxLength2 {
|
||||
return StraightChordAngle
|
||||
}
|
||||
|
||||
// Let a and b be the (non-squared) chord lengths, and let c = a+b.
|
||||
// Let A, B, and C be the corresponding half-angles (a = 2*sin(A), etc).
|
||||
// Then the formula below can be derived from c = 2 * sin(A+B) and the
|
||||
// relationships sin(A+B) = sin(A)*cos(B) + sin(B)*cos(A)
|
||||
// cos(X) = sqrt(1 - sin^2(X))
|
||||
x := float64(c * (1 - 0.25*other))
|
||||
y := float64(other * (1 - 0.25*c))
|
||||
return ChordAngle(math.Min(maxLength2, x+y+2*math.Sqrt(x*y)))
|
||||
}
|
||||
|
||||
// Sub subtracts the other ChordAngle from this one and returns the resulting
|
||||
// value. This method assumes the ChordAngles are not special.
|
||||
func (c ChordAngle) Sub(other ChordAngle) ChordAngle {
|
||||
if other == 0 {
|
||||
return c
|
||||
}
|
||||
if c <= other {
|
||||
return 0
|
||||
}
|
||||
x := float64(c * (1 - 0.25*other))
|
||||
y := float64(other * (1 - 0.25*c))
|
||||
return ChordAngle(math.Max(0.0, x+y-2*math.Sqrt(x*y)))
|
||||
}
|
||||
|
||||
// Sin returns the sine of this chord angle. This method is more efficient
|
||||
// than converting to Angle and performing the computation.
|
||||
func (c ChordAngle) Sin() float64 {
|
||||
return math.Sqrt(c.Sin2())
|
||||
}
|
||||
|
||||
// Sin2 returns the square of the sine of this chord angle.
|
||||
// It is more efficient than Sin.
|
||||
func (c ChordAngle) Sin2() float64 {
|
||||
// Let a be the (non-squared) chord length, and let A be the corresponding
|
||||
// half-angle (a = 2*sin(A)). The formula below can be derived from:
|
||||
// sin(2*A) = 2 * sin(A) * cos(A)
|
||||
// cos^2(A) = 1 - sin^2(A)
|
||||
// This is much faster than converting to an angle and computing its sine.
|
||||
return float64(c * (1 - 0.25*c))
|
||||
}
|
||||
|
||||
// Cos returns the cosine of this chord angle. This method is more efficient
|
||||
// than converting to Angle and performing the computation.
|
||||
func (c ChordAngle) Cos() float64 {
|
||||
// cos(2*A) = cos^2(A) - sin^2(A) = 1 - 2*sin^2(A)
|
||||
return float64(1 - 0.5*c)
|
||||
}
|
||||
|
||||
// Tan returns the tangent of this chord angle.
|
||||
func (c ChordAngle) Tan() float64 {
|
||||
return c.Sin() / c.Cos()
|
||||
}
|
||||
|
||||
// TODO(roberts): Differences from C++:
|
||||
// Helpers to/from E5/E6/E7
|
||||
// Helpers to/from degrees and radians directly.
|
||||
// FastUpperBoundFrom(angle Angle)
|
20
vendor/github.com/golang/geo/s1/doc.go
generated
vendored
20
vendor/github.com/golang/geo/s1/doc.go
generated
vendored
@ -1,20 +0,0 @@
|
||||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package s1 implements types and functions for working with geometry in S¹ (circular geometry).
|
||||
|
||||
See ../s2 for a more detailed overview.
|
||||
*/
|
||||
package s1
|
462
vendor/github.com/golang/geo/s1/interval.go
generated
vendored
462
vendor/github.com/golang/geo/s1/interval.go
generated
vendored
@ -1,462 +0,0 @@
|
||||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package s1
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// An Interval represents a closed interval on a unit circle (also known
|
||||
// as a 1-dimensional sphere). It is capable of representing the empty
|
||||
// interval (containing no points), the full interval (containing all
|
||||
// points), and zero-length intervals (containing a single point).
|
||||
//
|
||||
// Points are represented by the angle they make with the positive x-axis in
|
||||
// the range [-π, π]. An interval is represented by its lower and upper
|
||||
// bounds (both inclusive, since the interval is closed). The lower bound may
|
||||
// be greater than the upper bound, in which case the interval is "inverted"
|
||||
// (i.e. it passes through the point (-1, 0)).
|
||||
//
|
||||
// The point (-1, 0) has two valid representations, π and -π. The
|
||||
// normalized representation of this point is π, so that endpoints
|
||||
// of normal intervals are in the range (-π, π]. We normalize the latter to
|
||||
// the former in IntervalFromEndpoints. However, we take advantage of the point
|
||||
// -π to construct two special intervals:
|
||||
// The full interval is [-π, π]
|
||||
// The empty interval is [π, -π].
|
||||
//
|
||||
// Treat the exported fields as read-only.
|
||||
type Interval struct {
|
||||
Lo, Hi float64
|
||||
}
|
||||
|
||||
// IntervalFromEndpoints constructs a new interval from endpoints.
|
||||
// Both arguments must be in the range [-π,π]. This function allows inverted intervals
|
||||
// to be created.
|
||||
func IntervalFromEndpoints(lo, hi float64) Interval {
|
||||
i := Interval{lo, hi}
|
||||
if lo == -math.Pi && hi != math.Pi {
|
||||
i.Lo = math.Pi
|
||||
}
|
||||
if hi == -math.Pi && lo != math.Pi {
|
||||
i.Hi = math.Pi
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// IntervalFromPointPair returns the minimal interval containing the two given points.
|
||||
// Both arguments must be in [-π,π].
|
||||
func IntervalFromPointPair(a, b float64) Interval {
|
||||
if a == -math.Pi {
|
||||
a = math.Pi
|
||||
}
|
||||
if b == -math.Pi {
|
||||
b = math.Pi
|
||||
}
|
||||
if positiveDistance(a, b) <= math.Pi {
|
||||
return Interval{a, b}
|
||||
}
|
||||
return Interval{b, a}
|
||||
}
|
||||
|
||||
// EmptyInterval returns an empty interval.
|
||||
func EmptyInterval() Interval { return Interval{math.Pi, -math.Pi} }
|
||||
|
||||
// FullInterval returns a full interval.
|
||||
func FullInterval() Interval { return Interval{-math.Pi, math.Pi} }
|
||||
|
||||
// IsValid reports whether the interval is valid.
|
||||
func (i Interval) IsValid() bool {
|
||||
return (math.Abs(i.Lo) <= math.Pi && math.Abs(i.Hi) <= math.Pi &&
|
||||
!(i.Lo == -math.Pi && i.Hi != math.Pi) &&
|
||||
!(i.Hi == -math.Pi && i.Lo != math.Pi))
|
||||
}
|
||||
|
||||
// IsFull reports whether the interval is full.
|
||||
func (i Interval) IsFull() bool { return i.Lo == -math.Pi && i.Hi == math.Pi }
|
||||
|
||||
// IsEmpty reports whether the interval is empty.
|
||||
func (i Interval) IsEmpty() bool { return i.Lo == math.Pi && i.Hi == -math.Pi }
|
||||
|
||||
// IsInverted reports whether the interval is inverted; that is, whether Lo > Hi.
|
||||
func (i Interval) IsInverted() bool { return i.Lo > i.Hi }
|
||||
|
||||
// Invert returns the interval with endpoints swapped.
|
||||
func (i Interval) Invert() Interval {
|
||||
return Interval{i.Hi, i.Lo}
|
||||
}
|
||||
|
||||
// Center returns the midpoint of the interval.
|
||||
// It is undefined for full and empty intervals.
|
||||
func (i Interval) Center() float64 {
|
||||
c := 0.5 * (i.Lo + i.Hi)
|
||||
if !i.IsInverted() {
|
||||
return c
|
||||
}
|
||||
if c <= 0 {
|
||||
return c + math.Pi
|
||||
}
|
||||
return c - math.Pi
|
||||
}
|
||||
|
||||
// Length returns the length of the interval.
|
||||
// The length of an empty interval is negative.
|
||||
func (i Interval) Length() float64 {
|
||||
l := i.Hi - i.Lo
|
||||
if l >= 0 {
|
||||
return l
|
||||
}
|
||||
l += 2 * math.Pi
|
||||
if l > 0 {
|
||||
return l
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Assumes p ∈ (-π,π].
|
||||
func (i Interval) fastContains(p float64) bool {
|
||||
if i.IsInverted() {
|
||||
return (p >= i.Lo || p <= i.Hi) && !i.IsEmpty()
|
||||
}
|
||||
return p >= i.Lo && p <= i.Hi
|
||||
}
|
||||
|
||||
// Contains returns true iff the interval contains p.
|
||||
// Assumes p ∈ [-π,π].
|
||||
func (i Interval) Contains(p float64) bool {
|
||||
if p == -math.Pi {
|
||||
p = math.Pi
|
||||
}
|
||||
return i.fastContains(p)
|
||||
}
|
||||
|
||||
// ContainsInterval returns true iff the interval contains oi.
|
||||
func (i Interval) ContainsInterval(oi Interval) bool {
|
||||
if i.IsInverted() {
|
||||
if oi.IsInverted() {
|
||||
return oi.Lo >= i.Lo && oi.Hi <= i.Hi
|
||||
}
|
||||
return (oi.Lo >= i.Lo || oi.Hi <= i.Hi) && !i.IsEmpty()
|
||||
}
|
||||
if oi.IsInverted() {
|
||||
return i.IsFull() || oi.IsEmpty()
|
||||
}
|
||||
return oi.Lo >= i.Lo && oi.Hi <= i.Hi
|
||||
}
|
||||
|
||||
// InteriorContains returns true iff the interior of the interval contains p.
|
||||
// Assumes p ∈ [-π,π].
|
||||
func (i Interval) InteriorContains(p float64) bool {
|
||||
if p == -math.Pi {
|
||||
p = math.Pi
|
||||
}
|
||||
if i.IsInverted() {
|
||||
return p > i.Lo || p < i.Hi
|
||||
}
|
||||
return (p > i.Lo && p < i.Hi) || i.IsFull()
|
||||
}
|
||||
|
||||
// InteriorContainsInterval returns true iff the interior of the interval contains oi.
|
||||
func (i Interval) InteriorContainsInterval(oi Interval) bool {
|
||||
if i.IsInverted() {
|
||||
if oi.IsInverted() {
|
||||
return (oi.Lo > i.Lo && oi.Hi < i.Hi) || oi.IsEmpty()
|
||||
}
|
||||
return oi.Lo > i.Lo || oi.Hi < i.Hi
|
||||
}
|
||||
if oi.IsInverted() {
|
||||
return i.IsFull() || oi.IsEmpty()
|
||||
}
|
||||
return (oi.Lo > i.Lo && oi.Hi < i.Hi) || i.IsFull()
|
||||
}
|
||||
|
||||
// Intersects returns true iff the interval contains any points in common with oi.
|
||||
func (i Interval) Intersects(oi Interval) bool {
|
||||
if i.IsEmpty() || oi.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
if i.IsInverted() {
|
||||
return oi.IsInverted() || oi.Lo <= i.Hi || oi.Hi >= i.Lo
|
||||
}
|
||||
if oi.IsInverted() {
|
||||
return oi.Lo <= i.Hi || oi.Hi >= i.Lo
|
||||
}
|
||||
return oi.Lo <= i.Hi && oi.Hi >= i.Lo
|
||||
}
|
||||
|
||||
// InteriorIntersects returns true iff the interior of the interval contains any points in common with oi, including the latter's boundary.
|
||||
func (i Interval) InteriorIntersects(oi Interval) bool {
|
||||
if i.IsEmpty() || oi.IsEmpty() || i.Lo == i.Hi {
|
||||
return false
|
||||
}
|
||||
if i.IsInverted() {
|
||||
return oi.IsInverted() || oi.Lo < i.Hi || oi.Hi > i.Lo
|
||||
}
|
||||
if oi.IsInverted() {
|
||||
return oi.Lo < i.Hi || oi.Hi > i.Lo
|
||||
}
|
||||
return (oi.Lo < i.Hi && oi.Hi > i.Lo) || i.IsFull()
|
||||
}
|
||||
|
||||
// Compute distance from a to b in [0,2π], in a numerically stable way.
|
||||
func positiveDistance(a, b float64) float64 {
|
||||
d := b - a
|
||||
if d >= 0 {
|
||||
return d
|
||||
}
|
||||
return (b + math.Pi) - (a - math.Pi)
|
||||
}
|
||||
|
||||
// Union returns the smallest interval that contains both the interval and oi.
|
||||
func (i Interval) Union(oi Interval) Interval {
|
||||
if oi.IsEmpty() {
|
||||
return i
|
||||
}
|
||||
if i.fastContains(oi.Lo) {
|
||||
if i.fastContains(oi.Hi) {
|
||||
// Either oi ⊂ i, or i ∪ oi is the full interval.
|
||||
if i.ContainsInterval(oi) {
|
||||
return i
|
||||
}
|
||||
return FullInterval()
|
||||
}
|
||||
return Interval{i.Lo, oi.Hi}
|
||||
}
|
||||
if i.fastContains(oi.Hi) {
|
||||
return Interval{oi.Lo, i.Hi}
|
||||
}
|
||||
|
||||
// Neither endpoint of oi is in i. Either i ⊂ oi, or i and oi are disjoint.
|
||||
if i.IsEmpty() || oi.fastContains(i.Lo) {
|
||||
return oi
|
||||
}
|
||||
|
||||
// This is the only hard case where we need to find the closest pair of endpoints.
|
||||
if positiveDistance(oi.Hi, i.Lo) < positiveDistance(i.Hi, oi.Lo) {
|
||||
return Interval{oi.Lo, i.Hi}
|
||||
}
|
||||
return Interval{i.Lo, oi.Hi}
|
||||
}
|
||||
|
||||
// Intersection returns the smallest interval that contains the intersection of the interval and oi.
|
||||
func (i Interval) Intersection(oi Interval) Interval {
|
||||
if oi.IsEmpty() {
|
||||
return EmptyInterval()
|
||||
}
|
||||
if i.fastContains(oi.Lo) {
|
||||
if i.fastContains(oi.Hi) {
|
||||
// Either oi ⊂ i, or i and oi intersect twice. Neither are empty.
|
||||
// In the first case we want to return i (which is shorter than oi).
|
||||
// In the second case one of them is inverted, and the smallest interval
|
||||
// that covers the two disjoint pieces is the shorter of i and oi.
|
||||
// We thus want to pick the shorter of i and oi in both cases.
|
||||
if oi.Length() < i.Length() {
|
||||
return oi
|
||||
}
|
||||
return i
|
||||
}
|
||||
return Interval{oi.Lo, i.Hi}
|
||||
}
|
||||
if i.fastContains(oi.Hi) {
|
||||
return Interval{i.Lo, oi.Hi}
|
||||
}
|
||||
|
||||
// Neither endpoint of oi is in i. Either i ⊂ oi, or i and oi are disjoint.
|
||||
if oi.fastContains(i.Lo) {
|
||||
return i
|
||||
}
|
||||
return EmptyInterval()
|
||||
}
|
||||
|
||||
// AddPoint returns the interval expanded by the minimum amount necessary such
|
||||
// that it contains the given point "p" (an angle in the range [-π, π]).
|
||||
func (i Interval) AddPoint(p float64) Interval {
|
||||
if math.Abs(p) > math.Pi {
|
||||
return i
|
||||
}
|
||||
if p == -math.Pi {
|
||||
p = math.Pi
|
||||
}
|
||||
if i.fastContains(p) {
|
||||
return i
|
||||
}
|
||||
if i.IsEmpty() {
|
||||
return Interval{p, p}
|
||||
}
|
||||
if positiveDistance(p, i.Lo) < positiveDistance(i.Hi, p) {
|
||||
return Interval{p, i.Hi}
|
||||
}
|
||||
return Interval{i.Lo, p}
|
||||
}
|
||||
|
||||
// Define the maximum rounding error for arithmetic operations. Depending on the
|
||||
// platform the mantissa precision may be different than others, so we choose to
|
||||
// use specific values to be consistent across all.
|
||||
// The values come from the C++ implementation.
|
||||
var (
|
||||
// epsilon is a small number that represents a reasonable level of noise between two
|
||||
// values that can be considered to be equal.
|
||||
epsilon = 1e-15
|
||||
// dblEpsilon is a smaller number for values that require more precision.
|
||||
dblEpsilon = 2.220446049e-16
|
||||
)
|
||||
|
||||
// Expanded returns an interval that has been expanded on each side by margin.
|
||||
// If margin is negative, then the function shrinks the interval on
|
||||
// each side by margin instead. The resulting interval may be empty or
|
||||
// full. Any expansion (positive or negative) of a full interval remains
|
||||
// full, and any expansion of an empty interval remains empty.
|
||||
func (i Interval) Expanded(margin float64) Interval {
|
||||
if margin >= 0 {
|
||||
if i.IsEmpty() {
|
||||
return i
|
||||
}
|
||||
// Check whether this interval will be full after expansion, allowing
|
||||
// for a rounding error when computing each endpoint.
|
||||
if i.Length()+2*margin+2*dblEpsilon >= 2*math.Pi {
|
||||
return FullInterval()
|
||||
}
|
||||
} else {
|
||||
if i.IsFull() {
|
||||
return i
|
||||
}
|
||||
// Check whether this interval will be empty after expansion, allowing
|
||||
// for a rounding error when computing each endpoint.
|
||||
if i.Length()+2*margin-2*dblEpsilon <= 0 {
|
||||
return EmptyInterval()
|
||||
}
|
||||
}
|
||||
result := IntervalFromEndpoints(
|
||||
math.Remainder(i.Lo-margin, 2*math.Pi),
|
||||
math.Remainder(i.Hi+margin, 2*math.Pi),
|
||||
)
|
||||
if result.Lo <= -math.Pi {
|
||||
result.Lo = math.Pi
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ApproxEqual reports whether this interval can be transformed into the given
|
||||
// interval by moving each endpoint by at most ε, without the
|
||||
// endpoints crossing (which would invert the interval). Empty and full
|
||||
// intervals are considered to start at an arbitrary point on the unit circle,
|
||||
// so any interval with (length <= 2*ε) matches the empty interval, and
|
||||
// any interval with (length >= 2*π - 2*ε) matches the full interval.
|
||||
func (i Interval) ApproxEqual(other Interval) bool {
|
||||
// Full and empty intervals require special cases because the endpoints
|
||||
// are considered to be positioned arbitrarily.
|
||||
if i.IsEmpty() {
|
||||
return other.Length() <= 2*epsilon
|
||||
}
|
||||
if other.IsEmpty() {
|
||||
return i.Length() <= 2*epsilon
|
||||
}
|
||||
if i.IsFull() {
|
||||
return other.Length() >= 2*(math.Pi-epsilon)
|
||||
}
|
||||
if other.IsFull() {
|
||||
return i.Length() >= 2*(math.Pi-epsilon)
|
||||
}
|
||||
|
||||
// The purpose of the last test below is to verify that moving the endpoints
|
||||
// does not invert the interval, e.g. [-1e20, 1e20] vs. [1e20, -1e20].
|
||||
return (math.Abs(math.Remainder(other.Lo-i.Lo, 2*math.Pi)) <= epsilon &&
|
||||
math.Abs(math.Remainder(other.Hi-i.Hi, 2*math.Pi)) <= epsilon &&
|
||||
math.Abs(i.Length()-other.Length()) <= 2*epsilon)
|
||||
|
||||
}
|
||||
|
||||
func (i Interval) String() string {
|
||||
// like "[%.7f, %.7f]"
|
||||
return "[" + strconv.FormatFloat(i.Lo, 'f', 7, 64) + ", " + strconv.FormatFloat(i.Hi, 'f', 7, 64) + "]"
|
||||
}
|
||||
|
||||
// Complement returns the complement of the interior of the interval. An interval and
|
||||
// its complement have the same boundary but do not share any interior
|
||||
// values. The complement operator is not a bijection, since the complement
|
||||
// of a singleton interval (containing a single value) is the same as the
|
||||
// complement of an empty interval.
|
||||
func (i Interval) Complement() Interval {
|
||||
if i.Lo == i.Hi {
|
||||
// Singleton. The interval just contains a single point.
|
||||
return FullInterval()
|
||||
}
|
||||
// Handles empty and full.
|
||||
return Interval{i.Hi, i.Lo}
|
||||
}
|
||||
|
||||
// ComplementCenter returns the midpoint of the complement of the interval. For full and empty
|
||||
// intervals, the result is arbitrary. For a singleton interval (containing a
|
||||
// single point), the result is its antipodal point on S1.
|
||||
func (i Interval) ComplementCenter() float64 {
|
||||
if i.Lo != i.Hi {
|
||||
return i.Complement().Center()
|
||||
}
|
||||
// Singleton. The interval just contains a single point.
|
||||
if i.Hi <= 0 {
|
||||
return i.Hi + math.Pi
|
||||
}
|
||||
return i.Hi - math.Pi
|
||||
}
|
||||
|
||||
// DirectedHausdorffDistance returns the Hausdorff distance to the given interval.
|
||||
// For two intervals i and y, this distance is defined by
|
||||
// h(i, y) = max_{p in i} min_{q in y} d(p, q),
|
||||
// where d(.,.) is measured along S1.
|
||||
func (i Interval) DirectedHausdorffDistance(y Interval) Angle {
|
||||
if y.ContainsInterval(i) {
|
||||
return 0 // This includes the case i is empty.
|
||||
}
|
||||
if y.IsEmpty() {
|
||||
return Angle(math.Pi) // maximum possible distance on s1.
|
||||
}
|
||||
yComplementCenter := y.ComplementCenter()
|
||||
if i.Contains(yComplementCenter) {
|
||||
return Angle(positiveDistance(y.Hi, yComplementCenter))
|
||||
}
|
||||
|
||||
// The Hausdorff distance is realized by either two i.Hi endpoints or two
|
||||
// i.Lo endpoints, whichever is farther apart.
|
||||
hiHi := 0.0
|
||||
if IntervalFromEndpoints(y.Hi, yComplementCenter).Contains(i.Hi) {
|
||||
hiHi = positiveDistance(y.Hi, i.Hi)
|
||||
}
|
||||
|
||||
loLo := 0.0
|
||||
if IntervalFromEndpoints(yComplementCenter, y.Lo).Contains(i.Lo) {
|
||||
loLo = positiveDistance(i.Lo, y.Lo)
|
||||
}
|
||||
|
||||
return Angle(math.Max(hiHi, loLo))
|
||||
}
|
||||
|
||||
// Project returns the closest point in the interval to the given point p.
|
||||
// The interval must be non-empty.
|
||||
func (i Interval) Project(p float64) float64 {
|
||||
if p == -math.Pi {
|
||||
p = math.Pi
|
||||
}
|
||||
if i.fastContains(p) {
|
||||
return p
|
||||
}
|
||||
// Compute distance from p to each endpoint.
|
||||
dlo := positiveDistance(p, i.Lo)
|
||||
dhi := positiveDistance(i.Hi, p)
|
||||
if dlo < dhi {
|
||||
return i.Lo
|
||||
}
|
||||
return i.Hi
|
||||
}
|
Reference in New Issue
Block a user