Skip to content

Commit 74921c6

Browse files
committed
use Matrix math
1 parent a76127a commit 74921c6

File tree

12 files changed

+447
-50
lines changed

12 files changed

+447
-50
lines changed

processing_app/library/vecmath/vec2d/circumcircle.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,26 @@ c = Vec2D.new(200, 200)
3333
(a - b).cross(b - c) == 0 # the area of the triangle is zero, so a, b, c are collinear
3434

3535
```
36+
37+
Also because we were able to separate the logic, we were able to confidently re-factor the Barbara Almeida sketch to use Matrix math to determine the circumcenter
38+
39+
### Matrix Math ###
40+
41+
For detailed workings see [Circumcircle at Mathworld Wolfram.com][circumcircle]
42+
43+
44+
a = {{x<sub>1</sub> y<sub>1</sub> 1}, {x<sub>2</sub> y<sub>2</sub> 1}, {x<sub>3</sub> y<sub>3</sub> 1}}
45+
46+
bx = -{{x<sub>1</sub><sup>2</sup> + y<sub>1</sub><sup>2</sup> y<sub>1</sub> 1}, {x<sub>2</sub><sup>2</sup> + y<sub>2</sub><sup>2</sup> y<sub>2</sub> 1}, {x<sub>3</sub><sup>2</sup> + y<sub>3</sub><sup>2</sup> y<sub>3</sub> 1}}
47+
48+
by = {{x<sub>1</sub><sup>2</sup> + y<sub>1</sub><sup>2</sup> x<sub>1</sub> 1}, {x<sub>2</sub><sup>2</sup> + y<sub>2</sub><sup>2</sup> x<sub>2</sub> 1}, {x<sub>3</sub><sup>2</sup> + y<sub>3</sub><sup>2</sup> x<sub>3</sub> 1}}
49+
50+
xo = -bx / 2 * a
51+
52+
yo = -by / 2 * a
53+
54+
55+
[circumcircle]:http://mathworld.wolfram.com/Circumcircle.html
56+
57+
58+
Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,43 @@
1-
# frozen_string_literal: true
2-
PBisector = Struct.new(:vector, :angle) # perpendicular bisector
3-
Vect = Struct.new(:x, :y, :z) # for calculation of center
41
# Circumcircle from 3 points
2+
require 'matrix'
3+
54
class Circumcircle
6-
include Math
75
attr_reader :center, :radius, :points
86
def initialize(points)
97
@points = points
108
end
119

1210
def calculate
13-
ab = bisector(points[0], points[1]) # find 2 midpoints
14-
bc = bisector(points[1], points[2])
15-
@center = circumcenter(ab, bc)
11+
@center = Vec2D.new(-(bx / am), -(by / am))
1612
@radius = center.dist(points[2]) # points[2] = c
1713
end
1814

19-
def bisector(a, b)
20-
midpoint = (a + b) / 2.0 # middle of ab (or bc)
21-
theta = atan2(b.y - a.y, b.x - a.x) # slope of ab (or bc)
22-
PBisector.new(midpoint, theta - PI / 2)
15+
private
16+
17+
# Matrix math see matrix_math.md and in detail
18+
# http://mathworld.wolfram.com/Circumcircle.html
19+
20+
def am
21+
2 * Matrix[
22+
[points[0].x, points[0].y, 1],
23+
[points[1].x, points[1].y, 1],
24+
[points[2].x, points[2].y, 1]
25+
].determinant
26+
end
27+
28+
def bx
29+
-Matrix[
30+
[points[0].x * points[0].x + points[0].y * points[0].y, points[0].y, 1],
31+
[points[1].x * points[1].x + points[1].y * points[1].y, points[1].y, 1],
32+
[points[2].x * points[2].x + points[2].y * points[2].y, points[2].y, 1]
33+
].determinant
2334
end
2435

25-
def circumcenter(pb1, pb2)
26-
# equation of the first bisector (ax - y = -b)
27-
a0 = tan pb1.angle
28-
v0 = pb1.vector
29-
a1 = tan pb2.angle
30-
v1 = pb2.vector
31-
eq0 = Vect.new(a0, -1, -1 * (v0.y - v0.x * a0))
32-
eq1 = Vect.new(a1, -1, -1 * (v1.y - v1.x * a1))
33-
# calculate x and y coordinates of the circumcenter
34-
ox = (eq1.y * eq0.z - eq0.y * eq1.z) /
35-
(eq0.x * eq1.y - eq1.x * eq0.y)
36-
oy = (eq0.x * eq1.z - eq1.x * eq0.z) /
37-
(eq0.x * eq1.y - eq1.x * eq0.y)
38-
Vec2D.new(ox, oy)
36+
def by
37+
Matrix[
38+
[points[0].x * points[0].x + points[0].y * points[0].y, points[0].x, 1],
39+
[points[1].x * points[1].x + points[1].y * points[1].y, points[1].x, 1],
40+
[points[2].x * points[2].x + points[2].y * points[2].y, points[2].x, 1]
41+
].determinant
3942
end
4043
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
require_relative 'lib/circumcircle'
2+
require_relative 'lib/t_points'
3+
require_relative 'lib/triangle_point'
Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,43 @@
1-
# frozen_string_literal: true
2-
PBisector = Struct.new(:vector, :angle) # perpendicular bisector
3-
Vect = Struct.new(:x, :y, :z) # for calculation of center
41
# Circumcircle from 3 points
2+
require 'matrix'
3+
54
class Circumcircle
6-
include Math
75
attr_reader :center, :radius, :points
86
def initialize(points)
97
@points = points
108
end
119

1210
def calculate
13-
ab = bisector(points[0], points[1]) # find 2 midpoints
14-
bc = bisector(points[1], points[2])
15-
@center = circumcenter(ab, bc)
11+
@center = Vec2D.new(-(bx / am), -(by / am))
1612
@radius = center.dist(points[2]) # points[2] = c
1713
end
1814

19-
def bisector(a, b)
20-
midpoint = (a + b) / 2.0 # middle of ab (or bc)
21-
theta = atan2(b.y - a.y, b.x - a.x) # slope of ab (or bc)
22-
PBisector.new(midpoint, theta - PI / 2)
15+
private
16+
17+
# Matrix math see matrix_math.md and in detail
18+
# http://mathworld.wolfram.com/Circumcircle.html
19+
20+
def am
21+
2 * Matrix[
22+
[points[0].x, points[0].y, 1],
23+
[points[1].x, points[1].y, 1],
24+
[points[2].x, points[2].y, 1]
25+
].determinant
26+
end
27+
28+
def bx
29+
-Matrix[
30+
[points[0].x * points[0].x + points[0].y * points[0].y, points[0].y, 1],
31+
[points[1].x * points[1].x + points[1].y * points[1].y, points[1].y, 1],
32+
[points[2].x * points[2].x + points[2].y * points[2].y, points[2].y, 1]
33+
].determinant
2334
end
2435

25-
def circumcenter(pb1, pb2)
26-
# equation of the first bisector (ax - y = -b)
27-
a0 = tan pb1.angle
28-
v0 = pb1.vector
29-
a1 = tan pb2.angle
30-
v1 = pb2.vector
31-
eq0 = Vect.new(a0, -1, -1 * (v0.y - v0.x * a0))
32-
eq1 = Vect.new(a1, -1, -1 * (v1.y - v1.x * a1))
33-
# calculate x and y coordinates of the circumcenter
34-
ox = (eq1.y * eq0.z - eq0.y * eq1.z) /
35-
(eq0.x * eq1.y - eq1.x * eq0.y)
36-
oy = (eq0.x * eq1.z - eq1.x * eq0.z) /
37-
(eq0.x * eq1.y - eq1.x * eq0.y)
38-
Vec2D.new(ox, oy)
36+
def by
37+
Matrix[
38+
[points[0].x * points[0].x + points[0].y * points[0].y, points[0].x, 1],
39+
[points[1].x * points[1].x + points[1].y * points[1].y, points[1].x, 1],
40+
[points[2].x * points[2].x + points[2].y * points[2].y, points[2].x, 1]
41+
].determinant
3942
end
4043
end
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# The Particle System
2+
3+
class ParticleSystem
4+
include Processing::Proxy
5+
6+
attr_reader :particles, :particle_shape
7+
8+
def initialize(width, height, sprite, n)
9+
@particles = []
10+
# The PShape is a group
11+
@particle_shape = create_shape(GROUP)
12+
13+
# Make all the Particles
14+
n.times do |i|
15+
particles << Particle.new(width, height, sprite)
16+
# Each particle's PShape gets added to the System PShape
17+
particle_shape.add_child(particles[i].s_shape)
18+
end
19+
end
20+
21+
def update
22+
particles.each do |p|
23+
p.update
24+
end
25+
end
26+
27+
def set_emitter(x, y)
28+
particles.each do |p|
29+
# Each particle gets reborn at the emitter location
30+
p.rebirth(x, y) if (p.dead?)
31+
end
32+
end
33+
34+
def display
35+
shape(particle_shape)
36+
end
37+
end
38+
39+
# An individual Particle
40+
41+
class Particle
42+
include Processing::Proxy
43+
44+
GRAVITY = Vec2D.new(0, 0.1)
45+
46+
attr_reader :center, :velocity, :lifespan, :s_shape, :part_size,
47+
:boundary_x, :boundary_y, :sprite
48+
49+
def initialize width, height, sprite
50+
@sprite = sprite
51+
@boundary_x = Boundary.new(0, width)
52+
@boundary_y = Boundary.new(0, height)
53+
part_size = rand(10..60)
54+
# The particle is a textured quad
55+
@s_shape = create_shape
56+
s_shape.begin_shape(QUAD)
57+
s_shape.no_stroke
58+
s_shape.texture(sprite)
59+
s_shape.normal(0, 0, 1)
60+
s_shape.vertex(-part_size / 2.0, -part_size / 2.0, 0, 0)
61+
s_shape.vertex(+part_size / 2.0, -part_size / 2.0, sprite.width, 0)
62+
s_shape.vertex(+part_size / 2.0, +part_size / 2.0, sprite.width, sprite.height)
63+
s_shape.vertex(-part_size / 2.0, +part_size / 2.0, 0, sprite.height)
64+
s_shape.end_shape
65+
# Initialize center vector
66+
@center = Vec2D.new
67+
# Set the particle starting location
68+
rebirth(width / 2.0, height / 2.0)
69+
end
70+
71+
def rebirth(x, y)
72+
theta = rand(-PI .. PI)
73+
speed = rand(0.5 .. 4)
74+
# A velocity with random angle and magnitude
75+
@velocity = Vec2D.from_angle(theta)
76+
@velocity *= speed
77+
# Set lifespan
78+
@lifespan = 255
79+
# Set location using translate
80+
s_shape.reset_matrix
81+
s_shape.translate(x, y)
82+
# Update center vector
83+
center.x, center.y = x, y
84+
end
85+
86+
# Is it off the screen, or its lifespan is over?
87+
def dead?
88+
return true if lifespan < 0
89+
return true if boundary_y.exclude? center.y
90+
return true if boundary_x.exclude? center.x
91+
false
92+
end
93+
94+
def update
95+
# Decrease life
96+
@lifespan = lifespan - 1
97+
# Apply gravity
98+
@velocity += GRAVITY
99+
s_shape.set_tint(color(255, lifespan))
100+
# Move the particle according to its velocity
101+
s_shape.translate(velocity.x, velocity.y)
102+
# and also update the center location
103+
@center += velocity
104+
end
105+
end
106+
107+
# unusually in this case we are looking for excluded values
108+
109+
Boundary = Struct.new(:lower, :upper) do
110+
def exclude? val
111+
true unless (lower...upper).cover? val
112+
end
113+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
require 'forwardable'
3+
MAX_POINT = 3
4+
# A collection of a maximum of 3 points in the processing world
5+
# includes a collinearity test using Vec2D
6+
class TPoints
7+
extend Forwardable
8+
def_delegators(:@points, :each, :map, :size, :shift, :clear, :[])
9+
include Enumerable
10+
11+
attr_reader :points
12+
13+
def initialize
14+
@points = []
15+
end
16+
17+
def <<(pt)
18+
points << pt
19+
shift if size > MAX_POINT
20+
end
21+
22+
def collinear?
23+
full? ? (vec[0] - vec[1]).cross(vec[1] - vec[2]).zero? : false
24+
end
25+
26+
def vec
27+
points.map { |point| point.pos }
28+
end
29+
30+
def full?
31+
points.length == MAX_POINT
32+
end
33+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
# particle and triangle point
3+
class TPoint
4+
include Processing::Proxy
5+
attr_reader :pos, :vel, :accel, :xbound, :ybound
6+
7+
def initialize(position)
8+
@pos = position
9+
@vel = Vec2D.new
10+
@accel = Vec2D.random
11+
@xbound = Boundary.new(0, width)
12+
@ybound = Boundary.new(0, height)
13+
end
14+
15+
def direction(angle)
16+
# direction of the acceleration is defined by the new angle
17+
@accel = Vec2D.from_angle(angle)
18+
# magnitude of the acceleration is proportional to the angle between
19+
# acceleration and velocity
20+
dif = accel.angle_between(vel)
21+
dif = map1d(dif, 0..PI, 0.1..0.001)
22+
@accel *= dif
23+
end
24+
25+
def update
26+
@vel += accel
27+
@vel.set_mag(1.5) { vel.mag > 1.5 }
28+
@pos += vel
29+
check_bounds
30+
end
31+
32+
private
33+
34+
def check_bounds
35+
@vel.x *= -1 if xbound.exclude? pos.x
36+
@vel.y *= -1 if ybound.exclude? pos.y
37+
end
38+
end
39+
40+
# we are looking for excluded values
41+
Boundary = Struct.new(:lower, :upper) do
42+
def exclude?(val)
43+
true unless (lower...upper).cover? val
44+
end
45+
end

0 commit comments

Comments
 (0)