Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 2 commits
  • 8 files changed
  • 0 comments
  • 1 contributor
5 source/css/style.css
@@ -217,6 +217,11 @@ header {
217 217 list-style: none;
218 218 }
219 219
  220 +#controls li {
  221 + margin: 4px 0 0 0;
  222 + font-size: 12px;
  223 +}
  224 +
220 225 #main {
221 226 margin: 10px auto;
222 227 width: 90%;
20 source/js/mylibs/Canvas.js.coffee
@@ -16,19 +16,28 @@ Canvas = (() ->
16 16 @ctxt = canvasEl.getContext(@mode)
17 17 @width = canvasEl.width
18 18 @height = canvasEl.height
  19 + @worldView = true # Default origin top left
19 20
20 21 context = () -> @ctxt
21 22
22 23 # Set the origin of the canvas
23 24 # @param {String} id (topleft | bottomleft)
24 25 setOrigin = (id) ->
25   - if id == 'topleft'
  26 + if @worldView && (id == 'bottomleft')
26 27 # translate world for origin at bottom left
  28 + @ctxt.translate(0, @height)
27 29 @ctxt.scale(1, -1)
28   - @ctxt.translate(0, -@height)
29   - else
  30 + @worldView = false
  31 + else if !@worldView
30 32 # bottom left is default
31   -
  33 + @ctxt.scale(1, -1)
  34 + @ctxt.translate(0, -@height)
  35 + @worldView = true
  36 +
  37 + # @return true if drawing with world view coordinates (default = true)
  38 + inWorldView = () ->
  39 + @worldView
  40 +
32 41 # Draw a circle on the canvas
33 42 # @param {Circle} c a Circle object
34 43 drawCircle = (c) ->
@@ -88,7 +97,7 @@ Canvas = (() ->
88 97 # Perform actions in a canvas context
89 98 inContext = (fn) ->
90 99 @ctxt.save()
91   - fn()
  100 + fn(@ctxt) # pass context back to callback as argument
92 101 @ctxt.restore()
93 102
94 103 # return the constructor
@@ -100,6 +109,7 @@ Canvas = (() ->
100 109 drawLine: drawLine
101 110 drawEllipse: drawEllipse
102 111 inContext: inContext
  112 + inWorldView: inWorldView
103 113 context: context
104 114 rotate: rotate
105 115 translate: translate
2  source/js/mylibs/Line.js.coffee
@@ -8,7 +8,7 @@ class Line extends Shape
8 8 super(sx, sy, 0, opts)
9 9 @vec = $V([ex, ey, 0])
10 10 @name = 'Line'
11   -
  11 +
12 12 toString: ->
13 13 "Line: " + super + " vector: " + @vec.inspect()
14 14
14 source/js/mylibs/Shape.js.coffee
@@ -18,9 +18,23 @@ class Shape
18 18 @angSpeed ?= 0
19 19 @rotDirection ?= 1
20 20
  21 + # move object position
  22 + # @param {Vector} vec vector of movement
  23 + # @return {Vector} new position
21 24 move: (vec) ->
22 25 @pos = @pos.add(vec)
23 26
  27 + # move object position using ahead by some timestep
  28 + # @param {Number} t timestep
  29 + moveByTime: (t) ->
  30 + @pos = @locationAfter(t)
  31 +
  32 + # determine shape's location after some time
  33 + # @param {Number} t timestep
  34 + # @return {Vector} new position
  35 + locationAfter: (t) ->
  36 + @pos.add(@velocity.x(t))
  37 +
24 38 x: -> @pos.e(1)
25 39 y: -> @pos.e(2)
26 40 z: -> @pos.e(3)
85 source/js/mylibs/collisions.js.coffee
@@ -2,12 +2,7 @@
2 2
3 3 #= require ../libs/sylvester
4 4
5   -collisions = {}
6   -collisions = (->
7   - # constants
8   - collisions.NONE = -1
9   - collisions.EMBEDDED = -2
10   -
  5 +collisions = (->
11 6 # intersection
12 7 # Finds the point where 2 lines AB and CD intersect
13 8 # @returns {Number} time
@@ -107,17 +102,22 @@ collisions = (->
107 102 t = intersectionTime(c.pos.add(r), c.displacement, wall.pos, wall.vec)
108 103 [t, collisionNormal]
109 104
110   - # Check for collision between rotating line and circle
111   - # Anglular values should be in radians
  105 + # Detect collision between rotating line and circle
  106 + # Anglular values must be in radians
112 107 # @param {Number} theta0 angle of starting position of line from the vertical
113 108 # @param {Number} omega angular velocity of rotating line
114 109 # @param {Number} l line length
115 110 # @param {Number} r ball radius
116 111 # @param {Number} d distance of line origin to ball center
117 112 # @param {Number} alpha angle of vertical and line to ball center
  113 + # @return {Number} NONE or EMBEDDED or time to collision (if < 1)
  114 + # TODO: Support for cases where the rotating line is offset from its rotating point.
  115 + # requires mystery value x for final equation:
  116 + # (alpha - theta0 - k * Math.asin((r - x) / d)) / omega
118 117 angularCollisionLineCircle = (theta0, omega, l, r, d, alpha) ->
119 118 return collisison.NONE if d > l + r
120 119 return collisions.EMBEDDED if d < r
  120 + # k = 1 for clockwise motion, -1 otherwise
121 121 k = 1
122 122 # move into a calculation within the range of [0,2pi]
123 123 alpha -= theta0
@@ -132,7 +132,8 @@ collisions = (->
132 132 while alpha > pi2
133 133 alpha -= pi2
134 134
135   - # check if there is a possible collision
  135 + # check if there is a possible collision.
  136 + # this means any collision in t > 1 will not be considered.
136 137 return collisions.NONE if alpha > omega
137 138
138 139 # now perform the appropriate collision check
@@ -141,6 +142,67 @@ collisions = (->
141 142 else
142 143 (alpha - k * Math.acos((l*l + d*d - r*r) / (2*l*d))) / omega
143 144
  145 + # Detect collision between rotating line and moving or stationary circle.
  146 + # This method is quite different than angularCollisionLineCircle() which only
  147 + # deals with stationary circles.
  148 + # From the MPFP CDROM collision simulation Director code.
  149 + # TODO: Support for cases where the rotating line is offset from its rotating point.
  150 + # @param {Line} line shape
  151 + # @param {Circle} circle shape
  152 + # @param {Number} ts current timestep
  153 + # @return {Object}
  154 + # t: 0 if collision detected, collisions.NONE if not
  155 + # moment1: no idea
  156 + # moment2: no idea
  157 + angularCollisionLineCircle2 = (line, circle, ts) ->
  158 + # determine start and end positions
  159 + startPos = circle.pos.subtract(line.pos)
  160 + endPos = circle.locationAfter(ts).subtract(line.locationAfter(ts))
  161 +
  162 + lineStartAng = Math.degreesToRadians(line.rotation)
  163 + lineAngDisp = line.angularVelocity() * ts
  164 + lineEndAng = lineStartAng + lineAngDisp
  165 +
  166 + n1 = Vector.directionVector(lineStartAng + Math.PI/2)
  167 + n2 = Vector.directionVector(lineEndAng + Math.PI/2)
  168 +
  169 + # point n1 and n2 towards the initial position of the circle
  170 + startDist = n1.dot(startPos)
  171 + if startDist < 0
  172 + n1 = n1.x(-1)
  173 + n2 = n2.x(-2)
  174 + startDist = -startDist
  175 +
  176 + noColl = {t: collisions.NONE}
  177 + r = circle.radius
  178 +
  179 + # debug shit
  180 + console.log "start: #{startPos.inspect()} end: #{endPos.inspect()}"
  181 + console.log "line start ang: #{lineStartAng} lineEndAng: #{lineEndAng}"
  182 + console.log "n1: #{n1.inspect()}"
  183 + console.log "n2: #{n2.inspect()}"
  184 + # NOTE:
  185 + # Equation can't be solved algebraically - an approximation method must be used
  186 + # Save time by checking whether it's possible for the 2 objects to collide at all
  187 +
  188 + # 1. Check if circle intersects the complete circle swept out by the line
  189 + unless (startDist - r) * (n2.dot(endPos) - r) < 0
  190 + return noColl
  191 +
  192 + # 2. Check if angle swept out by the circle during the time interval
  193 + # overlaps with the angle swept out by the line.
  194 + if ((startPos.subtract(n1.x(r)).mag() <= line.length) ||
  195 + (endPos.subtract(n2.x(r)).mag() <= line.length))
  196 + return {t: 0,
  197 + normal: n1.x(-1),
  198 + moment1: $V([0, 0, 0]),
  199 + moment2: startPos.subtract(n1.x(r))
  200 + }
  201 +
  202 + # otherwise, check for intersection with line endpoints
  203 + # TODO:
  204 + noColl
  205 +
144 206 isImpendingCollision = (ts) ->
145 207 0 < ts <= 1
146 208
@@ -357,9 +419,14 @@ collisions = (->
357 419 resolveCollision,
358 420 resolveInelasticCollisionFixed,
359 421 angularCollisionLineCircle,
  422 + angularCollisionLineCircle2,
360 423 pointInTriangle,
361 424 pointInPolygon}
362 425 )()
363 426
  427 +# constants
  428 +collisions.NONE = -1
  429 +collisions.EMBEDDED = -2
  430 +
364 431 root = exports ? window
365 432 root.collisions = collisions
12 source/js/mylibs/vec.js.coffee
@@ -15,12 +15,14 @@ Vector.isClockwise = (v, p) ->
15 15 n = p.clockwiseNormal()
16 16 v.component(n) > 0
17 17
18   -# unitVectorFromAngle
  18 +# directionVector
19 19 #
20   -# general-purpose function to keep clockwiseNormal() consistent
21   -# @returns {Vector}
22   -Vector.unitVectorFromAngle = (angle) ->
23   - $V(Math.sin(angle), Math.cos(angle))
  20 +# @param {Number} angle angle in radians or degrees
  21 +# @param {String} unit d|r degrees or radians
  22 +# @return a 3D vector pointing at ang clockwise from the positive x-axis
  23 +Vector.directionVector = (angle, unit='r') ->
  24 + angle *= (Math.PI / 180) if unit == 'd'
  25 + $V([Math.cos(angle), Math.sin(angle), 0])
24 26
25 27 # Instance functions
26 28
57 source/js/rot_collisions.js.coffee
@@ -12,7 +12,7 @@
12 12
13 13 $(document).ready ->
14 14 canvas = new Canvas($("#maincanvas").get(0))
15   - #canvas.setOrigin('topleft')
  15 + canvas.setOrigin('bottomleft')
16 16 elapsed = lastTime = startingAngle = startingTheta = 0
17 17
18 18 angle = 0
@@ -21,6 +21,7 @@ $(document).ready ->
21 21 ball = null
22 22 objects = []
23 23 paused = true
  24 + ballMoving = false
24 25 angVel = ballDistance = ballAngle = collisionIn = 0
25 26
26 27 drawInfo = (text) ->
@@ -30,27 +31,28 @@ $(document).ready ->
30 31 $('#info').html('')
31 32
32 33 drawText = (text) ->
33   - canvas.inContext ->
34   - ctxt = canvas.context()
  34 + canvas.inContext((ctxt) ->
  35 + # when origin is cartesian, we must restore to world view to draw text
  36 + if !canvas.inWorldView()
  37 + ctxt.scale(1, -1);
  38 + ctxt.translate(0, -canvas.height);
35 39 ctxt.fillStyle = 'black'
36 40 ctxt.font = '12px Arial sans-serif'
37 41 ctxt.textBaseline = 'top'
38   - ctxt.fillText(text, canvas.width/2, 10)
  42 + ctxt.fillText(text, canvas.width/2, 20)
  43 + )
39 44
40 45 drawScene = () ->
41 46 canvas.clear()
42 47
43   - if collisions.isImpendingCollision(collisionIn)
44   - drawText "Collision in #{collisionIn}"
45   - else
46   - drawText "Collision time > 1"
  48 + drawText "Collision in #{collisionIn}"
47 49
48 50 for obj in objects
49 51 switch obj.name
50 52 when 'Line'
51 53 canvas.inContext ->
52 54 canvas.translate obj.pos.e(1), obj.pos.e(2)
53   - canvas.rotate(Math.degreesToRadians(-obj.rotation))
  55 + canvas.rotate(Math.degreesToRadians(obj.rotation))
54 56 canvas.translate(-obj.pos.e(1), -obj.pos.e(2))
55 57 canvas.drawLine(obj)
56 58
@@ -59,22 +61,26 @@ $(document).ready ->
59 61 canvas.drawCircle obj
60 62
61 63 setupScene = ->
62   - startingAngle = $("input[name=sAngle]").val() || 20
63   - angularVel = $("input[name=angVel]").val() || 15
  64 + startingAngle = $("input[name=sAngle]").val() || 0
  65 + angularVel = $("input[name=angVel]").val() || 0
  66 + $('#sangle').html("#{startingAngle} deg.")
64 67 startingTheta = 90 - startingAngle
65 68
66 69 # draw rotating line
67 70 angle = lastAngle = startingAngle
68 71 line = new Line(canvas.width/2, canvas.height/2, lineLength, 0, {
69 72 color: 'black',
70   - rotation: startingAngle
  73 + rotation: startingAngle,
  74 + length: lineLength
71 75 })
72 76 # draw stationary circle
73 77 ball = new Circle(canvas.width/2+lineLength/1.2, canvas.height/2, 0, {
74 78 radius: lineLength/8,
75 79 color: 'blue'
76 80 })
77   -
  81 + if ballMoving = $("input[name=movingBall]:checked").val() == "1"
  82 + ball.velocity = $V([-5, 10, 0])
  83 +
78 84 # these two variable can be computed dynamically if the ball is moving
79 85 ballDistance = ball.pos.subtract(line.pos).mag()
80 86 ballAngle = Math.degreesToRadians 90
@@ -94,23 +100,32 @@ $(document).ready ->
94 100
95 101 updateObjects = (t) ->
96 102 # update the line's rotation
97   - rot = line.rotation - line.angularVelocity() * (t / 1000)
  103 + rot = line.rotation - line.angularVelocity() * t
98 104 if Math.abs(rot) >= 360
99 105 rot = 0
100 106 line.rotation = rot
  107 + ball.moveByTime(t)
101 108
102 109 checkCollisions = (t) ->
103   - collisionIn = collisions.angularCollisionLineCircle(Math.degreesToRadians(90-line.rotation),
104   - angVel, lineLength, ball.radius, ballDistance, ballAngle)
  110 + # Use different detection methods depending on ball movement
  111 + if ballMoving
  112 + res = collisions.angularCollisionLineCircle2 line, ball, t
  113 + collisionIn = res.t
  114 + paused = collisionIn == 0
  115 + else
  116 + collisionIn = collisions.angularCollisionLineCircle(Math.degreesToRadians(90-line.rotation),
  117 + angVel, lineLength, ball.radius, ballDistance, ballAngle)
105 118
  119 +
  120 +
106 121 # animate all objects
107 122 update = ->
108 123 # Pass latest timestep to the collision detection function
109 124 timeNow = new Date().getTime()
110 125 if lastTime != 0
111   - elapsed = timeNow - lastTime
  126 + elapsed = (timeNow - lastTime) / 1000
112 127 updateObjects(elapsed)
113   - checkCollisions()
  128 + checkCollisions(elapsed)
114 129
115 130 lastTime = timeNow
116 131
@@ -121,14 +136,12 @@ $(document).ready ->
121 136 update()
122 137
123 138 drawScene(objects, elapsed)
124   -
  139 +
125 140 # event handlers
126 141 $('canvas').click ->
127   - checkCollisions elapsed
128 142 paused = !paused
129 143
130   - $('input#update').click ->
131   - setupScene()
  144 + $('input').change -> setupScene()
132 145
133 146 setupScene()
134 147 tick()
5 source/rotation_cols.html.erb
@@ -8,13 +8,14 @@
8 8 <div id="main" role="main">
9 9 <ul id="controls">
10 10 <li>
11   - Starting angle: <input name="sAngle" default="90" size="3" value="90"/> degrees
  11 + Starting angle: <input type="range" name="sAngle" min="0" max="90" step="1" value="75">
  12 + <span id="sangle"></span>
12 13 </li>
13 14 <li>
14 15 Angular velocity: <input name="angVel" size="3" value="15"/> degrees/second
15 16 </li>
16 17 <li>
17   - <input type="submit" id="update" value="Refresh"/>
  18 + <input type="checkbox" name="movingBall" value="1"> Moving ball
18 19 </li>
19 20 </ul>
20 21 Click on canvas to play or pause

No commit comments for this range

Something went wrong with that request. Please try again.