dem-curves 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/lib/core/constraint.rb +79 -0
- data/lib/core/control-point.rb +106 -0
- data/lib/core/path-element.rb +88 -0
- data/lib/core/path.rb +87 -0
- data/lib/core/util.rb +26 -0
- data/lib/dem-curves.rb +19 -0
- data/lib/rubygame-util/control-handles.rb +76 -0
- data/lib/rubygame-util/gfx.rb +15 -0
- metadata +52 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 95c1fab002f919736b30a60e299e82c05a10f1fb
|
4
|
+
data.tar.gz: 9a9a364baf601ea4c82d302b38cb2fb7b62fa543
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ce2ca54589e1a097467d565a315383b3269a3cee37f7dbfc8c6e12514ae9ebf38a909721ce7de4c797896f8e16f2b0ea49034bcec57efd2eb7d3def32ffb6757
|
7
|
+
data.tar.gz: f78075a71096ba4ffc79600ed2ddc28a2667a5d234b86923a85b69295ade8c878823960109b19f07f3846347fabf402e7d07a47dfe15bda70901b31039eba1ba
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module DemCurves
|
2
|
+
module BaseConstraint
|
3
|
+
master_point = nil
|
4
|
+
slave_points = []
|
5
|
+
|
6
|
+
def notify(src, orig_src, params)
|
7
|
+
if src == @master_point
|
8
|
+
handle_master src, orig_src, params
|
9
|
+
elsif @slave_points.include? src
|
10
|
+
handle_slave src, orig_src, params
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle_master(master, orig_src, params)
|
15
|
+
# This has to be implemented by the class
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle_slave(slave, orig_src, params)
|
19
|
+
# This has to be implemented by the class
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
class LineUpConstraint
|
25
|
+
include BaseConstraint
|
26
|
+
def initialize(pivot, p0, p1, mirror_distance=false, follow=:pivot)
|
27
|
+
pivot.add_constraint self
|
28
|
+
p0.add_constraint self
|
29
|
+
p1.add_constraint self
|
30
|
+
|
31
|
+
@master_point = pivot
|
32
|
+
@slave_points = [p0, p1]
|
33
|
+
@mirror_distance = mirror_distance
|
34
|
+
@follow = follow
|
35
|
+
p1.move_to p1.loc #hacky way to trigger readjustment
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle_master(master, orig_src, params)
|
39
|
+
if (params.include? :new_pos and params.include? :old_pos) or params.include? :rel
|
40
|
+
case @follow
|
41
|
+
when :pivot
|
42
|
+
unless params.include? :rel
|
43
|
+
params[:rel] = params[:new_pos], params[:old_pos]
|
44
|
+
end
|
45
|
+
|
46
|
+
params.delete(:new_pos)
|
47
|
+
params.delete(:old_pos)
|
48
|
+
|
49
|
+
@slave_points.each do |slave|
|
50
|
+
slave.notify_to_move orig_src, self, params
|
51
|
+
end
|
52
|
+
when :p0
|
53
|
+
direction = (params[:new_pos] - @slave_points[0].loc).unit
|
54
|
+
distance = (params[:new_pos] - @slave_points[1].loc).r
|
55
|
+
@slave_points[1].notify_to_move orig_src, self, {:new_pos => params[:new_pos] + (distance * direction)}
|
56
|
+
when :p1
|
57
|
+
direction = (params[:new_pos] - @slave_points[1].loc).unit
|
58
|
+
distance = (params[:new_pos] - @slave_points[0].loc).r
|
59
|
+
@slave_points[0].notify_to_move orig_src, self, {:new_pos => params[:new_pos] + (distance * direction)}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def handle_slave(slave, orig_src, params)
|
65
|
+
if params.include? :new_pos
|
66
|
+
other_slave = (@slave_points.select {|s| s!=slave})[0]
|
67
|
+
direction = (params[:new_pos] - @master_point.loc).unit
|
68
|
+
if @mirror_distance
|
69
|
+
distance = (slave.loc - @master_point.loc).r
|
70
|
+
else
|
71
|
+
distance = (other_slave.loc - @master_point.loc).r
|
72
|
+
end
|
73
|
+
|
74
|
+
new_loc = @master_point.loc + (-1 * direction * distance)
|
75
|
+
other_slave.notify_to_move orig_src, self, {:new_pos => new_loc}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
require 'core/util.rb'
|
3
|
+
|
4
|
+
module DemCurves
|
5
|
+
class ControlPoint
|
6
|
+
# this class is necessary to let other curves and path elements modify
|
7
|
+
# points, it works much better than calling "notify movement" functions
|
8
|
+
# every time a control point moves, this comes in handy when you want a curve
|
9
|
+
# to use another curve's end point as a starting point.
|
10
|
+
attr_reader :loc
|
11
|
+
|
12
|
+
def initialize(loc)
|
13
|
+
@loc = Vector.elements(loc)
|
14
|
+
@path_elements = []
|
15
|
+
@constraints = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_path_element(path_element)
|
19
|
+
@path_elements << path_element
|
20
|
+
end
|
21
|
+
|
22
|
+
def replace(other)
|
23
|
+
old_pos = @loc
|
24
|
+
case other
|
25
|
+
when ControlPoint
|
26
|
+
@loc = Vector.elements(other.loc)
|
27
|
+
when Vector, Array
|
28
|
+
unless other.size == 2
|
29
|
+
raise "Wrong number of dimensions, must be [x, y]"
|
30
|
+
end
|
31
|
+
@loc = Vector.elements(other)
|
32
|
+
else
|
33
|
+
raise "Argument is instance of #{other.class}! Replacement argument must be an instance of Vector, Array or ControlPoint."
|
34
|
+
end
|
35
|
+
|
36
|
+
@path_elements.each do |path_element|
|
37
|
+
path_element.generate
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def shift(rel)
|
42
|
+
unless rel.size == 2
|
43
|
+
raise "Wrong number of dimensions, must be [x, y]"
|
44
|
+
end
|
45
|
+
|
46
|
+
old_pos = @loc
|
47
|
+
move_to @loc + Vector.elements(rel)
|
48
|
+
end
|
49
|
+
|
50
|
+
def move_to(destination)
|
51
|
+
old_pos = @loc
|
52
|
+
replace destination
|
53
|
+
|
54
|
+
new_pos = @loc
|
55
|
+
rel = new_pos - old_pos
|
56
|
+
|
57
|
+
@constraints.each do |constraint|
|
58
|
+
constraint.notify self, self, {:new_pos => new_pos, :old_pos => old_pos, :rel => rel}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def rotate_around(pivot_ctl, angle)
|
63
|
+
offset = @loc - pivot_ctl.loc
|
64
|
+
new_offset = Matrix[[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]] * offset
|
65
|
+
replace new_offset + pivot_ctl.loc
|
66
|
+
end
|
67
|
+
|
68
|
+
def notify_to_move(orig_src, src_constraint, params)
|
69
|
+
unless orig_src == self
|
70
|
+
# Safety measure, it avoids infinite recursion, but produces weird results
|
71
|
+
# with cyclic constraint structures.
|
72
|
+
old_pos = @loc
|
73
|
+
if params.include? :new_pos
|
74
|
+
replace params[:new_pos]
|
75
|
+
elsif params.include? :rel
|
76
|
+
replace params[:rel] + @loc
|
77
|
+
else
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
new_pos = @loc
|
82
|
+
rel = new_pos - old_pos
|
83
|
+
|
84
|
+
@constraints.each do |constraint|
|
85
|
+
unless constraint == src_constraint
|
86
|
+
constraint.notify self, orig_src, {:new_pos => new_pos, :old_pos => old_pos, :rel => rel}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_constraint(constraint)
|
93
|
+
unless @constraints.include? constraint
|
94
|
+
@constraints << constraint
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def clear_constraints
|
99
|
+
@constraints = []
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def ControlPoint.[](*loc)
|
104
|
+
return ControlPoint.new loc
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'core/util.rb'
|
2
|
+
|
3
|
+
module DemCurves
|
4
|
+
class PathElement
|
5
|
+
# Do not directly instantiate this class
|
6
|
+
attr_reader :path_points, :control_points
|
7
|
+
def initialize(control_points)
|
8
|
+
@control_points = control_points
|
9
|
+
@control_points.each_value do |control_point|
|
10
|
+
control_point.add_path_element self
|
11
|
+
end
|
12
|
+
|
13
|
+
@path_points = []
|
14
|
+
generate
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate
|
18
|
+
@path_points = @control_points.values.collect {|point| point.loc}
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_control(control_id, loc)
|
22
|
+
unless control_id.class == Symbol
|
23
|
+
raise 'control_id must be a symbol'
|
24
|
+
end
|
25
|
+
|
26
|
+
@control_points[control_id].move_to location
|
27
|
+
generate
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(control_id, loc)
|
31
|
+
set_control control_id, loc
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_control(control_id)
|
35
|
+
unless control_id.class == Symbol
|
36
|
+
raise 'control_id must be a symbol'
|
37
|
+
end
|
38
|
+
|
39
|
+
return @control_points[control_id]
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](control_id)
|
43
|
+
get_control control_id
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
class CubicBezier < PathElement
|
49
|
+
def initialize(start_point, start_handle, end_handle, end_point)
|
50
|
+
super({
|
51
|
+
:start => start_point,
|
52
|
+
:start_handle => start_handle,
|
53
|
+
:end_handle => end_handle,
|
54
|
+
:end => end_point})
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate(t_freq=32)
|
58
|
+
step = 1.0 / t_freq
|
59
|
+
@path_points = (0..t_freq).collect {|i| interpolate(step * i)}
|
60
|
+
end
|
61
|
+
|
62
|
+
def interpolate(t)
|
63
|
+
# http://mathworld.wolfram.com/BezierCurve.html
|
64
|
+
(0..3).inject(Vector[0, 0]) do |mem, i|
|
65
|
+
mem += @control_points.values[i].loc.clone * bernstein_basis(i, 3, t)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_guides
|
70
|
+
# this will be removed soon.
|
71
|
+
return [[self[:start].loc, self[:start_handle].loc], [self[:end_handle].loc, self[:end].loc]]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
class Line < PathElement
|
77
|
+
def initialize(start_point, center_point, end_point)
|
78
|
+
super({
|
79
|
+
:start => start_point,
|
80
|
+
:center => center_point,
|
81
|
+
:end => end_point})
|
82
|
+
end
|
83
|
+
|
84
|
+
def generate
|
85
|
+
@path_points = [self[:start].loc, self[:end].loc]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/core/path.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'core/path-element.rb'
|
2
|
+
|
3
|
+
module DemCurves
|
4
|
+
class Path
|
5
|
+
include Enumerable
|
6
|
+
attr_reader :path_points, :path_elements, :control_points
|
7
|
+
|
8
|
+
def initialize(point)
|
9
|
+
@path_elements = []
|
10
|
+
|
11
|
+
@start_point = ControlPoint[*point]
|
12
|
+
@end_point = @start_point
|
13
|
+
|
14
|
+
@path_points = to_a
|
15
|
+
@control_points = [@start_point]
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_bezier(start_handle, end_handle, end_point, tangent_lock=true)
|
19
|
+
new_bezier = CubicBezier.new(
|
20
|
+
@end_point,
|
21
|
+
ControlPoint[*start_handle],
|
22
|
+
ControlPoint[*end_handle],
|
23
|
+
ControlPoint[*end_point])
|
24
|
+
|
25
|
+
if tangent_lock and @path_elements.last
|
26
|
+
start_length = (new_bezier[:start_handle].loc - @end_point.loc).r
|
27
|
+
last_element = @path_elements.last
|
28
|
+
|
29
|
+
case last_element
|
30
|
+
when CubicBezier
|
31
|
+
LineUpConstraint.new @end_point, last_element[:end_handle], new_bezier[:start_handle]
|
32
|
+
when Line
|
33
|
+
LineUpConstraint.new @end_point, last_element[:center], new_bezier[:start_handle], morror_distance=false, follow=:p0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
@end_point = new_bezier[:end]
|
38
|
+
@path_elements << new_bezier
|
39
|
+
|
40
|
+
@path_points = to_a
|
41
|
+
@control_points += new_bezier.control_points.values[1..-1]
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_line(end_point, tangent_lock=true)
|
45
|
+
center_point = ControlPoint[*(@end_point.loc + (Vector[*end_point] - @end_point.loc) * 0.5)]
|
46
|
+
new_line = Line.new @end_point, center_point, ControlPoint[*end_point]
|
47
|
+
LineUpConstraint.new center_point, new_line[:end], @end_point
|
48
|
+
|
49
|
+
if tangent_lock and @path_elements.last
|
50
|
+
last_element = @path_elements.last
|
51
|
+
|
52
|
+
case last_element
|
53
|
+
when CubicBezier
|
54
|
+
LineUpConstraint.new @end_point, last_element[:end_handle], new_line[:end], morror_distance=false, follow=:p1
|
55
|
+
when Line
|
56
|
+
@end_point.move_to end_point
|
57
|
+
return
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
@end_point = new_line[:end]
|
62
|
+
@path_elements << new_line
|
63
|
+
|
64
|
+
@path_points = to_a
|
65
|
+
@control_points += new_line.control_points.values[1..-1]
|
66
|
+
end
|
67
|
+
|
68
|
+
def each
|
69
|
+
yield @start_point.loc
|
70
|
+
@path_elements.each do |path_element|
|
71
|
+
(1..path_element.path_points.size-1).each do |index|
|
72
|
+
yield path_element.path_points[index]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def size
|
78
|
+
return @path_points.size
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_guides
|
82
|
+
@path_elements.inject([]) do |mem, element|
|
83
|
+
mem += element.get_guides
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/core/util.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Integer
|
2
|
+
def choose(k)
|
3
|
+
# binominal coefficient
|
4
|
+
return self.factorial / ((self -k).factorial * k.factorial)
|
5
|
+
end
|
6
|
+
|
7
|
+
def factorial()
|
8
|
+
# n!
|
9
|
+
return (1..self).inject(1, &:*)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def bernstein_basis(i, n, t)
|
14
|
+
# http://mathworld.wolfram.com/BernsteinPolynomial.html
|
15
|
+
return n.choose(i) * t ** i * (1 - t) ** (n - i)
|
16
|
+
end
|
17
|
+
|
18
|
+
class Vector
|
19
|
+
def unit
|
20
|
+
return self / self.r
|
21
|
+
end
|
22
|
+
|
23
|
+
def angle(other)
|
24
|
+
return Math.acos(self.inner_product other / (self.r * other.r))
|
25
|
+
end
|
26
|
+
end
|
data/lib/dem-curves.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# The backend files
|
2
|
+
require 'core/control-point'
|
3
|
+
require 'core/constraint'
|
4
|
+
require 'core/path-element'
|
5
|
+
require 'core/path'
|
6
|
+
require 'core/util'
|
7
|
+
|
8
|
+
# These are the utils for integration with rubygame
|
9
|
+
begin
|
10
|
+
require 'rubygame'
|
11
|
+
unless Rubygame::Surface.public_method_defined? :draw_line
|
12
|
+
raise LoadError, 'Loading the Rubygame utils for DemCurves requires SDL_GFX to be present on the system'
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'rubygame-util/control-handles'
|
16
|
+
require 'rubygame-util/gfx'
|
17
|
+
rescue LoadError => e
|
18
|
+
puts 'The Rubygame utils for DemCurves require SDL_GFX and Rubygame.'
|
19
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygame'
|
2
|
+
require 'matrix'
|
3
|
+
|
4
|
+
module DemCurves
|
5
|
+
module RubygameUtils
|
6
|
+
def self.populate_handles(ctl_points, drag_group)
|
7
|
+
ctl_points.each do |control_point|
|
8
|
+
new_handle = EditorHandle.new
|
9
|
+
new_handle.attach_to control_point
|
10
|
+
drag_group << new_handle
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DragGroup < Rubygame::Sprites::Group
|
15
|
+
dragged_object = nil
|
16
|
+
|
17
|
+
def on_press(evt)
|
18
|
+
self.each do |sprite|
|
19
|
+
if sprite.rect.collide_point? *evt.pos
|
20
|
+
@dragged_object = sprite
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_release(evt)
|
27
|
+
@dragged_object = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_move(evt)
|
31
|
+
if @dragged_object
|
32
|
+
@dragged_object.move evt.rel
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class EditorHandle
|
39
|
+
include Rubygame::Sprites::Sprite
|
40
|
+
|
41
|
+
def initialize(loc=[50, 50], size=10)
|
42
|
+
@groups =[]
|
43
|
+
@depth = 0
|
44
|
+
|
45
|
+
@rect = Rubygame::Rect.new 0, 0, size, size
|
46
|
+
@rect.c = loc
|
47
|
+
|
48
|
+
@image = Rubygame::Surface.new [size, size], 0, [Rubygame::HWSURFACE, Rubygame::SRCALPHA]
|
49
|
+
@image.fill([180, 180, 180])
|
50
|
+
@attached = false
|
51
|
+
|
52
|
+
@constraints = []
|
53
|
+
end
|
54
|
+
|
55
|
+
def attach_to(control_point)
|
56
|
+
unless @attached
|
57
|
+
@attached = true
|
58
|
+
@control_point = control_point
|
59
|
+
@rect.c = control_point.loc.to_a
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def move(rel)
|
64
|
+
if @attached
|
65
|
+
@control_point.shift rel
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def update
|
70
|
+
if @attached
|
71
|
+
@rect.c = @control_point.loc.to_a
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygame'
|
2
|
+
|
3
|
+
class Rubygame::Surface
|
4
|
+
def draw_path(path, color)
|
5
|
+
(0..path.size - 2).each do |index|
|
6
|
+
_draw_line path.to_a[index], path.to_a[index + 1], color, false
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def draw_path_a(path, color)
|
11
|
+
(0..path.size - 2).each do |index|
|
12
|
+
_draw_line path.to_a[index], path.to_a[index + 1], color, true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dem-curves
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Huba Nagy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: 12huba@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/core/constraint.rb
|
20
|
+
- lib/core/control-point.rb
|
21
|
+
- lib/core/path-element.rb
|
22
|
+
- lib/core/path.rb
|
23
|
+
- lib/core/util.rb
|
24
|
+
- lib/dem-curves.rb
|
25
|
+
- lib/rubygame-util/control-handles.rb
|
26
|
+
- lib/rubygame-util/gfx.rb
|
27
|
+
homepage: https://github.com/huba/DemCurves
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.4.5
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: A library for generating bezier curve based paths from control_points. It
|
51
|
+
can be used with Rubygame
|
52
|
+
test_files: []
|