diff options
Diffstat (limited to 'public/assets/javascripts/rectangles')
43 files changed, 2244 insertions, 68 deletions
diff --git a/public/assets/javascripts/rectangles/_env.js b/public/assets/javascripts/rectangles/_env.js index 3221bac..b3c7d66 100644 --- a/public/assets/javascripts/rectangles/_env.js +++ b/public/assets/javascripts/rectangles/_env.js @@ -1,6 +1,22 @@ var environment = new function(){} environment.init = function(){ + scene = new MX.Scene().addTo('#scene') + scene.width = window.innerWidth + scene.height = window.innerHeight + scene.perspective = window.innerHeight + + cam = scene.camera + cam.y = viewHeight + + if (is_mobile) { + app.movements = new MX.MobileMovements(cam, viewHeight) + } + else { + app.movements = new MX.Movements(cam, viewHeight) + } + app.movements.init() + map = new Map () if (window.scene) { @@ -16,10 +32,18 @@ environment.init = function(){ scene.camera.radius = 20 } - + + window.onresize = function () { + scene.width = window.innerWidth + scene.height = window.innerHeight + scene.perspective = window.innerHeight + scene.update() + } + Rooms.init() Walls.init() Scenery.init() + Sculpture.init() scene.update() environment.update() @@ -51,8 +75,13 @@ environment.init = function(){ } }) } -environment.update = function(t){ +environment.minimal = function(){ + environment.update = function(t){} +} +environment.update = function(t, dt){ + app.movements.update(dt || 0) + scene.update() map.update() - window.minimap && window.minimap.update && minimap.update() + window.minimap && minimap.update && minimap.update() z = false } diff --git a/public/assets/javascripts/rectangles/engine/map/_map.js b/public/assets/javascripts/rectangles/engine/map/_map.js index 202803a..6492db6 100644 --- a/public/assets/javascripts/rectangles/engine/map/_map.js +++ b/public/assets/javascripts/rectangles/engine/map/_map.js @@ -16,6 +16,7 @@ var Map = function(opt){ var base = this base.el = opt.el base.$el = $(base.el) + base.opt = opt if (! base.el) return @@ -33,7 +34,6 @@ var Map = function(opt){ scene.camera.x + sides.a, scene.camera.z + sides.b ) } - base.set_zoom = function (n) { n = clamp(n, opt.zoom_min, opt.zoom_max) base.zoom_exponent = n @@ -44,12 +44,21 @@ var Map = function(opt){ var canvas = base.canvas = document.createElement("canvas") canvas.width = base.dimensions.a canvas.height = base.dimensions.b - + base.el.appendChild(canvas) switch (opt.type) { + case "ortho": + base.draw = new Map.Draw (base, { ortho: true }) + base.draw.grid_size = MAP_GRID_SIZE + base.ui = new Map.UI.Ortho (base) + base.sides = base.sides_for_center + $(window).resize(base.resize) + break + case "editor": base.draw = new Map.Draw (base) + base.draw.grid_size = MAP_GRID_SIZE base.ui = new Map.UI.Editor (base) base.sides = base.sides_for_center $(window).resize(base.resize) @@ -57,14 +66,16 @@ var Map = function(opt){ case "minimap": base.draw = new Map.Draw (base, { center: scene.camera, minimap: true }) + base.draw.grid_size = MAP_GRID_SIZE * 10 base.ui = new Map.UI.Minimap (base) base.sides = base.sides_for_camera break } - base.resize = function(){ - canvas.width = base.dimensions.a = window.innerWidth - canvas.height = base.dimensions.b = window.innerHeight + base.resize = function(w, h){ + canvas.width = base.dimensions.a = w || window.innerWidth + canvas.height = base.dimensions.b = h || window.innerHeight + // resize here - esp if 2d-hires } base.toggle = function(state){ diff --git a/public/assets/javascripts/rectangles/engine/map/draw.js b/public/assets/javascripts/rectangles/engine/map/draw.js index eceda3c..4f4759f 100644 --- a/public/assets/javascripts/rectangles/engine/map/draw.js +++ b/public/assets/javascripts/rectangles/engine/map/draw.js @@ -5,24 +5,32 @@ Map.Draw = function(map, opt){ var draw = this - var ctx = map.canvas.getContext("2d") + var ctx = draw.ctx = map.canvas.getContext("2d") draw.animate = function(){ ctx.save() draw.clear() draw.fill("rgba(255,255,255,0.98)") - if (opt.minimap) { + if (opt.ortho) { + } + else if (opt.minimap) { ctx.translate( map.dimensions.a * 1/2, map.dimensions.b * 1/2) ctx.scale( map.zoom, map.zoom ) + if (map.opt.pivot) { ctx.rotate(scene.camera.rotationY + PI) } ctx.translate( opt.center.x, - opt.center.z ) ctx.scale( -1, 1 ) draw.coords() - draw.regions(Rooms.regions, [ "#fff" ], "#000") + if (Rooms.shapesMode) { + shapes.draw(draw.ctx, "#fff", null) + shapes.draw(draw.ctx, null, "#000") + } + else { + draw.regions(Rooms.regions, [ "#fff" ], "#000") + } draw.camera(scene.camera) } - else { ctx.translate( map.dimensions.a * 1/2, map.dimensions.b * 1/2) ctx.scale( map.zoom, map.zoom ) @@ -31,12 +39,19 @@ Map.Draw = function(map, opt){ draw.regions(Rooms.regions, [ "#f8f8f8" ], "#000") draw.mouse(map.ui.mouse.cursor) + draw.mouse_dimensions(map.ui.mouse.cursor) draw.coords() scene && draw.camera(scene.camera) } ctx.restore() } + draw.translate = function(){ + ctx.translate( map.dimensions.a * 1/2, map.dimensions.b * 1/2) + ctx.scale( map.zoom, map.zoom ) + ctx.translate( map.center.a, map.center.b ) + ctx.scale( -1, 1 ) + } // changes the ctx temporarily draw.render = function(){ @@ -56,9 +71,9 @@ Map.Draw = function(map, opt){ } var canvas = document.createElement("canvas") - ctx = canvas.getContext('2d') canvas.width = thumbnail_width canvas.height = thumbnail_height + ctx = canvas.getContext('2d') draw.clear() @@ -87,7 +102,7 @@ Map.Draw = function(map, opt){ ctx.putImageData(pixelData, 0, 0) // reset the ctx - ctx = map.canvas.getContext("2d") + ctx = draw.ctx return canvas } @@ -123,7 +138,8 @@ Map.Draw = function(map, opt){ ctx.beginPath(); ctx.arc(mouse.x.b, mouse.y.b, radius, 0, 2*Math.PI, false); ctx.fill(); - + } + draw.mouse_dimensions = function(mouse){ if (mouse.width() != 0 && mouse.height() != 0) { if (map.ui.dragging) { stroke_rect(mouse) @@ -159,36 +175,39 @@ Map.Draw = function(map, opt){ } draw.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(MAP_GRID_SIZE) - for (var x = quant.x.a - MAP_GRID_SIZE; x <= quant.x.b; x += MAP_GRID_SIZE) { - line(x, sides.y.a, x, sides.y.b) - } - for (var y = quant.y.a - MAP_GRID_SIZE; y <= quant.y.b; y += MAP_GRID_SIZE) { - line(sides.x.a, y, sides.x.b, y) + var quant = sides.clone().quantize(draw.grid_size) + for (var x = quant.x.a - draw.grid_size; x <= quant.x.b; x += draw.grid_size) { + if (Math.round(x) % 360 == 0) { + ctx.strokeStyle = "rgba(0,0,0,0.3)" + ctx.lineWidth = 1/map.zoom + } + else { + ctx.strokeStyle = "rgba(0,0,0,0.05)" + ctx.lineWidth = 1/map.zoom + } + line(x, sides.y.a, x, sides.y.b) } + + for (var y = quant.y.a - draw.grid_size; y <= quant.y.b; y += draw.grid_size) { + if (Math.round(y) % 360 == 0) { + ctx.strokeStyle = "rgba(0,0,0,0.3)" + ctx.lineWidth = 1/map.zoom + } + else { + ctx.strokeStyle = "rgba(0,0,0,0.05)" + ctx.lineWidth = 1/map.zoom + } + line(sides.x.a, y, sides.x.b, y) + } } // - function line (x,y,a,b,translation){ + var line = draw.line = function (x,y,a,b,translation){ if (translation) { x += translation.a a += translation.a @@ -219,11 +238,11 @@ Map.Draw = function(map, opt){ line(r.x.a, r.y.a, r.x.b, r.y.b, r.translation) } - function dot_at (x,z){ + draw.dot_at = function dot_at (x, z, radius){ ctx.save() ctx.translate(x,z) - var radius = 2 / map.zoom + radius = (radius || 2) / map.zoom ctx.beginPath(); ctx.arc(0, 0, radius, 0, 2*Math.PI, false); @@ -231,4 +250,29 @@ Map.Draw = function(map, opt){ ctx.restore() } + + draw.x_at = function x_at (x, z, length){ + ctx.save() + if (x && 'x' in x) { + length = z + ctx.translate(x.x,x.z) + } + else { + ctx.translate(x,z) + } + + var len = (length/2 || 4) / map.zoom + + ctx.lineCap = "square" + ctx.lineWidth = 2/map.zoom + ctx.beginPath() + ctx.moveTo( -len, -len); + ctx.lineTo( len, len); + ctx.moveTo( -len, len); + ctx.lineTo( len, -len); + ctx.stroke(); + + ctx.restore() + } + }
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/tools/_base.js b/public/assets/javascripts/rectangles/engine/map/tools/_base.js new file mode 100644 index 0000000..17b247d --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/_base.js @@ -0,0 +1,11 @@ +var MapTool = Fiber.extend(function(base){ + var exports = { + recenterCursor: true, + down: function(e, cursor){}, + move: function(e, cursor){}, + drag: function(e, cursor){}, + up: function(e, cursor, new_cursor){}, + cancel: function(){}, + } + return exports +})
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/tools/arrow.js b/public/assets/javascripts/rectangles/engine/map/tools/arrow.js new file mode 100644 index 0000000..0b0557e --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/arrow.js @@ -0,0 +1,83 @@ +// Tool used to move corners of polylines + +var ArrowTool = MapTool.extend(function(base){ + var exports = {} + + var selected_point = null, selected_segment = null, original_point = null, selected_shape = null + var src_points, dest_points + + exports.down = function(e, cursor){ + last_point.a = cursor.x.a + last_point.b = cursor.y.a + var p = shapes.findClosestPoint(last_point) + if (p && p.shape.type() !== "ortho") { + selected_shape = p.shape + selected_point = p.point + original_point = selected_point.clone() + return + } + var segment = shapes.findClosestSegment(last_point) + if (segment) { + document.body.style.cursor = "pointer" + + selected_segment = segment + console.log(segment.head, segment.tail) + selected_shape = segment.shape + src_points = segment.shape.cloneSegment( segment ) + dest_points = segment.shape.getSegment( segment ) + + last_point.a = segment.x + last_point.b = segment.y + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + } + else { + map.ui.set_drag_tool("position") + } + } + + exports.move = function(e, cursor){ + last_point.a = cursor.x.a + last_point.b = cursor.y.a + var p = shapes.findClosestPoint(last_point) + if (p) { + document.body.style.cursor = "pointer" + last_point.assign(p.point) + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + return + } + var segment = shapes.findClosestSegment(last_point) + if (segment) { + document.body.style.cursor = "pointer" + last_point.a = segment.x + last_point.b = segment.y + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + } + else { + document.body.style.cursor = "crosshair" + } + } + + exports.drag = function(e, cursor){ + if (selected_point) { + selected_point.a = original_point.a + cursor.x.magnitude() + selected_point.b = original_point.b + cursor.y.magnitude() + selected_shape.rebuild() + } + else if (selected_segment) { + selected_shape.translateSegment( + src_points, dest_points, + cursor.x.magnitude(), cursor.y.magnitude() + ) + selected_shape.rebuild() + } + } + + exports.up = function(e, cursor){ + selected_point = selected_shape = selected_segment = original_point = null + } + + return exports +})
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/tools/eraser.js b/public/assets/javascripts/rectangles/engine/map/tools/eraser.js new file mode 100644 index 0000000..8fc3687 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/eraser.js @@ -0,0 +1,29 @@ +// Tool used to delete lines + +var EraserTool = MapTool.extend(function(base){ + var exports = {} + exports.down = function(e, cursor){ + last_point.a = cursor.x.a + last_point.b = cursor.y.a + var segment = shapes.findClosestSegment(last_point) + if (segment) { + shapes.removeSegment(segment) + } + } + exports.move = function(e, cursor){ + last_point.a = cursor.x.a + last_point.b = cursor.y.a + var segment = shapes.findClosestSegment(last_point) + if (segment) { + document.body.style.cursor = "pointer" + last_point.a = segment.x + last_point.b = segment.y + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + } + else { + document.body.style.cursor = "crosshair" + } + } + return exports +}) diff --git a/public/assets/javascripts/rectangles/engine/map/tools/line.js b/public/assets/javascripts/rectangles/engine/map/tools/line.js new file mode 100644 index 0000000..8175d66 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/line.js @@ -0,0 +1,63 @@ +// Tool is used to define a very simple line between two points. +// It is used by the BlueprintScaler to specify a sample distance to scale. + +var LineTool = MapTool.extend(function(base){ + var exports = {} + + var selected_point = null + + var line = exports.line = [] + + var can_drag, dragging + + exports.down = function(e, cursor){ + + // rightclick? + if (e.ctrlKey || e.which === 3) { + cursor.quantize(1/map.zoom) + app.router.blueprintView.map.center.a = cursor.x.a + app.router.blueprintView.map.center.b = -cursor.y.a + cursor.x.b = cursor.x.a + cursor.y.b = cursor.y.a + return + } + + this.cursor = cursor + switch (line.length) { + case 0: + line[0] = cursor.x_component() + can_drag = true + break + case 1: + line[1] = cursor.x_component() + can_drag = false + break + case 2: + line[0] = cursor.x_component() + line.pop() + can_drag = true + break + } + } + + exports.move = function(e, cursor){ + this.cursor = cursor + } + + exports.drag = function(e, cursor){ + if (dragging) { + line[1].a = cursor.x.b + line[1].b = cursor.y.b + } + else if (can_drag && cursor.magnitude() > 10/map.zoom) { + line[1] = cursor.y_component() + dragging = true + } + } + + exports.up = function(e, cursor){ + can_drag = dragging = false + } + + return exports +})
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/tools/ortho.js b/public/assets/javascripts/rectangles/engine/map/tools/ortho.js new file mode 100644 index 0000000..374c822 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/ortho.js @@ -0,0 +1,118 @@ +// Tool to make a polyline where all walls are orthogonal + +var OrthoPolylineTool = MapTool.extend(function (base) { + + var prev_point, horizontal = false, first_edge_is_horizontal = false + + var exports = {} + exports.down = function(e, cursor){ + // rightclick? + if (e.ctrlKey || e.which === 3) { + e.preventDefault() + e.stopPropagation() + if (map.ui.placing) { + // close polyline or cancel + map.ui.placing = false + if (shapes.workline.points.length > 2) { + shapes.workline.build() + shapes.add(shapes.workline) + } + else { + shapes.workline.reset() + } + return + } + else { + map.ui.tools.position.rightclick(e, cursor) + } + return + } + + // compare to initial point + var p = last_point.clone() + if (map.ui.placing) { + if (shapes.workline.lastPoint().eq(p)) { + return + } + else if (shapes.workline.canCloseWith(p)) { + shapes.workline.close() + shapes.workline.build() + shapes.add(shapes.workline) + map.ui.placing = false + } + else { + shapes.workline.add(p) + prev_point = p + horizontal = ! horizontal + } + } + else { + map.ui.placing = true + shapes.workline = new OrthoPolyline () + shapes.workline.add(p) + first_point = prev_point = p + horizontal = false + } + } + exports.move = function(e, cursor){ + last_point.a = cursor.x.a + last_point.b = cursor.y.a + if (map.ui.placing) { + if (shapes.workline.points.length == 1) { + var x = abs(prev_point.a - last_point.a) + var y = abs(prev_point.b - last_point.b) + if (x > y) { + last_point.b = prev_point.b + first_edge_is_horizontal = horizontal = true + } + else { + last_point.a = prev_point.a + first_edge_is_horizontal = horizontal = false + } + } + else { + if (horizontal) { + last_point.b = prev_point.b + } + else { + last_point.a = prev_point.a + } + if (horizontal == first_edge_is_horizontal) { + // check if this point is within N pixels of the normal + // and lock it into place if so + if (horizontal && abs( first_point.a - last_point.a ) < 10/map.zoom) { + last_point.a = first_point.a + } + else if (! horizontal && abs( first_point.b - last_point.b ) < 10/map.zoom) { + last_point.b = first_point.b + } + } + } + + if (shapes.workline.canCloseWith(last_point)) { + document.body.style.cursor = "pointer" + last_point.assign(first_point) + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + } + return + } + var end_point = shapes.findClosestEndPoint(last_point) + if (end_point) { + document.body.style.cursor = "pointer" + last_point.assign(end_point.point) + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + return + } + else { + document.body.style.cursor = "crosshair" + } + } + exports.cancel = function(){ + if (map.ui.placing) { shapes.workline.reset() } + first_point = null + map.ui.placing = false + } + return exports +})
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/tools/polyline.js b/public/assets/javascripts/rectangles/engine/map/tools/polyline.js new file mode 100644 index 0000000..445ae26 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/polyline.js @@ -0,0 +1,73 @@ +// Tool used to draw polylines with arbitrary angles + +var PolylineTool = MapTool.extend(function (base) { + var exports = {} + exports.down = function(e, cursor){ + + // rightclick? + if (e.ctrlKey || e.which === 3) { + e.preventDefault() + e.stopPropagation() + if (map.ui.placing) { + // close polyline or cancel + map.ui.placing = false + if (shapes.workline.points.length > 2) { + shapes.workline.build() + shapes.add(shapes.workline) + } + else { + shapes.workline.reset() + } + return + } + map.ui.tools.position.rightclick(e, cursor) + return + } + + // compare to initial point + var p = last_point.clone() + if (map.ui.placing) { + if (shapes.workline.canCloseWith(p)) { + shapes.workline.close() + shapes.workline.build() + shapes.add(shapes.workline) + map.ui.placing = false + } + else { + shapes.workline.add(p) + } + } + else { + map.ui.placing = true + shapes.workline = new Polyline () + shapes.workline.add(p) + } + } + exports.move = function(e, cursor){ + last_point.a = cursor.x.a + last_point.b = cursor.y.a + if (map.ui.placing && shapes.workline.canCloseWith(last_point)) { + document.body.style.cursor = "pointer" + last_point.assign(shapes.workline.points[0]) + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + return + } + var end_point = shapes.findClosestEndPoint(last_point) + if (end_point) { + document.body.style.cursor = "pointer" + last_point.assign(end_point.point) + cursor.x.a = cursor.x.b = last_point.a + cursor.y.a = cursor.y.b = last_point.b + return + } + else { + document.body.style.cursor = "crosshair" + } + } + exports.cancel = function(){ + if (map.ui.placing) { shapes.workline.reset() } + map.ui.placing = false + } + return exports +})
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/tools/position.js b/public/assets/javascripts/rectangles/engine/map/tools/position.js new file mode 100644 index 0000000..f8365bc --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/position.js @@ -0,0 +1,19 @@ +// Tool used to set position on the map and let you change the view by dragging. + +var PositionTool = MapTool.extend(function(base){ + var exports = { + recenterCursor: false, + drag: function(e, cursor){ + map.center.a = -cursor.x.magnitude() + map.center.b = cursor.y.magnitude() + }, + rightclick: function(e, cursor){ + cursor.quantize(1/map.zoom) + map.center.a = cursor.x.a + map.center.b = -cursor.y.a + cursor.x.b = cursor.x.a + cursor.y.b = cursor.y.a + } + } + return exports +})
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/map/tools/start.js b/public/assets/javascripts/rectangles/engine/map/tools/start.js new file mode 100644 index 0000000..203a85f --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/tools/start.js @@ -0,0 +1,29 @@ +// Tool is used to set the start position on the map. + +var StartPositionTool = MapTool.extend(function(base){ + var exports = {} + + var selected_point = null + + var line = exports.line = [] + + var can_drag, dragging + + exports.down = function(e, cursor){ + // rightclick? + if (e.ctrlKey || e.which === 3) { + cursor.quantize(1/map.zoom) + app.router.blueprintView.map.center.a = cursor.x.a + app.router.blueprintView.map.center.b = -cursor.y.a + cursor.x.b = cursor.x.a + cursor.y.b = cursor.y.a + return + } + + cam.x = app.controller.startPosition.x = cursor.x.a + cam.z = app.controller.startPosition.z = cursor.y.a + } + + return exports + +}) diff --git a/public/assets/javascripts/rectangles/engine/map/ui_editor.js b/public/assets/javascripts/rectangles/engine/map/ui/editor.js index 7308344..3e1d720 100644 --- a/public/assets/javascripts/rectangles/engine/map/ui_editor.js +++ b/public/assets/javascripts/rectangles/engine/map/ui/editor.js @@ -73,7 +73,7 @@ Map.UI.Editor = function(map){ app.tube("builder-destroy-room", room) // TODO: watch individual scenery object here - Minotaur.watch( app.router.editorView.settings ) + Minotaur.watch( app.controller.settings ) return } else if (intersects.length) { @@ -205,7 +205,7 @@ Map.UI.Editor = function(map){ Rooms.rebuild() // TODO: watch individual scenery object here - Minotaur.watch( app.router.editorView.settings ) + Minotaur.watch( app.controller.settings ) } var intersects = Rooms.filter(function(r){ @@ -247,7 +247,7 @@ Map.UI.Editor = function(map){ Rooms.rebuild() // TODO: watch individual scenery object here - Minotaur.watch( app.router.editorView.settings ) + Minotaur.watch( app.controller.settings ) wheelState = null }, 250) @@ -268,7 +268,7 @@ Map.UI.Editor = function(map){ Rooms.rebuild() // TODO: watch individual scenery object here - Minotaur.watch( app.router.editorView.settings ) + Minotaur.watch( app.controller.settings ) wheelState = null }, 250) diff --git a/public/assets/javascripts/rectangles/engine/map/ui_minimap.js b/public/assets/javascripts/rectangles/engine/map/ui/minimap.js index 0fdd336..0fdd336 100644 --- a/public/assets/javascripts/rectangles/engine/map/ui_minimap.js +++ b/public/assets/javascripts/rectangles/engine/map/ui/minimap.js diff --git a/public/assets/javascripts/rectangles/engine/map/ui/ortho.js b/public/assets/javascripts/rectangles/engine/map/ui/ortho.js new file mode 100644 index 0000000..5be7446 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/map/ui/ortho.js @@ -0,0 +1,107 @@ +Map.UI = Map.UI || {} +Map.UI.Ortho = function(map){ + + var base = this + var last_event = null + + base.creating = base.dragging = base.resizing = false + + base.mouse = new mouse({ + el: map.el, + down: function(e, cursor){ + last_event = e + cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom) + cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom) + if (tool.recenterCursor) { + cursor.x.add(map.center.a) + cursor.y.sub(map.center.b) + base.tools[currentTool].down(e, cursor) + } + else { + base.tools[currentTool].down(e, cursor) + cursor.x.add(map.center.a) + cursor.y.sub(map.center.b) + } + }, + move: function(e, cursor){ + last_event = e + cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom) + cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom) + if (tool.recenterCursor) { + cursor.x.add(map.center.a) + cursor.y.sub(map.center.b) + base.tools[currentTool].move(e, cursor) + } + else { + base.tools[currentTool].move(e, cursor) + cursor.x.add(map.center.a) + cursor.y.sub(map.center.b) + } + }, + drag: function(e, cursor){ + last_event = e + cursor.x.b = ((cursor.x.b/map.dimensions.a)+0.5) * map.dimensions.a / map.zoom + cursor.y.b = ((cursor.y.b/map.dimensions.b)-0.5) * map.dimensions.b / map.zoom + if (tool.recenterCursor) { + cursor.x.b += map.center.a + cursor.y.b -= map.center.b + base.tools[currentTool].drag(e, cursor) + } + else { + base.tools[currentTool].drag(e, cursor) + cursor.x.b += map.center.a + cursor.y.b -= map.center.b + } + }, + up: function(e, cursor, new_cursor){ + last_event = e + new_cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom) + new_cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom) + if (tool.recenterCursor) { + new_cursor.x.add(map.center.a) + new_cursor.y.sub(map.center.b) + base.tools[currentTool].up(e, cursor, new_cursor) + } + else { + base.tools[currentTool].up(e, cursor, new_cursor) + new_cursor.x.add(map.center.a) + new_cursor.y.sub(map.center.b) + } + if (nextTool) { + console.log('found nextTool') + base.set_tool(nextTool) + nextTool = null + } + } + }) + + var currentTool = "polyline", nextTool, tool + base.add_tool = function(name, tool){ + base.tools[name] = tool + } + base.set_tool = function(s){ + console.log("set tool to", s) + if (base.tools[currentTool]) { + base.tools[currentTool].cancel() + } + currentTool = s + tool = base.tools[currentTool] + } + base.set_drag_tool = function(s){ + console.log('set drag tool to', s) + nextTool = currentTool + currentTool = s + tool = base.tools[currentTool] + base.tools[currentTool].down(last_event, base.mouse.cursor) + } + base.tools = {} + + base.wheel = new wheel({ + el: map.el, + update: mousewheel, + }) + + function mousewheel (e, deltaY, deltaX){ + map.set_zoom(map.zoom_exponent - deltaY/20) + } +} diff --git a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js index 46c1d7f..9aff33f 100644 --- a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js +++ b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js @@ -37,6 +37,7 @@ base.list = {} base.regions = [] + base.shapesMode = false base.uid = new UidGenerator(base.list) @@ -95,6 +96,7 @@ } base.rebuild = function(walls_data){ + if (base.shapesMode) return walls_data = walls_data || Walls.serialize() Rooms.clipper.update() Rooms.builder.rebuild() @@ -124,6 +126,45 @@ }) Rooms.rebuild(walls_data) } + + base.deserializeFromShapes = function(data, walls_data) { + walls_data = walls_data || Walls.serialize() + base.shapesMode = true + window.viewHeight = data.viewHeight || app.defaults.viewHeight + window.wallHeight = data.wallHeight || app.defaults.wallHeight + $(".units").val( data.units ) + + Rooms.builder.clear() + + shapes.deserialize( data.shapes ) + // shapes.build() + var regions = RegionList.build() + + regions.forEach(function(region){ + var room = new Room({ + rect: region, + regions: [region], + height: wallHeight, + }) + + room.sides = region.sides + region.id = Rooms.uid("room_") + Rooms.list[ region.id ] = room + var mx_walls = Rooms.builder.build_walls(region) + room.mx_floor = Rooms.builder.make_floor(room, region) + room.mx_ceiling = Rooms.builder.make_ceiling(room, region) + + mx_walls.forEach(function(mx){ scene.add(mx) }) + scene.add(room.mx_floor) + scene.add(room.mx_ceiling) + }) + + Rooms.grouper.build() + + Walls.paint() + Walls.deserialize(walls_data) + app.tube("rooms-built") + } base.report = function(){ var data = [] diff --git a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js index d52fe21..6203c20 100644 --- a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js +++ b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js @@ -11,6 +11,7 @@ var Scenery = new function(){ base.init = function(){ base.resize.init() + base.sound.init() } base.add = function(opt){ @@ -113,6 +114,35 @@ var Scenery = new function(){ return added } + base.rewindAll = function(){ + base.forEach(function(scenery){ + if (scenery.type == "video") scenery.seek(0) + }) + } + base.playAll = function(){ + base.forEach(function(scenery){ + if (scenery.type == "video") { + scenery.unmute() + scenery.play() + } + }) + } + base.pauseAll = function(){ + base.forEach(function(scenery){ + if (scenery.type == "video") scenery.pause() + }) + } + base.muteAll = function(){ + base.forEach(function(scenery){ + if (scenery.type == "video") scenery.mx.mute() + }) + } + base.unmuteAll = function(){ + base.forEach(function(scenery){ + if (scenery.type == "video") scenery.mx.unmute() + }) + } + return base } diff --git a/public/assets/javascripts/rectangles/engine/scenery/move.js b/public/assets/javascripts/rectangles/engine/scenery/move.js index f57ddba..94c6281 100644 --- a/public/assets/javascripts/rectangles/engine/scenery/move.js +++ b/public/assets/javascripts/rectangles/engine/scenery/move.js @@ -28,7 +28,7 @@ Scenery.move = function(base){ base.remove() return } - + // load the modal app.controller.pick(base) @@ -75,13 +75,14 @@ Scenery.move = function(base){ switch (base.wall.side) { case FRONT: case BACK: - base.mx.x = position.a + delta.a * cos(wall_rotation[base.wall.side]) + dimension.a / 2 + base.mx.x = position.a + delta.a * cos(base.wall.rotationY) + dimension.a / 2 break case LEFT: case RIGHT: - base.mx.z = position.a + delta.a * sin(wall_rotation[base.wall.side]) + dimension.a / 2 + base.mx.z = position.a + delta.a * sin(base.wall.rotationY) + dimension.a / 2 break } + if (editor.permissions.resize) { Scenery.resize.move_dots() } @@ -104,7 +105,6 @@ Scenery.move = function(base){ dragging = moved = false oldState = null document.body.classList.remove("dragging") - } function switch_wall (e, target, cursor){ diff --git a/public/assets/javascripts/rectangles/engine/scenery/resize.js b/public/assets/javascripts/rectangles/engine/scenery/resize.js index 5af7f3f..252af74 100644 --- a/public/assets/javascripts/rectangles/engine/scenery/resize.js +++ b/public/assets/javascripts/rectangles/engine/scenery/resize.js @@ -8,6 +8,7 @@ Scenery.resize = new function(){ var dragging = false var naturalDimension, naturalDimensionCopy, dimension, position, scale var oldState + var rotationY var dots = [], dot, selected_dot @@ -49,7 +50,8 @@ Scenery.resize = new function(){ // rotate the dots as appropriate base.rotate_dots = function(){ - rotationY = wall_rotation[obj.wall.side] + // console.trace() + rotationY = obj.wall.rotationY dots.forEach(function(dot){ dot.rotationY = rotationY }) @@ -93,6 +95,7 @@ Scenery.resize = new function(){ base.add_dots() base.rotate_dots() base.move_dots() + Sculpture.resize.hide() } // dismiss the dots on blur diff --git a/public/assets/javascripts/rectangles/engine/scenery/sound.js b/public/assets/javascripts/rectangles/engine/scenery/sound.js new file mode 100644 index 0000000..5a783d6 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/scenery/sound.js @@ -0,0 +1,16 @@ + +Scenery.sound = {} +Scenery.sound.max_distance = 1500 +Scenery.sound.init = function(){ + app.tube.on("move", Scenery.sound.move) +} +Scenery.sound.move = function(){ + Scenery.forEach(function(scenery){ + if ((scenery.type == "video" || scenery.type == "audio")) { // && ! scenery.muted()) { + var distance = dist(cam.x, cam.z, + scenery.mx.x, scenery.mx.z) + var volume = 1 - (clamp( distance, 0, Scenery.sound.max_distance ) / Scenery.sound.max_distance) + scenery.setVolume(volume) + } + }) +} diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/_object.js b/public/assets/javascripts/rectangles/engine/scenery/types/_object.js index cd3f981..e3b9b4d 100644 --- a/public/assets/javascripts/rectangles/engine/scenery/types/_object.js +++ b/public/assets/javascripts/rectangles/engine/scenery/types/_object.js @@ -90,11 +90,11 @@ Scenery.types.base = Fiber.extend(function(base){ switch (this.wall.side) { case FRONT: case BACK: - this.position.a += delta.a * cos(wall_rotation[this.wall.side]) + this.position.a += delta.a * cos(this.wall.rotationY) break case LEFT: case RIGHT: - this.position.a += delta.a * sin(wall_rotation[this.wall.side]) + this.position.a += delta.a * sin(this.wall.rotationY) break } }, diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/audio.js b/public/assets/javascripts/rectangles/engine/scenery/types/audio.js index 82f984e..fdd221d 100644 --- a/public/assets/javascripts/rectangles/engine/scenery/types/audio.js +++ b/public/assets/javascripts/rectangles/engine/scenery/types/audio.js @@ -39,6 +39,10 @@ Scenery.types.audio = Scenery.types.base.extend(function(base){ this.dimensions.deserialize(data.dimensions) }, + setVolume: function(n){ + this.mx.setVolume(n) + }, + play: function(){ this.mx.play() }, diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/video.js b/public/assets/javascripts/rectangles/engine/scenery/types/video.js index d1b1763..163e19e 100644 --- a/public/assets/javascripts/rectangles/engine/scenery/types/video.js +++ b/public/assets/javascripts/rectangles/engine/scenery/types/video.js @@ -38,7 +38,8 @@ Scenery.types.video = Scenery.types.base.extend(function(base){ backface: false, }) scene.add(this.mx) - this.mx.load() + + this.mx.load() }, play: function(){ @@ -90,6 +91,14 @@ Scenery.types.video = Scenery.types.base.extend(function(base){ } }, + unmute: function(){ + this.mx.unmute() + }, + + setVolume: function(n){ + this.mx.setVolume(n) + }, + serialize: function(){ var data = base.serialize.call(this) return data diff --git a/public/assets/javascripts/rectangles/engine/scenery/undo.js b/public/assets/javascripts/rectangles/engine/scenery/undo.js index 1232780..b976ea2 100644 --- a/public/assets/javascripts/rectangles/engine/scenery/undo.js +++ b/public/assets/javascripts/rectangles/engine/scenery/undo.js @@ -195,5 +195,70 @@ }, }, + // + { + type: "create-sculpture", + undo: function(state){ + Sculpture.remove(state.id) + Sculpture.resize.hide() + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + }, + redo: function(state){ + var scenery = Sculpture.deserialize([ state ]) + Sculpture.resize.show( sculpture ) + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + }, + }, + { + type: "update-sculpture", + undo: function(state){ + var sculpture = Sculpture.find(state.id) + + sculpture.deserialize(state) + + if (editor.permissions.resize) { + Sculpture.resize.show(sculpture) + } + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + }, + redo: function(state){ + var sculpture = Sculpture.find(state.id) + + sculpture.deserialize(state) + + if (editor.permissions.resize) { + Sculpture.resize.show(sculpture) + Sculpture.resize.move_dots() + Sculpture.resize.rotate_dots() + } + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + }, + }, + { + type: "destroy-sculpture", + undo: function(state){ + var sculpture = Sculpture.deserialize([ state ]) + Sculpture.resize.show( sculpture ) + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + }, + redo: function(state){ + Sculpture.resize.hide() + Sculpture.remove(state.id) + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + }, + }, + ]) })() diff --git a/public/assets/javascripts/rectangles/engine/sculpture/_sculpture.js b/public/assets/javascripts/rectangles/engine/sculpture/_sculpture.js new file mode 100644 index 0000000..888b925 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/sculpture/_sculpture.js @@ -0,0 +1,105 @@ + +var Sculpture = new function(){ + + var base = this; + + base.list = {} + + base.mouse = new mouse ({ use_offset: false, mousedownUsesCapture: true }) + + base.init = function(){ + app.on("move", base.updateBillboards) + base.resize.init() + } + + base.updateBillboards = function(){ + base.forEach(function(sculpture){ + if (sculpture.billboard) { + sculpture.mx.rotationY = cam.rotationY + } + }) + if (Sculpture.resize.obj && Sculpture.resize.obj.billboard) { + Sculpture.resize.move_dots() + } + } + + base.add = function(opt){ + var sculpture + switch (opt.media.type) { + case 'image': + sculpture = new Sculpture.types.image (opt) + break + } + base.list[sculpture.id] = sculpture + return sculpture + } + + base.addNext = function(opt){ + opt.newMedia = true + opt.media = Scenery.nextMedia + var sculpture = base.add(opt) + + // test if sculpture was placed here + if (! sculpture) { + return null + } + else { + Scenery.nextMedia = null + return sculpture + } + } + + base.find = function(id){ + return base.list[id] || null + } + + base.remove = function(id){ + var scene_media = base.list[id] + delete base.list[id] + scene_media && scene_media.destroy() + } + + base.removeAll = function(){ + base.forEach(function(scene_media){ + base.remove(scene_media.id) + }) + } + + base.uid = new UidGenerator(base.list) + + base.forEach = function(f){ + return base.values().forEach(f) + } + + base.map = function(f){ + return base.values().map(f) + } + + base.values = function(){ + return _.values(base.list) + } + + base.serialize = function(){ + var sculptures = base.map(function(sculpture){ + return sculpture.serialize() + }) + return sculptures + } + + base.deserialize = function(sculpture_data){ + var added = [] + sculpture_data.forEach(function(data){ + var scene_media = base.add({ + id: data.id, + data: data, + media: data.media, + }) + added.push(scene_media) + }) + return added + } + + return base +} + +Sculpture.types = {} diff --git a/public/assets/javascripts/rectangles/engine/sculpture/move.js b/public/assets/javascripts/rectangles/engine/sculpture/move.js new file mode 100644 index 0000000..0cbeccd --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/sculpture/move.js @@ -0,0 +1,111 @@ + +Sculpture.move = function(base){ + + var x, y, z, position, dimension, bounds + var rotationY + var dragging = false, altPressed = false, shiftPressed = false, moved = false + var oldState + var height, width + + this.bind = function(){ + Sculpture.mouse.bind_el(base.mx.el) + Sculpture.mouse.on("down", down) + Sculpture.mouse.on("drag", drag) + Sculpture.mouse.on("up", up) + } + + this.unbind = function(){ + base.focused = false + Sculpture.mouse.unbind_el(base.mx.el) + Sculpture.mouse.off("down", down) + Sculpture.mouse.off("drag", drag) + Sculpture.mouse.off("up", up) + } + + function down (e, cursor){ + if (e.target != base.mx.el && (e.target != base.mx.overlay)) return; + if (editor.permissions.destroy) { + base.remove() + return + } + + // load the modal + app.controller.pick(base) + + if (! base.focused) { + e.clickAccepted = false + base.focused = true + return + } + if (! (editor.permissions.move || editor.permissions.resize) ) { + e.clickAccepted = false + return + } + altPressed = (e.altKey && ! base.billboard) + shiftPressed = e.shiftKey + dragging = true + moved = false + x = base.mx.x + y = base.mx.y + z = base.mx.z + rotationY = base.mx.rotationY + height = base.mx.height * base.mx.scale + + bounds = base.bounds + dimension = base.dimensions + + oldState = base.serialize() + document.body.classList.add("dragging") + } + + function drag (e, cursor){ + if (! dragging) return + + moved = true + + var delta = cursor.delta() + delta.mul( cursor_amp ) // TODO: this should be proportional to your distance from the wall + delta.b *= -1 + + // here we need to move the element based on the XY coords, as + // base.mx.y = position.b + delta.b + dimension.b / 2 + if (shiftPressed) { + base.mx.y = clamp( y + delta.b, height/2 + sculpture_distance_from_floor, Infinity ) + } + else if (altPressed) { + base.mx.rotationY = mod( rotationY + delta.a / (window.innerWidth / 2) , TWO_PI ) + Sculpture.resize.move_dots() + } + else { + base.mx.x = x + delta.a * cos(cam.rotationY) - delta.b * sin(cam.rotationY) + base.mx.z = z + delta.a * sin(cam.rotationY) + delta.b * cos(cam.rotationY) + } + + if (editor.permissions.resize) { + Sculpture.resize.move_dots() + base.updateOutline() + } + } + + function up (e, cursor){ + if (! dragging || ! oldState) return + + if (moved) { + UndoStack.push({ + type: 'update-sculpture', + undo: oldState, + redo: base.serialize(), + }) + + // TODO: watch individual sculpture object here + Minotaur.watch( app.router.editorView.settings ) + } + + dragging = moved = false + oldState = null + document.body.classList.remove("dragging") + } + + return this + +}
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/sculpture/resize.js b/public/assets/javascripts/rectangles/engine/sculpture/resize.js new file mode 100644 index 0000000..5f21d66 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/sculpture/resize.js @@ -0,0 +1,208 @@ +Sculpture.resize = new function(){ + + var base = this + + var obj + var x, y, z, bounds + var dragging = false + var naturalDimension, naturalDimensionCopy, dimension, position, scale + var oldState + var rotationY + + 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 * 2 + dot.scale = 0.5 + 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) + }) + } + + // move all the dots to the object's current position + base.move_dots = function(){ + rotationY = obj.mx.rotationY + + var x = obj.mx.x + sin(rotationY) * dot_distance_from_picture + var y = obj.mx.y + var z = obj.mx.z - cos(rotationY) * dot_distance_from_picture + + dots.forEach(function(dot){ + base.move_dot(dot, { x: x, y: y, z: z, rotationY: rotationY }) + }) + } + + // move a dot .. to the initial position of the image + base.move_dot = function(dot, pos){ + if (dot.side & TOP) { + pos.y += obj.dimensions.b / 2 + } + if (dot.side & BOTTOM) { + pos.y -= obj.dimensions.b / 2 + } + if (dot.side & LEFT) { + pos.x -= cos(rotationY) * (obj.dimensions.a) / 2 + pos.z -= sin(rotationY) * (obj.dimensions.a) / 2 + } + if (dot.side & RIGHT) { + pos.x += cos(rotationY) * (obj.dimensions.a) / 2 + pos.z += sin(rotationY) * (obj.dimensions.a) / 2 + } + dot.move(pos) + } + + // pick a new object to focus on and show the dots + base.show = function(new_object) { + // if (obj === new_object) return + if (! new_object) return + base.obj = obj = new_object + base.add_dots() + base.move_dots() + } + + // dismiss the dots on blur + var dotsHideTimeout; + base.defer_hide = function(){ + clearTimeout(dotsHideTimeout) + + dotsHideTimeout = setTimeout(function(){ + if (Scenery.hovering || Scenery.resize.hovering || Scenery.mouse.down) return + Scenery.resize.hide() + }, dot_hide_delay) + } + + base.hide = function () { + if (! obj) return + base.obj = obj = null + dots.forEach(function(dot){ + scene.remove(dot) + }) + } + + base.bind = function(){ + dots.forEach(function(dot){ + Sculpture.mouse.bind_el(dot.el) + }) + Sculpture.mouse.on("down", down) + Sculpture.mouse.on("drag", drag) + Sculpture.mouse.on("up", up) + } + + base.unbind = function(){ + dots.forEach(function(dot){ + Sculpture.mouse.unbind_el(dot.el) + }) + Sculpture.mouse.off("down", down) + Sculpture.mouse.off("drag", drag) + Sculpture.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 + + naturalDimension = obj.naturalDimensions + dimension = obj.dimensions + position = new vec3(obj.mx.x, obj.mx.y, obj.mx.z) + oldState = obj.serialize() + + if (obj.type == "text") { + naturalDimensionCopy = naturalDimension.clone() + positionCopy = position.clone() + } + + document.body.classList.add("dragging") + } + + function drag (e, cursor){ + if (! dragging) return + + 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 width = cursor.x.magnitude() + var height = cursor.y.magnitude() + var mag = cursor.magnitude() + + if (abs(width) > abs(height)) { + mag = x_sign * mag * sign(width) + } + else { + mag = y_sign * mag * sign(height) + } + + if (obj.type == "text") { + obj.mx.width = obj.media.width = naturalDimension.a = naturalDimensionCopy.a + (mag * 2) + obj.mx.height = obj.media.height = naturalDimension.b = naturalDimensionCopy.b + (mag * 2) + dimension.a = naturalDimension.a * obj.scale + dimension.b = naturalDimension.b * obj.scale + } + else { + obj.set_scale( ( dimension.a + mag ) / naturalDimension.a ) + } + + if (selected_dot.side & LEFT_RIGHT) { + obj.mx.x = position.a + cos(rotationY) * mag/2 * (x_sign) + obj.mx.z = position.c + sin(rotationY) * mag/2 * (x_sign) + } + if (selected_dot.side & TOP_BOTTOM) { + obj.mx.y = position.b - mag/2 * y_sign + } + + base.move_dots() + obj.updateOutline() + + app.controller.sculptureEditor.setDimensions() + } + + function up (e, cursor){ + if (! dragging) return + dragging = false + if (! editor.permissions.resize) { return } + + obj.scale = obj.mx.ops.scale = obj.mx.scale + obj.dimensions.assign(obj.naturalDimensions).mul(obj.scale) + + UndoStack.push({ + type: 'update-sculpture', + undo: oldState, + redo: obj.serialize(), + }) + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + + document.body.classList.remove("dragging") + selected_dot = null + } +}
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/sculpture/types/_object.js b/public/assets/javascripts/rectangles/engine/sculpture/types/_object.js new file mode 100644 index 0000000..2f593e8 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/sculpture/types/_object.js @@ -0,0 +1,176 @@ +Sculpture.types.base = Fiber.extend(function(base){ + + var exports = { + + init: function(opt){ + this.id = opt.id || Sculpture.uid("sculpture") + // this.move = new Sculpture.move (this) + this.media = opt.media + this.naturalDimensions = new vec3(this.media.width, this.media.height, this.media.width) + this.dimensions = new vec3(this.media.width, this.media.height, this.media.width) + + this.move = new Sculpture.move (this) + this.billboard = true + this.backface = true + + this.set_scale( opt.scale || this.media.scale || 1.0 ) + this.position = new vec2(0,0) + + this.isSculpture = true + }, + + set_scale: function(scale){ + this.scale = scale || 1.0 + if (this.mx) { + this.mx.scale = this.mx.ops.scale = this.scale + } + this.dimensions = this.naturalDimensions.clone().mul(this.scale) + }, + set_depth: function(depth){ + console.log(this.dimensions.c, this.naturalDimensions.c, depth) + this.dimensions.c = depth + this.naturalDimensions.c = depth / this.scale + }, + + place: function(opt){ + if (opt.data) { + this.deserialize(opt.data) + } + else { + this.mx.move(opt.position) + } + }, + + bind: function(){ + this.move.bind() + }, + + unbind: function(){ + this.move.unbind() + }, + + setOutline: function(val){ + this.outline = val + if (val && ! this.outlineEl) { + this.buildOutline() + } + this.outlineEl.el.style.display = val ? "block" : "none" + }, + setOutlineColor: function(val){ + this.outlineColor = val + if (this.outlineEl) { + this.outlineColor = this.outlineEl.el.style.borderColor = val + } + }, + setBillboard: function(val){ + this.billboard = val + if (this.billboard) { + this.mx.el.classList.add("backface-hidden") + this.mx.el.classList.remove("backface-visible") + this.mx.rotationY = cam.rotationY + } + else { + this.mx.el.classList.add("backface-visible") + this.mx.el.classList.remove("backface-hidden") + this.mx.rotationY = quantize(cam.rotationY, PI/2) + } + Sculpture.resize.move_dots() + }, + + buildOutline: function(){ + this.outlineEl = new MX.Object3D(".mx-outline") + this.outlineEl.width = this.naturalDimensions.a + this.outlineEl.height = this.naturalDimensions.c + this.outlineEl.scale = this.mx.scale + this.outlineEl.rotationX = -PI/2 + this.outlineEl.x = this.mx.x + this.outlineEl.y = sculpture_distance_from_floor + this.outlineEl.z = this.mx.z + this.outlineEl.el.style.borderColor = this.outlineColor || "#000000" + scene.add(this.outlineEl) + }, + updateOutline: function(){ + if (! this.outline) { return } + if (! this.outlineEl) { + this.buildOutline() + } + this.outlineEl.x = this.mx.x + this.outlineEl.y = sculpture_distance_from_floor + this.outlineEl.z = this.mx.z + this.outlineEl.width = this.naturalDimensions.a + this.outlineEl.height = this.naturalDimensions.c + + this.outlineEl.scale = this.mx.scale + }, + + remove: function(){ + if (this.removed) return + this.removed = true + + UndoStack.push({ + type: 'destroy-sculpture', + undo: this.serialize(), + redo: { id: this.id }, + }) + + // TODO: watch individual scenery object here + Minotaur.watch( app.router.editorView.settings ) + + Sculpture.remove(this.id) + + Sculpture.resize.hide() + if (app.controller.sculptureEditor) { + app.controller.sculptureEditor.tainted = false + app.controller.sculptureEditor.hide() + } + }, + + destroy: function(){ + this.unbind() + scene.remove(this.mx) + this.mx.media = null + this.mx.ops = null + this.mx = null + this.move = null + this.media = null + this.dimensions = null + this.naturalDimensions = null + this.wall = null + this.bounds = null + this.center = null + }, + + serialize: function(){ + var data = { + id: this.id, + dimensions: this.dimensions.serialize(), + position: app.position(this.mx), + scale: this.scale, + media: this.media, + billboard: this.billboard, + outline: this.outline, + outlineColor: this.outlineColor || "#000000", + backface: this.backface, + } + return data + }, + + deserialize: function(data){ + this.mx.move(data.position) + this.mx.ops.width = data.dimensions.a + this.mx.ops.height = data.dimensions.b + this.billboard = data.billboard + this.outline = data.outline + this.outlineColor = data.outlineColor || "#000000" + this.backface = data.backface + if (! this.backface) this.mx.el.classList.remove("backface-visible") + this.dimensions.deserialize(data.dimensions) + if (this.outline) { + this.buildOutline() + } + }, + } + + return exports + +}) diff --git a/public/assets/javascripts/rectangles/engine/sculpture/types/image.js b/public/assets/javascripts/rectangles/engine/sculpture/types/image.js new file mode 100644 index 0000000..1a53f5b --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/sculpture/types/image.js @@ -0,0 +1,38 @@ + +Sculpture.types.image = Sculpture.types.base.extend(function(base){ + + var exports = { + + type: 'image', + + init: function(opt){ + + opt.scale = opt.scale || (opt.data && opt.data.scale) || DEFAULT_PICTURE_WIDTH / max(DEFAULT_PICTURE_WIDTH, opt.media.width) + + base.init.call(this, opt) + + this.build(opt) + this.bind() + this.place(opt) + }, + + build: function(opt){ + this.mx = new MX.Image({ + src: this.media.url, + scale: this.scale, + media: this.media, + backface: true, + }) + + if (opt.position) { + opt.position.y = opt.position.y || this.scale * this.media.height/2 + sculpture_distance_from_floor + opt.position.rotationY = opt.position.rotationY || scene.camera.rotationY + } + + scene.add( this.mx ) + }, + + } + + return exports +}) diff --git a/public/assets/javascripts/rectangles/engine/shapes/ortho.js b/public/assets/javascripts/rectangles/engine/shapes/ortho.js new file mode 100644 index 0000000..163f646 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/shapes/ortho.js @@ -0,0 +1,66 @@ +// An OrthoPolyline is a Polyline where all angles are 90 degrees. + +if (! ('window' in this) ) { + var Polyline = require("./polyline.js") +} + +var OrthoPolyline = Polyline.extend(function(base){ + var exports = {} + exports.type = function(){ + return "ortho" + } + exports.instantiate = function(){ + return new OrthoPolyline + } + exports.canCloseWith = function(p){ + return (this.points.length > 2 && this.points[0].distanceTo( p ) < 10/map.zoom) + } + exports.draw = function(ctx, fillStyle, strokeStyle){ + var points = this.points + if (! points.length) return + if (points.length == 1) { + ctx.fillStyle = "#f80" + map.draw.dot_at(this.points[0].a, points[0].b, 5) + } + if (points.length > 1) { + ctx.fillStyle = fillStyle + ctx.strokeStyle = strokeStyle + ctx.lineWidth = 2 / map.zoom + ctx.beginPath() + ctx.moveTo(points[0].a, points[0].b) + points.forEach(function(point, i){ + i && ctx.lineTo(point.a, point.b) + }) + strokeStyle && ctx.stroke() + if (! map.ui.placing || this.closed) { + fillStyle && ctx.fill() + } + } + } + exports.translateSegment = function(src, dest, dx, dy) { + if (src[0].a == src[1].a) { + dest[0].a = src[0].a + dx + dest[1].a = src[1].a + dx + if (src.length == 3) { + dest[2].a = src[2].a + dx + } + } + else { + dest[0].b = src[0].b + dy + dest[1].b = src[1].b + dy + if (src.length == 3) { + dest[2].b = src[2].b + dy + } + } + } + exports.close = function(){ + this.points[this.points.length] = this.points[0] + this.closed = true + } + return exports +}) + + +if (! ('window' in this) ) { + module.exports = OrthoPolyline +} diff --git a/public/assets/javascripts/rectangles/engine/shapes/polyline.js b/public/assets/javascripts/rectangles/engine/shapes/polyline.js new file mode 100644 index 0000000..b2cd92f --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/shapes/polyline.js @@ -0,0 +1,212 @@ +// A Polyline is a set of points inputted by the user using the V2 editor to trace a blueprint. +// Additionally, it manages a set of MX objects which correspond to the walls in 3D. +// In this way, it attempts to bridge the 2D (canvas, imperative) and 3D (css, declarative) views. + +if (! ('window' in this) ) { + var Fiber = require("../../../vendor/bower_components/fiber/src/fiber.js") + var vec2 = require("../../models/vec2") +} + +var Polyline = Fiber.extend(function(base){ + var exports = {} + exports.init = function(){ + this.points = [] + this.mx_points = [] + this.closed = false + } + exports.type = function(){ + return "polyline" + } + exports.instantiate = function(){ + return new Polyline + } + exports.add = function(p){ + this.points.push( p ) + this.mx_points.push( new MX.Point(p) ) + } + exports.firstPoint = function(){ + return this.points[0] + } + exports.lastPoint = function(){ + return this.points[this.points.length-1] + } + exports.canCloseWith = function(p){ + return (this.points.length > 2 && this.points[0].distanceTo( p ) < 10/map.zoom) + } + exports.getHeadAtIndex = function(index){ + if (index == 0) { return null } + if (index == this.points.length-1) { return this.clone() } + var head = this.instantiate() + head.points = this.points.slice(0, index+1) + return head + } + exports.getTailAtIndex = function(index){ + if (index == this.points.length-1) { return null } + if (index == 0) { return this.clone() } + var tail = this.instantiate() + tail.points = this.points.slice(index, this.points.length) + return tail + } + exports.clone = function(){ + var clone = this.instantiate() + clone.points = this.points.concat() + } + exports.getSegment = function(segment){ + var seg = [ + this.points[segment.head], + this.points[segment.tail], + ] + if (segment.head == 0) { + seg.push( this.lastPoint() ) + } + else if (segment.tail == this.points.length-1) { + seg.push( this.firstPoint() ) + } + return seg + } + exports.cloneSegment = function(segment){ + return this.getSegment(segment).map(function(point){ return point.clone() }) + } + exports.translateSegment = function(src, dest, dx, dy){ + dest[0].a = src[0].a + dx + dest[0].b = src[0].b + dy + dest[1].a = src[1].a + dx + dest[1].b = src[1].b + dy + if (src.length == 3) { + dest[2].a = src[2].a + dx + dest[2].b = src[2].b + dy + } + } + exports.hasPointNear = function(p){ + var point + for (var i = 0; i < this.points.length; i++){ + point = this.points[i] + if (point.distanceTo( p ) < 10/map.zoom) { + return point + } + } + return null + } + exports.hasEndPointNear = function(p){ + if (this.closed || ! this.points.length) return null + if (this.firstPoint().distanceTo( p ) < 10/map.zoom) { + return this.firstPoint() + } + if (this.lastPoint().distanceTo( p ) < 10/map.zoom) { + return this.lastPoint() + } + return null + } + exports.hasSegmentNear = function(p, min_dist){ + var p1, p2, d1, d2, sum, rat + var dx, dy, new_x, new_y, x, y, closest_distance = min_dist || Infinity + var closest_i = -1 + var points = this.points + var p1, p2 = points[0] + for (var i = 1; i < points.length; i++) { + p1 = p2 + p2 = points[i] + d1 = p2.a - p1.a + d2 = p2.b - p1.b + sum = d1*d1 + d2*d2 + rat = ((p.a - p1.a) * d1 + (p.b - p1.b) * d2) / sum + rat = rat < 0 ? 0 : rat < 1 ? rat : 1 + new_x = p1.a + rat * d1 + new_y = p1.b + rat * d2 + dx = new_x - p.a + dy = new_y - p.b + sum2 = sqrt(dx*dx+dy*dy) + if (sum2 < closest_distance) { + x = new_x + y = new_y + closest_distance = sum2 + closest_i = i + } + } + if (closest_i == -1) return null + return { + x: x, + y: y, + distance: closest_distance, + head: closest_i-1, + tail: closest_i, + } + } + exports.draw = function(ctx, fillStyle, strokeStyle){ + var points = this.points + if (! points.length) return + if (points.length == 1) { + ctx.fillStyle = "#f80" + map.draw.dot_at(this.points[0].a, points[0].b, 5) + } + if (points.length > 1) { + ctx.fillStyle = fillStyle + ctx.strokeStyle = strokeStyle + ctx.lineWidth = 2 / map.zoom + ctx.beginPath() + ctx.moveTo(points[0].a, points[0].b) + points.forEach(function(point, i){ + i && ctx.lineTo(point.a, point.b) + }) + strokeStyle && ctx.stroke() + if (! map.ui.placing || this.closed) { + fillStyle && ctx.fill() + } + } + } + exports.draw_line = function (ctx, p){ + var last = this.points[this.points.length-1] + ctx.strokeStyle = "#f80" + ctx.lineWidth = 2 / map.zoom + ctx.beginPath() + ctx.moveTo(last.a, last.b) + ctx.lineTo(p.a, p.b) + ctx.stroke() + } + exports.close = function(){ + this.points[this.points.length] = this.points[0] + this.closed = true + } + exports.build = function(){ + this.mx_points && this.mx_points.forEach(function(mx){ scene.remove(mx) }) + this.mx = new MX.Polyline(this) + } + exports.rebuild = function(){ + this.mx.rebuild() + } + exports.getSegments = function(){ + if (this.points.length == 1) { + return [] + } + var segments = [] + for (var i = 1; i < this.points.length; i++) { + segments.push( [ this.points[i-1], this.points[i] ] ) + } + return segments + } + exports.serialize = function(){ + return { + type: this.type(), + closed: this.closed, + points: this.points.map(function(point){ return [point.a, point.b] }), + } + } + exports.deserialize = function(data){ + this.closed = data.closed || false + this.points = (data.points || data).map(function(point){ return new vec2(point[0], point[1]) }) + } + exports.reset = function(){ + this.mx_points.forEach(function(mx){ scene.remove(mx) }) + this.mx_points.length = 0 + this.points.length = 0 + } + exports.destroy = function(){ + this.reset() + this.mx && this.mx.destroy() + } + return exports +}) + +if (! ('window' in this) ) { + module.exports = Polyline +} diff --git a/public/assets/javascripts/rectangles/engine/shapes/regionlist.js b/public/assets/javascripts/rectangles/engine/shapes/regionlist.js new file mode 100644 index 0000000..8c9e732 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/shapes/regionlist.js @@ -0,0 +1,240 @@ +// This algorithm takes the polylines from ShapeList as input and produces +// a set of Rooms which can be used by the existing V1 mover and editor. + +// The algorithm assumes that +// 1) all angles are orthogonal +// 2) all polylines are closed + +if (! ('window' in this) ) { + var Fiber = require("../../../vendor/bower_components/fiber/src/fiber.js") + var vec2 = require("../../models/vec2") + var Rect = require("../../models/rect") + var sort = require("../../util/sort") +} + +var RegionList = (function(){ + + var RegionList = {} + var regions = RegionList.regions + + // Build a list of regions from the existing shapes. + // Operates on all the shapes at once. + RegionList.build = function(){ + var segments = RegionList.getSortedSegments() + return RegionList.buildRoomsFromSegments(segments) + } + + // Build a list of regions from the individual shapes. + // Same, but operates on the shapes individually + RegionList.buildByShape = function(){ + var shapes = RegionList.getSortedSegmentsByShape() + var region_lists = shapes.map(function(shape){ + return RegionList.buildRoomsFromSegments(shape.segments) + }) + var regions = [] + return regions.concat.apply(regions, region_lists); + } + + RegionList.buildRoomsFromSegments = function(segments){ + + var rooms = [] + var seen_rooms = {} + var open_segments = [] + + var segment, open_segment, x_segment, y_segments, overlapped, seen_segments + + // first pass: generate rooms from the vertical segments only + for (var i = 0; i < segments.length; i++) { + segment = segments[i] + if (! segment.isVertical()) { + continue + } + + // check all the "open segments" we know about, i.e. rooms where we've only found + // the right wall. + overlapped = false + for (var j = 0; j < open_segments.length; j++) { + open_segment = open_segments[j] + + // if these two segments overlap each other, then there is a room between them. + if (segment.y.overlaps(open_segment.y)) { + overlapped = true + open_segments.splice(j--, 1) + + // the X part of the room will be the span between these two vertical segments' + // X components. the Y part of the room is the "split" or subdivision between + // the two horizontal vectors. + + // the split function is non-commutative, + // so we need to call A split B and B split A, + // then dedupe the segments we got back.. + x_segment = new vec2( open_segment.x.a, segment.x.b ) + y_segments = open_segment.y.split(segment.y, 0, 0) + + seen_segments = {} + + // check each of the splits.. if the two segments overlap, then we definitely + // have a room here. + y_segments.forEach(function(seg){ + seen_segments[ seg[0]+"" ] = true + var room = new Rect( x_segment, seg[0] ) + + if (seen_rooms[ room+"" ]) return + seen_rooms[ room+"" ] = true + + room.sides = 0 + + rooms.push(room) + var new_seg = new Rect( segment.x, seg[0] ) + open_segments.unshift(new_seg) + j++ + }) + + // splitting the other way.. + y_segments = segment.y.split(open_segment.y, 0, 0) + y_segments.forEach(function(seg){ + if (seen_segments[ seg[0]+"" ]) return; + var new_seg = new Rect( segment.x, seg[0] ) + open_segments.unshift(new_seg) + j++ + }) + } + } + + // if we have overlap, then re-sort the open segments Y-wise + // and (again) dedupe.. + if (overlapped) { + open_segments = open_segments.sort(function(a,b){ + if (a.y.a < b.y.a) { return -1 } + if (a.y.a == b.y.a) { return 0 } + if (a.y.a > b.y.a) { return 1 } + }) + + if (open_segments.length < 2) { continue } + + for (var k = 1; k < open_segments.length; k++) { + if (open_segments[k-1].y.containsVec(open_segments[k].y)) { + open_segments.splice(k--, 1) + } + else if (open_segments[k-1].y.overlaps(open_segments[k].y)) { + open_segments[k].y = open_segments[k].y.clone() + open_segments[k].y.a = open_segments[k-1].y.b + } + } + } + // if we don't have any overlap, then this is a new open segment. + else { + open_segments.push(segment) + } + } + + var splits, splitter + + // second pass: now that we have a bunch of rooms, assign sides to all of them. + // sides are used in the "mover" script to do bounds checking. + for (var i = 0; i < segments.length; i++) { + segment = segments[i] + var horizontal = segment.isHorizontal(), vertical = segment.isVertical() + for (var r = 0; r < rooms.length; r++){ + room = rooms[r] + + // vertical segments determine the left and right edges of the room, fairly simply. + if (vertical) { + if (segment.y.containsVec(room.y)) { + if (segment.x.a == room.x.a) { + room.sides |= LEFT + } + if (segment.x.a == room.x.b) { + room.sides |= RIGHT + } + } + } + + // horizontal segments determine the front and back edges of the room. + if (horizontal) { + if (segment.x.containsVec(room.x)) { + if (segment.y.a == room.y.a) { + room.sides |= FRONT + } + if (segment.y.a == room.y.b) { + room.sides |= BACK + } + } + + // however, since we did not split on horizontal segments, our rooms may + // only have partial overlap with these segments, in which case we need to + // split the rooms apart. + else if ((segment.y.a == room.y.a || segment.y.a == room.y.b) && room.x.overlaps(segment.x)) { + + // split the room across the segment. preserve whether or not we know the + // room borders a segment on the left or right. + splits = room.x.split(segment.x, room.sides & LEFT, room.sides & RIGHT) + rooms.splice(r--, 1) + for (var k = 0; k < splits.length; k++) { + splitter = splits[k] + var new_room = new Rect( splitter[0], room.y ) + new_room.sides = splitter[1] | ( room.sides & FRONT_BACK ) + if (segment.x.overlaps( splitter[0] )) { + if (segment.y.a == new_room.y.a) { + new_room.sides |= FRONT + } + if (segment.y.a == new_room.y.b) { + new_room.sides |= BACK + } + } + rooms.unshift(new_room) + r++ + } + } + + } + } + } + + return rooms + } + + // Gets a list of polylines from the ShapeList and sorts the segments. + RegionList.getSortedSegments = function(){ + // get a list of all segments from these polylines + var segments = shapes.getAllSegments() + + segments = segments.map(RegionList.segmentsToRects) + + return sort.rects_by_position(segments) + } + + RegionList.getSortedSegmentsByShape = function(){ + return shapes.getAllShapeSegments().map(function(shape){ + var segments = shape.map(RegionList.segmentsToRects) + return { + shape: shape, + segments: sort.rects_by_position(segments) + } + }) + } + + // re-orient a segment so it's either facing up or right and make it into a rect + RegionList.segmentsToRects = function(segment){ + // vertical + if (segment[0].a == segment[1].a) { + if (segment[0].b > segment[1].b) { + segment.push(segment.shift()) + } + } + // horizontal + else if (segment[0].b == segment[1].b) { + if (segment[0].a > segment[1].a) { + segment.push(segment.shift()) + } + } + return new Rect( segment[0].a, segment[0].b, segment[1].a, segment[1].b ) + } + + if (! ('window' in this) ) { + module.exports = RegionList + } + + return RegionList + +})()
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/engine/shapes/shapelist.js b/public/assets/javascripts/rectangles/engine/shapes/shapelist.js new file mode 100644 index 0000000..21beb76 --- /dev/null +++ b/public/assets/javascripts/rectangles/engine/shapes/shapelist.js @@ -0,0 +1,124 @@ +// The ShapeList manages the list of polylines which form a V2 layout. + +if (! ('window' in this) ) { + var Fiber = require("../../../vendor/bower_components/fiber/src/fiber.js") + var Polyline = require("./polyline.js") + var OrthoPolyline = require("./ortho.js") +} + +var ShapeList = Fiber.extend(function(base){ + var exports = {} + exports.init = function(){ + this.shapes = [] + this.workline = null + } + exports.add = function(shape){ + this.shapes.push(shape) + } + exports.remove = function(shape){ + var index = this.shapes.indexOf(shape) + if (index !== -1) { + this.shapes.splice(index, 1) + } + } + exports.destroy = function(){ + this.shapes.forEach(function(shape){ + shape.destroy() + }) + this.shapes = [] + } + exports.count = function(){ + return this.shapes.length + } + exports.removeSegment = function (segment){ + var shape = segment.shape + var head = shape.getHeadAtIndex(segment.head) + var tail = shape.getTailAtIndex(segment.tail) + this.remove(shape) + shape.destroy() + if (head) { + this.add(head) + head.build() + } + if (tail) { + this.add(tail) + tail.build() + } + } + exports.findClosestPoint = function (p){ + var point + for (var i = 0; i < this.shapes.length; i++) { + point = this.shapes[i].hasPointNear(p) + if (point) return { point: point, shape: this.shapes[i] } + } + return null + } + exports.findClosestEndPoint = function (p){ + var point + for (var i = 0; i < this.shapes.length; i++) { + point = this.shapes[i].hasEndPointNear(p) + if (point) return { point: point, shape: this.shapes[i] } + } + return null + } + exports.findClosestSegment = function (p){ + var segment = null, closest_segment = null + for (var i = 0; i < this.shapes.length; i++) { + segment = this.shapes[i].hasSegmentNear(p, 10) + if (segment && (! closest_segment || segment.distance < closest_segment.distance)) { + closest_segment = segment + closest_segment.shape = this.shapes[i] + } + } + return closest_segment + } + exports.forEach = function(fn){ + this.shapes.forEach(fn) + } + exports.getAllShapeSegments = function(){ + return this.shapes.map(function(shape){ return shape.getSegments() }) + } + exports.getAllSegments = function(){ + var segments = [] + this.shapes.forEach(function(shape){ + segments = segments.concat( shape.getSegments() ) + }) + return segments + } + exports.draw = function(ctx, fillStyle, strokeStyle) { + this.shapes.forEach(function(shape){ + shape.draw(ctx, fillStyle, strokeStyle) + }) + } + exports.serialize = function(){ + return this.shapes.map(function(shape){ + return shape.serialize() + }) + } + exports.deserialize = function(data){ + data && data.forEach(function(shape_data){ + var line + switch (shape_data.type) { + case 'ortho': + line = new OrthoPolyline() + break + default: + line = new Polyline() + break + } + line.deserialize(shape_data) + shapes.add(line) + }.bind(this)) + } + exports.build = function(){ + this.shapes.forEach(function(shape){ + shape.build() + }) + } + return exports +}) + +if (! ('window' in this) ) { + shapes = new ShapeList + module.exports = shapes +} diff --git a/public/assets/javascripts/rectangles/models/floor.js b/public/assets/javascripts/rectangles/models/floor.js index 51537f3..63eebcc 100644 --- a/public/assets/javascripts/rectangles/models/floor.js +++ b/public/assets/javascripts/rectangles/models/floor.js @@ -68,8 +68,43 @@ } return } + - if (Scenery.nextWallpaper) { + var offset = offsetFromPoint(e, mx.el) + if (! offset) { return } + + var x = mx.x + mx.width * (offset.left-0.5) + var z = mx.z + mx.height * (0.5-offset.top) + + if (Scenery.nextMedia) { + e.preventDefault() + + var sculpture = Sculpture.addNext({ + position: { x: x, y: 0, z: z }, + }) + + // scenery was not placed + if (! sculpture) { + e.stopPropagation() + return + } + + app.controller.toolbar.resetPermissions() + Sculpture.resize.show(sculpture) + Sculpture.hovering = true + + // app.controller.pick(sculpture) + + UndoStack.push({ + type: 'create-sculpture', + undo: { id: sculpture.id }, + redo: sculpture.serialize(), + }) + + // TODO: watch individual sculpture object here + Minotaur.watch( app.router.editorView.settings ) + } + else if (Scenery.nextWallpaper) { var oldState = base.serialize() base.wallpaper(Scenery.nextWallpaper) // Scenery.nextWallpaper = null @@ -98,7 +133,7 @@ this.mx.reverse() } } - + Floor.prototype.color = function(color){ this.$els.css("background-color", color) } @@ -118,7 +153,7 @@ background.scale = background.scale || 1 this.background = background - this.background.src = this.background.src.replace(/url\(\"?\'?/,"").replace(/\"?\'?\)/,"") + this.background.src = this.background.src.replace(/url\(\"?\'?/,"").replace(/\"?\'?\)/,"") if (this.background.src == "none") { this.wallpaperLoad(this.background.src) diff --git a/public/assets/javascripts/rectangles/models/rect.js b/public/assets/javascripts/rectangles/models/rect.js index c667cf5..a4756ed 100644 --- a/public/assets/javascripts/rectangles/models/rect.js +++ b/public/assets/javascripts/rectangles/models/rect.js @@ -39,6 +39,12 @@ Rect.prototype.clone = function(){ return new Rect( this.x.clone(), this.y.clone() ) } + Rect.prototype.x_component = function(){ + return new vec2( this.x.a, this.y.a ) + } + Rect.prototype.y_component = function(){ + return new vec2( this.x.b, this.y.b ) + } Rect.prototype.assign = function(r) { this.x.assign(r.x) this.y.assign(r.y) @@ -56,6 +62,12 @@ Rect.prototype.maxDimension = function(){ return abs(this.width) > abs(this.height) ? this.width : this.height } + Rect.prototype.isVertical = function(){ + return this.x.isPoint() + } + Rect.prototype.isHorizontal = function(){ + return this.y.isPoint() + } Rect.prototype.mul = function(n){ this.x.mul(n) diff --git a/public/assets/javascripts/rectangles/models/room.js b/public/assets/javascripts/rectangles/models/room.js index 26bf055..1abe2ba 100644 --- a/public/assets/javascripts/rectangles/models/room.js +++ b/public/assets/javascripts/rectangles/models/room.js @@ -31,10 +31,14 @@ var Room = function(opt){ this.id = opt.id || Rooms.uid("room_") this.rect = opt.rect - this.regions = [] + this.regions = opt.regions || [] this.height = opt.height || 200 this.focused = false + + this.mx_walls = [] + this.mx_floor = [] + this.mx_ceiling = [] } Room.prototype.copy = function(){ @@ -125,6 +129,7 @@ Room.prototype.collidesDisc = function(src, dest, radius){ var x = dest.x, y = dest.z var collision = 0, wall_collision, contains_x, contains_y + this.regions.forEach(function(r){ if (! r.sides) return diff --git a/public/assets/javascripts/rectangles/models/vec2.js b/public/assets/javascripts/rectangles/models/vec2.js index 14d0e6b..8942d92 100644 --- a/public/assets/javascripts/rectangles/models/vec2.js +++ b/public/assets/javascripts/rectangles/models/vec2.js @@ -43,6 +43,9 @@ vec2.prototype.eq = function(v){ return this.a == v.a && this.b == v.b } + vec2.prototype.isPoint = function(){ + return this.a == this.b + } vec2.prototype.add = function(n){ this.a += n this.b += n @@ -80,6 +83,11 @@ this.a = Math.round(this.a) this.b = Math.round(this.b) } + vec2.prototype.distanceTo = function(v){ + var va = (this.a - v.a) + var vb = (this.b - v.b) + return Math.sqrt( va*va + vb*vb ) + } vec2.prototype.setPosition = function(n){ var len = this.length() this.a = n @@ -103,6 +111,12 @@ vec2.prototype.containsDisc = function(n,r){ return this.a <= n-r && n+r <= this.b } + vec2.prototype.containsVec = function(v){ + return this.a <= v.a && v.b <= this.b + } + vec2.prototype.containsCenterVec = function(v){ + return this.a < v.a && v.b < this.b + } vec2.prototype.clamp = function(n){ return clamp(n, this.a, this.b) } @@ -200,13 +214,13 @@ } vec2.prototype.toString = function(){ - return "[" + round(this.a) + " " + round(this.b) + "]" + return "[" + Math.round(this.a) + " " + Math.round(this.b) + "]" } vec2.prototype.exactString = function(){ return "[" + this.a + " " + this.b + "]" } vec2.prototype.serialize = function(){ - return [ round(this.a), round(this.b) ] + return [ Math.round(this.a), Math.round(this.b) ] } vec2.prototype.deserialize = function(data){ this.a = data[0] diff --git a/public/assets/javascripts/rectangles/models/vec3.js b/public/assets/javascripts/rectangles/models/vec3.js index c44dfe6..b3825a9 100644 --- a/public/assets/javascripts/rectangles/models/vec3.js +++ b/public/assets/javascripts/rectangles/models/vec3.js @@ -32,3 +32,28 @@ vec3.prototype.apply_projection = function (m) { return this; } + +vec3.prototype.serialize = function(){ + return [ round(this.a), round(this.b), round(this.c) ] +} +vec3.prototype.deserialize = function(data){ + this.a = data[0] + this.b = data[1] + this.c = data[2] || data[0] + return this +} +vec3.prototype.clone = function(){ + return new vec3(this.a, this.b, this.c) +} +vec3.prototype.assign = function(v){ + this.a = v.a + this.b = v.b + this.c = v.c + return this +} +vec3.prototype.mul = function(n) { + this.a *= n + this.b *= n + this.c *= n + return this +}
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/models/wall.js b/public/assets/javascripts/rectangles/models/wall.js index fc1ccbe..cf3cea8 100644 --- a/public/assets/javascripts/rectangles/models/wall.js +++ b/public/assets/javascripts/rectangles/models/wall.js @@ -10,6 +10,11 @@ vec2 = require('./vec2') Rect = require('./rect') UidGenerator = require('../util/uid') + wall_rotation = {} + wall_rotation[FRONT] = PI + wall_rotation[BACK] = 0 + wall_rotation[LEFT] = HALF_PI + wall_rotation[RIGHT] = -HALF_PI } var Wall = function(opt){ @@ -18,6 +23,7 @@ this.edge = opt.edge this.side = opt.side this.surface = opt.surface + this.rotationY = ('rotationY' in opt) ? opt.rotationY : wall_rotation[opt.side] this.mx = opt.mx this.background = { src: "none" } } @@ -89,7 +95,7 @@ mx_dot.move(mx_pos) mx_dot.width = 5 mx_dot.height = 5 - mx_dot.rotationY = wall_rotation[base.side] + mx_dot.rotationY = base.rotationY mx_dot.el.style.backgroundColor = "red" scene.add(mx_dot) } @@ -192,7 +198,7 @@ } Wall.prototype.serialize = function(){ - return { + return { id: this.id, background: this.background, } @@ -236,7 +242,7 @@ x: x, y: position.b + dimension.b / 2, z: z, - rotationY: wall_rotation[ this.side ], + rotationY: this.rotationY, } } Wall.prototype.mxToPosition = function(mx, dimension) { @@ -288,7 +294,7 @@ background.scale = background.scale || 1 this.background = background - this.background.src = this.background.src.replace(/url\(\"?\'?/,"").replace(/\"?\'?\)/,"") + this.background.src = this.background.src.replace(/url\(\"?\'?/,"").replace(/\"?\'?\)/,"") if (this.background.src == "none") { this.wallpaperLoad(this.background.src) diff --git a/public/assets/javascripts/rectangles/util/constants.js b/public/assets/javascripts/rectangles/util/constants.js index 3bc314c..522689b 100644 --- a/public/assets/javascripts/rectangles/util/constants.js +++ b/public/assets/javascripts/rectangles/util/constants.js @@ -22,10 +22,11 @@ var height_min = 200, resize_margin = 8, cursor_amp = 1.5, DEFAULT_PICTURE_WIDTH = 350, - MAP_GRID_SIZE = 360 // 10 feet + MAP_GRID_SIZE = 36 // 10 feet var painting_distance_from_wall = 10, - dot_distance_from_picture = 3 + dot_distance_from_picture = 3, + sculpture_distance_from_floor = 3 var dot_hide_delay = 50, // ms dot_side = 20 diff --git a/public/assets/javascripts/rectangles/util/coords.js b/public/assets/javascripts/rectangles/util/coords.js index 74b7fda..ff56199 100644 --- a/public/assets/javascripts/rectangles/util/coords.js +++ b/public/assets/javascripts/rectangles/util/coords.js @@ -30,4 +30,4 @@ function offsetFromPoint(event, element) { return 'left: ' + l + '%, top: ' + t + '%'; } } : null; -} +}
\ No newline at end of file diff --git a/public/assets/javascripts/rectangles/util/measurement.js b/public/assets/javascripts/rectangles/util/measurement.js index d6a0b35..6346eac 100644 --- a/public/assets/javascripts/rectangles/util/measurement.js +++ b/public/assets/javascripts/rectangles/util/measurement.js @@ -28,6 +28,10 @@ function measurementToString( n ) { case 'ft': ft = floor(n / 36) inch = abs(round((n % 36) / 3)) + if (inch == 12) { + inch = 0 + ft += 1 + } s = ft + "'" if (inch > 0) { s += " " + inch + '"' diff --git a/public/assets/javascripts/rectangles/util/mouse.js b/public/assets/javascripts/rectangles/util/mouse.js index cb36038..2b98cee 100644 --- a/public/assets/javascripts/rectangles/util/mouse.js +++ b/public/assets/javascripts/rectangles/util/mouse.js @@ -76,6 +76,14 @@ function mouse (opt) { window.addEventListener("mousemove", base.mousemove) window.addEventListener("mouseup", base.mouseup) } + base.unbind = function(){ + if (opt.el) { + opt.el.removeEventListener("mousedown", base.mousedown) + opt.el.removeEventListener("contextmenu", base.contextmenu) + } + window.removeEventListener("mousemove", base.mousemove) + window.removeEventListener("mouseup", base.mouseup) + } base.bind_el = function(el){ el.addEventListener("mousedown", base.mousedown) @@ -126,7 +134,7 @@ function mouse (opt) { } var x = pos.a, y = pos.b - + if (base.down) { base.cursor.x.b = x base.cursor.y.b = y diff --git a/public/assets/javascripts/rectangles/util/wheel.js b/public/assets/javascripts/rectangles/util/wheel.js index 712d470..4155a70 100644 --- a/public/assets/javascripts/rectangles/util/wheel.js +++ b/public/assets/javascripts/rectangles/util/wheel.js @@ -3,8 +3,8 @@ base.wheel = new wheel({ el: document.querySelector("#map"), - update: function(e, val, delta){ - // do something with val + update: function(e, delta){ + // do something with delta }, }) @@ -13,7 +13,7 @@ function wheel (opt) { opt = defaults(opt, { el: document, - fn: function(e, val, delta){}, + update: function(e, delta){}, propagate: false, locked: false, reversible: true, @@ -22,7 +22,7 @@ function wheel (opt) { }) opt.el.addEventListener('wheel', onMouseWheel, false); -// opt.el.addEventListener('mousewheel', onMouseWheel, false); + // opt.el.addEventListener('mousewheel', onMouseWheel, false); opt.el.addEventListener('DOMMouseScroll', onMouseWheel, false); function onMouseWheel (e) { @@ -58,6 +58,8 @@ function wheel (opt) { // opt.val = clamp(opt.val + delta, opt.min, opt.max) + // deltaX is also passed, but these values tend to be unusable + // try http://vvalls.com/assets/test/wheel.html with a trackpad opt.update(e, deltaY, deltaX) } |
