diff options
| author | Jules Laplace <jules@okfoc.us> | 2014-06-03 17:51:34 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2014-06-03 17:51:34 -0400 |
| commit | 90142bd07f926ef8a7f3ea86a563ec0ca648ca5d (patch) | |
| tree | 2851e990b4b71e215ad2fff4dbcaf80229953c35 /public/assets/javascripts/rectangles | |
| parent | 607f69c67a5b4dc72d2754192e3cdf67d0ad11d0 (diff) | |
pulling in more stuff from posthang
Diffstat (limited to 'public/assets/javascripts/rectangles')
25 files changed, 2554 insertions, 0 deletions
diff --git a/public/assets/javascripts/rectangles/_env.js b/public/assets/javascripts/rectangles/_env.js new file mode 100644 index 0000000..972b1fe --- /dev/null +++ b/public/assets/javascripts/rectangles/_env.js @@ -0,0 +1,49 @@ + +var environment = new function(){} +environment.init = function(){ + if (window.scene) { + scene.camera.move({ + "x": 0, + "y": 0, + "z": 0, + "rotationX": 0, // PI/2, + "rotationY": PI/2, // PI +// "rotationX": 0, +// "rotationY": PI + }) + + scene.camera.radius = 20 + } + +// map.center.a = scene.camera.x +// map.center.b = scene.camera.z + map.center.a = 0 + map.center.b = 0 + + Rooms.list.push( new Room ({ + rect: new Rect(-500,-500, 500,500), + height: 500, + })) + Rooms.list.push( new Room ({ + rect: new Rect(600,0, 1100,500), + height: 500, + })) + Rooms.list.push( new Room ({ + rect: new Rect(450,150, 650,350), + height: 300, + })) + + app.movements.gravity(true) + + $("#map").hide() + + Rooms.init() + Scenery.init() + + scene.update() + environment.update() +} +environment.update = function(t){ + map.update() + z = false +} diff --git a/public/assets/javascripts/rectangles/engine/map/_map.js b/public/assets/javascripts/rectangles/engine/map/_map.js new file mode 100644 index 0000000..53084bb --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/_map.js @@ -0,0 +1,40 @@ +/* +*/ + +window.ctx = window.w = window.h = null; + +var map = new function(){ + var base = this + base.el = document.querySelector("#map") + base.dimensions = new vec2(500,500) + base.bounds = new vec2(500,500) + base.center = new vec2(0,0) + + base.sides = function(){ + var sides = base.bounds.clone().div(2).div(base.zoom) + return new Rect( base.center.a - sides.a, -base.center.b - sides.b, + base.center.a + sides.a, -base.center.b + sides.b ) + } + + base.zoom = 1/8 + base.zoom_exponent = -3 + base.set_zoom = function (n) { + base.zoom_exponent = n + base.zoom = pow(2, base.zoom_exponent) + } + + 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 + document.querySelector("#map").appendChild(canvas) + + base.update = function(){ + base.draw.animate() + } + + base.toggle = function(){ + $(base.el).toggle() + } + +} diff --git a/public/assets/javascripts/rectangles/engine/map/draw.js b/public/assets/javascripts/rectangles/engine/map/draw.js new file mode 100644 index 0000000..b2fc05f --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/draw.js @@ -0,0 +1,162 @@ + +map.draw = new function(){ + + var base = this + + base.animate = function(){ + ctx.save() + map.draw.clear_canvas() + map.draw.ruler() + + ctx.translate( map.bounds.a * 1/2, map.bounds.b * 1/2) + ctx.scale( map.zoom, map.zoom ) + ctx.translate( map.center.a, map.center.b) + ctx.scale( -1, 1 ) + + map.draw.regions(Rooms.regions) + map.draw.mouse(map.ui.mouse.cursor) + map.draw.coords() + scene && map.draw.camera(scene.camera) + + ctx.restore() + } + + base.clear_canvas = function(){ + ctx.fillStyle = "rgba(255,255,255,0.99)" + ctx.clearRect(0,0,w,h) + ctx.fillRect(0,0,w,h) + } + + base.ruler = function (){ + 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) + } + } + + base.regions = function(regions){ + for (var i = 0; i < regions.length; i++) { + if (regions[i].dupe) continue + ctx.fillStyle = colors[i % colors.length] + fill_region(regions[i]) + stroke_sides(regions[i]) + } + } + + base.mouse = function(mouse){ + var radius = 3 / map.zoom + + ctx.fillStyle = "rgba(255,0,0,0.4)"; + ctx.beginPath(); + ctx.arc(mouse.x.b, mouse.y.b, radius, 0, 2*Math.PI, false); + ctx.fill(); + + if (mouse.width() != 0 && mouse.height() != 0) { + if (map.ui.dragging) { + stroke_rect(mouse) + } + else { + ctx.fillStyle = "rgba(255,255,0,0.5)" + fill_region( mouse.clone().translate() ) + } + } + } + + base.camera = function(cam){ + ctx.lineWidth = 0.5 + + ctx.save() + + ctx.translate(cam.x, cam.z) + ctx.rotate(cam.rotationY) + + var radius = 3 / map.zoom + + ctx.fillStyle = '#888'; + + ctx.beginPath(); + ctx.moveTo(0,0) + ctx.lineTo(-radius,-radius/2) + ctx.lineTo(0,radius*3) + ctx.lineTo(radius,-radius/2) + ctx.moveTo(0,0) + ctx.fill() + + ctx.restore() + } + + base.coords = function(){ + ctx.fillStyle = "#888"; + dot_at(0,0) + ctx.fillStyle = "#bbb"; + dot_at(100,0) + dot_at(0,100) + ctx.fillStyle = "#ddd"; + dot_at(200,0) + dot_at(0,200) + ctx.fillStyle = "#eee"; + dot_at(300,0) + dot_at(0,300) + + ctx.strokeStyle = "rgba(0,0,0,0.1)" + ctx.lineWidth = 1/map.zoom + + var sides = map.sides() + var quant = sides.clone().quantize(200) + for (var x = quant.x.a - 200; x <= quant.x.b; x += 200) { + line(x, sides.y.a, x, sides.y.b) + } + for (var y = quant.y.a - 200; y <= quant.y.b; y += 200) { + line(sides.x.a, y, sides.x.b, y) + } + } + + // + + 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 fill_region(r){ + ctx.fillRect(r.x.a + r.translation.a, + r.y.a + r.translation.b, + r.x.length(), + r.y.length()) + } + + function stroke_sides (r){ + if (r.sides & FRONT) line(r.x.a, r.y.a, r.x.b, r.y.a) + if (r.sides & BACK) line(r.x.a, r.y.b, r.x.b, r.y.b) + if (r.sides & LEFT) line(r.x.a, r.y.a, r.x.a, r.y.b) + if (r.sides & RIGHT) line(r.x.b, r.y.a, r.x.b, r.y.b) + } + + function stroke_rect (r){ + line(r.x.a, r.y.a, r.x.b, r.y.b, r.translation) + } + + function dot_at (x,z){ + ctx.save() + ctx.translate(x,z) + + var radius = 2 / map.zoom + + ctx.beginPath(); + ctx.arc(0, 0, radius, 0, 2*Math.PI, false); + ctx.fill(); + + ctx.restore() + } +}
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/ui.js b/public/assets/javascripts/rectangles/engine/map/ui.js new file mode 100644 index 0000000..6e9a5ab --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/ui.js @@ -0,0 +1,131 @@ + +map.ui = new function(){ + + var base = this + + base.creating = base.dragging = base.resizing = false + + base.mouse = new mouse({ + el: map.el, + down: down, + move: move, + drag: drag, + up: up, + rightclick: rightclick, + }) + + base.wheel = new wheel({ + el: map.el, + update: mousewheel, + }) + + // + + function down (e, cursor){ + cursor.x.div(w).add(0.5).mul(map.bounds.a / map.zoom).add(map.center.a) + cursor.y.div(h).sub(0.5).mul(map.bounds.b / map.zoom).sub(map.center.b) + + if (e.ctrlKey || e.which === 3) { + cursor.quantize(1) + map.center.a = cursor.x.a + map.center.b = -cursor.y.a + console.log(map.center+"") + cursor.x.b = cursor.x.a + cursor.y.b = cursor.y.a + base.mouse.down = false + e.preventDefault() + e.stopPropagation() + return + } + + var intersects = Rooms.filter(function(r){ + return r.focused = r.rect.contains(cursor.x.a, cursor.y.a) + }) + + if (intersects.length) { + base.dragging = intersects[0] + base.resizing = base.dragging.rect.nearEdge(cursor.x.a, cursor.y.a, resize_margin / map.zoom) + base.dragging.rect.translation.sides = base.resizing + } + else { + base.creating = true + } + + if (e.shiftKey && base.dragging) { + base.dragging.rect.quantize(10) + } + } + + function move (e, cursor) { + cursor.x.div(w).add(0.5).mul(map.bounds.a / map.zoom).add(map.center.a) + cursor.y.div(h).sub(0.5).mul(map.bounds.b / map.zoom).sub(map.center.b) + } + + function drag (e, cursor) { + cursor.x.b = ((cursor.x.b/w)+0.5) * map.bounds.a / map.zoom + map.center.a + cursor.y.b = ((cursor.y.b/h)-0.5) * map.bounds.b / map.zoom - map.center.b + + if (base.resizing) { + var x_length = base.dragging.rect.x.length(), + y_length = base.dragging.rect.y.length() + + if (base.resizing & LEFT) { + base.dragging.rect.translation.a = clamp( cursor.x.magnitude(), x_length - side_max, x_length - side_min ) + } + if (base.resizing & RIGHT) { + base.dragging.rect.translation.a = clamp( cursor.x.magnitude(), side_min - x_length, side_max - x_length ) + } + if (base.resizing & FRONT) { + base.dragging.rect.translation.b = clamp( cursor.y.magnitude(), y_length - side_max, y_length - side_min ) + } + if (base.resizing & BACK) { + base.dragging.rect.translation.b = clamp( cursor.y.magnitude(), side_min - y_length, side_max - y_length ) + } + } + else if (base.dragging) { + base.dragging.rect.translation.a = cursor.x.magnitude() + base.dragging.rect.translation.b = cursor.y.magnitude() + } + } + + function up (e, cursor, new_cursor) { + new_cursor.x.div(w).add(0.5).mul(map.bounds.a / map.zoom).add(map.center.a) + new_cursor.y.div(h).sub(0.5).mul(map.bounds.b / map.zoom).sub(map.center.b) + + if (base.creating) { + if (cursor.height() > side_min && cursor.width() > side_min) { + cursor.x.abs().quantize(1) + cursor.y.abs().quantize(1) + Rooms.add_with_rect( cursor ) + } + } + + if (base.resizing) { + base.dragging.rect.resize() + } + else if (base.dragging) { + base.dragging.rect.translate() + } + + base.creating = base.dragging = base.resizing = false + } + + function mousewheel (e, val, delta){ + var cursor = base.mouse.cursor + + var intersects = Rooms.filter(function(r){ + return r.focused = r.rect.contains(cursor.x.a, cursor.y.a) + }) + + if (intersects.length) { + intersects[0].height = clamp( ~~(intersects[0].height - delta), height_min, height_max ) + Rooms.clipper.update() + } + else { + map.set_zoom(map.zoom_exponent - delta/20) + } + } + + function rightclick (e){ + } +} diff --git a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js new file mode 100644 index 0000000..411a093 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js @@ -0,0 +1,41 @@ +var Rooms = new function(){ + + var base = this + + base.list = [] + base.regions = [] + + base.init = function(){ + Rooms.builder.init() + Rooms.clipper.init() + Rooms.mover.init() + } + + base.filter = function(f){ + return base.list.filter(f) + } + + base.add_with_rect = function(rect){ + var room = new Room({ + id: base.list.length, + rect: rect, + height: quantize(randrange(300,800), 50), + }) + base.list.push(room) + } + + base.forEach = function(f){ + return base.list.forEach(f) + } + + base.sorted_by_position = function(){ + return sort_rooms_by_position( base.list ) + } + base.sorted_by_height = function(){ + return sort_rooms_by_height( base.list ) + } + base.sorted_by_area = function(){ + return sort_rooms_by_area( base.list ) + } + +} diff --git a/public/assets/javascripts/rectangles/engine/rooms/builder.js b/public/assets/javascripts/rectangles/engine/rooms/builder.js new file mode 100644 index 0000000..8586a7b --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/rooms/builder.js @@ -0,0 +1,294 @@ + +Rooms.builder = new function(){ + var base = this + + var els = [] + + base.init = function(){ + base.bind() + } + + base.bind = function(){ + app.on("clip", rebuild) + } + + function rebuild(){ + if (window.scene) { + clear() + build() + bind() + } + } + function build (){ + Rooms.regions.forEach(function(room){ + build_walls(room).forEach(function(el){ + els.push(el) + scene.add(el) + }) + }) + + Rooms.sorted_by_height().forEach(function(room){ + build_floors(room).forEach(function(el){ + els.push(el) + scene.add(el) + }) + }) + } + + function bind (){ + Rooms.forEach(function(room){ + room.walls = room.group_mx_walls() + room.walls.forEach(function(wall){ + wall.bind() + wall.randomize_colors() + }) + }) + } + + function clear (){ + els.forEach(function(el){ + scene.remove(el) + el.destroy && el.destroy() + }) + els = [] + } + + function build_walls (region){ + var room = Rooms.list[ region.id ] + + var list = [], el = null + + var width = region.x.length() + var depth = region.y.length() + var height = room.height + + if (region.sides & FRONT) { + el = make_wall('.front') + el.width = width + el.height = height + el.rotationY = PI + el.x = region.x.a + width/2 + el.y = height/2 + el.z = region.y.a + el.rect = region + el.side = FRONT + room.mx_walls.push(el) + list.push(el) + } + if (region.sides & BACK) { + var el = make_wall('.back') + el.width = width + el.height = height + el.rotationY = 0 + el.x = region.x.b - width/2 + el.y = height/2 + el.z = region.y.b + el.rect = region + el.side = BACK + room.mx_walls.push(el) + list.push(el) + } + if (region.sides & LEFT) { + el = make_wall('.left') + el.rotationY = HALF_PI + el.height = height + el.width = depth + el.x = region.x.a + el.y = height/2 + el.z = region.y.a + depth/2 + el.rect = region + el.side = LEFT + room.mx_walls.push(el) + list.push(el) + } + if (region.sides & RIGHT) { + el = make_wall('.right') + el.rotationY = -HALF_PI + el.height = height + el.width = depth + el.x = region.x.b + el.y = height/2 + el.z = region.y.b - depth/2 + el.rect = region + el.side = RIGHT + room.mx_walls.push(el) + list.push(el) + } + + return list + } + + function build_floors(room){ + var list = [], el = null + + var constructed = room.intersects.filter(function(room){ return room.constructed }) + sort_rooms_by_height(constructed) + + if (constructed.length > 0) { + // render the regions that don't intersect with anything we've already rendered + // if the height is different, calculate the overlapping sides and render half-walls + room.regions.forEach(function(region){ + var intersected = false + for (var i = 0; i < constructed.length; i++) { + if (constructed[i].rect.contains(region)) { + intersected = true + // r.sides = 0xf + // half_sides + } + else if (constructed[i].rect.intersects(region)) { + intersected = true + if (room.height < constructed[i].height) { + var ceiling_walls = make_ceiling_walls( room, constructed[i], region ) + list = list.concat(ceiling_walls) + } + } + } + if (! intersected) { + el = make_floor(room, region) + list.push( el ) + room.mx_floor.push(el) + + el = make_ceiling(room, region) + list.push( el ) + room.mx_ceiling.push(el) + } + }) + + } + else { + // render floor and ceiling for the entire rectangle + el = make_floor(room, room.rect) + list.push( el ) + room.mx_floor.push(el) + + el = make_ceiling(room, room.rect) + list.push( el ) + room.mx_ceiling.push(el) + } + + room.constructed = true + return list + } + + function make_ceiling_walls( lo, hi, region ){ + var list = [] + + var width = region.x.length() + var depth = region.y.length() + var height = hi.height - lo.height + + if (! (region.half_sides & LEFT) && region.x.a == hi.rect.x.a) { + el = make_wall('.left') + el.rotationY = HALF_PI + el.height = height + el.width = depth + el.x = region.x.a + el.y = lo.height + height/2 + el.z = region.y.a + depth/2 + el.rect = region + list.push(el) + hi.mx_walls.push(el) + region.half_sides |= LEFT + el.half_side = LEFT + } + + if (! (region.half_sides & RIGHT) && region.x.b == hi.rect.x.b) { + el = make_wall('.right') + el.rotationY = -HALF_PI + el.height = height + el.width = depth + el.x = region.x.b + el.y = lo.height + height/2 + el.z = region.y.b - depth/2 + el.rect = region + list.push(el) + hi.mx_walls.push(el) + region.half_sides |= RIGHT + el.half_side = RIGHT + } + + if (! (region.half_sides & FRONT) && region.y.a == hi.rect.y.a) { + el = make_wall('.front') + el.width = width + el.height = height + el.rotationY = PI + el.x = region.x.a + width/2 + el.y = lo.height + height/2 + el.z = region.y.a + el.rect = region + list.push(el) + hi.mx_walls.push(el) + region.half_sides |= FRONT + el.half_side = FRONT + } + + if (! (region.half_sides & BACK) && region.y.b == hi.rect.y.b) { + el = make_wall('.back') + el.width = width + el.height = height + el.rotationY = 0 + el.x = region.x.b - width/2 + el.y = lo.height + height/2 + el.z = region.y.b + el.rect = region + list.push(el) + hi.mx_walls.push(el) + region.half_sides |= BACK + el.half_side = BACK + } + return list + } + + function make_floor(room, region){ + var width = region.x.length() + var depth = region.y.length() + + var el = make_wall('.floor') + el.height = depth + el.width = width + el.x = region.x.a + width/2 + el.y = 0 + el.z = region.y.a + depth/2 + el.rotationX = PI/2 + el.rect = region + el.side = FLOOR + return el + } + function make_ceiling(room, region){ + var width = region.x.length() + var depth = region.y.length() + var height = room.height + + var el = make_wall('.ceiling') + el.height = depth + el.width = width + el.x = region.x.a + width/2 + el.y = height + el.z = region.y.a + depth/2 + el.rotationX = -PI/2 + el.rect = region + el.side = CEILING + return el + } + + function make_wall(klass){ + var el = new MX.Object3D(".face" + (klass || "")) + el.width = el.height = el.scaleX = el.scaleY = el.scaleZ = 1 + el.z = el.y = el.x = 0 + el.side = 0 + el.type = "Face" + el.el.style.opacity = 1.0 + el.side = 0 + el.rect = null + el.destroy = function(){ + this.el = this.rect = null + } + + // possible if walls are opaque + // el.el.classList.add("backface-hidden") + + return el + } + +} + diff --git a/public/assets/javascripts/rectangles/engine/rooms/clipper.js b/public/assets/javascripts/rectangles/engine/rooms/clipper.js new file mode 100644 index 0000000..8989ba8 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/rooms/clipper.js @@ -0,0 +1,104 @@ + +Rooms.clipper = new function(){ + var base = this + + base.init = function(){ + base.bind() + base.update() + } + + base.bind = function(){ + map.ui.mouse.tube.on("up", function(){ base.update() }) + } + + base.update = function(){ + base.solve_rects() + app.tube("clip") + } + + var rooms, regions + + // Given a set of overlapping rooms, clip any intersections, then cull any duplicate polygons + base.solve_rects = function(){ + if (Rooms.list.length == 0) return + + base.reset_rects() + base.clip_rects() + base.cull_rects() + + Rooms.regions = sort_rects_by_position(regions) + } + + // Reset the clipping/culling states of each of the rooms + base.reset_rects = function(){ + for (var i = 0; i < Rooms.list.length; i++) { + Rooms.list[i].reset() + } + } + + // Compare each room to the rooms it overlaps, and subdivide + base.clip_rects = function(){ + var rooms = Rooms.sorted_by_position() + regions = [] + + var left, right + for (var i = 0; i < rooms.length; i++) { + left = rooms[i] + for (var j = i+1; j < rooms.length; j++) { + right = rooms[j] + if (left.rect.intersects(right.rect)) { + left.clipTo(right.rect) + right.clipTo(left.rect) + left.intersects.push(right) + right.intersects.push(left) + } + if (left.rect.x.b < right.rect.x.a) { + break + } + } + } + for (var i = 0; i < rooms.length; i++) { + rooms[i].regions = rooms[i].regions.filter(function(r){ return !!r }) + regions = regions.concat(rooms[i].regions) + } + } + + // Find overlapping regions (of the same size) and dedupe + base.cull_rects = function(){ + regions = sort_rects_by_area( regions ) + + 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]]) + } + } + } + + return base +} + diff --git a/public/assets/javascripts/rectangles/engine/rooms/mover.js b/public/assets/javascripts/rectangles/engine/rooms/mover.js new file mode 100644 index 0000000..e67d9bc --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/rooms/mover.js @@ -0,0 +1,82 @@ +Rooms.mover = new function(){ + + var base = this + base.room = null + base.noclip = false + + base.init = function(){ + base.bind() + base.update(scene.camera) + } + + base.bind = function(){ + app.on("move", base.update) + keys.on("backslash", function(){ + base.noclip = ! base.noclip + base.room = null + app.movements.gravity( ! base.noclip ) + }) + } + + base.update = function(pos){ + var radius = scene.camera.radius + + if (base.noclip) { + cam.x = pos.x + cam.y = pos.y + cam.z = pos.z + return + } + + cam.y = pos.y + + // if we were in a room already.. + if (base.room) { + // check if we're still in the room + if (base.room.rect.containsDisc(pos.x, pos.z, radius)) { + cam.x = pos.x + cam.z = pos.z + return + } + + // check if we've breached one of the walls.. clamp position if so + var collision = base.room.collidesDisc(pos.x, pos.z, radius) + + if (collision) { + if (! (collision & LEFT_RIGHT)) { + cam.x = base.room.rect.x.clampDisc(pos.x, radius) + } + else { + // cam.x = base.room.rect.x.clampDisc(pos.x, radius) + } + if (! (collision & FRONT_BACK)) { + cam.z = base.room.rect.y.clampDisc(pos.z, radius) + } + return + } + + // in this case, we appear to have left the room.. + $(".face.active").removeClass("active") + base.room = null + } + + // collision test failed, so update position + cam.x = pos.x + cam.z = pos.z + + // determine what room we are in now + var intersects = Rooms.filter(function(r){ + return r.rect.contains(pos.x, pos.z) + }) + + // did we actually enter a room? + if (intersects.length) { + base.room = intersects[0] + base.room.mx_floor.forEach(function(w){ $(w.el).addClass("active") }) + base.room.mx_ceiling.forEach(function(w){ $(w.el).addClass("active") }) + base.room.mx_walls.forEach(function(w){ $(w.el).addClass("active") }) + } + + } + +} diff --git a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js new file mode 100644 index 0000000..867bb6f --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js @@ -0,0 +1,45 @@ + +var Scenery = new function(){ + + var base = this; + + base.images = [] + + base.mouse = new mouse ({ use_offset: false }) + + base.init = function(){ + base.resize.init() + + var urls = [ + "http://okfocus.s3.amazonaws.com/office/ducks/duck1.jpg", + "http://okfocus.s3.amazonaws.com/office/ducks/duck2.jpg", + "http://okfocus.s3.amazonaws.com/office/ducks/duck3.jpg", + "http://okfocus.s3.amazonaws.com/office/ducks/duck4.jpg", + "http://okfocus.s3.amazonaws.com/office/ducks/duck5.jpg", + ] + var loader = new Loader(function(){ + base.load(loader.images) + }) + loader.preloadImages(urls) + } + + base.load = function(images){ + images.forEach(function(img){ + img.width = 300 + img.height = ~~(300 * img.naturalHeight/img.naturalWidth) + }) + + Rooms.forEach(function(room){ + room.walls.forEach(function(wall){ + var img = choice(images) + if (wall.fits(img)) { + var scene_img = new Scenery.image (wall, img) + base.images.push(scene_img) + scene_img.init() + } + }) + }) + } + + return base +} diff --git a/public/assets/javascripts/rectangles/engine/scenery/image/_image.js b/public/assets/javascripts/rectangles/engine/scenery/image/_image.js new file mode 100644 index 0000000..dadb2d2 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/scenery/image/_image.js @@ -0,0 +1,52 @@ +Scenery.image = function (wall, img) { + + var base = this + + base.wall = wall + base.img = img + base.dimensions = new vec2(img.width, img.height) + base.center = wall.center() + base.bounds = wall.bounds_for(img) + + // should be proportional to distance from wall + var cursor_amp = 1.5 + + base.init = function(){ + base.build() + base.bind() + } + + base.build = function(){ + base.mx_img = new MX.Image({ + src: img.src, + x: base.center.a, + y: Rooms.list[wall.room].height/2 - img.height/2 - 20, + z: base.center.b, + scale: 300/img.naturalWidth, + rotationY: wall_rotation[ wall.side ], + backface: false, + }) + scene.add( base.mx_img ) + base.move = new Scenery.image.move (base) + } + + base.bind = function(){ + base.move.bind() +// base.resize.bind() + $(base.mx_img.el).bind({ + mouseenter: function(e){ +// console.log('entered an image') + // show the resize points for this image + Scenery.resize.show(base) + Scenery.image.hovering = true + }, + mouseleave: function(e){ +// console.log('left an image') + Scenery.resize.defer_hide(base) + Scenery.image.hovering = false + } + }) + } + + return base +} diff --git a/public/assets/javascripts/rectangles/engine/scenery/image/move.js b/public/assets/javascripts/rectangles/engine/scenery/image/move.js new file mode 100644 index 0000000..e79ede9 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/scenery/image/move.js @@ -0,0 +1,103 @@ + + +Scenery.image.move = function(base){ + + var x, y, z, bounds + var dragging = false + + this.bind = function(){ + Scenery.mouse.bind_el(base.mx_img.el) + Scenery.mouse.on("down", down) + Scenery.mouse.on("enter", switch_wall) + Scenery.mouse.on("drag", drag) + Scenery.mouse.on("up", up) + } + + this.unbind = function(){ + Scenery.mouse.bind_el(base.mx_img.el) + Scenery.mouse.off("down", down) + Scenery.mouse.off("enter", switch_wall) + Scenery.mouse.off("drag", drag) + Scenery.mouse.off("up", up) + } + + function down (e, cursor){ + if (e.target != base.mx_img.el) return; + dragging = true + x = base.mx_img.x + y = base.mx_img.y + z = base.mx_img.z + bounds = base.bounds + document.body.classList.add("dragging") + } + + function drag (e, cursor){ + if (! dragging) return + + base.mx_img.y = bounds.y.clamp( y - cursor.y.magnitude()*cursor_amp ) + switch (base.wall.side) { + case FRONT: + case BACK: + base.mx_img.x = bounds.x.clamp( x + cos(wall_rotation[base.wall.side]) * cursor.x.magnitude()*cursor_amp ) + break + case LEFT: + case RIGHT: + base.mx_img.z = bounds.x.clamp( z + sin(wall_rotation[base.wall.side]) * cursor.x.magnitude()*cursor_amp ) + break + } + + Scenery.resize.move_dots() + } + + function up (e, cursor){ + dragging = false + document.body.classList.remove("dragging") + } + + function switch_wall (e, new_wall, cursor){ + if (! dragging) return + if (new_wall.uid == base.wall.uid) return + if (! new_wall.fits(base.img)) return + + base.bounds = bounds = new_wall.bounds_for(base.img) + base.center = new_wall.center() + + x = base.center.a + z = base.center.b + + var wall_group = base.wall.side | new_wall.side + + if (base.wall.side !== new_wall.side && wall_group !== FRONT_BACK && wall_group !== LEFT_RIGHT) { + switch (base.wall.side) { + case FRONT: + z = bounds.x.a + break + case BACK: + z = bounds.x.b + break + case LEFT: + x = bounds.x.a + break + case RIGHT: + x = bounds.x.b + break + } + } + + cursor.x.a = cursor.x.b + + base.mx_img.move({ + x: x, + z: z, + rotationY: wall_rotation[ new_wall.side ] + }) + + base.wall = new_wall + + Scenery.resize.rotate_dots() + Scenery.resize.move_dots() + } + + return this + +}
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/scenery/image/resize.js b/public/assets/javascripts/rectangles/engine/scenery/image/resize.js new file mode 100644 index 0000000..a0a98c5 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/scenery/image/resize.js @@ -0,0 +1,201 @@ +Scenery.resize = new function(){ + + var base = this + + var obj + var x, y, z, bounds + var dragging = false + var dimensions, position + + var dots = [], dot, selected_dot + + base.init = function(){ + base.build() + base.bind() + } + + // create 9 dots at the corners of the div + base.build = function(){ + [ TOP, + TOP_RIGHT, + RIGHT, + BOTTOM_RIGHT, + BOTTOM, + BOTTOM_LEFT, + LEFT, + TOP_LEFT ].forEach(base.build_dot) + } + + // generate a dot element + base.build_dot = function(side) { + var dot = new MX.Object3D('.dot') + dot.width = dot.height = dot_side + dot.side = side + $(dot.el).on({ + mouseenter: function(){ base.hovering = true }, + mouseleave: function(){ base.hovering = false }, + }) + dots.push(dot) + } + + base.add_dots = function(){ + dots.forEach(function(dot){ + scene.add(dot) + }) + } + + // rotate the dots as appropriate + base.rotate_dots = function(){ + rotationY = wall_rotation[obj.wall.side] + dots.forEach(function(dot){ + dot.rotationY = rotationY + }) + } + + // move all the dots to the object's current position + base.move_dots = function(){ + x = obj.mx_img.x + sin(rotationY) * dot_distance_from_picture + y = obj.mx_img.y + z = obj.mx_img.z - cos(rotationY) * dot_distance_from_picture + + dots.forEach(function(dot){ + base.move_dot(dot) + }) + } + + // move a dot .. to the initial position of the image + base.move_dot = function(dot){ + dot.x = x + dot.y = y + dot.z = z + + if (dot.side & TOP) { + dot.y += obj.mx_img.height * obj.mx_img.scale / 2 + } + if (dot.side & BOTTOM) { + dot.y -= obj.mx_img.height * obj.mx_img.scale / 2 + } + if (dot.side & LEFT) { + dot.x -= cos(rotationY) * (obj.mx_img.width * obj.mx_img.scale) / 2 + dot.z -= sin(rotationY) * (obj.mx_img.width * obj.mx_img.scale) / 2 + } + if (dot.side & RIGHT) { + dot.x += cos(rotationY) * (obj.mx_img.width * obj.mx_img.scale) / 2 + dot.z += sin(rotationY) * (obj.mx_img.width * obj.mx_img.scale) / 2 + } + } + + // pick a new object to focus on and show the dots + base.show = function(new_object) { + if (obj === new_object) return + + obj = new_object + + base.add_dots() + base.rotate_dots() + base.move_dots() + } + + // dismiss the dots on blur + var dotsHideTimeout; + base.defer_hide = function(){ + clearTimeout(dotsHideTimeout) + + dotsHideTimeout = setTimeout(function(){ + if (Scenery.image.hovering || Scenery.resize.hovering || Scenery.mouse.down) return + Scenery.resize.hide() + }, dot_hide_delay) + } + base.hide = function () { + obj = null + dots.forEach(function(dot){ + scene.remove(dot) + }) + } + + base.bind = function(){ + dots.forEach(function(dot){ + Scenery.mouse.bind_el(dot.el) + $(dot.el).bind({ + mouseenter: function(e){ + Scenery.resize.hovering = true + }, + mouseleave: function(e){ + Scenery.resize.hovering = false + base.defer_hide() + } + }) + }) + Scenery.mouse.on("down", down) + Scenery.mouse.on("drag", drag) + Scenery.mouse.on("up", up) + } + + this.unbind = function(){ + dots.forEach(function(dot){ + Scenery.mouse.unbind_el(dot.el) + }) + Scenery.mouse.off("down", down) + Scenery.mouse.off("drag", drag) + Scenery.mouse.off("up", up) + } + + function down (e, cursor){ + var selection = dots.filter(function(dot){return e.target == dot.el}) + if (! selection.length) return + + selected_dot = selection[0] + dragging = true + + dimensions = new vec2(obj.mx_img.width, obj.mx_img.height) + position = new vec3(obj.mx_img.x, obj.mx_img.y, obj.mx_img.z) + + console.log("down on", sidesToString(selected_dot.side)) + + document.body.classList.add("dragging") + } + + function drag (e, cursor){ + if (! dragging) return + // cursor.x.magnitude()*cursor_amp + + var x_sign = selected_dot.side & LEFT ? -1 : selected_dot.side & RIGHT ? 1 : 0 + var y_sign = selected_dot.side & TOP ? -1 : selected_dot.side & BOTTOM ? 1 : 0 + + var translation = new vec2( x_sign * cursor.x.magnitude() * cursor_amp, y_sign * cursor.y.magnitude() * cursor_amp ) + + if (selected_dot.side & LEFT_RIGHT) { + obj.mx_img.width = dimensions.a + translation.a + obj.mx_img.x = position.a + x_sign * cos(rotationY) * translation.a/2 * obj.mx_img.scale + obj.mx_img.z = position.c + x_sign * sin(rotationY) * translation.a/2 * obj.mx_img.scale + } + if (selected_dot.side & TOP_BOTTOM) { + obj.mx_img.height = dimensions.b + translation.b + obj.mx_img.y = position.b - y_sign * translation.b/2 * obj.mx_img.scale + } + +// bounds = obj.wall.bounds_for(dimensions) + +// base.mx_img.y = bounds.y.clamp( y - cursor.y.magnitude()*cursor_amp ) +// switch (base.wall.side) { +// case FRONT: +// case BACK: +// base.mx_img.x = bounds.x.clamp( x + cos(wall_rotation[base.wall.side]) * cursor.x.magnitude()*cursor_amp ) +// break +// case LEFT: +// case RIGHT: +// base.mx_img.z = bounds.x.clamp( z + sin(wall_rotation[base.wall.side]) * cursor.x.magnitude()*cursor_amp ) +// break +// } + + base.move_dots() + + } + + function up (e, cursor){ + dragging = false + selected_dot = null + document.body.classList.remove("dragging") + } + +} diff --git a/public/assets/javascripts/rectangles/models/rect.js b/public/assets/javascripts/rectangles/models/rect.js new file mode 100644 index 0000000..7a2ac6f --- /dev/null +++ b/public/assets/javascripts/rectangles/models/rect.js @@ -0,0 +1,176 @@ + +window.Rect = (function(){ + 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 + } + 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.mul = function(n){ + this.x.mul(n) + this.y.mul(n) + return this + } + Rect.prototype.div = function(n){ + this.x.div(n) + this.y.div(n) + return this + } + + Rect.prototype.translate = function(translation){ + var translation = translation || this.translation + this.x.abs().add(translation.a) + this.y.abs().add(translation.b) + this.translation.a = this.translation.b = 0 + return this + } + Rect.prototype.resize = function(translation, sides){ + var translation = translation || this.translation + sides = sides || translation.sides + + if (sides & LEFT) { + this.x.a += translation.a + } + if (sides & RIGHT) { + this.x.b += translation.a + } + if (sides & FRONT) { + this.y.a += translation.b + } + if (sides & BACK) { + this.y.b += translation.b + } + this.translation.a = this.translation.b = 0 + } + Rect.prototype.contains = function(x,y){ + return this.x.contains(x) && this.y.contains(y) + } + Rect.prototype.containsDisc = function(x,y,r){ + return this.x.containsDisc(x,r) && this.y.containsDisc(y,r) + } + Rect.prototype.intersects = function(r){ + return this.x.intersects(r.x) && this.y.intersects(r.y) + } + Rect.prototype.nearEdge = function (x, y, r) { + var edges = 0 + if (x < this.x.a+r) { + edges |= LEFT + } + else if (x > this.x.b-r) { + edges |= RIGHT + } + if (y < this.y.a+r) { + edges |= FRONT + } + else if (y > this.y.b-r) { + edges |= BACK + } + return edges + } + Rect.prototype.width = function(){ return this.x.length() } + Rect.prototype.height = function(){ return this.y.length() } + Rect.prototype.toString = function(){ + var sides = sidesToString(this.sides) + var s = "[" + this.x.toString() + " " + this.y.toString() + "] " + sides + return s + } + Rect.prototype.quantize = function(n){ + this.x.quantize(n) + this.y.quantize(n) + return this + } + 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 + } + + return Rect + +})() diff --git a/public/assets/javascripts/rectangles/models/room.js b/public/assets/javascripts/rectangles/models/room.js new file mode 100644 index 0000000..731411c --- /dev/null +++ b/public/assets/javascripts/rectangles/models/room.js @@ -0,0 +1,213 @@ +window.Room = (function(){ + + var Room = function(opt){ + this.id = opt.id || Rooms.list.length + this.rect = opt.rect + this.regions = [] + this.walls = [] + this.floor = [] + this.ceiling = [] + + this.height = opt.height || 200 + this.focused = false + } + + Room.prototype.toString = function(){ + return this.rect.toString() + } + + Room.prototype.reset = function(){ + var copy = this.rect.clone() + copy.id = this.id + copy.sides = FRONT | BACK | LEFT | RIGHT + this.regions = [ copy ] + + this.intersects = [] + this.constructed = false + + this.walls = [] + + this.mx_walls = [] + this.mx_floor = [] + this.mx_ceiling = [] + } + + Room.prototype.bind = function(){ + var base = this + base.mx_walls.forEach(function(wall){ + $(wall.el).bind({ + mouseover: function(){ + }, + mousemove: function(e){ + var color = choice(window.palettes.colors) + base.mx_walls.forEach(function(wall){ + $(wall.el).css("background-color", color) + }) + }, + mousedown: function(){ + } + }) + }) + } + + Room.prototype.group_mx_walls = function(){ + var base = this + var side_groups = {}, walls = [] + + // group the walls by side + base.mx_walls.forEach(function(wall){ + + // ignore half-walls for now + var side = wall.side || wall.half_side + + if (side_groups[side]) { + side_groups[side].push(wall) + } + else { + side_groups[side] = [wall] + } + }) + + // sort the subgroups, and then combine mx objects under wall objects + pairs(side_groups).forEach(function(pair){ + var side = pair[0], els = pair[1] + + if (side & LEFT_RIGHT) { + els.sort(compare_x) + } + else if (side & FRONT_BACK) { + els.sort(compare_z) + } + + var wall + + els.forEach(function(el){ + if (el.half_side) { + wall = new_wall(el) + walls.push(wall) + wall = null + } + else if (! wall) { + wall = new_wall(el) + walls.push(wall) + } + else if (side & FRONT_BACK && wall.rect.x.b == el.rect.x.a) { + wall.rect.x.b = el.rect.x.b + wall.mx.push(el) + } + else if (side & LEFT_RIGHT && wall.rect.y.b == el.rect.y.a) { + wall.rect.y.b = el.rect.y.b + wall.mx.push(el) + } + else { + wall = new_wall(el) + walls.push(wall) + } + }) + }) + + function new_wall (el) { + return new Wall ({ + room: base.id, + side: el.side | el.half_side, + half_side: el.half_side, + rect: el.rect.clone(), + el: el, + }) + } + + return walls + } + + Room.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 + } + + Room.prototype.collides = function(x,y){ + var collision = 0, wall_collision, contains_x, contains_y + this.regions.forEach(function(r){ + if (! r.sides) return + + wall_collision = 0 + + if ((r.sides & FRONT) && y < r.y.a) { + wall_collision |= FRONT + } + if ((r.sides & BACK) && r.y.b < y) { + wall_collision |= BACK + } + if ((r.sides & LEFT) && x < r.x.a) { + wall_collision |= LEFT + } + if ((r.sides & RIGHT) && r.x.b < x) { + wall_collision |= RIGHT + } + if (! wall_collision) return + + contains_y = r.y.contains(y) + contains_x = r.x.contains(x) + + if (contains_x) { + collision |= wall_collision & FRONT_BACK + } + else if (contains_y) { + collision |= wall_collision & LEFT_RIGHT + } + else if (bitcount(wall_collision) > 1) { + collision |= wall_collision + } + }) + return collision + } + + Room.prototype.collidesDisc = function(x,y,radius){ + var collision = 0, wall_collision, contains_x, contains_y + this.regions.forEach(function(r){ + if (! r.sides) return + + wall_collision = 0 + + if ((r.sides & FRONT) && y-radius < r.y.a) { + wall_collision |= FRONT + } + if ((r.sides & BACK) && r.y.b < y+radius) { + wall_collision |= BACK + } + if ((r.sides & LEFT) && x-radius < r.x.a) { + wall_collision |= LEFT + } + if ((r.sides & RIGHT) && r.x.b < x+radius) { + wall_collision |= RIGHT + } + if (! wall_collision) return + + contains_x = r.x.contains(x, radius) + contains_y = r.y.contains(y, radius) + + if (contains_x) { + collision |= wall_collision & FRONT_BACK + } + else if (contains_y) { + collision |= wall_collision & LEFT_RIGHT + } + else if (bitcount(wall_collision) > 1) { + collision |= wall_collision + } + }) + return collision + } + + return Room + +})() + diff --git a/public/assets/javascripts/rectangles/models/tree.js b/public/assets/javascripts/rectangles/models/tree.js new file mode 100644 index 0000000..8193988 --- /dev/null +++ b/public/assets/javascripts/rectangles/models/tree.js @@ -0,0 +1,37 @@ +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.toArray = function(){ + var a = [] + if (this.lo) a = a.concat(this.lo.toArray()) + a.push(this.data) + if (this.hi) a = a.concat(this.hi.toArray()) + return a +} +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/public/assets/javascripts/rectangles/models/vec2.js b/public/assets/javascripts/rectangles/models/vec2.js new file mode 100644 index 0000000..9b0447c --- /dev/null +++ b/public/assets/javascripts/rectangles/models/vec2.js @@ -0,0 +1,99 @@ +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.invert() + } + return this +} +vec2.prototype.invert = function(){ + 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 + return this +} +vec2.prototype.sub = function(n){ + this.a -= n + this.b -= n + return this +} +vec2.prototype.mul = function(n){ + this.a *= n + this.b *= n + return this +} +vec2.prototype.div = function(n){ + this.a /= n + this.b /= n + return this +} +vec2.prototype.normalize = function(){ + var dim = max(this.a, this.b) + this.a = this.a/dim + this.b = this.b/dim + return this +} +vec2.prototype.contains = function(n){ + return this.a <= n && n <= this.b +} +vec2.prototype.containsDisc = function(n,r){ + return this.a <= n-r && n+r <= this.b +} +vec2.prototype.clamp = function(n){ + return clamp(n, this.a, this.b) +} +vec2.prototype.clampDisc = function(n,r){ + return clamp(n, this.a+r, this.b-r) +} +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) +} + diff --git a/public/assets/javascripts/rectangles/models/vec3.js b/public/assets/javascripts/rectangles/models/vec3.js new file mode 100644 index 0000000..4e9f3cb --- /dev/null +++ b/public/assets/javascripts/rectangles/models/vec3.js @@ -0,0 +1,5 @@ +function vec3(a,b,c){ + this.a = a + this.b = b + this.c = c +} diff --git a/public/assets/javascripts/rectangles/models/wall.js b/public/assets/javascripts/rectangles/models/wall.js new file mode 100644 index 0000000..4270551 --- /dev/null +++ b/public/assets/javascripts/rectangles/models/wall.js @@ -0,0 +1,120 @@ +window.Wall = (function(){ + + var Wall = function(opt){ + this.id = opt.id + this.uid = Uid() + this.room = opt.room + this.rect = opt.rect || new Rect (0,0,0,0) + this.rect.sides = opt.side + this.side = opt.side + this.mx = [] + this.els = [] + if (opt.el) { + this.mx.push(opt.el) + } + } + + Wall.prototype.toString = function(){ + return this.rect.toString() + } + + Wall.prototype.reset = function(){ + } + + Wall.prototype.destroy = function(){ + this.mx.forEach(function(mx){ + mx.destroy && mx.destroy() + }) + this.room = this.rect = this.mx = this.els = null + } + + Wall.prototype.bind = function(){ + var base = this + base.$walls = $( this.mx.map(function(mx){ return mx.el }) ) + base.$walls.bind({ + mouseover: function(){ + }, + mouseenter: function(e){ + Scenery.mouse.mouseenter(e, base) + }, + mousemove: function(e){ + }, + mousedown: function(){ + base.randomize_colors() + console.log(sidesToString(base.side)) + } + }) + } + + Wall.prototype.bounds_for = function(img) { + var coord = this.side & FRONT_BACK ? this.rect.x : this.rect.y + return new Rect( new vec2( coord.a + img.width/2, coord.b - img.width/2 ), + new vec2( img.height/2, Rooms.list[this.room].height - img.height/2 ) ) + } + Wall.prototype.fits = function(img){ + if (this.side & FRONT_BACK && this.rect.x.length() < img.width) { + return false + } + if (this.side & LEFT_RIGHT && this.rect.y.length() < img.width) { + return false + } + return true + } + + Wall.prototype.center = function(offset){ + + offset = offset || 0 + + var major_axis, minor_axis + if (this.side & FRONT_BACK) { + major_axis = this.rect.x + minor_axis = this.rect.y + } + else { + major_axis = this.rect.y + minor_axis = this.rect.x + } + + switch (this.side) { + case FRONT: + x = major_axis.midpoint() + z = minor_axis.a + painting_distance_from_wall + offset + break + case BACK: + x = major_axis.midpoint() + z = minor_axis.b - painting_distance_from_wall + offset + break + case LEFT: + x = minor_axis.a + painting_distance_from_wall + offset + z = major_axis.midpoint() + break + case RIGHT: + x = minor_axis.b - painting_distance_from_wall + offset + z = major_axis.midpoint() + break + } + + return new vec2 (x, z) + } + + Wall.prototype.color = function(color){ + this.$walls && this.$walls.css("background-color", color) + } + + Wall.prototype.siblings = function(){ + var base = this + var match = base.side | base.half_side + var walls = Rooms.list[this.room].walls.filter(function(w){ + return (w.side | w.half_side) & match + }) + return walls; + } + + Wall.prototype.randomize_colors = function(){ + var color = choice(window.colors) + this.siblings().forEach(function(w){ w.color(color) }) + } + + return Wall + +})() diff --git a/public/assets/javascripts/rectangles/util/colors.js b/public/assets/javascripts/rectangles/util/colors.js new file mode 100644 index 0000000..e8e60d0 --- /dev/null +++ b/public/assets/javascripts/rectangles/util/colors.js @@ -0,0 +1,57 @@ +(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)", + ], + bone: [ + "hsla(0,0%,90%,0.95)", + "hsla(0,0%,80%,0.95)", + "hsla(0,0%,85%,0.95)", + "hsla(0,0%,75%,0.95)", + ], + 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)", + ], + white: [ + "rgba(255,255,255,0.9)", + ], + black: [ + "rgba(0,0,0,0.9)", + ], + } + + var select = document.querySelector("#palette") + select.addEventListener("change", function(){ + colors = color_palettes[select.value] + Rooms.list.forEach(function(room){ + room.walls.forEach(function(wall){ + wall.randomize_colors() + }) + }) + select.blur() + }) + + window.colors = color_palettes[select.value] + window.palettes = color_palettes +})() diff --git a/public/assets/javascripts/rectangles/util/constants.js b/public/assets/javascripts/rectangles/util/constants.js new file mode 100644 index 0000000..58cb1a5 --- /dev/null +++ b/public/assets/javascripts/rectangles/util/constants.js @@ -0,0 +1,57 @@ +var FRONT = 0x1, BACK = 0x2, LEFT = 0x4, RIGHT = 0x8, FLOOR = 0x10, CEILING = 0x20 + FRONT_BACK = FRONT | BACK, LEFT_RIGHT = LEFT | RIGHT, FLOOR_CEILING = FLOOR | CEILING + +var TOP = CEILING, BOTTOM = FLOOR, + TOP_LEFT = TOP | LEFT, + TOP_RIGHT = TOP | RIGHT, + BOTTOM_LEFT = BOTTOM | LEFT, + BOTTOM_RIGHT = BOTTOM | RIGHT, + TOP_BOTTOM = TOP | BOTTOM + +var height_min = 200, + height_max = 2000, + side_min = 10, + side_max = 5000, + resize_margin = 8, + cursor_amp = 1.5 + +var painting_distance_from_wall = 8, + dot_distance_from_picture = 3 + +var dot_hide_delay = 50, // ms + dot_side = 20 + +var wall_rotation = {} +wall_rotation[FRONT] = PI +wall_rotation[BACK] = 0 +wall_rotation[LEFT] = HALF_PI +wall_rotation[RIGHT] = -HALF_PI + + + +function sidesToString(sides){ + var s = "" + if (sides & FRONT) s += "front " + if (sides & BACK) s += "back " + if (sides & LEFT) s += "left " + if (sides & RIGHT) s += "right " + if (sides & TOP) s += "top " + if (sides & BOTTOM) s += "bottom " + return s +} + +function side_direction (a, b) { + if (a === b) return 0 + if ((a | b) === FRONT_BACK) return 0 + if ((a | b) === LEFT_RIGHT) return 0 + switch (a) { + case FRONT: + return b & LEFT ? -1 : 1 + case BACK: + return b & RIGHT ? -1 : 1 + case LEFT: + return b & FRONT ? -1 : 1 + case RIGHT: + return b & BACK ? -1 : 1 + } +} diff --git a/public/assets/javascripts/rectangles/util/debug.js b/public/assets/javascripts/rectangles/util/debug.js new file mode 100644 index 0000000..924c816 --- /dev/null +++ b/public/assets/javascripts/rectangles/util/debug.js @@ -0,0 +1,8 @@ +/* + on mousedown, z := true + + if (z) // log stuff +*/ + +window.z = true; +document.body.addEventListener("mousedown", function(){ z = true }) diff --git a/public/assets/javascripts/rectangles/util/keys.js b/public/assets/javascripts/rectangles/util/keys.js new file mode 100644 index 0000000..5a5c9d2 --- /dev/null +++ b/public/assets/javascripts/rectangles/util/keys.js @@ -0,0 +1,165 @@ +var keys = (function(){ + + var base = new function(){} + base.tube = new Tube () + base.debug = false + + base.on = function(){ + base.tube.on.apply(base.tube, arguments) + } + + base.off = function(){ + base.tube.off.apply(base.tube, arguments) + } + + $(window).keydown(function(e){ + var key = KEY_NAMES[e.keyCode]; + switch (key) { + case undefined: + break; + default: + if (keys.debug) console.log(key) + base.tube(key) + break; + } + }) + var KEYMAP = { + STRG: 17, + CTRL: 17, + CTRLRIGHT: 18, + CTRLR: 18, + SHIFT: 16, + RETURN: 13, + ENTER: 13, + BACKSPACE: 8, + BCKSP:8, + ALT: 18, + ALTR: 17, + ALTRIGHT: 17, + SPACE: 32, + WIN: 91, + MAC: 91, + FN: null, + UP: 38, + DOWN: 40, + LEFT: 37, + RIGHT: 39, + ESC: 27, + DEL: 46, + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123 + }, + KEYCODES = { + 'backspace' : '8', + 'tab' : '9', + 'enter' : '13', + 'shift' : '16', + 'ctrl' : '17', + 'alt' : '18', + 'pause_break' : '19', + 'caps_lock' : '20', + 'escape' : '27', + 'page_up' : '33', + 'page down' : '34', + 'end' : '35', + 'home' : '36', + 'left_arrow' : '37', + 'up_arrow' : '38', + 'right_arrow' : '39', + 'down_arrow' : '40', + 'insert' : '45', + 'delete' : '46', + '0' : '48', + '1' : '49', + '2' : '50', + '3' : '51', + '4' : '52', + '5' : '53', + '6' : '54', + '7' : '55', + '8' : '56', + '9' : '57', + 'a' : '65', + 'b' : '66', + 'c' : '67', + 'd' : '68', + 'e' : '69', + 'f' : '70', + 'g' : '71', + 'h' : '72', + 'i' : '73', + 'j' : '74', + 'k' : '75', + 'l' : '76', + 'm' : '77', + 'n' : '78', + 'o' : '79', + 'p' : '80', + 'q' : '81', + 'r' : '82', + 's' : '83', + 't' : '84', + 'u' : '85', + 'v' : '86', + 'w' : '87', + 'x' : '88', + 'y' : '89', + 'z' : '90', + 'left_window key' : '91', + 'right_window key' : '92', + 'select_key' : '93', + 'numpad 0' : '96', + 'numpad 1' : '97', + 'numpad 2' : '98', + 'numpad 3' : '99', + 'numpad 4' : '100', + 'numpad 5' : '101', + 'numpad 6' : '102', + 'numpad 7' : '103', + 'numpad 8' : '104', + 'numpad 9' : '105', + 'multiply' : '106', + 'add' : '107', + 'subtract' : '109', + 'decimal point' : '110', + 'divide' : '111', + 'f1' : '112', + 'f2' : '113', + 'f3' : '114', + 'f4' : '115', + 'f5' : '116', + 'f6' : '117', + 'f7' : '118', + 'f8' : '119', + 'f9' : '120', + 'f10' : '121', + 'f11' : '122', + 'f12' : '123', + 'num_lock' : '144', + 'scroll_lock' : '145', + 'semi_colon' : '186', + 'equal_sign' : '187', + 'comma' : '188', + 'dash' : '189', + 'period' : '190', + 'forward_slash' : '191', + 'grave_accent' : '192', + 'open_bracket' : '219', + 'backslash' : '220', + 'closebracket' : '221', + 'single_quote' : '222' + } + var KEY_NAMES = invert_hash(KEYCODES) + + return base +})()
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/util/mouse.js b/public/assets/javascripts/rectangles/util/mouse.js new file mode 100644 index 0000000..3aa7cfc --- /dev/null +++ b/public/assets/javascripts/rectangles/util/mouse.js @@ -0,0 +1,165 @@ +/* + usage: + + base.mouse = new mouse({ + el: document.querySelector("#map"), + down: function(e, cursor){ + // do something with val + // cursor.x.a + // cursor.y.a + }, + move: function(e, cursor, delta){ + // delta.a (x) + // delta.b (y) + }, + up: function(e, cursor, new_cursor){ + // cursor.x.a + // cursor.y.a + }, + }) + +*/ + +function mouse (opt) { + var base = this + + opt = defaults(opt, { + el: null, + down: null, + move: null, + drag: null, + enter: null, + up: null, + rightclick: null, + propagate: false, + locked: false, + use_offset: true, + val: 0, + }) + + base.down = false + + base.creating = false + base.dragging = false + + base.cursor = new Rect(0,0,0,0) + + base.tube = new Tube () + opt.down && base.tube.on("down", opt.down) + opt.move && base.tube.on("move", opt.move) + opt.drag && base.tube.on("drag", opt.drag) + opt.enter && base.tube.on("enter", opt.enter) + opt.leave && base.tube.on("leave", opt.leave) + opt.up && base.tube.on("up", opt.up) + opt.rightclick && base.tube.on("rightclick", opt.rightclick) + + var offset = (opt.use_offset && opt.el) ? opt.el.getBoundingClientRect() : null + + base.init = function (){ + base.bind() + } + + base.on = function(){ + base.tube.on.apply(base.tube, arguments) + } + + base.off = function(){ + base.tube.off.apply(base.tube, arguments) + } + + base.bind = function(){ + if (opt.el) { + opt.el.addEventListener("mousedown", base.mousedown) + opt.el.addEventListener("contextmenu", base.contextmenu) + } + window.addEventListener("mousemove", base.mousemove) + window.addEventListener("mouseup", base.mouseup) + } + + base.bind_el = function(el){ + el.addEventListener("mousedown", base.mousedown) + el.addEventListener("mousemove", base.mousemove) + } + base.unbind_el = function(el){ + el.removeEventListener("mousedown", base.mousedown) + el.removeEventListener("mousemove", base.mousemove) + } + + function positionFromMouse(e) { + if (offset) { + return new vec2(offset.left - e.pageX, e.pageY - offset.top) + } + else { + return new vec2(e.pageX, e.pageY) + } + } + + base.mousedown = function(e){ + e.stopPropagation() + + if (opt.use_offset) { + offset = this.getBoundingClientRect() + } + + var pos = positionFromMouse(e) + + var x = pos.a, y = pos.b + base.cursor = new Rect (x,y, x,y) + base.down = true + + base.tube("down", e, base.cursor) + } + base.mousemove = function(e){ + e.stopPropagation() + + if (opt.use_offset && ! offset) return + + var pos = positionFromMouse(e) + + if (e.shiftKey) { + pos.quantize(10) + } + + var x = pos.a, y = pos.b + + if (base.down) { + base.cursor.x.b = x + base.cursor.y.b = y + base.tube("drag", e, base.cursor) + } + else { + base.cursor.x.a = base.cursor.x.b = x + base.cursor.y.a = base.cursor.y.b = y + base.tube("move", e, base.cursor) + } + } + base.mouseenter = function(e, el){ + if (! base.down) return + if (opt.use_offset && ! offset) return + base.tube("enter", e, el, base.cursor) + } + base.mouseleave = function(e, el){ + if (! base.down) return + if (opt.use_offset && ! offset) return + base.tube("leave", e, el, base.cursor) + } + base.mouseup = function(e){ + var pos, new_cursor + + if (base.down) { + e.stopPropagation() + base.down = false + pos = positionFromMouse(e) + new_cursor = new Rect (pos.a, pos.b) + base.tube("up", e, base.cursor, new_cursor) + base.cursor = new_cursor + } + } + base.contextmenu = function(e){ + e.preventDefault() + base.tube("rightclick", e, base.cursor) + } + + base.init() +} + diff --git a/public/assets/javascripts/rectangles/util/sort.js b/public/assets/javascripts/rectangles/util/sort.js new file mode 100644 index 0000000..0985b75 --- /dev/null +++ b/public/assets/javascripts/rectangles/util/sort.js @@ -0,0 +1,86 @@ + + +function compare_rect_position(a,b){ + if (a[0].x.a < b[0].x.a) { + return -1 + } + if (a[0].x.a > b[0].x.a) { + return 1 + } + if (a[0].y.a < b[0].y.a) { + return -1 + } + if (a[0].y.a > b[0].y.a) { + return 1 + } + return 0 +} + +function compare_car_reversed (a,b){ + if (a[0] < b[0]) { + return 1 + } + if (a[0] > b[0]) { + return -1 + } + return 0 +} +function compare_car (a,b){ + if (a[0] < b[0]) { + return -1 + } + if (a[0] > b[0]) { + return 1 + } + return 0 +} + +function room_id_tuple (r){ return [r.id, r] } +function room_height_tuple (r){ return [r.height, r] } +function room_area_tuple (r){ return [r.rect.area(), r] } +function rect_area_tuple (r){ return [r.area(), r] } + +function room_rect_tuple (r){ return [r.rect, r] } +function identity_tuple (r){ return [r, r] } +function car (r){ return r[0] } +function cdr (r){ return r[1] } + + +function sort_rooms_by_id(list){ + return list.map(room_id_tuple) + .sort(compare_car) + .map(cdr) +} +function sort_rooms_by_height(list){ + return list.map(room_height_tuple) + .sort(compare_car_reversed) + .map(cdr) +} +function sort_rooms_by_position(list){ + return list.map(room_rect_tuple) + .sort(compare_rect_position) + .map(cdr) +} +function sort_rooms_by_area(list){ + return list.map(room_area_tuple) + .sort(compare_car) + .map(cdr) +} + +function sort_rects_by_position(list){ + return list.map(identity_tuple) + .sort(compare_rect_position) + .map(cdr) +} +function sort_rects_by_area(list){ + return list.map(rect_area_tuple) + .sort(compare_car) + .map(cdr) +} + +function compare_z(a,b){ + return a.rect.y.a < b.rect.y.a ? -1 : a.rect.y.a == b.rect.y.a ? 0 : 1 +} +function compare_x(a,b){ + return a.rect.x.a > b.rect.x.a ? -1 : a.rect.x.a == b.rect.x.a ? 0 : 1 +} diff --git a/public/assets/javascripts/rectangles/util/wheel.js b/public/assets/javascripts/rectangles/util/wheel.js new file mode 100644 index 0000000..6836772 --- /dev/null +++ b/public/assets/javascripts/rectangles/util/wheel.js @@ -0,0 +1,62 @@ +/* + usage: + + base.wheel = new wheel({ + el: document.querySelector("#map"), + update: function(e, val, delta){ + // do something with val + }, + }) + +*/ + +function wheel (opt) { + opt = defaults(opt, { + el: document, + fn: function(e, val, delta){}, + propagate: false, + locked: false, + reversible: true, + ratio: 0.02, + val: 0, + }) + + opt.el.addEventListener('mousewheel', onMouseWheel, false); + opt.el.addEventListener('DOMMouseScroll', onMouseWheel, false); + + function onMouseWheel (e) { + if (opt.locked) { + return + } + if (! opt.propagate) { + e.stopPropagation() + e.preventDefault() + } + + var delta = 0; + + // WebKit + if ( event.wheelDeltaY ) { + delta -= event.wheelDeltaY * opt.ratio + } + // Opera / Explorer 9 + else if ( event.wheelDelta ) { + delta -= event.wheelDelta * opt.ratio + } + // Firefox + else if ( event.detail ) { + delta += event.detail * 2 + } + if (! opt.reversible && delta < 0) return; + + opt.val = clamp(opt.val + delta, opt.min, opt.max) + + opt.update(e, opt.val, delta) + } + + opt.lock = function(){ opt.locked = true } + opt.unlock = function(){ opt.locked = false } + + return opt +} + |
