summaryrefslogtreecommitdiff
path: root/public/assets/javascripts/rectangles
diff options
context:
space:
mode:
Diffstat (limited to 'public/assets/javascripts/rectangles')
-rw-r--r--public/assets/javascripts/rectangles/_env.js44
-rw-r--r--public/assets/javascripts/rectangles/engine/map/_map.js43
-rw-r--r--public/assets/javascripts/rectangles/engine/map/draw.js183
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/_base.js11
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/arrow.js83
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/eraser.js29
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/line.js63
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/ortho.js118
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/polyline.js73
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/position.js19
-rw-r--r--public/assets/javascripts/rectangles/engine/map/tools/start.js29
-rw-r--r--public/assets/javascripts/rectangles/engine/map/ui/editor.js (renamed from public/assets/javascripts/rectangles/engine/map/ui_editor.js)77
-rw-r--r--public/assets/javascripts/rectangles/engine/map/ui/minimap.js (renamed from public/assets/javascripts/rectangles/engine/map/ui_minimap.js)4
-rw-r--r--public/assets/javascripts/rectangles/engine/map/ui/ortho.js107
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/_rooms.js72
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/_walls.js137
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/builder.js16
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/clipper.js3
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/grouper.js21
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/mover.js219
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/projector.js30
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/_scenery.js55
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/move.js58
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/randomize.js115
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/resize.js109
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/sound.js16
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/_object.js123
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/audio.js77
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/image.js19
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/text.js58
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/video.js35
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/undo.js161
-rw-r--r--public/assets/javascripts/rectangles/engine/sculpture/_sculpture.js105
-rw-r--r--public/assets/javascripts/rectangles/engine/sculpture/move.js111
-rw-r--r--public/assets/javascripts/rectangles/engine/sculpture/resize.js208
-rw-r--r--public/assets/javascripts/rectangles/engine/sculpture/types/_object.js176
-rw-r--r--public/assets/javascripts/rectangles/engine/sculpture/types/image.js38
-rw-r--r--public/assets/javascripts/rectangles/engine/shapes/ortho.js66
-rw-r--r--public/assets/javascripts/rectangles/engine/shapes/polyline.js212
-rw-r--r--public/assets/javascripts/rectangles/engine/shapes/regionlist.js240
-rw-r--r--public/assets/javascripts/rectangles/engine/shapes/shapelist.js124
-rw-r--r--public/assets/javascripts/rectangles/models/floor.js218
-rw-r--r--public/assets/javascripts/rectangles/models/mat4.js78
-rw-r--r--public/assets/javascripts/rectangles/models/rect.js62
-rw-r--r--public/assets/javascripts/rectangles/models/room.js27
-rw-r--r--public/assets/javascripts/rectangles/models/surface.js22
-rw-r--r--public/assets/javascripts/rectangles/models/vec2.js42
-rw-r--r--public/assets/javascripts/rectangles/models/vec3.js28
-rw-r--r--public/assets/javascripts/rectangles/models/wall.js219
-rw-r--r--public/assets/javascripts/rectangles/util/constants.js10
-rw-r--r--public/assets/javascripts/rectangles/util/coords.js33
-rw-r--r--public/assets/javascripts/rectangles/util/measurement.js4
-rw-r--r--public/assets/javascripts/rectangles/util/minotaur.js8
-rw-r--r--public/assets/javascripts/rectangles/util/mouse.js25
-rw-r--r--public/assets/javascripts/rectangles/util/permissions.js2
-rw-r--r--public/assets/javascripts/rectangles/util/undostack.js1
-rw-r--r--public/assets/javascripts/rectangles/util/wheel.js33
57 files changed, 3839 insertions, 460 deletions
diff --git a/public/assets/javascripts/rectangles/_env.js b/public/assets/javascripts/rectangles/_env.js
index 4b14a21..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) {
@@ -10,21 +26,30 @@ environment.init = function(){
"z": 0,
"rotationX": 0, // PI/2,
"rotationY": PI/2, // PI
-// "rotationX": 0,
-// "rotationY": PI
+ // "rotationX": 0,
+ // "rotationY": PI
})
scene.camera.radius = 20
}
-
- app.movements.gravity(true)
-
+
+ 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()
+ app.movements.gravity(true)
+
var minimap_el = document.querySelector("#minimap .el")
if (minimap_el) {
window.minimap = new Map ({
@@ -50,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 99ede82..ba3ec92 100644
--- a/public/assets/javascripts/rectangles/engine/map/_map.js
+++ b/public/assets/javascripts/rectangles/engine/map/_map.js
@@ -15,6 +15,8 @@ var Map = function(opt){
var base = this
base.el = opt.el
+ base.$el = $(base.el)
+ base.opt = opt
if (! base.el) return
@@ -32,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
@@ -40,15 +41,40 @@ var Map = function(opt){
}
base.set_zoom(opt.zoom)
+ base.resize = function(w, h){
+ if (w && h) {
+ canvas.width = base.dimensions.a = w
+ canvas.height = base.dimensions.b = h
+ }
+ else {
+ // resize here - esp if 2d-hires
+ canvas.width = base.dimensions.a = base.el.parentNode.offsetWidth
+ canvas.height = base.dimensions.b = base.el.parentNode.offsetHeight
+ }
+ }
+
+ base.toggle = function(state){
+ return $(base.el).toggle(state).is(':visible')
+ }
+
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)
@@ -56,20 +82,11 @@ 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.toggle = function(){
- $(base.el).toggle()
- }
-
}
Map.prototype.update = function(){
diff --git a/public/assets/javascripts/rectangles/engine/map/draw.js b/public/assets/javascripts/rectangles/engine/map/draw.js
index 3e185d2..4f4759f 100644
--- a/public/assets/javascripts/rectangles/engine/map/draw.js
+++ b/public/assets/javascripts/rectangles/engine/map/draw.js
@@ -5,79 +5,129 @@ 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.ruler()
+ 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" ])
+ 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 )
ctx.translate( map.center.a, map.center.b )
ctx.scale( -1, 1 )
- draw.regions(Rooms.regions, [ "#f8f8f8" ])
+ 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(){
- ctx.save()
+ var thumbnail_width = 1000
+ var thumbnail_height = 750
+
+ var extent = Rooms.extent()
+ var center = extent.center()
+ var extent_width = extent.width()
+ var extent_height = extent.height()
+ var zoom
+ if (extent_width > extent_height) {
+ zoom = thumbnail_width / extent.width() * 0.9
+ }
+ else {
+ zoom = thumbnail_height / extent.height() * 0.9
+ }
+
+ var canvas = document.createElement("canvas")
+ canvas.width = thumbnail_width
+ canvas.height = thumbnail_height
+ ctx = canvas.getContext('2d')
+
draw.clear()
- 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.translate( thumbnail_width * 1/2, thumbnail_height * 1/2)
+ ctx.scale( zoom, zoom )
+ ctx.translate( center.a, -center.b )
ctx.scale( -1, 1 )
- draw.regions(Rooms.regions, ["#fff"])
- draw.mouse(map.ui.mouse.cursor)
- scene && draw.camera(scene.camera)
-
+ draw.regions(Rooms.regions, ["#fff"], null)
+
ctx.restore()
+
+ // invert opacity
+ var pixelData = ctx.getImageData(0, 0, canvas.width, canvas.height)
+ var pixels = pixelData.data
+ for (var i = 0, k, _len = pixels.length; i < _len; i++) {
+ k = i*4
+ if (pixels[k+3] == 0) {
+ pixels[k] = pixels[k+1] = pixels[k+2] = pixels[k+3] = 255
+ }
+ else {
+ pixels[k] = pixels[k+1] = pixels[k+2] = 255
+ pixels[k+3] = 0
+ }
+ }
+ ctx.putImageData(pixelData, 0, 0)
+
+ // reset the ctx
+ ctx = draw.ctx
+
+ return canvas
}
draw.clear = function(){
- ctx.fillStyle = "rgba(255,255,255,0.98)"
ctx.clearRect(0, 0, map.dimensions.a, map.dimensions.b)
- ctx.fillRect(0, 0, map.dimensions.a, map.dimensions.b)
}
- draw.ruler = function (){
- ctx.strokeStyle = "rgba(80,80,80,0.5)"
- ctx.lineWidth = 1
- var len = 5
- for (var i = 0.5; i < map.dimensions.a; i += 10) {
- line(i, 0, i, len)
- line(0, i, len, i)
- }
+ draw.fill = function(fillStyle){
+ ctx.fillStyle = fillStyle
+ ctx.fillRect(0, 0, map.dimensions.a, map.dimensions.b)
}
- draw.regions = function(regions, colors){
+ draw.regions = function(regions, colors, stroke){
+ if (stroke) {
+ ctx.strokeStyle = "#000"
+ ctx.lineWidth = (1 / map.zoom)
+ }
for (var i = 0; i < regions.length; i++) {
if (regions[i].dupe) continue
ctx.fillStyle = colors[i % colors.length]
- ctx.strokeStyle = "#000"
- ctx.lineWidth = (1 / map.zoom)
fill_region(regions[i])
- stroke_sides(regions[i])
+ if (stroke) {
+ stroke_sides(regions[i])
+ }
}
}
@@ -88,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)
@@ -124,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(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)
+ 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
@@ -184,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);
@@ -196,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 f9334e6..699597a 100644
--- a/public/assets/javascripts/rectangles/engine/map/ui_editor.js
+++ b/public/assets/javascripts/rectangles/engine/map/ui/editor.js
@@ -26,6 +26,13 @@ Map.UI.Editor = function(map){
resize: true,
destroy: false,
})
+
+ base.blur = function(){
+ Rooms.forEach(function(r){
+ return r.focused = false
+ })
+ app.tube("builder-pick-nothing")
+ }
//
@@ -35,6 +42,7 @@ Map.UI.Editor = function(map){
cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom).sub(map.center.b)
if (e.ctrlKey || e.which === 3) {
+ if (Rooms.regions.length == 0) return
cursor.quantize(1/map.zoom)
map.center.a = cursor.x.a
map.center.b = -cursor.y.a
@@ -50,7 +58,7 @@ Map.UI.Editor = function(map){
return r.focused = r.rect.contains(cursor.x.a, cursor.y.a)
})
- if (intersects.length && base.permissions.destroy) {
+ if (intersects.length && (base.permissions.destroy || e.altKey)) {
base.mouse.down = false
room = intersects[0]
@@ -63,6 +71,9 @@ Map.UI.Editor = function(map){
Rooms.remove(room)
app.tube("builder-destroy-room", room)
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( (app.router.builderView || app.router.editorView).settings )
return
}
else if (intersects.length) {
@@ -161,18 +172,22 @@ Map.UI.Editor = function(map){
cursor.y.abs().quantize(1)
var room = Rooms.add_with_rect( cursor )
+ Rooms.rebuild()
+
UndoStack.push({
type: "create-room",
undo: { id: room.id },
redo: room.copy()
})
+ Rooms.rebuild()
app.tube("builder-pick-room", room)
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.controller.settings )
}
}
-
if (base.resizing || base.dragging) {
-
var oldState = base.dragging.copy()
if (base.resizing) {
@@ -187,24 +202,61 @@ Map.UI.Editor = function(map){
undo: oldState,
redo: base.dragging.copy()
})
+ Rooms.rebuild()
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( (app.router.builderView || app.router.editorView).settings )
}
+
+ var intersects = Rooms.filter(function(r){
+ return r.focused = r.rect.contains(cursor.x.a, cursor.y.a)
+ })
+ if (! base.dragging && ! intersects.length) {
+ app.tube("builder-pick-nothing")
+ }
base.creating = base.dragging = base.resizing = false
}
var wheelState, wheelTimeout
- function mousewheel (e, val, delta){
+ function mousewheel (e, deltaY, deltaX){
var cursor = base.mouse.cursor
var intersects = Rooms.filter(function(r){
- return r.focused = r.rect.contains(cursor.x.a, cursor.y.a)
+ return r.focused // = r.rect.contains(cursor.x.a, cursor.y.a)
})
- if (intersects.length) {
+ if (intersects.length && window.heightIsGlobal) {
+ var rooms = Rooms.values()
+ wheelState = wheelState || rooms[0].height
+ var height = clamp( ~~(rooms[0].height + deltaY * 2), height_min, height_max )
+ rooms.forEach(function(room){
+ room.height = height
+ })
+
+ app.tube("builder-pick-room", intersects[0])
+
+ clearTimeout(wheelTimeout)
+ wheelTimeout = setTimeout(function(){
+ UndoStack.push({
+ type: "update-rooms-height",
+ undo: wheelState,
+ redo: height
+ })
+ Rooms.rebuild()
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( (app.router.builderView || app.router.editorView).settings )
+
+ wheelState = null
+ }, 250)
+ }
+ else if (intersects.length) {
wheelState = wheelState || intersects[0].copy()
- intersects[0].height = clamp( ~~(intersects[0].height - delta), height_min, height_max )
+ intersects[0].height = clamp( ~~(intersects[0].height + deltaY * 2), height_min, height_max )
+ app.tube("builder-pick-room", intersects[0])
clearTimeout(wheelTimeout)
wheelTimeout = setTimeout(function(){
@@ -213,15 +265,20 @@ Map.UI.Editor = function(map){
undo: wheelState,
redo: intersects[0].copy()
})
- Rooms.clipper.update()
+ Rooms.rebuild()
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+
wheelState = null
- }, 500)
+ }, 250)
}
else {
- map.set_zoom(map.zoom_exponent - delta/20)
+ map.set_zoom(map.zoom_exponent + deltaY/20)
}
}
function rightclick (e){
}
+
}
diff --git a/public/assets/javascripts/rectangles/engine/map/ui_minimap.js b/public/assets/javascripts/rectangles/engine/map/ui/minimap.js
index fabbdb9..0fdd336 100644
--- a/public/assets/javascripts/rectangles/engine/map/ui_minimap.js
+++ b/public/assets/javascripts/rectangles/engine/map/ui/minimap.js
@@ -72,8 +72,8 @@ Map.UI.Minimap = function(map){
base.dragging = false
}
- function mousewheel (e, val, delta){
- map.set_zoom(map.zoom_exponent - delta/20)
+ function mousewheel (e, deltaY, deltaX){
+ map.set_zoom(map.zoom_exponent - deltaY/20)
}
function rightclick (e){
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 6f96275..9aff33f 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
@@ -13,6 +13,7 @@
vec2 = require('../../models/vec2')
Rect = require('../../models/rect')
Room = require('../../models/room')
+ Walls = require('./_walls')
UidGenerator = require('../../util/uid')
sort = require('../../util/sort')
_ = require('lodash')
@@ -36,6 +37,7 @@
base.list = {}
base.regions = []
+ base.shapesMode = false
base.uid = new UidGenerator(base.list)
@@ -64,13 +66,13 @@
base.remove = function(room){
delete base.list[room.id]
- Rooms.clipper.update()
+ Rooms.rebuild()
}
base.removeAll = function(){
base.list = {}
base.regions = []
- Rooms.clipper.update()
+ Rooms.rebuild()
}
base.count = function(){
@@ -80,6 +82,10 @@
base.forEach = function(f){
return base.values().forEach(f)
}
+
+ base.some = function(f){
+ return base.values().some(f)
+ }
base.map = function(f){
return base.values().map(f)
@@ -88,6 +94,17 @@
base.values = function(){
return _.values(base.list)
}
+
+ base.rebuild = function(walls_data){
+ if (base.shapesMode) return
+ walls_data = walls_data || Walls.serialize()
+ Rooms.clipper.update()
+ Rooms.builder.rebuild()
+ Rooms.grouper.build()
+ Walls.paint()
+ Walls.deserialize(walls_data)
+ app.tube("rooms-built")
+ }
base.serialize = function(){
var rooms = base.map(function(room){
@@ -96,7 +113,7 @@
return rooms
}
- base.deserialize = function(rooms_data){
+ base.deserialize = function(rooms_data, walls_data){
rooms_data.forEach(function(data){
if (! data || ! data.rect) return
var rect = new Rect(data.rect.x[0], data.rect.y[0], data.rect.x[1], data.rect.y[1])
@@ -107,7 +124,46 @@
})
base.add(room)
})
- Rooms.clipper.update()
+ 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(){
@@ -118,6 +174,14 @@
return data
}
+ base.extent = function(){
+ var extent = new Rect ( new vec2(Infinity, -Infinity), new vec2(Infinity, -Infinity) )
+ base.forEach(function(room){
+ extent.expand(room.rect)
+ })
+ return extent
+ }
+
base.sorted_by_position = function(){
return sort.rooms_by_position( base.values() )
}
diff --git a/public/assets/javascripts/rectangles/engine/rooms/_walls.js b/public/assets/javascripts/rectangles/engine/rooms/_walls.js
index 82ccb87..38dac84 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/_walls.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/_walls.js
@@ -35,8 +35,23 @@
var base = this
base.list = []
+ base.floors = []
base.lookup = {}
base.colors = {}
+
+ base.init = function(){
+ base.colors = base.copyColors( app.defaults.colors )
+ }
+
+ base.copyColors = function(colors){
+ var copy = {
+ wall: colors.wall.slice(),
+ outline: colors.outline.slice(),
+ floor: colors.floor.slice(),
+ ceiling: colors.ceiling.slice(),
+ }
+ return copy
+ }
base.first = function(){
for (var i in base.list) {
@@ -46,6 +61,10 @@
}
}
+ base.find = function(id){
+ return base.lookup[id]
+ }
+
base.assign = function(list){
base.list = list
base.lookup = {}
@@ -53,11 +72,21 @@
base.lookup[wall.id] = wall
})
}
+
+ base.assignFloors = function(floors){
+ base.floors = floors
+ floors.forEach(function(floor){
+ base.lookup[floor.id] = floor
+ })
+ }
base.bind = function(){
base.list.forEach(function(wall){
wall.bind()
})
+ base.floors.forEach(function(floor){
+ floor.bind()
+ })
}
base.count = function(){
@@ -65,16 +94,20 @@
}
base.forEach = function(f){
- return base.list.forEach(f)
+ return base.values().forEach(f)
}
base.map = function(f){
- return base.list.map(f)
+ return base.values().map(f)
}
-
+
+ base.values = function(){
+ return _.values(base.lookup)
+ }
+
base.serialize = function(){
var data = []
- base.list.forEach(function(wall){
+ base.forEach(function(wall){
data.push(wall.serialize())
})
return data
@@ -84,6 +117,9 @@
walls_data.forEach(function(wall_data){
if (! wall_data) { return }
var wall = base.lookup[ wall_data.id ]
+ if (! wall) {
+ return
+ }
wall.deserialize( wall_data )
})
},
@@ -93,21 +129,91 @@
var outlineColor = rgb_string(Walls.colors.outline)
var floorColor = rgb_string(Walls.colors.floor)
var ceilingColor = rgb_string(Walls.colors.ceiling)
- Walls.forEach(function(wall){
+ Walls.list.forEach(function(wall){
wall.outline(wallColor, outlineColor)
})
- Rooms.forEach(function(room){
- room.setFloorColor(floorColor)
- room.setCeilingColor(ceilingColor)
+ Walls.floors.forEach(function(floor){
+ if (floor.ceiling) floor.color(ceilingColor)
+ else floor.color(floorColor)
})
}
+ base.luminance = function(rgb){
+ rgb = rgb || Walls.colors.ceiling
+ var rgb_max = Math.max.apply(0, rgb)
+ var rgb_min = Math.min.apply(255, rgb)
+ return (rgb_max + rgb_min ) / 2
+ }
+
+ base.setBodyColor = function(){
+ $("#header").toggleClass("black", Walls.luminance() < 100)
+ $("body").css("background-color", rgb_string( Walls.colors.wall ))
+ }
+ base.clearBodyColor = function(){
+ $("#header").removeClass("black")
+ $("body").css("background-color", "transparent")
+ }
+
+ base.setWallpaper = {
+ wall: function(background){
+ var img = new Image ()
+ img.onload = function(){
+ Walls.list.forEach(function(wall){
+ wall.wallpaper(background, img)
+ })
+ }.bind(this)
+ img.src = background.src.replace(/url\(\"?\'?/,"").replace(/\"?\'?\)/,"")
+ img.complete && img.onload()
+ },
+ floor: function(background){
+ Walls.floors.forEach(function(floor){
+ if (floor.ceiling) return
+ floor.wallpaper(background)
+ })
+ },
+ ceiling: function(background){
+ Walls.floors.forEach(function(floor){
+ if (! floor.ceiling) return
+ floor.wallpaper(background)
+ })
+ },
+ }
+
+ base.clearWallpaper = {
+ wall: function(){
+ Walls.list.forEach(function(wall){
+ wall.wallpaper("none")
+ })
+ },
+ outline: function(){
+ },
+ floor: function(){
+ Walls.floors.forEach(function(floor){
+ if (floor.ceiling) return
+ floor.wallpaper("none")
+ })
+ },
+ ceiling: function(){
+ Walls.floors.forEach(function(floor){
+ if (! floor.ceiling) return
+ floor.wallpaper("none")
+ })
+ },
+ }
+
base.setColor = {
wall: function(rgb){
var rgbaColor = rgba_string(rgb, app.defaults.wallOpacity)
+ var rgbColor = rgb_string(rgb)
+
+ if (Rooms.mover.room) {
+ $("#header").toggleClass("black", base.luminance() < 100)
+ $("body").css("background-color", rgbColor)
+ }
+
Walls.colors.wall = rgb
- Walls.forEach(function(wall){
+ Walls.list.forEach(function(wall){
wall.outline(rgbaColor, null)
})
},
@@ -115,7 +221,7 @@
outline: function(rgb){
var rgbColor = rgb_string(rgb)
Walls.colors.outline = rgb
- Walls.forEach(function(wall){
+ Walls.list.forEach(function(wall){
wall.outline(null, rgbColor)
})
},
@@ -123,19 +229,20 @@
floor: function(rgb){
var rgbColor = rgb_string(rgb)
Walls.colors.floor = rgb
- Rooms.forEach(function(room){
- room.setFloorColor(rgbColor)
+ Walls.floors.forEach(function(floor){
+ if (floor.ceiling) return
+ floor.color(rgbColor)
})
},
ceiling: function(rgb){
var rgbColor = rgb_string(rgb)
Walls.colors.ceiling = rgb
- Rooms.forEach(function(room){
- room.setCeilingColor(rgbColor)
+ Walls.floors.forEach(function(floor){
+ if (! floor.ceiling) return
+ floor.color(rgbColor)
})
},
-
}
}
diff --git a/public/assets/javascripts/rectangles/engine/rooms/builder.js b/public/assets/javascripts/rectangles/engine/rooms/builder.js
index 6a89158..f78fb91 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/builder.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/builder.js
@@ -14,6 +14,7 @@
PI = Math.PI
HALF_PI = PI/2
TOP = CEILING, BOTTOM = FLOOR
+ $ = { browser: { mozilla: false } }
function sidesToString(sides){
var s = ""
if (sides & FRONT) s += "front "
@@ -32,19 +33,16 @@
var els = []
base.init = function(){
- base.bind()
+ // base.bind()
}
base.bind = function(){
- app.on("clip", base.rebuild.bind(base))
}
base.rebuild = function(){
if (window.scene) {
base.clear()
base.build()
- Rooms.grouper.build()
- app.tube("rooms-built")
}
}
@@ -264,6 +262,9 @@
el.rotationX = PI/2
el.rect = region
el.side = FLOOR
+ if ($.browser.mozilla) {
+ el.el.style.display = "none"
+ }
return el
}
this.make_ceiling = function (room, region) {
@@ -280,10 +281,13 @@
el.rotationX = -PI/2
el.rect = region
el.side = CEILING
+ if ($.browser.mozilla) {
+ el.el.style.display = "none"
+ }
return el
}
this.make_wall = function (klass) {
- // klass += ".backface-hidden"
+ klass += ".backface-hidden"
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
@@ -296,7 +300,7 @@
this.el = this.rect = this.face = null
}
- // possible if walls are opaque
+ // preferable if walls are opaque
// el.el.classList.add("backface-hidden")
return el
diff --git a/public/assets/javascripts/rectangles/engine/rooms/clipper.js b/public/assets/javascripts/rectangles/engine/rooms/clipper.js
index 33e3a84..df83cc8 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/clipper.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/clipper.js
@@ -38,7 +38,6 @@
base.update = function(){
base.solve_rects()
- app.tube("clip")
}
var regions
@@ -49,7 +48,7 @@
Rooms.regions = regions = []
return
}
-
+
base.reset_rects()
base.clip_rects()
var culled = base.cull_rects_iterative()
diff --git a/public/assets/javascripts/rectangles/engine/rooms/grouper.js b/public/assets/javascripts/rectangles/engine/rooms/grouper.js
index 663d29d..890a476 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/grouper.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/grouper.js
@@ -48,13 +48,16 @@
base.build = function (){
var walls = []
+ var floors = []
var collections = base.collect()
base.cull(collections)
base.group(walls, collections, FRONT)
base.group(walls, collections, BACK)
base.group(walls, collections, LEFT)
base.group(walls, collections, RIGHT)
+ base.groupFloors(floors, collections)
Walls.assign( walls )
+ Walls.assignFloors( floors )
Walls.bind()
}
base.collect = function(){
@@ -63,12 +66,16 @@
collections[BACK] = []
collections[LEFT] = []
collections[RIGHT] = []
+ collections[FLOOR] = []
+ collections[CEILING] = []
Rooms.forEach(function(room){
room.mx_walls.forEach(function(mx){
var side = mx.side || mx.half_side
collections[side].push(mx)
})
+ collections[FLOOR] = collections[FLOOR].concat( room.mx_floor )
+ collections[CEILING] = collections[CEILING].concat( room.mx_ceiling )
})
base.cull(collections)
@@ -161,6 +168,20 @@
return walls
}
+ base.groupFloors = function(floors, collections){
+ var floor = new Floor ({
+ id: 'floor',
+ side: FLOOR,
+ mx: collections[FLOOR]
+ })
+ var ceiling = new Floor ({
+ id: 'ceiling',
+ side: CEILING,
+ mx: collections[CEILING]
+ })
+ floors.push(floor)
+ floors.push(ceiling)
+ }
}
diff --git a/public/assets/javascripts/rectangles/engine/rooms/mover.js b/public/assets/javascripts/rectangles/engine/rooms/mover.js
index 7195fcc..ac27aba 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/mover.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/mover.js
@@ -4,6 +4,15 @@ Rooms.mover = new function(){
base.room = null
base.noclip = false
+ var cursor = base.cursor = new Rect (0,0,0,0)
+ var cursor_copy = new Rect (0,0,0,0)
+ var wall_vec = new Rect (0,0,0,0)
+ var intersect = new vec2(0,0)
+
+ var origins = new Rect()
+ origins.x = cursor.x
+ origins.y = wall_vec.x
+
base.init = function(){
base.bind()
base.update(scene.camera)
@@ -11,7 +20,8 @@ Rooms.mover = new function(){
base.bind = function(){
app.on("move", base.update)
- keys.on("backslash", function(){
+ keys.on("backslash", function(e){
+ if ( ! (e.ctrlKey || e.metaKey) ) return
base.noclip = ! base.noclip
base.room = null
app.movements.gravity( ! base.noclip )
@@ -19,15 +29,9 @@ Rooms.mover = new function(){
}
base.update = function(pos){
+ var intersect, collision, distance
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..
@@ -39,17 +43,41 @@ Rooms.mover = new function(){
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) {
- cam.x = (collision & LEFT_RIGHT) ? base.room.rect.x.clampDisc(pos.x, radius) : pos.x
- cam.z = (collision & FRONT_BACK) ? base.room.rect.y.clampDisc(pos.z, radius) : pos.z
- return
+ distance = sqrt(pow(cam.x-pos.x, 2), pow(cam.z-pos.z, 2))
+
+ // check if we've breached one of the walls.. clamp position if so.
+ // there are two algorithms for this now, ridiculously..
+ // - the first one is smoother, best for keyboard use
+ // but if the distance travelled is greater than the min. distance from the wall,
+ // then there is a possibility of ejection from the room. so is bad for phone/mousewheel.
+
+ // - the second one determines fortuitously if we have breached any of the walls
+ // however it can get jumpy if you run into a wall.. thus it is best for devices like phone or mousewheel
+ // the benefit is you will never leave the room.
+ if (base.noclip) {
+ // in no-clip mode we walk through walls.
}
+ else if (distance < radius) {
+ collision = base.room.collidesDisc(cam, pos, radius)
+
+ if (collision) {
+ cam.x = (collision & LEFT_RIGHT) ? base.room.rect.x.clampDisc(pos.x, radius) : pos.x
+ cam.z = (collision & FRONT_BACK) ? base.room.rect.y.clampDisc(pos.z, radius) : pos.z
+ return
+ }
+ }
+ else {
+ intersect = base.intersect(pos)
+ if (intersect) {
+ cam.x = intersect.a
+ cam.z = intersect.b
+ return
+ }
+ }
// in this case, we appear to have left the room..
- $(".face.active").removeClass("active")
+ // $(".face.active").removeClass("active")
+ Walls.clearBodyColor()
base.room = null
}
@@ -65,11 +93,162 @@ Rooms.mover = new function(){
// 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") })
+ if (! base.noclip) {
+ Walls.setBodyColor()
+ }
+ app.tube("change-room", { room: base.room })
}
-
}
+ base.intersect = function(pos){
+ var closest_intersect, closest_wall, new_t, wall_t, t, min_t = 1
+
+ cursor_copy.x.a = cursor.x.a = cam.x
+ cursor_copy.x.b = cursor.x.b = cam.z
+ cursor_copy.y.a = cursor.y.a = pos.x
+ cursor_copy.y.b = cursor.y.b = pos.z
+
+ cursor_copy.extend_ends(scene.camera.radius)
+
+ origins.x = cursor_copy.x
+ origins.y = wall_vec.x
+
+ Walls.list.forEach(function(wall, i){
+ var actually_intersects = false, intersecting_face
+
+ wall.get_points(wall_vec)
+
+ t = perp(origins, wall_vec) / ( perp(cursor_copy, wall_vec) || 0.0000001 )
+
+ if ( min_t < t || t < 0 || 1 < t ) return
+
+ intersect.a = cursor_copy.x.a + ( cursor_copy.y.a - cursor_copy.x.a ) * t
+ intersect.b = cursor_copy.x.b + ( cursor_copy.y.b - cursor_copy.x.b ) * t
+
+ if ( ! is_collinear( intersect, wall_vec ) ) return
+
+ if (wall.side & LEFT_RIGHT) {
+ intersecting_face = wall.surface.face_for_x(intersect.b)
+ }
+ else {
+ intersecting_face = wall.surface.face_for_x(intersect.a)
+ }
+
+ actually_intersects = !! (intersecting_face && intersecting_face.y.a == 0)
+ if (actually_intersects) {
+ closest_intersect = intersect.clone()
+ closest_wall = wall_vec.clone()
+ min_t = t
+ }
+ })
+
+ if (closest_intersect) {
+
+ var aw, len, dd
+ aw = angle(closest_wall)
+ wall_vec.assign(closest_wall)
+ wall_vec.x.a -= scene.camera.radius * sin(aw)
+ wall_vec.x.b += scene.camera.radius * cos(aw)
+ wall_vec.y.a -= scene.camera.radius * sin(aw)
+ wall_vec.y.b += scene.camera.radius * cos(aw)
+
+ origins.x = cursor.x
+ origins.y = wall_vec.x
+
+ new_t = perp(origins, wall_vec) / ( perp(cursor, wall_vec) || 0.0000001 )
+ wall_t = perp(origins, cursor) / ( perp(wall_vec, cursor) || 0.0000001 )
+
+ intersect.a = cursor.x.a + ( cursor.y.a - cursor.x.a ) * new_t
+ intersect.b = cursor.x.b + ( cursor.y.b - cursor.x.b ) * new_t
+
+ // here compare len to the length of the wall in the direction we are travelling
+ dd = dot2(diff(closest_wall), diff(cursor))
+ len = sqrt(dot(wall_vec, wall_vec))
+ if (dd > 0) {
+ len *= 1-abs(wall_t)
+ }
+ else {
+ len *= abs(wall_t)
+ aw += PI
+ }
+
+ len = clamp(len, 0, (1-min_t) * sqrt(dot(cursor, cursor)))
+
+ intersect.a += len * cos(aw)
+ intersect.b += len * sin(aw)
+
+ wall_vec.normalize()
+ intersect.a = clamp(intersect.a, wall_vec.x.a, wall_vec.y.a)
+ intersect.b = clamp(intersect.b, wall_vec.x.b, wall_vec.y.b)
+
+ return intersect
+ }
+ else {
+ return cursor.y
+ }
+ }
+ function diff (v) {
+ return new vec2(v.y.a - v.x.a, v.y.b - v.x.b)
+ }
+ function angle (va) {
+ return atan2(va.y.b - va.x.b, va.y.a - va.x.a)
+ }
+ function angle2 (pa, pb) {
+ return atan2(pb.b - pa.b, pb.a - pa.a)
+ }
+ function normal (va) {
+ return atan2(va.x.a - va.y.a, va.y.b - va.x.b)
+ }
+ function dot (va, vb) {
+ return (va.y.a - va.x.a) * (vb.y.a - vb.x.a) + (va.y.b - va.x.b) * (vb.y.b - vb.x.b)
+ }
+ function dot2 (pa, pb) {
+ return pa.a * pb.a + pa.b * pb.b
+ }
+ function perp (va, vb) {
+ return (va.y.a - va.x.a) * (vb.y.b - vb.x.b) - (va.y.b - va.x.b) * (vb.y.a - vb.x.a)
+ }
+ function is_collinear (p, vec) {
+ var on_x, on_y
+ var pa = round(p.a), pb = round(p.b)
+
+ if (vec.x.a < vec.y.a) {
+ on_x = vec.x.a <= pa && pa <= vec.y.a
+ }
+ else {
+ on_x = vec.x.a >= pa && pa >= vec.y.a
+ }
+
+ if (vec.x.b < vec.y.b) {
+ on_y = vec.x.b <= pb && pb <= vec.y.b
+ }
+ else {
+ on_y = vec.x.b >= pb && pb >= vec.y.b
+ }
+
+ return !! (on_x && on_y)
+ }
+ cursor_copy.extend_ends = function(n){
+ var a = angle(this)
+ this.x.a -= n*cos(a)
+ this.x.b -= n*sin(a)
+ this.y.a += n*cos(a)
+ this.y.b += n*sin(a)
+ return this
+ }
+ wall_vec.normalize = function(){
+ var carry
+ if (this.x.a > this.y.a) {
+// console.log("SWAP X")
+ carry = this.x.a
+ this.x.a = this.y.a
+ this.y.a = carry
+ }
+ if (this.x.b > this.y.b) {
+// console.log("SWAP Y")
+ carry = this.x.b
+ this.x.b = this.y.b
+ this.y.b = carry
+ }
+ }
}
diff --git a/public/assets/javascripts/rectangles/engine/rooms/projector.js b/public/assets/javascripts/rectangles/engine/rooms/projector.js
deleted file mode 100644
index 2eac314..0000000
--- a/public/assets/javascripts/rectangles/engine/rooms/projector.js
+++ /dev/null
@@ -1,30 +0,0 @@
-
-rooms.projector = new function(){
-
- projector = new THREE.Projector();
- vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
- projector.unprojectVector( vector, camera );
-
- raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
- intersects = raycaster.intersectObjects( scene.children, true );
-
-}
-
-
-
-THREE.Projector = function () {
-
- _viewProjectionMatrix = new THREE.Matrix4(),
-
- this.unprojectVector = function ( vector, camera ) {
- camera.projectionMatrixInverse.getInverse( camera.projectionMatrix );
-
- _viewProjectionMatrix.multiplyMatrices(
- camera.matrixWorld,
- camera.projectionMatrixInverse
- );
-
- return vector.applyProjection( _viewProjectionMatrix );
- };
-
-}
diff --git a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
index d34e299..6203c20 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
@@ -5,11 +5,13 @@ var Scenery = new function(){
base.list = {}
base.nextMedia = null
+ base.nextWallpaper = null
- base.mouse = new mouse ({ use_offset: false })
+ base.mouse = new mouse ({ use_offset: false, mousedownUsesCapture: true })
base.init = function(){
base.resize.init()
+ base.sound.init()
}
base.add = function(opt){
@@ -22,20 +24,35 @@ var Scenery = new function(){
case 'video':
case 'youtube':
case 'vimeo':
+ if (is_mobile) return
scene_media = new Scenery.types.video (opt)
break
+
+ case 'soundcloud':
+ if (is_mobile) return
+ scene_media = new Scenery.types.audio (opt)
+ break
+
+ case 'text':
+ scene_media = new Scenery.types.text (opt)
+ scene_media.focused = !! opt.newMedia
+ break
}
base.list[scene_media.id] = scene_media
return scene_media
}
base.addNextToWall = function(opt){
+ opt.newMedia = true
opt.media = base.nextMedia
opt.index = opt.index || 0
var scene_media = base.add(opt)
// test if scenery was placed here
- if (! scene_media.bounds) {
+ if (! scene_media) {
+ return null
+ }
+ else if (! scene_media.bounds) {
base.remove( scene_media.id )
return null
}
@@ -48,7 +65,7 @@ var Scenery = new function(){
base.find = function(id){
return base.list[id] || null
}
-
+
base.remove = function(id){
var scene_media = base.list[id]
delete base.list[id]
@@ -83,6 +100,7 @@ var Scenery = new function(){
}
base.deserialize = function(scenery_data){
+ var added = []
scenery_data.forEach(function(data){
var wall = Walls.lookup[data.wall_id] || Walls.first()
var scene_media = base.add({
@@ -91,9 +109,40 @@ var Scenery = new function(){
media: data.media,
id: data.id
})
+ added.push(scene_media)
})
+ 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 93bccb0..94c6281 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/move.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/move.js
@@ -2,7 +2,7 @@
Scenery.move = function(base){
var x, y, z, position, dimension, bounds
- var dragging = false
+ var dragging = false, moved = false
var oldState
this.bind = function(){
@@ -14,6 +14,7 @@ Scenery.move = function(base){
}
this.unbind = function(){
+ base.focused = false
Scenery.mouse.unbind_el(base.mx.el)
Scenery.mouse.off("down", down)
Scenery.mouse.off("enter", switch_wall)
@@ -22,29 +23,26 @@ Scenery.move = function(base){
}
function down (e, cursor){
- if (e.target != base.mx.el) return;
+ if (e.target != base.mx.el && (e.target != base.mx.overlay)) return;
if (editor.permissions.destroy) {
- UndoStack.push({
- type: 'destroy-scenery',
- undo: base.serialize(),
- redo: { id: base.id },
- })
-
- // TODO: watch individual scenery object here
- Minotaur.watch( app.router.editorView.settings )
-
- Scenery.remove(base.id)
+ 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
}
dragging = true
+ moved = false
x = base.mx.x
y = base.mx.y
z = base.mx.z
@@ -59,6 +57,8 @@ Scenery.move = function(base){
function drag (e, cursor){
if (! dragging) return
+
+ moved = true
var flipX = base.wall.side & (FRONT | RIGHT)
@@ -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()
}
@@ -89,21 +90,22 @@ Scenery.move = function(base){
function up (e, cursor){
if (! dragging || ! oldState) return
-
- dragging = false
- document.body.classList.remove("dragging")
-
- UndoStack.push({
- type: 'update-scenery',
- undo: oldState,
- redo: base.serialize(),
- })
+
+ if (moved) {
+ UndoStack.push({
+ type: 'update-scenery',
+ undo: oldState,
+ redo: base.serialize(),
+ })
- // TODO: watch individual scenery object here
- Minotaur.watch( app.router.editorView.settings )
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ }
+ dragging = moved = false
oldState = null
- }
+ document.body.classList.remove("dragging")
+ }
function switch_wall (e, target, cursor){
if (! dragging) return
@@ -142,7 +144,7 @@ Scenery.move = function(base){
if (editor.permissions.resize) {
Scenery.resize.rotate_dots()
-// Scenery.resize.move_dots()
+ Scenery.resize.move_dots()
}
cursor.x.a = cursor.x.b
diff --git a/public/assets/javascripts/rectangles/engine/scenery/randomize.js b/public/assets/javascripts/rectangles/engine/scenery/randomize.js
new file mode 100644
index 0000000..6581f38
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/scenery/randomize.js
@@ -0,0 +1,115 @@
+/*
+ // get the list of media we want to place
+ var media_objs = $(".mediaContainer").toArray().map(function(el){
+ return $(el).data("media")
+ })
+ Scenery.randomize.add( media_objs )
+*/
+
+Scenery.randomize = {};
+
+// Given a list of media objects, generate their ideal dimensions
+Scenery.randomize.get_dimensions = function (media_objs){
+ var media_list = media_objs.map(function(media){
+ var width, height
+ if (media.width > media.height) {
+ width = Math.min(DEFAULT_PICTURE_WIDTH, media.width)
+ height = media.height/media.width * width
+ }
+ else {
+ height = Math.min(DEFAULT_PICTURE_WIDTH, media.height)
+ width = media.width/media.height * height
+ }
+ return {
+ dimensions: new vec2( width, height ),
+ media: media,
+ }
+ })
+ return media_list
+}
+
+// Build the lookup of empty walls.
+// Takes a list of walls to use, or undefined to use all empty walls.
+// Returns a lookup of walls to use, keyed by wall ID.
+Scenery.randomize.get_empty_walls = function(wall_list){
+ // get a list of all walls
+ var walls = {}, removed = [];
+
+ (wall_list || Walls.list).forEach(function(wall){
+ walls[wall.id] = wall
+ })
+
+ // remove the walls that already have stuff on them
+ if (! wall_list) {
+ Scenery.forEach(function(scenery){
+ if (scenery.was_randomly_placed) {
+ // remove it and reuse this wall?
+ removed.push( scenery.serialize() )
+ Scenery.remove( scenery.id )
+ }
+ else {
+ delete walls[scenery.wall.id]
+ }
+ })
+ }
+
+ return { walls: walls, removed: removed }
+}
+
+// Randomly place a set of media objects on empty walls.
+// Only one object per wall will be added.
+// Optionally takes a list of walls to use.
+Scenery.randomize.add = function (media_objs, wall_list) {
+ var media_list = Scenery.randomize.get_dimensions(media_objs)
+ var empty_data = Scenery.randomize.get_empty_walls(wall_list)
+ var walls = empty_data.walls
+ var removed = empty_data.removed
+ var added = []
+
+ var wall_ids = _.keys(walls)
+ if (! wall_ids.length) { return }
+
+ // randomize walls
+ shuffle(wall_ids)
+ shuffle(media_list)
+
+ // assign each of the media to the walls, until we run out of either
+ media_list.some(function(media){
+ // bail if we're out of walls
+ if (wall_ids.length == 0) { return true }
+
+ var i, fits = -1
+
+ for (i = 0; i < wall_ids.length; i++) {
+ if (walls[wall_ids[i]].surface.fits(media.dimensions)) {
+ fits = i
+ break
+ }
+ }
+
+ if (fits != -1) {
+ var wall = walls[wall_ids[fits]]
+ wall_ids.splice(fits, 1)
+
+ var scenery = Scenery.add({
+ media: media.media,
+ wall: wall,
+ index: 0,
+ })
+ scenery.was_randomly_placed = true
+ added.push(scenery.serialize())
+ }
+ else {
+ // artwork won't fit anywhere??
+ }
+
+ return false
+ })
+
+ UndoStack.push({
+ type: "randomize-scenery",
+ undo: { added: added, removed: removed },
+ redo: { added: added, removed: removed },
+ })
+
+} \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/scenery/resize.js b/public/assets/javascripts/rectangles/engine/scenery/resize.js
index e26c0a7..252af74 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/resize.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/resize.js
@@ -6,8 +6,9 @@ Scenery.resize = new function(){
var obj
var x, y, z, bounds
var dragging = false
- var naturalDimension, dimension, position, scale
+ var naturalDimension, naturalDimensionCopy, dimension, position, scale
var oldState
+ var rotationY
var dots = [], dot, selected_dot
@@ -31,7 +32,8 @@ Scenery.resize = new function(){
// generate a dot element
base.build_dot = function(side) {
var dot = new MX.Object3D('.dot')
- dot.width = dot.height = dot_side
+ dot.width = dot.height = dot_side * 2
+ dot.scale = 0.5
dot.side = side
$(dot.el).on({
mouseenter: function(){ base.hovering = true },
@@ -48,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
})
@@ -56,45 +59,43 @@ Scenery.resize = new function(){
// move all the dots to the object's current position
base.move_dots = function(){
- x = obj.mx.x + sin(rotationY) * dot_distance_from_picture
- y = obj.mx.y
- z = obj.mx.z - cos(rotationY) * dot_distance_from_picture
+ 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)
+ base.move_dot(dot, { x: x, y: y, z: z })
})
}
// move a dot .. to the initial position of the image
- base.move_dot = function(dot){
- dot.x = x
- dot.y = y
- dot.z = z
-
+ base.move_dot = function(dot, pos){
if (dot.side & TOP) {
- dot.y += obj.mx.height * obj.mx.scale / 2
+ pos.y += obj.dimensions.b / 2
}
if (dot.side & BOTTOM) {
- dot.y -= obj.mx.height * obj.mx.scale / 2
+ pos.y -= obj.dimensions.b / 2
}
if (dot.side & LEFT) {
- dot.x -= cos(rotationY) * (obj.mx.width * obj.mx.scale) / 2
- dot.z -= sin(rotationY) * (obj.mx.width * obj.mx.scale) / 2
+ pos.x -= cos(rotationY) * (obj.dimensions.a) / 2
+ pos.z -= sin(rotationY) * (obj.dimensions.a) / 2
}
if (dot.side & RIGHT) {
- dot.x += cos(rotationY) * (obj.mx.width * obj.mx.scale) / 2
- dot.z += sin(rotationY) * (obj.mx.width * obj.mx.scale) / 2
+ 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
obj = new_object
-
base.add_dots()
base.rotate_dots()
base.move_dots()
+ Sculpture.resize.hide()
}
// dismiss the dots on blur
@@ -119,22 +120,13 @@ Scenery.resize = new function(){
base.bind = function(){
dots.forEach(function(dot){
Scenery.mouse.bind_el(dot.el)
- $(dot.el).bind({
- mouseenter: function(e){
- Scenery.resize.hovering = true
- },
- mouseleave: function(e){
- Scenery.resize.hovering = false
- base.defer_hide()
- }
- })
})
Scenery.mouse.on("down", down)
Scenery.mouse.on("drag", drag)
Scenery.mouse.on("up", up)
}
- this.unbind = function(){
+ base.unbind = function(){
dots.forEach(function(dot){
Scenery.mouse.unbind_el(dot.el)
})
@@ -146,15 +138,19 @@ Scenery.resize = new function(){
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)
- scale = obj.mx.scale
oldState = obj.serialize()
+
+ if (obj.type == "text") {
+ naturalDimensionCopy = naturalDimension.clone()
+ positionCopy = position.clone()
+ }
document.body.classList.add("dragging")
}
@@ -167,7 +163,6 @@ Scenery.resize = new function(){
var width = cursor.x.magnitude()
var height = cursor.y.magnitude()
var mag = cursor.magnitude()
- var old_width = dimension.a * scale
if (abs(width) > abs(height)) {
mag = x_sign * mag * sign(width)
@@ -176,18 +171,23 @@ Scenery.resize = new function(){
mag = y_sign * mag * sign(height)
}
- obj.set_scale( ( dimension.a + mag ) / naturalDimension.a )
- // dimension.a // scale * (old_width + mag) / old_width
+ 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 )
+ }
-// console.log(scale, obj.mx.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
- }
+ 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()
@@ -195,12 +195,28 @@ Scenery.resize = new function(){
}
function up (e, cursor){
+ if (! dragging) return
dragging = false
- selected_dot = null
if (! editor.permissions.resize) { return }
- obj.scale = obj.mx.ops.scale = obj.mx.scale
- obj.dimensions.assign(obj.naturalDimensions).mul(obj.scale)
+ if (obj.type == "text") {
+ var newHeight = $(obj.mx.inner).height()
+ if (selected_dot.side & BOTTOM) {
+ obj.mx.y = position.b + (naturalDimensionCopy.b - newHeight) / 2 * obj.scale
+ }
+ else {
+ obj.mx.y = dots[0].y - newHeight/2*obj.scale
+ }
+ obj.mx.height = obj.media.height = naturalDimension.b = newHeight
+ dimension.a = naturalDimension.a * obj.scale
+ dimension.b = naturalDimension.b * obj.scale
+ base.move_dots()
+ }
+ else {
+ obj.scale = obj.mx.ops.scale = obj.mx.scale
+ obj.dimensions.assign(obj.naturalDimensions).mul(obj.scale)
+ }
+
UndoStack.push({
type: 'update-scenery',
undo: oldState,
@@ -211,6 +227,7 @@ Scenery.resize = new function(){
Minotaur.watch( app.router.editorView.settings )
document.body.classList.remove("dragging")
+ selected_dot = null
}
}
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 2dbae48..e3b9b4d 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
@@ -28,32 +28,119 @@ Scenery.types.base = Fiber.extend(function(base){
this.dimensions = this.naturalDimensions.clone().mul(this.scale)
},
+ place: function(opt){
+ if (opt.data) {
+ if (opt.wall) {
+ var position = opt.wall.mxToPosition(opt.data.position)
+ opt.index = opt.wall.surface.index_for_x( position.a, 0 )
+ }
+ this.set_wall(opt)
+ this.deserialize(opt.data)
+ }
+ else {
+ this.set_wall(opt)
+ if (this.wall && ! this.bounds) {
+ this.find_minimum_scale(opt)
+ if (! this.bounds) return
+ }
+
+ this.recenter()
+ if (opt.position) {
+ this.translateTo(opt.position)
+ }
+ var mx_position = this.wall.positionToMx( this.position, this.dimensions )
+ this.mx.move(mx_position)
+ }
+ },
+
+ find_minimum_scale: function(opt){
+ var bounds = this.wall.surface.bounds_at_index_with_dimensions(opt.index || 0, new vec2(50, 50))
+ var scale = 1
+ if (! bounds || bounds.width() < 50 || bounds.height < 50) return
+ if (this.naturalDimensions.a > bounds.width()) {
+ scale = bounds.width() / (this.naturalDimensions.a + 10)
+ }
+ if (this.naturalDimensions.b > bounds.height()) {
+ scale = Math.min(scale, bounds.height() / (this.naturalDimensions.b + 10))
+ }
+ this.set_scale(scale)
+ this.set_wall(opt)
+ },
+
recenter: function () {
if (! this.bounds) return
var center = this.bounds.center()
center.a -= this.dimensions.a / 2
center.b -= this.dimensions.b / 2
- var mx_position = this.wall.positionToMx( center, this.dimensions )
- this.mx.move(mx_position)
this.position.assign(center)
},
+ translateTo: function (position){
+ var flipX = this.wall.side & (FRONT | RIGHT)
+ var delta = position.clone().subVec(this.position)
+ delta.a -= this.dimensions.a/2
+ delta.b -= this.dimensions.b/2
+
+ if (flipX) { delta.a *= -1 }
+
+ var new_bounds = this.wall.surface.translate( this.bounds, this.dimensions, this.position, delta )
+
+ this.position.b += delta.b
+
+ switch (this.wall.side) {
+ case FRONT:
+ case BACK:
+ this.position.a += delta.a * cos(this.wall.rotationY)
+ break
+ case LEFT:
+ case RIGHT:
+ this.position.a += delta.a * sin(this.wall.rotationY)
+ break
+ }
+ },
+
bind: function(){
this.move.bind()
- $(this.mx.el).bind({
- mouseenter: this.enter,
- mouseleave: this.leave,
- })
+// $(this.mx.el).bind({
+// mouseenter: this.enter,
+// mouseleave: this.leave,
+// })
},
unbind: function(){
this.move.unbind()
- $(this.mx.el).unbind({
- mouseenter: this.enter,
- mouseleave: this.leave,
- })
+// $(this.mx.el).unbind({
+// mouseenter: this.enter,
+// mouseleave: this.leave,
+// })
},
+ remove: function(){
+ if (this.removed) return
+ this.removed = true
+
+ UndoStack.push({
+ type: 'destroy-scenery',
+ undo: this.serialize(),
+ redo: { id: this.id },
+ })
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+
+ Scenery.remove(this.id)
+
+ Scenery.resize.hide()
+ if (app.controller.mediaEditor) {
+ app.controller.mediaEditor.tainted = false
+ app.controller.mediaEditor.hide()
+ }
+ if (app.controller.textEditor) {
+ app.controller.textEditor.tainted = false
+ app.controller.textEditor.hide()
+ }
+ },
+
destroy: function(){
this.unbind()
scene.remove(this.mx)
@@ -70,17 +157,17 @@ Scenery.types.base = Fiber.extend(function(base){
},
enter: function(e){
- if (editor.permissions.resize) {
- Scenery.resize.show(this)
- Scenery.hovering = true
- }
+// if (editor.permissions.resize) {
+// Scenery.resize.show(this)
+// Scenery.hovering = true
+// }
},
leave: function(e){
- if (editor.permissions.resize) {
- Scenery.resize.defer_hide(this)
- Scenery.hovering = false
- }
+// if (editor.permissions.resize) {
+// Scenery.resize.defer_hide(this)
+// Scenery.hovering = false
+// }
},
serialize: function(){
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/audio.js b/public/assets/javascripts/rectangles/engine/scenery/types/audio.js
new file mode 100644
index 0000000..fdd221d
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/audio.js
@@ -0,0 +1,77 @@
+
+Scenery.types.audio = Scenery.types.base.extend(function(base){
+
+ var exports = {
+
+ type: 'audio',
+
+ init: function(opt){
+
+ opt.scale = 1.0
+
+ base.init.call(this, opt)
+
+ this.build()
+ this.bind()
+ this.place(opt)
+ },
+
+ build: function(){
+ this.mx = new MX.Soundcloud({
+ scale: this.scale,
+ media: this.media,
+ y: this.scale * this.media.height/2,
+ backface: false,
+ })
+ scene.add( this.mx )
+ this.mx.load()
+ },
+
+ serialize: function(){
+ var data = base.serialize.call(this)
+ 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.dimensions.deserialize(data.dimensions)
+ },
+
+ setVolume: function(n){
+ this.mx.setVolume(n)
+ },
+
+ play: function(){
+ this.mx.play()
+ },
+
+ pause: function(){
+ this.mx.pause()
+ },
+
+ toggle: function(){
+ this.mx.toggle()
+ },
+
+ paused: function(){
+ return this.mx.paused
+ },
+
+ muted: function(){
+ return this.mx.muted
+ },
+
+ seek: function(n){
+ this.mx.seek(n)
+ },
+
+ setLoop: function(shouldLoop){
+ this.mx.setLoop(shouldLoop)
+ },
+
+ }
+
+ return exports
+})
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/image.js b/public/assets/javascripts/rectangles/engine/scenery/types/image.js
index d2fa3ab..0e5e77c 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/types/image.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/image.js
@@ -3,27 +3,17 @@ Scenery.types.image = Scenery.types.base.extend(function(base){
var exports = {
+ type: 'image',
+
init: function(opt){
- opt.scale = opt.scale || (opt.data && opt.data.scale) || 300 / max(300, opt.media.width)
+ 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()
this.bind()
-
- if (opt.data) {
- if (opt.wall) {
- var position = opt.wall.mxToPosition(opt.data.position)
- opt.index = opt.wall.surface.index_for_x( position.a, 0 )
- }
- this.set_wall(opt)
- this.deserialize(opt.data)
- }
- else {
- this.set_wall(opt)
- this.bounds && this.recenter()
- }
+ this.place(opt)
},
build: function(){
@@ -46,6 +36,7 @@ Scenery.types.image = Scenery.types.base.extend(function(base){
this.mx.move(data.position)
this.mx.ops.width = data.dimensions.a
this.mx.ops.height = data.dimensions.b
+ this.dimensions.deserialize(data.dimensions)
},
}
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/text.js b/public/assets/javascripts/rectangles/engine/scenery/types/text.js
new file mode 100644
index 0000000..6e11da2
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/text.js
@@ -0,0 +1,58 @@
+
+Scenery.types.text = Scenery.types.base.extend(function(base){
+
+ var exports = {
+
+ type: 'text',
+
+ init: function(opt){
+
+ opt.scale = 0.5
+
+ base.init.call(this, opt)
+
+ this.build()
+ this.bind()
+ this.place(opt)
+ },
+
+ build: function(){
+ this.mx = new MX.Text({
+ scale: this.scale,
+ media: this.media,
+ y: this.scale * this.media.height/2,
+ backface: false,
+ })
+ scene.add( this.mx )
+ },
+
+ setText: function(text){
+ this.media.description = text
+ this.mx.setText( text )
+ },
+
+ setFont: function(style){
+ for (var i in style) {
+ if (style.hasOwnProperty(i)) {
+ this.media.font[i] = style[i]
+ }
+ }
+ this.mx.setFont(this.media.font)
+ },
+
+ serialize: function(){
+ var data = base.serialize.call(this)
+ 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.dimensions.deserialize(data.dimensions)
+ },
+
+ }
+
+ return exports
+})
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/video.js b/public/assets/javascripts/rectangles/engine/scenery/types/video.js
index ef25d8d..163e19e 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/types/video.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/video.js
@@ -2,27 +2,18 @@
Scenery.types.video = Scenery.types.base.extend(function(base){
var exports = {
+
+ type: 'video',
init: function(opt){
- opt.scale = opt.scale || 300 / max(300, opt.media.width)
-
+
+ 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()
this.bind()
-
- if (opt.data) {
- if (opt.wall) {
- var position = opt.wall.mxToPosition(opt.data.position)
- opt.index = opt.wall.surface.index_for_x( position.a, 0 )
- }
- this.set_wall(opt)
- this.deserialize(opt.data)
- }
- else {
- this.set_wall(opt)
- this.bounds && this.recenter()
- }
+ this.place(opt)
},
build: function(){
@@ -47,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(){
@@ -99,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
@@ -106,6 +106,9 @@ Scenery.types.video = Scenery.types.base.extend(function(base){
deserialize: function(data){
this.mx.move(data.position)
+ this.mx.ops.width = data.dimensions.a
+ this.mx.ops.height = data.dimensions.b
+ this.dimensions.deserialize(data.dimensions)
},
}
diff --git a/public/assets/javascripts/rectangles/engine/scenery/undo.js b/public/assets/javascripts/rectangles/engine/scenery/undo.js
index e5624a0..b976ea2 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/undo.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/undo.js
@@ -4,12 +4,14 @@
type: "create-scenery",
undo: function(state){
Scenery.remove(state.id)
+ Scenery.resize.hide()
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
},
redo: function(state){
- Scenery.deserialize([ state ])
+ var scenery = Scenery.deserialize([ state ])
+ Scenery.resize.show( scenery )
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
@@ -19,8 +21,10 @@
type: "update-scenery",
undo: function(state){
var scenery = Scenery.find(state.id)
+ var wall = Walls.find( state.wall_id )
+
scenery.deserialize(state)
- scenery.set_wall(Walls.find( state.wall_id ))
+ scenery.set_wall({ wall: wall })
if (editor.permissions.resize) {
Scenery.resize.show(scenery)
@@ -31,13 +35,15 @@
},
redo: function(state){
var scenery = Scenery.find(state.id)
+ var wall = Walls.find( state.wall_id )
+
scenery.deserialize(state)
- scenery.set_wall(Walls.find( state.wall_id ))
+ scenery.set_wall({ wall: wall })
if (editor.permissions.resize) {
Scenery.resize.show(scenery)
- Scenery.resize.rotate_dots()
Scenery.resize.move_dots()
+ Scenery.resize.rotate_dots()
}
// TODO: watch individual scenery object here
@@ -47,12 +53,14 @@
{
type: "destroy-scenery",
undo: function(state){
- Scenery.deserialize([ state ])
+ var scenery = Scenery.deserialize([ state ])
+ Scenery.resize.show( scenery )
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
},
redo: function(state){
+ Scenery.resize.hide()
Scenery.remove(state.id)
// TODO: watch individual scenery object here
@@ -66,11 +74,11 @@
type: "create-room",
undo: function(room){
Rooms.remove(room)
- Rooms.clipper.update()
+ Rooms.rebuild()
},
redo: function(room){
Rooms.add(new Room(room))
- Rooms.clipper.update()
+ Rooms.rebuild()
app.tube("builder-pick-room", room)
},
},
@@ -80,14 +88,14 @@
var room = Rooms.list[state.id]
room.rect.assign( state.rect )
room.height = state.height
- Rooms.clipper.update()
+ Rooms.rebuild()
app.tube("builder-pick-room", room)
},
redo: function(state){
var room = Rooms.list[state.id]
room.rect.assign( state.rect )
room.height = state.height
- Rooms.clipper.update()
+ Rooms.rebuild()
app.tube("builder-pick-room", room)
},
},
@@ -95,12 +103,22 @@
type: "destroy-room",
undo: function(room){
Rooms.add(new Room(room))
- Rooms.clipper.update()
+ Rooms.rebuild()
app.tube("builder-pick-room", room)
},
redo: function(room){
Rooms.remove(room)
- Rooms.clipper.update()
+ Rooms.rebuild()
+ },
+ },
+
+ {
+ type: "update-rooms-height",
+ undo: function(state){
+ var rooms = Rooms.values()
+ rooms.forEach(function(room){
+ room.height = state
+ })
},
},
@@ -111,7 +129,32 @@
undo: function(state){
var wall = Walls.lookup[state.id]
wall.deserialize(state)
-
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ },
+ {
+ type: "update-all-wallpaper",
+ undo: function(state){
+ Walls.deserialize(state)
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ },
+ {
+ type: "choose-preset",
+ undo: function(state){
+ app.controller.colorControl.load(state.colors)
+ Walls.deserialize(state.walls)
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ redo: function(state){
+ app.controller.presets.loadByName(state)
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ },
+ {
+ type: "choose-another-preset",
+ undo: function(state){
+ app.controller.presets.loadByName(state)
Minotaur.watch( app.router.editorView.settings )
},
},
@@ -119,11 +162,103 @@
type: "update-colors",
undo: function(state){
Walls.setColor[ state.mode ]( state.rgb )
- app.router.editorView.lightControl.setSwatchColor( state.mode, state.rgb )
+ app.router.editorView.colorControl.setSwatchColor( state.mode, state.rgb )
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ },
+ {
+ type: "randomize-scenery",
+ undo: function(state){
+ state.added.forEach(function(_scenery){
+ Scenery.remove(_scenery.id)
+ })
+ var scenery_list = Scenery.deserialize(state.removed)
+ scenery_list.forEach(function(scenery){
+ scenery.was_randomly_placed = true
+ })
+ Scenery.resize.hide()
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ redo: function(state){
+ state.removed.forEach(function(_scenery){
+ Scenery.remove(_scenery.id)
+ })
+ var scenery_list = Scenery.deserialize(state.added)
+ scenery_list.forEach(function(scenery){
+ scenery.was_randomly_placed = true
+ })
+
+ // TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
},
},
+ //
+ {
+ 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
new file mode 100644
index 0000000..63eebcc
--- /dev/null
+++ b/public/assets/javascripts/rectangles/models/floor.js
@@ -0,0 +1,218 @@
+(function(){
+
+ var vec2, Rect, sort
+ if ('window' in this) {
+ vec2 = window.vec2
+ Rect = window.Rect
+ sort = window.sort
+ }
+ else {
+ vec2 = require('./vec2')
+ Rect = require('./rect')
+ UidGenerator = require('../util/uid')
+ }
+
+ var Floor = function(opt){
+ this.id = opt.id
+ this.side = opt.side
+ this.ceiling = opt.side & CEILING
+ this.mx = opt.mx
+ }
+
+ Floor.prototype.serialize = function(){
+ return {
+ id: this.id,
+ background: this.background,
+ }
+ }
+
+ Floor.prototype.deserialize = function(data){
+ this.wallpaper( data.background )
+ }
+
+ Floor.prototype.bind = function(){
+ var base = this
+ base.$els = $( this.mx.map(function(mx){ return mx.el }) )
+
+ this.mx.forEach(function(mx, index){
+ $(mx.el).bind({
+ contextmenu: function(e){
+ if (! (e.ctrlKey || e.metaKey || e.shiftKey) ) {
+ e.preventDefault()
+ }
+ if (Scenery.nextMedia) {
+ e.preventDefault()
+ Scenery.nextMedia = null
+ app.tube('cancel-scenery')
+ }
+ else if (Scenery.nextWallpaper) {
+ e.preventDefault()
+ Scenery.nextWallpaper = null
+ app.tube('cancel-wallpaper')
+ }
+ },
+
+ mousedown: function(e){
+
+ // right-click
+ if (e.which == 3) {
+ if (Scenery.nextMedia) {
+ e.preventDefault()
+ Scenery.nextMedia = null
+ app.tube('cancel-scenery')
+ }
+ else if (Scenery.nextWallpaper) {
+ e.preventDefault()
+ Scenery.nextWallpaper = null
+ app.tube('cancel-wallpaper')
+ }
+ return
+ }
+
+
+ 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
+
+ UndoStack.push({
+ type: 'update-wallpaper',
+ undo: oldState,
+ redo: base.serialize(),
+ })
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+
+ app.controller.pickWall(base, null)
+ }
+ else {
+ app.controller.pickWall(base, null)
+ }
+ }
+ })
+ })
+
+ // flip the mx order
+ var shouldFlip = this.side & (CEILING)
+ if (! shouldFlip) {
+ this.mx.reverse()
+ }
+ }
+
+ Floor.prototype.color = function(color){
+ this.$els.css("background-color", color)
+ }
+
+ Floor.prototype.wallpaper = function(background){
+ if (! background) {
+ background = { src: "none" }
+ }
+ else if (typeof background == "string") {
+ background = { src: background }
+ }
+ else if (! background.src) {
+ background = { src: "none" }
+ }
+ background.x = background.x || 0
+ background.y = background.y || 0
+ background.scale = background.scale || 1
+
+ this.background = background
+ this.background.src = this.background.src.replace(/url\(\"?\'?/,"").replace(/\"?\'?\)/,"")
+
+ if (this.background.src == "none") {
+ this.wallpaperLoad(this.background.src)
+ return
+ }
+
+ var img = new Image ()
+ img.onload = function(){
+ this.backgroundImage = img
+ this.wallpaperLoad(this.background.src)
+ this.wallpaperPosition(background)
+ }.bind(this)
+ img.src = this.background.src
+ img.complete && img.onload()
+ }
+
+ Floor.prototype.wallpaperLoad = function(url){
+ if (url !== "none") {
+ url = "url(" + url + ")"
+ }
+ this.mx.forEach(function(mx){
+ mx.el.style.backgroundImage = url
+ })
+ }
+
+ Floor.prototype.wallpaperPosition = function(background){
+ if (this.background.src == "none") { return }
+
+ this.background.x = background.x || this.background.x || 0
+ this.background.y = background.y || this.background.y || 0
+ this.background.scale = background.scale || this.background.scale || 1
+
+ var mx, dx, dy
+ var w = Math.round( this.backgroundImage.naturalWidth * this.background.scale )
+ var h = Math.round( this.backgroundImage.naturalHeight * this.background.scale )
+
+ this.mx.forEach(function(mx, i){
+
+ var region = mx.rect
+
+ if (this.ceiling) {
+ dx = Math.round( this.background.x - region.x.a )
+ dy = Math.round( this.background.y - region.y.a )
+ }
+ else {
+ dx = Math.round( this.background.x - region.x.a )
+ dy = Math.round( this.background.y + region.y.b )
+ }
+
+ mx.el.style.backgroundPosition = dx + 'px ' + dy + 'px'
+ mx.el.style.backgroundSize = w + 'px ' + h + 'px'
+ }.bind(this))
+ bbb = this
+ }
+
+ if ('window' in this) {
+ window.Floor = Floor
+ }
+ else {
+ module.exports = Floor
+ }
+})()
diff --git a/public/assets/javascripts/rectangles/models/mat4.js b/public/assets/javascripts/rectangles/models/mat4.js
deleted file mode 100644
index b061199..0000000
--- a/public/assets/javascripts/rectangles/models/mat4.js
+++ /dev/null
@@ -1,78 +0,0 @@
-function mat4(e){
- this.elements = [ 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1 ]
- return this
-}
-mat4.prototype.set = function (a) {
- var els = this.elements
- a.forEach(function(n,i){ els[i] = n })
- return this
-}
-mat4.prototype.clone = function(){
- return (new mat4).set(this.els)
-}
-mat4.prototype.identity = function () {
- this.set([
- 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1
- ]);
- return this;
-}
-mat4.prototype.getInverse = function (m, throwOnInvertible) {
-
- // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
- var te = this.elements;
- var me = m.elements;
-
- var n11 = me[0], n12 = me[4], n13 = me[8], n14 = me[12];
- var n21 = me[1], n22 = me[5], n23 = me[9], n24 = me[13];
- var n31 = me[2], n32 = me[6], n33 = me[10], n34 = me[14];
- var n41 = me[3], n42 = me[7], n43 = me[11], n44 = me[15];
-
- te[0] = n23*n34*n42 - n24*n33*n42 + n24*n32*n43 - n22*n34*n43 - n23*n32*n44 + n22*n33*n44;
- te[4] = n14*n33*n42 - n13*n34*n42 - n14*n32*n43 + n12*n34*n43 + n13*n32*n44 - n12*n33*n44;
- te[8] = n13*n24*n42 - n14*n23*n42 + n14*n22*n43 - n12*n24*n43 - n13*n22*n44 + n12*n23*n44;
- te[12] = n14*n23*n32 - n13*n24*n32 - n14*n22*n33 + n12*n24*n33 + n13*n22*n34 - n12*n23*n34;
- te[1] = n24*n33*n41 - n23*n34*n41 - n24*n31*n43 + n21*n34*n43 + n23*n31*n44 - n21*n33*n44;
- te[5] = n13*n34*n41 - n14*n33*n41 + n14*n31*n43 - n11*n34*n43 - n13*n31*n44 + n11*n33*n44;
- te[9] = n14*n23*n41 - n13*n24*n41 - n14*n21*n43 + n11*n24*n43 + n13*n21*n44 - n11*n23*n44;
- te[13] = n13*n24*n31 - n14*n23*n31 + n14*n21*n33 - n11*n24*n33 - n13*n21*n34 + n11*n23*n34;
- te[2] = n22*n34*n41 - n24*n32*n41 + n24*n31*n42 - n21*n34*n42 - n22*n31*n44 + n21*n32*n44;
- te[6] = n14*n32*n41 - n12*n34*n41 - n14*n31*n42 + n11*n34*n42 + n12*n31*n44 - n11*n32*n44;
- te[10] = n12*n24*n41 - n14*n22*n41 + n14*n21*n42 - n11*n24*n42 - n12*n21*n44 + n11*n22*n44;
- te[14] = n14*n22*n31 - n12*n24*n31 - n14*n21*n32 + n11*n24*n32 + n12*n21*n34 - n11*n22*n34;
- te[3] = n23*n32*n41 - n22*n33*n41 - n23*n31*n42 + n21*n33*n42 + n22*n31*n43 - n21*n32*n43;
- te[7] = n12*n33*n41 - n13*n32*n41 + n13*n31*n42 - n11*n33*n42 - n12*n31*n43 + n11*n32*n43;
- te[11] = n13*n22*n41 - n12*n23*n41 - n13*n21*n42 + n11*n23*n42 + n12*n21*n43 - n11*n22*n43;
- te[15] = n12*n23*n31 - n13*n22*n31 + n13*n21*n32 - n11*n23*n32 - n12*n21*n33 + n11*n22*n33;
-
- var det = n11 * te[ 0 ] + n21 * te[ 4 ] + n31 * te[ 8 ] + n41 * te[ 12 ];
-
- if ( det == 0 ) {
- var msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0";
-
- if ( throwOnInvertible || false ) {
- throw new Error( msg )
- }
- else {
- console.warn( msg )
- }
- this.identity();
- return this
- }
- this.multiplyScalar( 1 / det );
- return this
-}
-mat4.prototype.multiplyScalar = function (n) {
- var els = this.elements
- els[0] *= n; els[4] *= n; els[8] *= n; els[12] *= n
- els[1] *= n; els[5] *= n; els[9] *= n; els[13] *= n
- els[2] *= n; els[6] *= n; els[10] *= n; els[14] *= n
- els[3] *= n; els[7] *= n; els[11] *= n; els[15] *= n
- return this
-}
-
diff --git a/public/assets/javascripts/rectangles/models/rect.js b/public/assets/javascripts/rectangles/models/rect.js
index a08176a..4f73bec 100644
--- a/public/assets/javascripts/rectangles/models/rect.js
+++ b/public/assets/javascripts/rectangles/models/rect.js
@@ -1,4 +1,3 @@
-
(function(){
var vec2
if ('window' in this) {
@@ -8,17 +7,17 @@
vec2 = require('./vec2')
FRONT = 0x1, BACK = 0x2, LEFT = 0x4, RIGHT = 0x8, FLOOR = 0x10, CEILING = 0x20
TOP = CEILING, BOTTOM = FLOOR
- function sidesToString(sides){
- var s = ""
- if (sides & FRONT) s += "front "
- if (sides & BACK) s += "back "
- if (sides & LEFT) s += "left "
- if (sides & RIGHT) s += "right "
- if (sides & TOP) s += "top "
- if (sides & BOTTOM) s += "bottom "
- return s
- }
}
+ function sidesToString(sides){
+ var s = ""
+ if (sides & FRONT) s += "front "
+ if (sides & BACK) s += "back "
+ if (sides & LEFT) s += "left "
+ if (sides & RIGHT) s += "right "
+ if (sides & TOP) s += "top "
+ if (sides & BOTTOM) s += "bottom "
+ return s
+ }
var Rect = function (x0,y0,x1,y1){
if (x0 instanceof vec2) {
@@ -39,6 +38,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 +61,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)
@@ -141,11 +152,40 @@
Rect.prototype.width = function(){ return this.x.length() }
Rect.prototype.height = function(){ return this.y.length() }
Rect.prototype.delta = function(){ return new vec2( this.x.magnitude(), this.y.magnitude() ) }
+ Rect.prototype.expand = function(rect){
+ this.x.a = Math.min( this.x.a, rect.x.a )
+ this.x.b = Math.max( this.x.b, rect.x.b )
+ this.y.a = Math.min( this.y.a, rect.y.a )
+ this.y.b = Math.max( this.y.b, rect.y.b )
+ return this
+ }
+ Rect.prototype.square = function(){
+ var width = this.x.length()
+ var height = this.y.length()
+ var diff
+ if (width < height) {
+ diff = (height - width) / 2
+ this.x.a -= diff
+ this.x.b += diff
+ }
+ else {
+ diff = (width - height) / 2
+ this.y.a -= diff
+ this.y.b += diff
+ }
+ return this
+ }
Rect.prototype.toString = function(){
var sides = sidesToString(this.sides)
var s = "[" + this.x.toString() + " " + this.y.toString() + "] " + sides
return s
}
+ Rect.prototype.exactString = function(){
+ var sides = sidesToString(this.sides)
+ var s = "[" + this.x.exactString() + " " + this.y.exactString() + "] " + sides
+ return s
+ }
+
Rect.prototype.serialize = function(){
return { x: this.x.serialize(), y: this.y.serialize() }
}
diff --git a/public/assets/javascripts/rectangles/models/room.js b/public/assets/javascripts/rectangles/models/room.js
index 0f09325..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(){
@@ -122,8 +126,10 @@
return collision
}
- Room.prototype.collidesDisc = function(x,y,radius){
+ 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
@@ -152,25 +158,10 @@
if (contains_y) {
collision |= wall_collision & LEFT_RIGHT
}
-// if (bitcount(wall_collision) > 1) {
-// collision |= wall_collision
-// }
})
return collision
}
-
- Room.prototype.setFloorColor = function(rgbColor) {
- this.mx_floor.map(function(mx){
- mx.el.style.backgroundColor = rgbColor
- })
- }
-
- Room.prototype.setCeilingColor = function(rgbColor) {
- this.mx_ceiling.map(function(mx){
- mx.el.style.backgroundColor = rgbColor
- })
- }
-
+
if ('window' in this) {
window.Room = Room
}
diff --git a/public/assets/javascripts/rectangles/models/surface.js b/public/assets/javascripts/rectangles/models/surface.js
index 53977c8..f031665 100644
--- a/public/assets/javascripts/rectangles/models/surface.js
+++ b/public/assets/javascripts/rectangles/models/surface.js
@@ -12,6 +12,7 @@
var Surface = function (face){
this.bounds = new Rect (new vec2(0, 0), new vec2(0, 0))
+ this.vec = new Rect (new vec2(0, 0), new vec2(0, 0))
this.faces = []
if (face) {
this.add(face)
@@ -36,7 +37,7 @@
Surface.prototype.fits = function(v){
var faces = this.faces
var scratch
- if (this.bounds.x.b < v.a || this.bounds.y.b < v.b) {
+ if (this.bounds.width() < v.a || this.bounds.height() < v.b) {
return null
}
for (var i = 0; i < faces.length; i++) {
@@ -46,7 +47,7 @@
}
scratch = new Rect (0,0,0,0)
for (var i = 0; i < faces.length; i++) {
- if (faces[i].y.length() < v.b) {
+ if (faces[i].height() < v.b) {
continue
}
scratch.x.a = faces[i].x.a
@@ -108,7 +109,7 @@
this.clamp_delta( this.bounds, dimension, position, delta )
var new_delta = delta.clone()
- if (this.clamp_delta(old_bounds, dimension, position, new_delta).eq(delta)) {
+ if (old_bounds && this.clamp_delta(old_bounds, dimension, position, new_delta).eq(delta)) {
return old_bounds
}
@@ -154,6 +155,21 @@
}
return -1
}
+ Surface.prototype.face_for_x = function(x, min_i){
+ min_i = min_i || 0
+ for (var i = min_i; i < this.faces.length; i++) {
+ if (this.faces[i].x.contains(x)) {
+ return this.faces[i]
+ }
+ }
+ if (x < this.faces[0].x.a) {
+ return this.faces[0]
+ }
+ else {
+ return this.faces[this.faces.length-1]
+ }
+ return null
+ }
Surface.prototype.bounds_at_index_with_dimensions = function(index, dimensions){
var faces = this.faces
diff --git a/public/assets/javascripts/rectangles/models/vec2.js b/public/assets/javascripts/rectangles/models/vec2.js
index 0040435..8942d92 100644
--- a/public/assets/javascripts/rectangles/models/vec2.js
+++ b/public/assets/javascripts/rectangles/models/vec2.js
@@ -37,9 +37,15 @@
vec2.prototype.midpoint = function(){
return lerp(0.5, this.a, this.b)
}
+ vec2.prototype.lerp = function(n){
+ return lerp(n, this.a, this.b)
+ }
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
@@ -60,9 +66,28 @@
this.b /= n
return this
}
+ vec2.prototype.addVec = function(v){
+ this.a += v.a
+ this.b += v.b
+ return this
+ }
+ vec2.prototype.subVec = function(v){
+ this.a -= v.a
+ this.b -= v.b
+ return this
+ }
vec2.prototype.zero = function(){
this.a = this.b = 0
}
+ vec2.prototype.round = function(){
+ 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
@@ -86,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)
}
@@ -183,10 +214,17 @@
}
vec2.prototype.toString = function(){
- return "[" + ~~this.a + " " + ~~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 [ ~~this.a, ~~this.b ]
+ return [ Math.round(this.a), Math.round(this.b) ]
+ }
+ vec2.prototype.deserialize = function(data){
+ this.a = data[0]
+ this.b = data[1]
}
vec2.prototype.quantize = function(n){
n = n || 10
diff --git a/public/assets/javascripts/rectangles/models/vec3.js b/public/assets/javascripts/rectangles/models/vec3.js
index 4e00b0c..b3825a9 100644
--- a/public/assets/javascripts/rectangles/models/vec3.js
+++ b/public/assets/javascripts/rectangles/models/vec3.js
@@ -15,6 +15,9 @@ vec3.prototype.sub = function(v){
this.c -= v.c
return this
}
+vec3.prototype.clone = function(){
+ return new vec3(this.a, this.b, this.c)
+}
// input: mat4 projection matrix
vec3.prototype.apply_projection = function (m) {
@@ -29,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 1a3ef7c..cf3cea8 100644
--- a/public/assets/javascripts/rectangles/models/wall.js
+++ b/public/assets/javascripts/rectangles/models/wall.js
@@ -10,21 +10,55 @@
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){
- this.id = [ opt.side, opt.edge, opt.vec.a ].join("_")
+ this.id = [ opt.side|0, opt.edge|0, opt.vec.a|0 ].join("_")
this.vec = opt.vec
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 = ""
+ this.background = { src: "none" }
}
Wall.prototype.toString = function(){
return this.vec.toString()
}
+ Wall.prototype.get_points = function(wall_vec){
+ wall_vec = wall_vec || new Rect ()
+ if (this.side & LEFT) {
+ wall_vec.x.a = this.edge
+ wall_vec.x.b = this.vec.b
+ wall_vec.y.a = this.edge
+ wall_vec.y.b = this.vec.a
+ }
+ else if (this.side & RIGHT) {
+ wall_vec.x.a = this.edge
+ wall_vec.x.b = this.vec.a
+ wall_vec.y.a = this.edge
+ wall_vec.y.b = this.vec.b
+ }
+ else if (this.side & FRONT) {
+ wall_vec.x.a = this.vec.a
+ wall_vec.x.b = this.edge
+ wall_vec.y.a = this.vec.b
+ wall_vec.y.b = this.edge
+ }
+ else if (this.side & BACK) {
+ wall_vec.x.a = this.vec.b
+ wall_vec.x.b = this.edge
+ wall_vec.y.a = this.vec.a
+ wall_vec.y.b = this.edge
+ }
+ return wall_vec
+ }
Wall.prototype.reset = function(){
}
@@ -50,13 +84,66 @@
index: index,
})
},
+/*
mousemove: function(e){
+ var offset = offsetFromPoint(e, mx.el)
+ if (offset) {
+ var pos = base.mxOffsetToPosition( offset, index )
+
+ var mx_pos = base.positionToMx(pos, new vec2(5,5))
+ var mx_dot = new MX.Object3D
+ mx_dot.move(mx_pos)
+ mx_dot.width = 5
+ mx_dot.height = 5
+ mx_dot.rotationY = base.rotationY
+ mx_dot.el.style.backgroundColor = "red"
+ scene.add(mx_dot)
+ }
},
+*/
+ contextmenu: function(e){
+ if (! (e.ctrlKey || e.metaKey || e.shiftKey) ) {
+ e.preventDefault()
+ }
+ if (Scenery.nextMedia) {
+ e.preventDefault()
+ Scenery.nextMedia = null
+ app.tube('cancel-scenery')
+ }
+ else if (Scenery.nextWallpaper) {
+ e.preventDefault()
+ Scenery.nextWallpaper = null
+ app.tube('cancel-wallpaper')
+ }
+ },
+
mousedown: function(e){
+
+ // right-click
+ if (e.which == 3) {
+ if (Scenery.nextMedia) {
+ e.preventDefault()
+ Scenery.nextMedia = null
+ app.tube('cancel-scenery')
+ }
+ else if (Scenery.nextWallpaper) {
+ e.preventDefault()
+ Scenery.nextWallpaper = null
+ app.tube('cancel-wallpaper')
+ }
+ return
+ }
+
+ var offset = offsetFromPoint(e, mx.el)
+ if (! offset) { return }
+
+ var pos = base.mxOffsetToPosition( offset, index )
+
if (Scenery.nextMedia) {
var scenery = Scenery.addNextToWall({
wall: base,
- index: index
+ index: index,
+ position: pos,
})
// scenery was not placed
@@ -64,20 +151,26 @@
e.stopPropagation()
return
}
-
+
+ app.controller.toolbar.resetPermissions()
+ Scenery.resize.show(scenery)
+ Scenery.hovering = true
+
+ app.controller.pick(scenery)
+
UndoStack.push({
type: 'create-scenery',
undo: { id: scenery.id },
redo: scenery.serialize(),
})
-
+
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
}
else if (Scenery.nextWallpaper) {
var oldState = base.serialize()
base.wallpaper(Scenery.nextWallpaper)
- Scenery.nextWallpaper = null
+ // Scenery.nextWallpaper = null
UndoStack.push({
type: 'update-wallpaper',
@@ -87,11 +180,13 @@
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
+
+ app.controller.pickWall(base, pos)
}
else {
- app.controller.hideExtras()
+ app.controller.pickWall(base, pos)
}
- }
+ },
})
})
@@ -100,12 +195,10 @@
if (! shouldFlip) {
this.mx.reverse()
}
-
- // this.outline(wallColor, outlineColor)
}
Wall.prototype.serialize = function(){
- return {
+ return {
id: this.id,
background: this.background,
}
@@ -149,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) {
@@ -172,30 +265,104 @@
}
return position
}
-
+ Wall.prototype.mxOffsetToPosition = function( offset, index ) {
+ var face = this.surface.faces[index]
+ var shouldFlip = this.side & (RIGHT | FRONT)
+ var position = new vec2(0,0)
+ position.a = face.x.lerp(shouldFlip ? 1-offset.left : offset.left)
+ position.b = face.y.lerp(1-offset.top)
+ position.round()
+ return position
+ }
+
Wall.prototype.color = function(color){
this.$walls.css("background-color", color)
}
- Wall.prototype.wallpaper = function(background){
- var useX = this.side & FRONT_BACK
- var shouldFlip = this.side & (LEFT | BACK)
-
- this.background = background || "none"
+ Wall.prototype.wallpaper = function(background, img){
+ if (! background) {
+ background = { src: "none" }
+ }
+ else if (typeof background == "string") {
+ background = { src: background }
+ }
+ else if (! background.src) {
+ background = { src: "none" }
+ }
+ background.x = background.x || 0
+ background.y = background.y || 0
+ background.scale = background.scale || 1
+
+ this.background = background
+ this.background.src = this.background.src.replace(/url\(\"?\'?/,"").replace(/\"?\'?\)/,"")
+
+ if (this.background.src == "none") {
+ this.wallpaperLoad(this.background.src)
+ return
+ }
+ img = img || new Image ()
+ img.onload = function(){
+ this.backgroundImage = img
+ this.wallpaperLoad(this.background.src)
+ this.wallpaperPosition(background)
+ }.bind(this)
+ img.src = this.background.src
+ img.complete && img.onload()
+ }
+
+ Wall.prototype.wallpaperLoad = function(url){
+ if (url !== "none") {
+ url = "url(" + url + ")"
+ }
this.mx.forEach(function(mx){
- var partitionOffset = useX ? mx.x : mx.z
- if (shouldFlip) partitionOffset *= -1
- partitionOffset += mx.width/2
- var floorOffset = mx.y + mx.height/2
-
- mx.el.style.backgroundImage = background
- mx.el.style.backgroundPosition = (~~partitionOffset) + "px " + (~~floorOffset) + "px"
+ if (mx.el) mx.el.style.backgroundImage = url
})
}
+
+ Wall.prototype.wallpaperPosition = function(background){
+ if (this.background.src == "none") { return }
+
+ this.background.x = background.x || this.background.x || 0
+ this.background.y = background.y || this.background.y || 0
+ this.background.scale = background.scale || this.background.scale || 1
+
+ var mx, dx, dy
+ var w = Math.round( this.backgroundImage.naturalWidth * this.background.scale )
+ var h = Math.round( this.backgroundImage.naturalHeight * this.background.scale )
+
+ this.surface.faces.forEach(function(face, i){
+ // this.mx[i].el.innerHTML = sidesToString(this.side)
+
+ switch (this.side) {
+ case LEFT:
+ mx = this.mx[this.mx.length-1-i]
+ dx = Math.round( this.background.x + face.x.a )
+ dy = Math.round( this.background.y + face.y.b )
+ break
+ case RIGHT:
+ mx = this.mx[this.mx.length-1-i]
+ dx = Math.round( this.background.x + face.x.b )
+ dy = Math.round( this.background.y + face.y.b )
+ break
+ case FRONT:
+ mx = this.mx[this.mx.length-1-i]
+ dx = Math.round( this.background.x + face.x.b )
+ dy = Math.round( this.background.y + face.y.b )
+ break
+ case BACK:
+ mx = this.mx[i]
+ dx = Math.round( this.background.x - face.x.a )
+ dy = Math.round( this.background.y + face.y.b )
+ break
+ }
+
+ mx.el.style.backgroundPosition = dx + 'px ' + dy + 'px'
+ mx.el.style.backgroundSize = w + 'px ' + h + 'px'
+ }.bind(this))
+ }
Wall.prototype.outline = function(wallColor, outlineColor){
- var useX = this.side & FRONT_BACK
var mx = this.mx
var len = this.mx.length
diff --git a/public/assets/javascripts/rectangles/util/constants.js b/public/assets/javascripts/rectangles/util/constants.js
index 4c6b3cc..522689b 100644
--- a/public/assets/javascripts/rectangles/util/constants.js
+++ b/public/assets/javascripts/rectangles/util/constants.js
@@ -1,5 +1,6 @@
var FRONT = 0x1, BACK = 0x2, LEFT = 0x4, RIGHT = 0x8, FLOOR = 0x10, CEILING = 0x20
- FRONT_BACK = FRONT | BACK, LEFT_RIGHT = LEFT | RIGHT, FLOOR_CEILING = FLOOR | CEILING
+var FRONT_BACK = FRONT | BACK, LEFT_RIGHT = LEFT | RIGHT, FLOOR_CEILING = FLOOR | CEILING
+var WALL_SIDES = FRONT | BACK | LEFT | RIGHT
var TOP = CEILING, BOTTOM = FLOOR,
TOP_LEFT = TOP | LEFT,
@@ -19,10 +20,13 @@ var height_min = 200,
side_min = 10,
side_max = 5000,
resize_margin = 8,
- cursor_amp = 1.5
+ cursor_amp = 1.5,
+ DEFAULT_PICTURE_WIDTH = 350,
+ 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
new file mode 100644
index 0000000..ff56199
--- /dev/null
+++ b/public/assets/javascripts/rectangles/util/coords.js
@@ -0,0 +1,33 @@
+function offsetFromPoint(event, element) {
+ function a(width) {
+ var l = 0, r = 200;
+ while (r - l > 0.0001) {
+ var mid = (r + l) / 2;
+ var a = document.createElement('div');
+ a.style.cssText = 'position: absolute;left:0;top:0;background: red;z-index: 1000;';
+ a.style[width ? 'width' : 'height'] = mid.toFixed(3) + '%';
+ a.style[width ? 'height' : 'width'] = '100%';
+ element.appendChild(a);
+ var x = document.elementFromPoint(event.clientX, event.clientY);
+ element.removeChild(a);
+ if (x === a) {
+ r = mid;
+ } else {
+ if (r === 200) {
+ return null;
+ }
+ l = mid;
+ }
+ }
+ return mid;
+ }
+ var l = a(1),
+ t = a(0);
+ return l && t ? {
+ left: l / 100,
+ top: t / 100,
+ toString: function () {
+ 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/minotaur.js b/public/assets/javascripts/rectangles/util/minotaur.js
index 0fcc766..8b1abfe 100644
--- a/public/assets/javascripts/rectangles/util/minotaur.js
+++ b/public/assets/javascripts/rectangles/util/minotaur.js
@@ -4,7 +4,7 @@
var base = this
base.$el = $("#minotaur")
base.timeout = null
- base.delay = 1000
+ base.delay = 2500
base.objects = {}
base.init = function () {
@@ -38,7 +38,7 @@
for (var id in base.objects[type]) {
var obj = base.objects[type][id]
if (obj) {
- obj.save(null, function(){ base.hide() }, function(){})
+ obj.save(null, function(){ base.hide() }, function(){ base.hide() })
}
delete base.objects[type][id]
saving = true
@@ -53,9 +53,7 @@
}
base.hide = function () {
- setTimeout(function(){
- base.$el.removeClass()
- }, 500)
+ base.$el.removeClass('saving')
}
base.init();
diff --git a/public/assets/javascripts/rectangles/util/mouse.js b/public/assets/javascripts/rectangles/util/mouse.js
index cb36038..6d9862c 100644
--- a/public/assets/javascripts/rectangles/util/mouse.js
+++ b/public/assets/javascripts/rectangles/util/mouse.js
@@ -54,10 +54,20 @@ function mouse (opt) {
opt.up && base.tube.on("up", opt.up)
opt.rightclick && base.tube.on("rightclick", opt.rightclick)
- var offset = (opt.use_offset && opt.el) ? opt.el.getBoundingClientRect() : null
+ var offset;
base.init = function (){
base.bind()
+ base.set_offset()
+ }
+
+ base.set_offset = function(){
+ if (opt.use_offset && opt.el) {
+ offset = opt.el.getBoundingClientRect()
+ }
+ else {
+ offset = null
+ }
}
base.on = function(){
@@ -73,9 +83,20 @@ function mouse (opt) {
opt.el.addEventListener("mousedown", base.mousedown)
opt.el.addEventListener("contextmenu", base.contextmenu)
}
+ if (opt.use_offset) {
+ window.addEventListener("resize", base.set_offset)
+ }
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 +147,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/permissions.js b/public/assets/javascripts/rectangles/util/permissions.js
index 1b5a1b5..9e3ef4d 100644
--- a/public/assets/javascripts/rectangles/util/permissions.js
+++ b/public/assets/javascripts/rectangles/util/permissions.js
@@ -31,7 +31,7 @@ Permissions.prototype.add = function (key) {
Permissions.prototype.remove = function (key) {
var base = this
- base[key] = true
+ base[key] = false
}
Permissions.prototype.clear = function () {
diff --git a/public/assets/javascripts/rectangles/util/undostack.js b/public/assets/javascripts/rectangles/util/undostack.js
index 959e3d1..040a4eb 100644
--- a/public/assets/javascripts/rectangles/util/undostack.js
+++ b/public/assets/javascripts/rectangles/util/undostack.js
@@ -10,6 +10,7 @@
this.pointer++
this.stack[this.pointer] = action
this.purge()
+ this.debug && console.log("push", action.type)
}
UndoStack.prototype.purge = function(){
if (this.stack.length-1 == this.pointer) return
diff --git a/public/assets/javascripts/rectangles/util/wheel.js b/public/assets/javascripts/rectangles/util/wheel.js
index 6836772..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,
@@ -21,37 +21,46 @@ function wheel (opt) {
val: 0,
})
- opt.el.addEventListener('mousewheel', onMouseWheel, false);
+ opt.el.addEventListener('wheel', onMouseWheel, false);
+ // opt.el.addEventListener('mousewheel', onMouseWheel, false);
opt.el.addEventListener('DOMMouseScroll', onMouseWheel, false);
function onMouseWheel (e) {
if (opt.locked) {
return
}
+
if (! opt.propagate) {
e.stopPropagation()
e.preventDefault()
}
- var delta = 0;
+ var deltaX = 0, deltaY = 0;
// WebKit
- if ( event.wheelDeltaY ) {
- delta -= event.wheelDeltaY * opt.ratio
+ if ( event.deltaY ) {
+ deltaY -= event.deltaY * opt.ratio
+ deltaX -= event.deltaX * opt.ratio
+ }
+ else if ( event.wheelDeltaY ) {
+ deltaY -= event.wheelDeltaY * opt.ratio
+ deltaX -= event.wheelDeltaX * opt.ratio
}
// Opera / Explorer 9
else if ( event.wheelDelta ) {
- delta -= event.wheelDelta * opt.ratio
+ deltaY -= event.wheelDelta * opt.ratio
}
// Firefox
else if ( event.detail ) {
- delta += event.detail * 2
+ deltaY += event.detail * 2
}
- if (! opt.reversible && delta < 0) return;
+ if (! opt.reversible && (deltaY < 0 && deltaX < 0)) return;
- opt.val = clamp(opt.val + delta, opt.min, opt.max)
+ // opt.val = clamp(opt.val + delta, opt.min, opt.max)
- opt.update(e, opt.val, delta)
+ // 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)
}
opt.lock = function(){ opt.locked = true }