summaryrefslogtreecommitdiff
path: root/assets/javascripts/rectangles
diff options
context:
space:
mode:
Diffstat (limited to 'assets/javascripts/rectangles')
-rw-r--r--assets/javascripts/rectangles/_env.js23
-rw-r--r--assets/javascripts/rectangles/builder.js32
-rw-r--r--assets/javascripts/rectangles/clipper.js173
-rw-r--r--assets/javascripts/rectangles/colors.js36
-rw-r--r--assets/javascripts/rectangles/debug.js2
-rw-r--r--assets/javascripts/rectangles/draw.js48
-rw-r--r--assets/javascripts/rectangles/rect.js242
-rw-r--r--assets/javascripts/rectangles/sort.js29
-rw-r--r--assets/javascripts/rectangles/tree.js30
-rw-r--r--assets/javascripts/rectangles/vec2.js81
10 files changed, 696 insertions, 0 deletions
diff --git a/assets/javascripts/rectangles/_env.js b/assets/javascripts/rectangles/_env.js
new file mode 100644
index 0000000..e2e8e31
--- /dev/null
+++ b/assets/javascripts/rectangles/_env.js
@@ -0,0 +1,23 @@
+
+var environment = new function(){}
+environment.init = function(){
+ scene.camera.move({
+ "x": 240,
+ "y": -1000,
+ "z": 240,
+ "rotationX": -PI/2,
+ "rotationY": 0 // PI
+ })
+ map && map.zoom(3.00) && map.recenter()
+
+ clipper.rects.push( new rect(100,100, 200,300) )
+ clipper.rects.push( new rect(200,300, 300,500) )
+
+ clipper.rects.push( new rect(300,100, 600,300) )
+ clipper.rects.push( new rect(400,200, 700,400) )
+
+ clipper.init()
+ scene.update()
+}
+environment.update = function(t){
+}
diff --git a/assets/javascripts/rectangles/builder.js b/assets/javascripts/rectangles/builder.js
new file mode 100644
index 0000000..ef0a28c
--- /dev/null
+++ b/assets/javascripts/rectangles/builder.js
@@ -0,0 +1,32 @@
+var builder = new function(){
+ var base = this
+ base.tube = new Tube ()
+
+ var els = []
+
+ base.tube.on("clipper:update", rebuild)
+
+ function rebuild(){
+ clear()
+ build()
+ }
+ function build (){
+ clipper.regions.forEach(function(r){
+ var walls = r.walls()
+ walls.forEach(function(el){
+ els.push(el)
+ scene.add(el)
+ })
+ })
+ }
+ function clear (){
+ console.log(els.length)
+ els.forEach(function(el){
+ scene.remove(el)
+ })
+ els = []
+ }
+
+}
+
+
diff --git a/assets/javascripts/rectangles/clipper.js b/assets/javascripts/rectangles/clipper.js
new file mode 100644
index 0000000..bd0c521
--- /dev/null
+++ b/assets/javascripts/rectangles/clipper.js
@@ -0,0 +1,173 @@
+window.ctx = window.w = window.h = null;
+
+var clipper = new function(){
+ var base = this
+ var canvas = document.createElement("canvas")
+ var ctx = window.ctx = canvas.getContext("2d")
+ var w = window.w = canvas.width = 500
+ var h = window.h = canvas.height = 500
+ var regions = []
+ document.querySelector("#map").appendChild(canvas)
+
+ base.init = function (){
+ bind()
+ animate()
+ }
+ function animate(){
+ requestAnimationFrame(animate)
+ clear_canvas()
+
+ if (modified) {
+ solve_rects()
+ builder.tube("clipper:update")
+ }
+ draw_ruler()
+ draw_regions(base.regions)
+ draw_mouse(mouse)
+ z = false
+ }
+
+ var rects = base.rects = []
+
+ this.creating = false
+ this.dragging = false
+
+ var modified = true
+ var mouse = new rect(0,0,0,0)
+
+ function bind(){
+ canvas.addEventListener("mousedown", function(e){
+ var x = e.pageX, y = e.pageY
+ mouse = new rect (x,y)
+ if (e.shiftKey) {
+ mouse.quantize(10)
+ }
+
+ var intersects = rects.filter(function(r){ return r.focused = r.contains(x,y) })
+ // console.log(intersects)
+
+ if (intersects.length){
+ clipper.dragging = intersects[0]
+ }
+ else {
+ clipper.creating = true
+ }
+ if (e.shiftKey && clipper.dragging) {
+ clipper.dragging.quantize(10)
+ }
+ })
+ canvas.addEventListener("mousemove", function(e){
+ var x, y
+ if (e.shiftKey) {
+ x = quantize( e.pageX, 10 )
+ y = quantize( e.pageY, 10 )
+ }
+ else {
+ x = e.pageX
+ y = e.pageY
+ }
+
+ mouse.x.b = x
+ mouse.y.b = y
+
+ if (clipper.dragging) {
+ clipper.dragging.translation.a = mouse.x.magnitude()
+ clipper.dragging.translation.b = mouse.y.magnitude()
+ }
+ else if (clipper.creating) {
+ mouse.x.b = x
+ mouse.y.b = y
+ }
+ else {
+ mouse.x.a = mouse.x.b
+ mouse.y.a = mouse.y.b
+ }
+ })
+ document.addEventListener("mouseup", function(e){
+ if (clipper.creating) {
+ if (mouse.height() != 0 && mouse.width() != 0) {
+ rects.push(mouse.translate())
+ }
+ }
+ if (clipper.dragging) {
+ clipper.dragging.translate()
+ }
+ mouse = new rect(e.pageX, e.pageY)
+ clipper.creating = clipper.dragging = false
+ modified = true
+ })
+ }
+
+ function solve_rects(){
+ var rects = sort_rects_by_position(base.rects)
+
+ for (var i = 0; i < rects.length; i++) {
+ rects[i].id = i
+ rects[i].reset()
+ }
+ var regions = []
+
+ var left, right
+ for (var i = 0; i < rects.length; i++) {
+ left = rects[i]
+ for (var j = i+1; j < rects.length; j++) {
+ right = rects[j]
+ if (left.intersects(right)) {
+ left.clipTo(right)
+ right.clipTo(left)
+ }
+ if (left.x.b < right.x.a) {
+ break
+ }
+ }
+ }
+ for (var i = 0; i < rects.length; i++) {
+ regions = regions.concat(rects[i].regions)
+ }
+
+ regions = sort_rects_by_area( regions.filter(function(r){ return !!r }) )
+
+ var ty = new tree (regions[0].y.a, [regions[0]])
+ var tx = new tree (regions[0].x.a, ty)
+ var ttx, tty
+
+ for (var i = 1; i < regions.length; i++) {
+ ttx = tx.add (regions[i].x.a, null)
+ if (ttx.data) {
+ tty = ttx.data.add (regions[i].y.a, null)
+ // duplicate polygon?
+ if (tty.data) {
+ tty.data.forEach(function(yy, ii){
+ if (yy.intersects(regions[i])) {
+ if (yy.area() > regions[i].area()) {
+ regions[i].dupe = true
+ }
+ else {
+ yy.dupe = true
+ tty.data[ii] = regions[i]
+ }
+ }
+ })
+ }
+ else {
+ tty.data = [regions[i]]
+ }
+ }
+ else {
+ ttx.data = new tree (regions[i].y.a, [regions[i]])
+ }
+ }
+
+ base.regions = sort_rects_by_position(regions)
+
+ modified = false
+ // document.getElementById("intersects").innerHTML = sort_rects_by_position(regions).join("<br>")
+ }
+
+ // generate floor and ceiling for some regions
+ // generate walls from surviving regions
+ // generate ceiling-walls where ceiling has discontinuity
+
+ return base
+}
+
diff --git a/assets/javascripts/rectangles/colors.js b/assets/javascripts/rectangles/colors.js
new file mode 100644
index 0000000..3bdebbc
--- /dev/null
+++ b/assets/javascripts/rectangles/colors.js
@@ -0,0 +1,36 @@
+(function(){
+ var color_palettes = {
+ alpha: [
+ "rgba(0,0,0,0.1)",
+ ],
+ redblue: [
+ "rgba(0,0,0,0.2)",
+ "rgba(255,0,0,0.2)",
+ "rgba(0,0,255,0.2)",
+ "rgba(0,255,0,0.2)",
+ ],
+ gray: [
+ "rgba(0,0,0,0.1)",
+ "rgba(0,0,0,0.2)",
+ "rgba(0,0,0,0.3)",
+ "rgba(0,0,0,0.4)",
+ ],
+ colors: [
+ "rgba(255,0,0,0.5)",
+ "rgba(255,128,0,0.5)",
+ "rgba(128,255,0,0.5)",
+ "rgba(0,255,0,0.5)",
+ "rgba(0,255,128,0.5)",
+ "rgba(0,128,255,0.5)",
+ "rgba(0,0,255,0.5)",
+ "rgba(128,0,255,0.5)",
+ "rgba(255,0,255,0.5)",
+ "rgba(255,0,128,0.5)",
+ ]
+ }
+
+ var select = document.querySelector("#palette")
+ select.addEventListener("change", function(){ colors = color_palettes[select.value] })
+
+ window.colors = color_palettes[select.value]
+})()
diff --git a/assets/javascripts/rectangles/debug.js b/assets/javascripts/rectangles/debug.js
new file mode 100644
index 0000000..437abb8
--- /dev/null
+++ b/assets/javascripts/rectangles/debug.js
@@ -0,0 +1,2 @@
+window.z = true;
+document.body.addEventListener("mousedown", function(){ z = true })
diff --git a/assets/javascripts/rectangles/draw.js b/assets/javascripts/rectangles/draw.js
new file mode 100644
index 0000000..560c281
--- /dev/null
+++ b/assets/javascripts/rectangles/draw.js
@@ -0,0 +1,48 @@
+function clear_canvas(){
+ ctx.fillStyle = "#fff"
+ ctx.fillRect(0,0,w,h)
+}
+function draw_ruler(){
+ ctx.strokeStyle = "rgba(80,80,80,0.5)"
+ ctx.lineWidth = 1
+ var len = 5
+ for (var i = 0.5; i < w; i += 10) {
+ line(i, 0, i, len)
+ line(0, i, len, i)
+ }
+}
+function line (x,y,a,b,translation){
+ if (translation) {
+ x += translation.a
+ a += translation.a
+ y += translation.b
+ b += translation.b
+ }
+ ctx.beginPath()
+ ctx.moveTo(x,y)
+ ctx.lineTo(a,b)
+ ctx.stroke()
+}
+function draw_regions(regions){
+ for (var i = 0; i < regions.length; i++) {
+ if (regions[i].dupe) continue
+ ctx.fillStyle = colors[i % colors.length]
+ regions[i].fill().stroke_sides()
+ }
+}
+function draw_mouse(mouse){
+ ctx.fillStyle = "rgba(255,0,0,0.4)";
+ ctx.beginPath();
+ ctx.arc(mouse.x.b, mouse.y.b, 5, 0, 2*Math.PI, false);
+ ctx.fill();
+
+ if (mouse.width() != 0 && mouse.height() != 0) {
+ if (clipper.dragging) {
+ mouse.stroke()
+ }
+ else {
+ ctx.fillStyle = "rgba(255,255,0,0.5)"
+ mouse.clone().translate().fill()
+ }
+ }
+}
diff --git a/assets/javascripts/rectangles/rect.js b/assets/javascripts/rectangles/rect.js
new file mode 100644
index 0000000..a539b74
--- /dev/null
+++ b/assets/javascripts/rectangles/rect.js
@@ -0,0 +1,242 @@
+window.rect = (function(){
+ var FRONT = 0x1, BACK = 0x2, LEFT = 0x4, RIGHT = 0x8
+
+ var rect = function (x0,y0,x1,y1){
+ if (x0 instanceof vec2) {
+ this.x = x0
+ this.y = y0
+ }
+ else if (x1 === undefined) {
+ this.x = new vec2(x0,x0)
+ this.y = new vec2(y0,y0)
+ }
+ else {
+ this.x = new vec2(x0,x1)
+ this.y = new vec2(y0,y1)
+ }
+ this.translation = new vec2(0,0)
+ this.sides = FRONT | BACK | LEFT | RIGHT
+ this.focused = false
+ this.id = 0
+ }
+ rect.prototype.clone = function(){
+ return new rect( this.x.clone(), this.y.clone() )
+ }
+ rect.prototype.center = function(){
+ return new vec2(this.x.midpoint(), this.y.midpoint())
+ }
+ rect.prototype.area = function(){
+ return this.x.length() * this.y.length()
+ }
+ rect.prototype.translate = function(){
+ this.x.abs().add(this.translation.a)
+ this.y.abs().add(this.translation.b)
+ this.translation.a = this.translation.b = 0
+ return this
+ }
+ rect.prototype.contains = function(x,y){
+ return this.x.contains(x) && this.y.contains(y)
+ }
+ rect.prototype.intersects = function(r){
+ return this.x.intersects(r.x) && this.y.intersects(r.y)
+ }
+ rect.prototype.width = function(){ return this.x.length() }
+ rect.prototype.height = function(){ return this.y.length() }
+ rect.prototype.fill = function(){
+ // if (! this.sides) return this
+ ctx.fillRect(this.x.a + this.translation.a, this.y.a + this.translation.b, this.x.length(), this.y.length())
+ return this
+ }
+ rect.prototype.stroke_sides = function(){
+ if (this.sides & FRONT) line(this.x.a, this.y.a, this.x.b, this.y.a)
+ if (this.sides & BACK) line(this.x.a, this.y.b, this.x.b, this.y.b)
+ if (this.sides & LEFT) line(this.x.a, this.y.a, this.x.a, this.y.b)
+ if (this.sides & RIGHT) line(this.x.b, this.y.a, this.x.b, this.y.b)
+
+ function line(a,b,c,d){
+ ctx.beginPath()
+ ctx.moveTo(a,b)
+ ctx.lineTo(c,d)
+ ctx.stroke()
+ }
+ return this
+ }
+ rect.prototype.stroke = function(){
+ ctx.beginPath()
+ ctx.moveTo(this.x.a, this.y.a)
+ ctx.lineTo(this.x.b, this.y.b)
+ ctx.stroke()
+ return this
+ }
+ rect.prototype.perimeter = function(){
+ line( this.x.a, this.y.a, this.x.b, this.y.a, this.translation )
+ line( this.x.a, this.y.b, this.x.b, this.y.b, this.translation )
+ line( this.x.a, this.y.a, this.x.a, this.y.b, this.translation )
+ line( this.x.b, this.y.a, this.x.b, this.y.b, this.translation )
+ }
+ rect.prototype.toString = function(){
+ var sides = ""
+ if (this.sides & FRONT) sides += "front "
+ if (this.sides & BACK) sides += "back "
+ if (this.sides & LEFT) sides += "left "
+ if (this.sides & RIGHT) sides += "right "
+ var s = this.id + " " + "[" + this.x.toString() + " " + this.y.toString() + "] " + sides
+ if (this.focused) return "<b>" + s + "</b>"
+ return s
+ }
+ rect.prototype.quantize = function(n){
+ this.x.quantize(n)
+ this.y.quantize(n)
+ }
+ rect.prototype.reset = function(){
+ var copy = this.clone()
+ copy.sides = FRONT | BACK | LEFT | RIGHT
+ copy.focused = this.focused
+ copy.id = this.id
+ this.regions = [ copy ]
+ }
+ rect.prototype.clipTo = function(r){
+ // for each of this rect's regions split the region if necessary
+ var regions = this.regions
+ var splits
+
+ for (var i = 0, len = regions.length; i < len; i++) {
+ if (regions[i] && regions[i].intersects(r)) {
+ splits = regions[i].split(r)
+ regions = regions.concat(splits)
+ regions[i] = null
+ }
+ }
+ this.regions = regions
+ }
+ rect.prototype.split = function(r){
+ var rz = this
+ var splits = []
+ var split_contains = 0
+ var x_intervals = [], y_intervals = []
+ var sides = this.sides
+
+ // Split vertically
+ if (this.x.contains(r.x.a) && r.x.contains(this.x.b)) {
+ x_intervals.push([ new vec2( this.x.a, r.x.a ), LEFT ])
+ x_intervals.push([ new vec2( r.x.a, this.x.b ), RIGHT ])
+ split_contains |= RIGHT
+ }
+
+ else if (r.x.contains(this.x.a) && this.x.contains(r.x.b)) {
+ x_intervals.push([ new vec2( this.x.a, r.x.b ), LEFT ])
+ x_intervals.push([ new vec2( r.x.b, this.x.b ), RIGHT ])
+ split_contains |= LEFT
+ }
+
+ else if (this.x.contains(r.x.a) && this.x.contains(r.x.b)) {
+ x_intervals.push([ new vec2( this.x.a, r.x.a ), LEFT ])
+ x_intervals.push([ new vec2( r.x.a, r.x.b ), 0 ])
+ x_intervals.push([ new vec2( r.x.b, this.x.b ), RIGHT ])
+ split_contains |= LEFT | RIGHT
+ }
+
+ else { // if (r.x.contains(this.x.a) && r.x.contains(r.x.b)) {
+ x_intervals.push([ new vec2( this.x.a, this.x.b ), LEFT | RIGHT ])
+ split_contains |= LEFT | RIGHT
+ }
+
+ // Split horizontally
+ if (this.y.contains(r.y.a) && r.y.contains(this.y.b)) {
+ y_intervals.push([ new vec2( this.y.a, r.y.a ), FRONT ])
+ y_intervals.push([ new vec2( r.y.a, this.y.b ), BACK ])
+ split_contains |= BACK
+ }
+
+ else if (r.y.contains(this.y.a) && this.y.contains(r.y.b)) {
+ y_intervals.push([ new vec2( this.y.a, r.y.b ), FRONT ])
+ y_intervals.push([ new vec2( r.y.b, this.y.b ), BACK ])
+ split_contains |= FRONT
+ }
+
+ else if (this.y.contains(r.y.a) && this.y.contains(r.y.b)) {
+ y_intervals.push([ new vec2( this.y.a, r.y.a ), FRONT ])
+ y_intervals.push([ new vec2( r.y.a, r.y.b ), 0 ])
+ y_intervals.push([ new vec2( r.y.b, this.y.b ), BACK ])
+ split_contains |= FRONT | BACK
+ }
+
+ else { // if (r.y.contains(this.y.a) && this.y.contains(r.y.b)) {
+ y_intervals.push([ new vec2( this.y.a, this.y.b ), FRONT | BACK ])
+ split_contains |= FRONT | BACK
+ }
+
+ x_intervals.forEach(function(x){
+ y_intervals.forEach(function(y){
+ var rn = new rect(x[0], y[0])
+ rn.id = rz.id
+ rn.sides = ((x[1] | y[1]) & sides)
+ if (r.intersects(rn)) {
+ rn.sides = 0
+ }
+ rn.focused = rz.focused
+ splits.push(rn)
+ })
+ })
+ return splits
+ }
+
+ rect.prototype.walls = function(){
+ var list = [], el = null
+
+ var width = this.x.length()
+ var depth = this.y.length()
+ var height = 500
+
+ if (this.sides & FRONT) {
+ el = wall('.face.front')
+ el.width = width
+ el.height = height
+ el.x = this.x.a
+ el.z = this.y.a
+ list.push(el)
+ }
+ if (this.sides & BACK) {
+ var el = wall('.face.back')
+ el.width = width
+ el.height = height
+ el.rotationY = PI
+ el.x = this.x.a
+ el.z = this.y.a + depth
+ list.push(el)
+ }
+
+// if (this.sides & LEFT) {
+// el = wall('.face.left')
+// el.rotationY = -HALF_PI
+// el.height = height
+// el.width = depth
+// el.z = this.y.a - depth/2
+// el.x = this.x.a
+// list.push(el)
+// }
+// if (this.sides & RIGHT) {
+// el = wall('.face.right')
+// el.rotationY = HALF_PI
+// el.height = height
+// el.width = depth
+// el.z = this.y.a - depth/2
+// el.x = this.x.b
+// list.push(el)
+// }
+
+ function wall(klass){
+ var el = new MX.Object3D(klass || ".face")
+ el.width = el.height = el.scaleX = el.scaleY = el.scaleZ = 1
+ el.z = el.y = el.x = 0
+ el.y = height/2
+ el.type = "Face"
+ return el
+ }
+
+ return list
+ }
+
+ return rect
+
+})()
diff --git a/assets/javascripts/rectangles/sort.js b/assets/javascripts/rectangles/sort.js
new file mode 100644
index 0000000..0d79af8
--- /dev/null
+++ b/assets/javascripts/rectangles/sort.js
@@ -0,0 +1,29 @@
+function sort_rects_by_position(list){
+ return list.sort(function(a,b){
+ if (a.x.a < b.x.a) {
+ return -1
+ }
+ if (a.x.a > b.x.a) {
+ return 1
+ }
+ if (a.y.a < b.y.a) {
+ return -1
+ }
+ if (a.y.a > b.y.a) {
+ return 1
+ }
+ return 0
+ })
+}
+
+function sort_rects_by_area(list){
+ return list.map(function(r){ return [r.area(), r] }).sort(function(a,b){
+ if (a[0] < b[0]) {
+ return 1
+ }
+ if (a[0] > b[0]) {
+ return -1
+ }
+ return 0
+ }).map(function(r){ return r[1] })
+}
diff --git a/assets/javascripts/rectangles/tree.js b/assets/javascripts/rectangles/tree.js
new file mode 100644
index 0000000..f5eb117
--- /dev/null
+++ b/assets/javascripts/rectangles/tree.js
@@ -0,0 +1,30 @@
+var tree = function(n, data){
+ this.lo = null
+ this.hi = null
+ this.value = n
+ this.data = data
+}
+tree.prototype.find = function(n){
+ if (n == this.value) return this
+ if (n < this.value) return this.lo ? this.lo.find(n) : this
+ if (n > this.value) return this.hi ? this.hi.find(n) : this
+}
+tree.prototype.add = function(n, data){
+ var closest = this.find(n)
+ if (n == closest.value) return closest
+ if (n < closest.value) return closest.lo = new tree(n, data)
+ if (n > closest.value) return closest.hi = new tree(n, data)
+}
+tree.prototype.toString = function(){
+ var s = "";
+ if (this.lo) s += this.lo.toString()
+ s += this.value + ","
+ if (this.hi) s += this.hi.toString()
+ return s
+}
+tree.prototype.depth = function(){
+ if (this.lo && this.hi) return 1 + max(this.lo.depth(), this.hi.depth())
+ else if (this.lo) return 1 + this.lo.depth()
+ else if (this.hi) return 1 + this.hi.depth()
+ else return 0
+}
diff --git a/assets/javascripts/rectangles/vec2.js b/assets/javascripts/rectangles/vec2.js
new file mode 100644
index 0000000..4e2ad36
--- /dev/null
+++ b/assets/javascripts/rectangles/vec2.js
@@ -0,0 +1,81 @@
+function vec2(a,b){
+ this.a = a
+ this.b = b
+}
+vec2.prototype.magnitude = function(){
+ return this.b-this.a
+}
+vec2.prototype.length = function(){
+ return abs(this.b-this.a)
+}
+vec2.prototype.clone = function(){
+ return new vec2(this.a, this.b)
+}
+vec2.prototype.abs = function(){
+ if (this.b < this.a) {
+ this.a = this.a ^ this.b
+ this.b = this.a ^ this.b
+ this.a = this.a ^ this.b
+ }
+ return this
+}
+vec2.prototype.midpoint = function(){
+ return lerp(0.5, this.a, this.b)
+}
+vec2.prototype.eq = function(v){
+ return this.a == v.a && this.b == v.b
+}
+vec2.prototype.add = function(n){
+ this.a += n
+ this.b += n
+}
+vec2.prototype.sub = function(n){
+ this.a -= n
+ this.b -= n
+}
+vec2.prototype.mul = function(n){
+ this.a *= n
+ this.b *= n
+}
+vec2.prototype.div = function(n){
+ this.a /= n
+ this.b /= n
+}
+vec2.normalize = function(){
+ var dim = max(this.a, this.b)
+ this.a = this.a/dim
+ this.b = this.b/dim
+}
+vec2.prototype.contains = function(n){
+ return this.a <= n && n <= this.b
+}
+vec2.prototype.intersects = function(v){
+ if (this.a < v.a) {
+ return (v.a < this.b && this.b <= v.b) || (this.a < v.b && v.b <= this.b)
+ }
+ else if (this.a == v.a) {
+ return true
+ }
+ else if (this.a > v.a) {
+ return (this.a < v.b && v.b <= this.b) || (v.a < this.b && this.b <= v.b)
+ }
+}
+vec2.prototype.union = function(v){
+ if (this.intersects(v)) {
+ return new vec2( min(this.a,v.a), max(this.b, v.b) )
+ }
+}
+vec2.prototype.intersection = function(v){
+ if (this.intersects(v)) {
+ return new vec2( max(this.a,v.a), min(this.b, v.b) )
+ }
+}
+vec2.prototype.toString = function(){
+ return "[" + this.a + " " + this.b + "]"
+}
+vec2.prototype.quantize = function(n){
+ n = n || 10
+ this.a = quantize(this.a, n)
+ this.b = quantize(this.b, n)
+}
+