agt 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.md +7 -0
- data/README.md +5 -0
- data/app/assets/javascripts/agt/config.coffee +9 -0
- data/app/assets/javascripts/agt/dom.coffee +12 -0
- data/app/assets/javascripts/agt/function.coffee +317 -0
- data/app/assets/javascripts/agt/geom/circle.coffee +338 -0
- data/app/assets/javascripts/agt/geom/cubic_bezier.coffee +37 -0
- data/app/assets/javascripts/agt/geom/diamond.coffee +241 -0
- data/app/assets/javascripts/agt/geom/ellipsis.coffee +141 -0
- data/app/assets/javascripts/agt/geom/linear_spline.coffee +56 -0
- data/app/assets/javascripts/agt/geom/matrix.coffee +240 -0
- data/app/assets/javascripts/agt/geom/mixins/geometry.coffee +171 -0
- data/app/assets/javascripts/agt/geom/mixins/intersections.coffee +150 -0
- data/app/assets/javascripts/agt/geom/mixins/path.coffee +145 -0
- data/app/assets/javascripts/agt/geom/mixins/proxyable.coffee +9 -0
- data/app/assets/javascripts/agt/geom/mixins/spline.coffee +329 -0
- data/app/assets/javascripts/agt/geom/mixins/surface.coffee +55 -0
- data/app/assets/javascripts/agt/geom/mixins/triangulable.coffee +112 -0
- data/app/assets/javascripts/agt/geom/point.coffee +454 -0
- data/app/assets/javascripts/agt/geom/polygon.coffee +119 -0
- data/app/assets/javascripts/agt/geom/quad_bezier.coffee +43 -0
- data/app/assets/javascripts/agt/geom/quint_bezier.coffee +42 -0
- data/app/assets/javascripts/agt/geom/rectangle.coffee +599 -0
- data/app/assets/javascripts/agt/geom/spiral.coffee +90 -0
- data/app/assets/javascripts/agt/geom/transformation_proxy.coffee +43 -0
- data/app/assets/javascripts/agt/geom/triangle.coffee +442 -0
- data/app/assets/javascripts/agt/impulse.coffee +105 -0
- data/app/assets/javascripts/agt/index.coffee +56 -0
- data/app/assets/javascripts/agt/inflector/inflection.coffee +21 -0
- data/app/assets/javascripts/agt/inflector/inflections.coffee +75 -0
- data/app/assets/javascripts/agt/inflector/inflector.coffee +235 -0
- data/app/assets/javascripts/agt/inheritance.coffee +132 -0
- data/app/assets/javascripts/agt/math.coffee +45 -0
- data/app/assets/javascripts/agt/mixins/activable.coffee +31 -0
- data/app/assets/javascripts/agt/mixins/aliasable.coffee +25 -0
- data/app/assets/javascripts/agt/mixins/alternate_case.coffee +72 -0
- data/app/assets/javascripts/agt/mixins/cloneable.coffee +71 -0
- data/app/assets/javascripts/agt/mixins/delegation.coffee +90 -0
- data/app/assets/javascripts/agt/mixins/disposable.coffee +7 -0
- data/app/assets/javascripts/agt/mixins/equatable.coffee +37 -0
- data/app/assets/javascripts/agt/mixins/formattable.coffee +52 -0
- data/app/assets/javascripts/agt/mixins/globalizable.coffee +175 -0
- data/app/assets/javascripts/agt/mixins/has_ancestors.coffee +47 -0
- data/app/assets/javascripts/agt/mixins/has_collection.coffee +107 -0
- data/app/assets/javascripts/agt/mixins/has_nested_collection.coffee +51 -0
- data/app/assets/javascripts/agt/mixins/memoizable.coffee +64 -0
- data/app/assets/javascripts/agt/mixins/parameterizable.coffee +101 -0
- data/app/assets/javascripts/agt/mixins/poolable.coffee +62 -0
- data/app/assets/javascripts/agt/mixins/sourcable.coffee +45 -0
- data/app/assets/javascripts/agt/mixins/state_machine.coffee +47 -0
- data/app/assets/javascripts/agt/net/router.coffee +165 -0
- data/app/assets/javascripts/agt/object.coffee +9 -0
- data/app/assets/javascripts/agt/particles/actions/base_action.coffee +7 -0
- data/app/assets/javascripts/agt/particles/actions/die_on_surface.coffee +14 -0
- data/app/assets/javascripts/agt/particles/actions/force.coffee +11 -0
- data/app/assets/javascripts/agt/particles/actions/friction.coffee +14 -0
- data/app/assets/javascripts/agt/particles/actions/live.coffee +9 -0
- data/app/assets/javascripts/agt/particles/actions/macro_action.coffee +13 -0
- data/app/assets/javascripts/agt/particles/actions/move.coffee +11 -0
- data/app/assets/javascripts/agt/particles/actions/null_action.coffee +9 -0
- data/app/assets/javascripts/agt/particles/counters/by_rate.coffee +17 -0
- data/app/assets/javascripts/agt/particles/counters/fixed.coffee +9 -0
- data/app/assets/javascripts/agt/particles/counters/null_counter.coffee +9 -0
- data/app/assets/javascripts/agt/particles/emission.coffee +35 -0
- data/app/assets/javascripts/agt/particles/emitters/null_emitter.coffee +7 -0
- data/app/assets/javascripts/agt/particles/emitters/path.coffee +10 -0
- data/app/assets/javascripts/agt/particles/emitters/ponctual.coffee +9 -0
- data/app/assets/javascripts/agt/particles/emitters/surface.coffee +10 -0
- data/app/assets/javascripts/agt/particles/initializers/explosion.coffee +19 -0
- data/app/assets/javascripts/agt/particles/initializers/life.coffee +16 -0
- data/app/assets/javascripts/agt/particles/initializers/macro_initializer.coffee +10 -0
- data/app/assets/javascripts/agt/particles/initializers/null_initializer.coffee +7 -0
- data/app/assets/javascripts/agt/particles/initializers/particle_sub_system.coffee +12 -0
- data/app/assets/javascripts/agt/particles/initializers/stream.coffee +20 -0
- data/app/assets/javascripts/agt/particles/mixins/randomizable.coffee +10 -0
- data/app/assets/javascripts/agt/particles/particle.coffee +32 -0
- data/app/assets/javascripts/agt/particles/sub_system.coffee +11 -0
- data/app/assets/javascripts/agt/particles/system.coffee +103 -0
- data/app/assets/javascripts/agt/particles/timers/instant.coffee +11 -0
- data/app/assets/javascripts/agt/particles/timers/limited.coffee +19 -0
- data/app/assets/javascripts/agt/particles/timers/null_timer.coffee +11 -0
- data/app/assets/javascripts/agt/particles/timers/unlimited.coffee +9 -0
- data/app/assets/javascripts/agt/particles/timers/until_death.coffee +11 -0
- data/app/assets/javascripts/agt/promise.coffee +214 -0
- data/app/assets/javascripts/agt/random/random.coffee +100 -0
- data/app/assets/javascripts/agt/random/seeds/lagged_fibonnacci.coffee +53 -0
- data/app/assets/javascripts/agt/random/seeds/linear.coffee +16 -0
- data/app/assets/javascripts/agt/random/seeds/linear_congruential.coffee +23 -0
- data/app/assets/javascripts/agt/random/seeds/math_random.coffee +9 -0
- data/app/assets/javascripts/agt/random/seeds/mersenne_twister.coffee +42 -0
- data/app/assets/javascripts/agt/random/seeds/no_random.coffee +12 -0
- data/app/assets/javascripts/agt/random/seeds/paul_houle.coffee +19 -0
- data/app/assets/javascripts/agt/signal.coffee +272 -0
- data/app/assets/javascripts/agt/sprites/animation.coffee +13 -0
- data/app/assets/javascripts/agt/sprites/sprite.coffee +30 -0
- data/app/assets/javascripts/agt/widgets/hash.coffee +36 -0
- data/app/assets/javascripts/agt/widgets/widgets.coffee +194 -0
- data/app/assets/javascripts/agt/widgets/widgets/checked_input.coffee +7 -0
- data/app/assets/javascripts/agt/widgets/widgets/focus_bubbling.coffee +15 -0
- data/lib/agt.rb +8 -0
- data/lib/agt/version.rb +5 -0
- metadata +173 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
namespace('agt.geom')
|
2
|
+
# Public: Every geometry should have the properties of a path. For closed
|
3
|
+
# geometries, the path starts and ends at the same point of the geometry
|
4
|
+
# shape. This point can vary from one geometry to another.
|
5
|
+
#
|
6
|
+
# Path properties includes:
|
7
|
+
#
|
8
|
+
# - **Length** - A path has a {::length} expressed in pixels.
|
9
|
+
# - **Positionning** - A path offers to get a [Point]{agt.geom.Point}
|
10
|
+
# along the geometry shape based on a {Number} in the range `0..1`.
|
11
|
+
# With this point it's also possible to retrieve the orientation
|
12
|
+
# of the path as well as the tangent vector at that point.
|
13
|
+
#
|
14
|
+
# ```coffeescript
|
15
|
+
# class DummyPath
|
16
|
+
# @include agt.geom.Path
|
17
|
+
#
|
18
|
+
# # Your path implementation
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
# The default implementation include length and path computations based
|
22
|
+
# on the distances between the [points]{agt.geom.Geometry::points}
|
23
|
+
# of the geometry.
|
24
|
+
#
|
25
|
+
# If a more accurate or faster implementation exists the path geometries
|
26
|
+
# generally overwrites the default methods with their implementations (see
|
27
|
+
# the [Circle]{agt.geom.Circle} or [Rectangle]{agt.geom.Rectangle} classes
|
28
|
+
# for real word examples).
|
29
|
+
#
|
30
|
+
# <script>window.exampleKey = 'geometry'</script>
|
31
|
+
#
|
32
|
+
class agt.geom.Path
|
33
|
+
|
34
|
+
### Public ###
|
35
|
+
|
36
|
+
# Returns the length of the path in pixels.
|
37
|
+
#
|
38
|
+
# Returns a {Number}.
|
39
|
+
length: ->
|
40
|
+
sum = 0
|
41
|
+
points = @points()
|
42
|
+
if points.length > 1
|
43
|
+
for i in [1..points.length]
|
44
|
+
sum += points[i-1].distance(points[i])
|
45
|
+
sum
|
46
|
+
|
47
|
+
# Returns the coordinates on the path at the given
|
48
|
+
# {Number} position.
|
49
|
+
#
|
50
|
+
# <script>drawGeometry(exampleKey, {paths: [0.1, 0.45, 0.73]})</script>
|
51
|
+
#
|
52
|
+
# pos - The {Number} between `0` and `1` at which get the path coordinates.
|
53
|
+
# pathBasedOnLength - A {Boolean} of whether the position on the path
|
54
|
+
# consider the length of the path segments or not.
|
55
|
+
# With the default implementation, when true, each
|
56
|
+
# segment will only weight as much as their own length.
|
57
|
+
# When false, every segment have the same weight,
|
58
|
+
# resulting in a difference in speed when animating
|
59
|
+
# an object along a path.
|
60
|
+
#
|
61
|
+
# Returns a [Point]{agt.geom.Point}.
|
62
|
+
pathPointAt: (pos, pathBasedOnLength=true) ->
|
63
|
+
pos = 0 if pos < 0
|
64
|
+
pos = 1 if pos > 1
|
65
|
+
points = @points()
|
66
|
+
|
67
|
+
return points[0] if pos is 0
|
68
|
+
return points[points.length - 1] if pos is 1
|
69
|
+
|
70
|
+
if pathBasedOnLength
|
71
|
+
@walkPathBasedOnLength pos, points
|
72
|
+
else
|
73
|
+
@walkPathBasedOnSegments pos, points
|
74
|
+
|
75
|
+
# Returns the orientation on the path at the given
|
76
|
+
# {Number} position.
|
77
|
+
#
|
78
|
+
# <script>drawGeometry(exampleKey, {paths: [0.1, 0.45, 0.73]})</script>
|
79
|
+
#
|
80
|
+
# pos - The {Number} between `0` and `1` at which get the path coordinates.
|
81
|
+
# pathBasedOnLength - A {Boolean} of whether the position on the path
|
82
|
+
# consider the length of the path segments or not.
|
83
|
+
# With the default implementation, when true, each
|
84
|
+
# segment will only weight as much as their own length.
|
85
|
+
# When false, every segment have the same weight,
|
86
|
+
# resulting in a difference in speed when animating
|
87
|
+
# an object along a path.
|
88
|
+
#
|
89
|
+
# Returns a {Number}.
|
90
|
+
pathOrientationAt: (pos, pathBasedOnLength=true) ->
|
91
|
+
p1 = @pathPointAt pos - 0.01, pathBasedOnLength
|
92
|
+
p2 = @pathPointAt pos + 0.01, pathBasedOnLength
|
93
|
+
d = p2.subtract p1
|
94
|
+
|
95
|
+
return d.angle()
|
96
|
+
|
97
|
+
# Returns the tangent vector at the given {Number} position.
|
98
|
+
#
|
99
|
+
# <script>drawGeometry(exampleKey, {paths: [0.1, 0.45, 0.73]})</script>
|
100
|
+
#
|
101
|
+
# pos - The {Number} between `0` and `1` at which get the path coordinates.
|
102
|
+
# accuracy - A {Number} giving the distance, relatively to the path length,
|
103
|
+
# at which sample path data around the position to approximate
|
104
|
+
# the tangent.
|
105
|
+
# pathBasedOnLength - A {Boolean} of whether the position on the path
|
106
|
+
# consider the length of the path segments or not.
|
107
|
+
# With the default implementation, when true, each
|
108
|
+
# segment will only weight as much as their own length.
|
109
|
+
# When false, every segment have the same weight,
|
110
|
+
# resulting in a difference in speed when animating
|
111
|
+
# an object along a path.
|
112
|
+
#
|
113
|
+
# Returns a [Point]{agt.geom.Point}
|
114
|
+
pathTangentAt: (pos, accuracy=1 / 100, pathBasedOnLength=true) ->
|
115
|
+
[pathBasedOnLength, accuracy] = [accuracy, 1/100] if typeof accuracy is 'boolean'
|
116
|
+
@pathPointAt((pos + accuracy) % 1, pathBasedOnLength)
|
117
|
+
.subtract(@pathPointAt((1 + pos - accuracy) % 1), pathBasedOnLength)
|
118
|
+
.normalize(1)
|
119
|
+
|
120
|
+
### Internal ###
|
121
|
+
|
122
|
+
walkPathBasedOnLength: (pos, points) ->
|
123
|
+
walked = 0
|
124
|
+
length = @length()
|
125
|
+
|
126
|
+
for i in [1..points.length-1]
|
127
|
+
p1 = points[i-1]
|
128
|
+
p2 = points[i]
|
129
|
+
stepLength = p1.distance(p2) / length
|
130
|
+
|
131
|
+
if walked + stepLength > pos
|
132
|
+
innerStepPos = Math.map pos, walked, walked + stepLength, 0, 1
|
133
|
+
return @pointInSegment innerStepPos, [p1, p2]
|
134
|
+
|
135
|
+
walked += stepLength
|
136
|
+
|
137
|
+
walkPathBasedOnSegments: (pos, points) ->
|
138
|
+
segments = points.length - 1
|
139
|
+
pos = pos * segments
|
140
|
+
segment = Math.floor pos
|
141
|
+
segment -= 1 if segment is segments
|
142
|
+
@pointInSegment pos - segment, points[segment..segment+1]
|
143
|
+
|
144
|
+
pointInSegment: (pos, segment) ->
|
145
|
+
segment[0].add segment[1].subtract(segment[0]).scale(pos)
|
@@ -0,0 +1,329 @@
|
|
1
|
+
namespace('agt.geom')
|
2
|
+
# Public: A spline is a curve made of [vertices]{agt.geom.Point} that
|
3
|
+
# controls the resulting geometry.
|
4
|
+
#
|
5
|
+
# The `Spline` mixin set the ground for all other curves classes such as
|
6
|
+
# the [CubicBezier]{agt.geom.CubicBezier} or the
|
7
|
+
# [LinearSpline]{agt.geom.LinearSpline} classes.
|
8
|
+
#
|
9
|
+
# <script>window.exampleKey = 'cubic_spline'</script>
|
10
|
+
# <script>drawGeometry('cubic_spline', {highlight: true})</script>
|
11
|
+
# <script>drawGeometry('linear_spline', {highlight: true})</script>
|
12
|
+
#
|
13
|
+
# ```coffeescript
|
14
|
+
# class DummySpline
|
15
|
+
# @include agt.geom.Spine(1)
|
16
|
+
#
|
17
|
+
# # Your spline implementation
|
18
|
+
# ```
|
19
|
+
#
|
20
|
+
# segmentSize - The {Number} of points constituting a segment minus one.
|
21
|
+
# For example, the [LinearSpline]{agt.geom.LinearSpline}
|
22
|
+
# class sets a segment size of `1`, meaning that for points
|
23
|
+
# `[a, b, c, d]` it will have 3 segments `ab`, `bc` and `cd`.
|
24
|
+
#
|
25
|
+
# Returns a {ConcreteSpline} mixin.
|
26
|
+
agt.geom.Spline = (segmentSize) ->
|
27
|
+
|
28
|
+
# Public: The concrete mixin as returned by the
|
29
|
+
# [Spline](../files/geom/mixins/spline.coffee.html) method.
|
30
|
+
#
|
31
|
+
# ### Concrete Splines
|
32
|
+
#
|
33
|
+
# - {agt.geom.LinearSpline}
|
34
|
+
# - {agt.geom.CubicBezier}
|
35
|
+
# - {agt.geom.QuadBezier}
|
36
|
+
# - {agt.geom.QuintBezier}
|
37
|
+
#
|
38
|
+
# ### Included Mixins
|
39
|
+
#
|
40
|
+
# - [agt.mixins.Memoizable](../../../files/mixins/memoizable.coffee.html)
|
41
|
+
#
|
42
|
+
# <script>window.exampleKey = 'cubic_spline'</script>
|
43
|
+
class ConcreteSpline
|
44
|
+
@include agt.mixins.Memoizable
|
45
|
+
|
46
|
+
### Public ###
|
47
|
+
|
48
|
+
# The `Spline` mixin is intended to be included, but it decorates
|
49
|
+
# the target class with a class method to return the segment size defined
|
50
|
+
# for the class.
|
51
|
+
#
|
52
|
+
# klass - The [Class]{Function} that receive the mixin.
|
53
|
+
@included: (klass) ->
|
54
|
+
klass.segmentSize = -> segmentSize
|
55
|
+
|
56
|
+
# Initializes the oject's spline properties. It will also proceed
|
57
|
+
# to the validation of the spline's `vertices` based on the segment
|
58
|
+
# size specified at the mixin creation.
|
59
|
+
#
|
60
|
+
# A vertices {Array} is valid when its length is equal
|
61
|
+
# to `x * segmentSize + 1` where `x` is any integer greater
|
62
|
+
# or equal to `1`.
|
63
|
+
#
|
64
|
+
# vertices - The {Array} of [Points]{agt.geom.Point} that forms the
|
65
|
+
# spline shape.
|
66
|
+
# bias - The {Number} of steps per segments when generating the points
|
67
|
+
# of the final geometry.
|
68
|
+
initSpline: (@vertices, @bias=20) ->
|
69
|
+
unless @validateVertices @vertices
|
70
|
+
throw new Error "The number of vertices for #{this} doesn't match"
|
71
|
+
|
72
|
+
# Returns the center of the spline by averaging its vertices.
|
73
|
+
#
|
74
|
+
# <script>drawGeometry(exampleKey, {center: true})</script>
|
75
|
+
#
|
76
|
+
# Returns a [Point]{agt.geom.Point}.
|
77
|
+
center: ->
|
78
|
+
x = y = 0
|
79
|
+
|
80
|
+
for vertex in @vertices
|
81
|
+
x += vertex.x
|
82
|
+
y += vertex.y
|
83
|
+
|
84
|
+
x = x / @vertices.length
|
85
|
+
y = y / @vertices.length
|
86
|
+
|
87
|
+
new agt.geom.Point x, y
|
88
|
+
|
89
|
+
# Applies a translation represented by the passed-in [point]{agt.geom.Point}
|
90
|
+
# to every vertices of the spline.
|
91
|
+
#
|
92
|
+
# <script>drawTransform(exampleKey, {type: 'translate', args: [50, 0], width: 150})</script>
|
93
|
+
#
|
94
|
+
# x - A {Number} for the x coordinate or a point-like {Object}.
|
95
|
+
# y - A {Number} for the y coordinate if the first argument is also a number.
|
96
|
+
#
|
97
|
+
# Returns this {ConcreteSpline}.
|
98
|
+
translate: (x,y) ->
|
99
|
+
{x,y} = agt.geom.Point.pointFrom x,y
|
100
|
+
for vertex,i in @vertices
|
101
|
+
@vertices[i] = vertex.add x, y
|
102
|
+
this
|
103
|
+
|
104
|
+
# Rotates every vertices around the spline center by an amount of `rotation`
|
105
|
+
# radians.
|
106
|
+
#
|
107
|
+
# <script>drawTransform(exampleKey, {type: 'rotate', args: [Math.PI / 3]})</script>
|
108
|
+
#
|
109
|
+
# rotation - The {Number} of radians to rotate the spline.
|
110
|
+
#
|
111
|
+
# Returns this {ConcreteSpline}.
|
112
|
+
rotate: (rotation) ->
|
113
|
+
center = @center()
|
114
|
+
for vertex,i in @vertices
|
115
|
+
@vertices[i] = vertex.rotateAround center, rotation
|
116
|
+
this
|
117
|
+
|
118
|
+
# Scales the spline by moving every vertices on the vector they forms with
|
119
|
+
# the spline center.
|
120
|
+
#
|
121
|
+
# <script>drawTransform(exampleKey, {type: 'scale', args: [0.6]})</script>
|
122
|
+
#
|
123
|
+
# scale - The scaling factor {Number}, a value of `0.5` will scale down
|
124
|
+
# the spline at half its original size when a value of `2` will
|
125
|
+
# double the size of the spline.
|
126
|
+
#
|
127
|
+
# Returns this {ConcreteSpline}.
|
128
|
+
scale: (scale) ->
|
129
|
+
center = @center()
|
130
|
+
for vertex,i in @vertices
|
131
|
+
@vertices[i] = center.add vertex.subtract(center).scale(scale)
|
132
|
+
this
|
133
|
+
|
134
|
+
# Returns the *final* points of the curve as determined by the `bias`
|
135
|
+
# property of the current spline. The total number of points for a geometry
|
136
|
+
# is always the result of the following equation: `segment size * number
|
137
|
+
# of segments`.
|
138
|
+
#
|
139
|
+
# <script>drawGeometryPoints(exampleKey, 'points')</script>
|
140
|
+
#
|
141
|
+
# Returns an {Array} of [Points]{agt.geom.Point}.
|
142
|
+
points: ->
|
143
|
+
return @memoFor('points').concat() if @memoized 'points'
|
144
|
+
segments = @segments() * @bias
|
145
|
+
points = (@pathPointAt i / segments for i in [0..segments])
|
146
|
+
@memoize('points', points).concat()
|
147
|
+
|
148
|
+
# Internal: Validates the length of the vertices {Array}.
|
149
|
+
#
|
150
|
+
# vertices - The {Array} of vertices to validate.
|
151
|
+
#
|
152
|
+
# Returns a {Boolean}.
|
153
|
+
validateVertices: (vertices) ->
|
154
|
+
vertices.length % segmentSize is 1 and
|
155
|
+
vertices.length >= segmentSize + 1
|
156
|
+
|
157
|
+
# Returns the {Number} of segments of the current spline based on the
|
158
|
+
# spline configuration.
|
159
|
+
#
|
160
|
+
# Returns a {Number}.
|
161
|
+
segments: ->
|
162
|
+
return 0 if not @vertices? or @vertices.length is 0
|
163
|
+
return @memoFor 'segments' if @memoized 'segments'
|
164
|
+
@memoize 'segments', (@vertices.length - 1) / segmentSize
|
165
|
+
|
166
|
+
# Returns the size {Number} of the spline segments.
|
167
|
+
#
|
168
|
+
# Returns a {Number}.
|
169
|
+
segmentSize: -> segmentSize
|
170
|
+
|
171
|
+
# Returns the segment at `index`. A segment is a pair of
|
172
|
+
# [Points]{agt.geom.Point} of each extremity of the segment.
|
173
|
+
#
|
174
|
+
# index - The index {Number} of the segment.
|
175
|
+
#
|
176
|
+
# Returns an {Array} of [Points]{agt.geom.Point}.
|
177
|
+
segment: (index) ->
|
178
|
+
if index < @segments()
|
179
|
+
k = "segment#{index}"
|
180
|
+
return @memoFor k if @memoized k
|
181
|
+
|
182
|
+
[start, end] = [index * segmentSize, (index + 1) * segmentSize + 1]
|
183
|
+
@memoize k, @vertices[start..end]
|
184
|
+
else
|
185
|
+
null
|
186
|
+
|
187
|
+
# Returns the length of the spline in pixels.
|
188
|
+
#
|
189
|
+
# This an approximative value based on the current spline `bias`, so the
|
190
|
+
# bigger the `bias` the more accurate the length is, with the downside
|
191
|
+
# of poorer performances.
|
192
|
+
#
|
193
|
+
# Returns a {Number}.
|
194
|
+
length: -> @measure @bias
|
195
|
+
|
196
|
+
# Internal: Measures the spline using the passed-in bias.
|
197
|
+
#
|
198
|
+
# bias - The {Number} of steps used to walk the spline segments.
|
199
|
+
#
|
200
|
+
# Returns a {Number}.
|
201
|
+
measure: (bias) ->
|
202
|
+
return @memoFor 'measure' if @memoized 'measure'
|
203
|
+
length = 0
|
204
|
+
length += @measureSegment @segment(i), bias for i in [0..@segments()-1]
|
205
|
+
@memoize 'measure', length
|
206
|
+
|
207
|
+
# Internal: Measures the given segment using the passed-in bias.
|
208
|
+
#
|
209
|
+
# segment - The index {Number} of the segment to measure.
|
210
|
+
# bias - The {Number} of steps used to walk the segment.
|
211
|
+
#
|
212
|
+
# Returns a {Number}.
|
213
|
+
measureSegment: (segment, bias) ->
|
214
|
+
k = "segment#{segment}_#{bias}Length"
|
215
|
+
return @memoFor k if @memoized k
|
216
|
+
|
217
|
+
step = 1 / bias
|
218
|
+
length = 0
|
219
|
+
|
220
|
+
for i in [1..bias]
|
221
|
+
length += @pointInSegment((i-1) * step, segment)
|
222
|
+
.distance(@pointInSegment(i * step, segment))
|
223
|
+
|
224
|
+
@memoize k, length
|
225
|
+
|
226
|
+
# {Delegates to: agt.geom.Path.pathPointAt}
|
227
|
+
pathPointAt: (pos, pathBasedOnLength=true) ->
|
228
|
+
pos = 0 if pos < 0
|
229
|
+
pos = 1 if pos > 1
|
230
|
+
|
231
|
+
return @vertices[0] if pos is 0
|
232
|
+
return @vertices[@vertices.length - 1] if pos is 1
|
233
|
+
|
234
|
+
if pathBasedOnLength
|
235
|
+
@walkPathBasedOnLength pos
|
236
|
+
else
|
237
|
+
@walkPathBasedOnSegments pos
|
238
|
+
|
239
|
+
# Internal: Iterates over the spline steps until the given `pos` and
|
240
|
+
# returns the corresponding coordinates.
|
241
|
+
#
|
242
|
+
# In this implementation the path is walked with each segment taking
|
243
|
+
# a space in the total spline length based on its own length.
|
244
|
+
#
|
245
|
+
# pos - The position {Number} between `0` and `1`.
|
246
|
+
#
|
247
|
+
# Returns a [Point]{agt.geom.Point}.
|
248
|
+
walkPathBasedOnLength: (pos) ->
|
249
|
+
walked = 0
|
250
|
+
length = @length()
|
251
|
+
segments = @segments()
|
252
|
+
|
253
|
+
for i in [0..segments-1]
|
254
|
+
segment = @segment i
|
255
|
+
stepLength = @measureSegment(segment, @bias) / length
|
256
|
+
|
257
|
+
if walked + stepLength > pos
|
258
|
+
innerStepPos = Math.map pos, walked, walked + stepLength, 0, 1
|
259
|
+
return @pointInSegment innerStepPos, segment
|
260
|
+
|
261
|
+
walked += stepLength
|
262
|
+
|
263
|
+
# Internal: Iterates over the spline steps until the given `pos` and
|
264
|
+
# returns the corresponding coordinates.
|
265
|
+
#
|
266
|
+
# In this implementation the path is walked with each segment taking
|
267
|
+
# based on the number of segments in the spline.
|
268
|
+
#
|
269
|
+
# pos - The position {Number} between `0` and `1`.
|
270
|
+
#
|
271
|
+
# Returns a [Point]{agt.geom.Point}.
|
272
|
+
walkPathBasedOnSegments: (pos) ->
|
273
|
+
segments = @segments()
|
274
|
+
pos = pos * segments
|
275
|
+
segment = Math.floor pos
|
276
|
+
segment -= 1 if segment is segments
|
277
|
+
@pointInSegment pos - segment, @segment segment
|
278
|
+
|
279
|
+
# **Unsupported** - The {agt.geom.Geometry::fill} method is not supported
|
280
|
+
# by splines as they are not closed geometry.
|
281
|
+
fill: ->
|
282
|
+
|
283
|
+
# {Delegates to: agt.geom.Geometry.drawPath}
|
284
|
+
drawPath: (context) ->
|
285
|
+
points = @points()
|
286
|
+
start = points.shift()
|
287
|
+
context.beginPath()
|
288
|
+
context.moveTo(start.x,start.y)
|
289
|
+
context.lineTo(p.x,p.y) for p in points
|
290
|
+
|
291
|
+
# Draws the spline vertices onto the passed-in canvas context.
|
292
|
+
#
|
293
|
+
# <script>drawGeometry(exampleKey, {vertices: true})</script>
|
294
|
+
#
|
295
|
+
# context - The canvas context to draw in.
|
296
|
+
# color - The color {String} to use for the vertices.
|
297
|
+
drawVertices: (context, color=agt.COLORS.VERTICES) ->
|
298
|
+
context.fillStyle = color
|
299
|
+
for vertex in @vertices
|
300
|
+
context.beginPath()
|
301
|
+
context.arc vertex.x, vertex.y, 2, 0, Math.PI*2
|
302
|
+
context.fill()
|
303
|
+
context.closePath()
|
304
|
+
|
305
|
+
# Draws the segments between each vertex onto the passed-in canvas context.
|
306
|
+
#
|
307
|
+
# <script>drawGeometry(exampleKey, {verticesConnections: true})</script>
|
308
|
+
#
|
309
|
+
# context - The canvas context to draw in.
|
310
|
+
# color - The color {String} to use for the vertices connections.
|
311
|
+
drawVerticesConnections: (context, color=agt.COLORS.VERTICES_CONNECTIONS) ->
|
312
|
+
context.strokeStyle = color
|
313
|
+
for i in [1..@vertices.length-1]
|
314
|
+
vertexStart = @vertices[i-1]
|
315
|
+
vertexEnd = @vertices[i]
|
316
|
+
context.beginPath()
|
317
|
+
context.moveTo vertexStart.x, vertexStart.y
|
318
|
+
context.lineTo vertexEnd.x, vertexEnd.y
|
319
|
+
context.stroke()
|
320
|
+
context.closePath()
|
321
|
+
|
322
|
+
# The memoization key of a spline is the concatenation of its vertices.
|
323
|
+
#
|
324
|
+
# Returns a {String}.
|
325
|
+
memoizationKey: ->
|
326
|
+
@vertices.map((pt) -> "#{pt.x};#{pt.y}").join(';')
|
327
|
+
|
328
|
+
# {Delegates to: ConcreteCloneable.clone}
|
329
|
+
clone: -> new @constructor @vertices.map((pt) -> pt.clone()), @bias
|