From 9f4204d35f1dbd861417cd8a04bb26c46299f55a Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 5 May 2014 15:56:07 -0400 Subject: happy with refactor --- assets/javascripts/rectangles/engine/builder.js | 294 --------------------- assets/javascripts/rectangles/engine/clipper.js | 104 -------- assets/javascripts/rectangles/engine/map/_map.js | 40 +++ assets/javascripts/rectangles/engine/map/draw.js | 162 ++++++++++++ assets/javascripts/rectangles/engine/map/ui.js | 131 +++++++++ assets/javascripts/rectangles/engine/mover.js | 82 ------ assets/javascripts/rectangles/engine/rooms.js | 41 --- .../javascripts/rectangles/engine/rooms/_rooms.js | 41 +++ .../javascripts/rectangles/engine/rooms/builder.js | 294 +++++++++++++++++++++ .../javascripts/rectangles/engine/rooms/clipper.js | 104 ++++++++ .../javascripts/rectangles/engine/rooms/mover.js | 82 ++++++ assets/javascripts/rectangles/engine/scenery.js | 177 ------------- .../rectangles/engine/scenery/_scenery.js | 44 +++ .../javascripts/rectangles/engine/scenery/image.js | 115 ++++++++ 14 files changed, 1013 insertions(+), 698 deletions(-) delete mode 100644 assets/javascripts/rectangles/engine/builder.js delete mode 100644 assets/javascripts/rectangles/engine/clipper.js create mode 100644 assets/javascripts/rectangles/engine/map/_map.js create mode 100644 assets/javascripts/rectangles/engine/map/draw.js create mode 100644 assets/javascripts/rectangles/engine/map/ui.js delete mode 100644 assets/javascripts/rectangles/engine/mover.js delete mode 100644 assets/javascripts/rectangles/engine/rooms.js create mode 100644 assets/javascripts/rectangles/engine/rooms/_rooms.js create mode 100644 assets/javascripts/rectangles/engine/rooms/builder.js create mode 100644 assets/javascripts/rectangles/engine/rooms/clipper.js create mode 100644 assets/javascripts/rectangles/engine/rooms/mover.js delete mode 100644 assets/javascripts/rectangles/engine/scenery.js create mode 100644 assets/javascripts/rectangles/engine/scenery/_scenery.js create mode 100644 assets/javascripts/rectangles/engine/scenery/image.js (limited to 'assets/javascripts/rectangles/engine') diff --git a/assets/javascripts/rectangles/engine/builder.js b/assets/javascripts/rectangles/engine/builder.js deleted file mode 100644 index 91bdeb4..0000000 --- a/assets/javascripts/rectangles/engine/builder.js +++ /dev/null @@ -1,294 +0,0 @@ - -var 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/assets/javascripts/rectangles/engine/clipper.js b/assets/javascripts/rectangles/engine/clipper.js deleted file mode 100644 index d814fec..0000000 --- a/assets/javascripts/rectangles/engine/clipper.js +++ /dev/null @@ -1,104 +0,0 @@ - -var 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/assets/javascripts/rectangles/engine/map/_map.js b/assets/javascripts/rectangles/engine/map/_map.js new file mode 100644 index 0000000..53084bb --- /dev/null +++ b/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/assets/javascripts/rectangles/engine/map/draw.js b/assets/javascripts/rectangles/engine/map/draw.js new file mode 100644 index 0000000..b2fc05f --- /dev/null +++ b/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/assets/javascripts/rectangles/engine/map/ui.js b/assets/javascripts/rectangles/engine/map/ui.js new file mode 100644 index 0000000..6e9a5ab --- /dev/null +++ b/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/assets/javascripts/rectangles/engine/mover.js b/assets/javascripts/rectangles/engine/mover.js deleted file mode 100644 index 413715a..0000000 --- a/assets/javascripts/rectangles/engine/mover.js +++ /dev/null @@ -1,82 +0,0 @@ -var 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( ! app.movements.gravity() ) - }) - } - - 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/assets/javascripts/rectangles/engine/rooms.js b/assets/javascripts/rectangles/engine/rooms.js deleted file mode 100644 index 93ba7b8..0000000 --- a/assets/javascripts/rectangles/engine/rooms.js +++ /dev/null @@ -1,41 +0,0 @@ -var Rooms = new function(){ - - var base = this - - base.list = [] - base.regions = [] - - base.init = function(){ - Builder.init() - Clipper.init() - 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/assets/javascripts/rectangles/engine/rooms/_rooms.js b/assets/javascripts/rectangles/engine/rooms/_rooms.js new file mode 100644 index 0000000..411a093 --- /dev/null +++ b/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/assets/javascripts/rectangles/engine/rooms/builder.js b/assets/javascripts/rectangles/engine/rooms/builder.js new file mode 100644 index 0000000..8586a7b --- /dev/null +++ b/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/assets/javascripts/rectangles/engine/rooms/clipper.js b/assets/javascripts/rectangles/engine/rooms/clipper.js new file mode 100644 index 0000000..8989ba8 --- /dev/null +++ b/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/assets/javascripts/rectangles/engine/rooms/mover.js b/assets/javascripts/rectangles/engine/rooms/mover.js new file mode 100644 index 0000000..e67d9bc --- /dev/null +++ b/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/assets/javascripts/rectangles/engine/scenery.js b/assets/javascripts/rectangles/engine/scenery.js deleted file mode 100644 index 1301949..0000000 --- a/assets/javascripts/rectangles/engine/scenery.js +++ /dev/null @@ -1,177 +0,0 @@ -var wall_rotation = {} -wall_rotation[FRONT] = PI -wall_rotation[BACK] = 0 -wall_rotation[LEFT] = HALF_PI -wall_rotation[RIGHT] = -HALF_PI - -var Scenery = new function(){ - - var base = this; - - base.mouse = new mouse ({ use_offset: false }) - - base.init = function(){ - 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){ - new_image(wall, choice(images)) - }) - }) - } - - function new_image (wall, img) { - var x, z - - if (! wall.fits(img)) return - - var mx_img = new MX.Image({ - src: img.src, - x: 0, - y: Rooms.list[wall.room].height/2 - img.height/2 - 20, - z: 0, - scale: 300/img.naturalWidth, - rotationY: 0, - backface: false, - }) - - var center = wall.center_for(img, null) - mx_img.move({ - x: center.a, - z: center.b, - rotationY: wall_rotation[ wall.side ] - }) - scene.add(mx_img) - - // https://s3.amazonaws.com/luckyplop/f5b2c20e602cdfc86383910f294dcf23d91fa956.png - - var x = 0, y = 0, z = 0, bounds - - // should be proportional to distance from wall - var cursor_amp = 1.5 - var dragging = false - - base.mouse.bind_el(mx_img.el) - base.mouse.tube.on("down", function(e, cursor){ - if (e.target != mx_img.el) return; - dragging = true - x = mx_img.x - y = mx_img.y - z = mx_img.z - bounds = wall.bounds_for(img) - document.body.classList.add("dragging") - }) - base.mouse.tube.on("enter", function(e, new_wall, cursor){ - if (! dragging) return - if (new_wall.uid == wall.uid) return - if (! new_wall.fits(img)) return - - bounds = new_wall.bounds_for(img) - center = new_wall.center_for(img) - - x = center.a - z = center.b - - var wall_group = wall.side | new_wall.side - - if (wall.side !== new_wall.side && wall_group !== FRONT_BACK && wall_group !== LEFT_RIGHT) { - switch (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 - - mx_img.move({ - x: x, - z: z, - rotationY: wall_rotation[ new_wall.side ] - }) - - wall = new_wall - }) - base.mouse.tube.on("drag", function(e, cursor){ - if (! dragging) return - - mx_img.y = bounds.y.clamp( y - cursor.y.magnitude()*cursor_amp ) - switch (wall.side) { - case FRONT: - mx_img.x = bounds.x.clamp( x - cursor.x.magnitude()*cursor_amp ) - break - case BACK: - mx_img.x = bounds.x.clamp( x + cursor.x.magnitude()*cursor_amp ) - break - case LEFT: - mx_img.z = bounds.x.clamp( z + cursor.x.magnitude()*cursor_amp ) - break - case RIGHT: - mx_img.z = bounds.x.clamp( z - cursor.x.magnitude()*cursor_amp ) - break - } - }) - base.mouse.tube.on("up", function(e, cursor){ - dragging = false - document.body.classList.remove("dragging") - }) - } - - 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 - } - } - -// console.log([ -// compare_sides( LEFT, RIGHT ), -// compare_sides( FRONT, BACK ), -// compare_sides( FRONT, LEFT ), -// compare_sides( FRONT, RIGHT ), -// compare_sides( BACK, RIGHT ), -// compare_sides( BACK, LEFT ), -// compare_sides( LEFT, FRONT ), -// compare_sides( LEFT, BACK ), -// compare_sides( RIGHT, BACK ), -// compare_sides( RIGHT, FRONT ), -// ].join("\n")) - - return base - -} diff --git a/assets/javascripts/rectangles/engine/scenery/_scenery.js b/assets/javascripts/rectangles/engine/scenery/_scenery.js new file mode 100644 index 0000000..dbd9dbe --- /dev/null +++ b/assets/javascripts/rectangles/engine/scenery/_scenery.js @@ -0,0 +1,44 @@ + +var Scenery = new function(){ + + var base = this; + + base.images = [] + + base.mouse = new mouse ({ use_offset: false }) + + base.init = function(){ + 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/assets/javascripts/rectangles/engine/scenery/image.js b/assets/javascripts/rectangles/engine/scenery/image.js new file mode 100644 index 0000000..6434603 --- /dev/null +++ b/assets/javascripts/rectangles/engine/scenery/image.js @@ -0,0 +1,115 @@ +Scenery.image = function (wall, img) { + + var base = this + var center + var x = 0, y = 0, z = 0, bounds + var dragging = false + + // should be proportional to distance from wall + var cursor_amp = 1.5 + + base.init = function(){ + base.build() + base.bind() + } + + base.build = function(){ + center = wall.center_for(img) + base.mx_img = new MX.Image({ + src: img.src, + x: center.a, + y: Rooms.list[wall.room].height/2 - img.height/2 - 20, + z: center.b, + scale: 300/img.naturalWidth, + rotationY: wall_rotation[ wall.side ], + backface: false, + }) + scene.add( base.mx_img ) + } + + base.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) + } + + 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 = wall.bounds_for(img) + document.body.classList.add("dragging") + } + + function switch_wall (e, new_wall, cursor){ + if (! dragging) return + if (new_wall.uid == wall.uid) return + if (! new_wall.fits(img)) return + + bounds = new_wall.bounds_for(img) + center = new_wall.center_for(img) + + x = center.a + z = center.b + + var wall_group = wall.side | new_wall.side + + if (wall.side !== new_wall.side && wall_group !== FRONT_BACK && wall_group !== LEFT_RIGHT) { + switch (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 ] + }) + + wall = new_wall + } + + function drag (e, cursor){ + if (! dragging) return + + base.mx_img.y = bounds.y.clamp( y - cursor.y.magnitude()*cursor_amp ) + switch (wall.side) { + case FRONT: + base.mx_img.x = bounds.x.clamp( x - cursor.x.magnitude()*cursor_amp ) + break + case BACK: + base.mx_img.x = bounds.x.clamp( x + cursor.x.magnitude()*cursor_amp ) + break + case LEFT: + base.mx_img.z = bounds.x.clamp( z + cursor.x.magnitude()*cursor_amp ) + break + case RIGHT: + base.mx_img.z = bounds.x.clamp( z - cursor.x.magnitude()*cursor_amp ) + break + } + } + + function up (e, cursor){ + dragging = false + document.body.classList.remove("dragging") + } + + return base +} -- cgit v1.2.3-70-g09d2