A toy project to play with canvas contexts, collision detection, and virtual duct tape :)
This repo has 5 progressive toy problems to solve to build an interactive duct-tape app on the HTML5 canvas. We've never run through this in it's entirety before, so we have no idea how long it's going to take people- our goal was to make a play space interesting and big enough that no one would get bored. If all the pieces come together you'll have a small functional canvas app that allows you to remove strips of duct tape from the covered image, but only when those strips aren't covered by any other strips.
It's strongly recommended you at least scan through the docs provided here before you YOLO into the code, but if you can't be bothered the challenge descriptions are in the last section of this manual.
You're welcome to use google, stackoverflow, and any resources that'll help you.
Feel free to divide and conquer the challenges to cover as many as you can or go heads down and do a solo deep-dive on just the parts that are most interesting to you- it's all good.
- Fork this repo and then check it out to a local directory
- From within that local directory, run
python -m SimpleHTTPServer 8000
This should start a simple http server on port 8000.
3. Navigate to:
http://localhost:8000/canvas.html
And you should see the Man Crates 2015 team photo covered by colorful strips of duct tape. Pop open your dev console and click around- you'll see a lot of scaffolding work is already done for you.
This is a small app with a few objects to manage drawing strips of duct tape, detecting how the strips interact with one another and how the user interacts with them.
First look at canvas.html and you'll see the small javascript block that initializes the
Wrapper object as soon as our texture image is loaded. We don't use the texture image yet,
but this pattern will save you a bunch of heartache if you choose to try problem B
below :)
The Wrapper object is our highest level class, storing the array of strips
we have in
the world, a board
object that abstracts away some of the low-level canvas interactions,
and methods to evaluate user interactions. The C
, D
, and E
challenges live in this
class but won't function until we address challenge A
.
There's some low level mathy stuff and most of our code to pre-populate the board with
strips in here. The only thing you'll need to use is the incredibly handy
getLineIntersection
method. More on that later.
This is a low level class to represent a strip and its properties, and it's where the
contains(coord)
method to detect coordinate collision detection and the
overlaps(otherStrip)
methods for overlap detection lives. If you're ready to dive in
you can skip the rest of the background reading and go straight into challenge A
to
fill in the overlaps(otherStrip)
method.
This is the singleton class responsible for handling all the actual draw commands on the canvas. If you've never worked with HTML5 canvases before there are some good patterns in here, the first is that you almost always want to keep your weird canvas code in a class all by itself :)
If you object to doing a little geometry and want to skip challenge A
, you could
probably spend the entire duration of the hackathon dressing up the drawStrip
method
described in challenge B
.
If you object to playing with low-level canvas drawing code, you can skip challenge B
and spend all day working on collision detection and interaction. :)
There are several ways to approach click detection working with HTML5 canvases, this repo started out as a Man Crates hack day project and we wanted to roll our own. There are some significant performance benefits to the approach we took, but it's tailored specifically to this problem and is pretty mathy.
Rather than be represented by a coordinate set in space, strips are represented by
a single line equation in the coordinate space. See the object initializer in
strip.js
.
From this single line equation we can derive everything we need and algebraically
see if a click at a given coordinate falls within the strip's boundaries. See the
Strip.prototype.contains(coord)
method in strip.js
We want our clicks to interact with the highest strip, and because we're using the sort
order of the Wrapper.strips
array to store our strip depth order we can easily iterate
from last to first to see which strip our click hits first. See getClickedStripIndex(coord)
in Wrapper.js
We can even use this same algebraic advantage to check to see if strips overlap by
solving the lines against each other to see if any of the 4 possible intersections
exist within our board space. The algebra work is already done for you in the
getLineIntersection(lineA, lineB, topLeft, bottomRight)
method in util.js
#Coding Challenges
We're going to complete the Strip.prototype.overlaps
method so we have a tool to see
if one strip overlaps another strip within the bounds of our canvas. If two strips are on
a collision course and will eventually overlap somewhere else in space, we don't count
that as an overlap.
This method should evaluate the geometry of the strip's own lines and the lines of another given strip to see if they intersect.
The getLineIntersection
method in util.js
is going to do your algebra homework for you.
Refer to the diagrams in the background info above for a refresher on lines and how to deal with strips as lines.
When you're done, take a screenshot to send to your 9th grade geometry teacher. They'll be really proud of you.
If you're completely stuck and tired of math, you can skip to challenge B
and spend all
your time working with canvas image manipulation. This is a totally valid choice and
you could easily spend a day refining the look & feel of the strips in challenge B. You'll
need to get the answer to A before you can complete challenges C
, D
, or E
.
This is an optional tangent to go deep into canvas image manipulation. If that sounds awful feel free to skip this one and keep jamming on the JS & interactivity problems in the other challenges.
If you do decide to go down this rabbit hole, you'll rewrite the
StripCanvas.prototype.drawStrip
method to use the strip.textureSrc
property to pull
in a duct tape texture and combine it with the strip.color
property to render out a
fancy version of the duct tape.
Unlike the other challenges, gauging success on this one is primarily up to you. Here's a screenshot of what our canvas looked like with a fancier drawing routine, but take this in any direction you'd like.
A recommended approach for this is:
- Do some
hello world
type image creation on the canvas - Read up on canvas context translation
- Figure out how to turn our
strip
geometry properties into useful translations - Experiment with multiple draws to combine texture and color, mess with texture offsets, etc.
Make sure to reset or track changes to your canvas context if you modify it.
##C. Devise a Strategy to See if Strips Overlap Now that we have a method to detect if one strip overlaps with another, we have everything we need to write a method in our high level object to see if the strip we're interacting with is covered by another strip- this is really useful if we want to make it so the user can only remove strips that aren't covered by other strips.
Using a simlar depth-sorted routine as Wrapper.getClickedStripIndex
to your advantage,
complete the Wrapper.getOverlappingStrips
method to see if the strip clicked is covered
by any others.
We really want to get back the full array of indices for overlapping strips, but a true / false method is a good start.
- a method that returns true or false will work for todo D
- you'll need the full set of overlapping strips for todo E
##D. Make the Board Interactive
This challenge requires successful completion of A
and C
.
Once we've completed our low-level methods, we're ready to make this an interactive app.
Combine the methods you've made above with the Wrapper.redraw
to make it so when the
user clicks a strip that isn't covered by any others, remove it and redraw the board.
##E. Provide the User with Validation Feedback
This challenge requires successful completion of A
, C
and D
, and a revisit to the
StripCanvas.prototype.drawStrip
method in B
.
Combine all the pieces of the solution so that when a user clicks a strip that's covered by another strip, you helpfully redraw the overlapping strips with a color indicating they're obstructing the first strip. Red is usually a good color for that kind of thing.
#Thanks for Joining Us Today!
We're so happy you decided to spend this time with us and hope to see you soon!
Cheers!
-Team Man Crates