agt 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|