summaryrefslogtreecommitdiff
path: root/public/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'public/assets/javascripts')
-rw-r--r--public/assets/javascripts/app.js34
-rw-r--r--public/assets/javascripts/defaults.js4
-rw-r--r--public/assets/javascripts/mx/extensions/mx.movements.js30
-rw-r--r--public/assets/javascripts/mx/extensions/mx.orbitCamera.js97
-rw-r--r--public/assets/javascripts/mx/mx.js16
-rw-r--r--public/assets/javascripts/mx/primitives/mx.grid.js70
-rw-r--r--public/assets/javascripts/mx/primitives/mx.image.js40
-rw-r--r--public/assets/javascripts/mx/primitives/mx.point.js17
-rw-r--r--public/assets/javascripts/mx/primitives/mx.polyline.js57
-rw-r--r--public/assets/javascripts/mx/primitives/mx.soundcloud.js5
-rw-r--r--public/assets/javascripts/mx/primitives/mx.text.js2
-rw-r--r--public/assets/javascripts/mx/primitives/mx.video.js8
-rw-r--r--public/assets/javascripts/mx/primitives/mx.vimeo.js23
-rw-r--r--public/assets/javascripts/mx/primitives/mx.youtube.js8
-rw-r--r--public/assets/javascripts/rectangles/_env.js35
-rw-r--r--public/assets/javascripts/rectangles/engine/map/_map.js42
-rw-r--r--public/assets/javascripts/rectangles/engine/map/draw.js104
-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)8
-rw-r--r--public/assets/javascripts/rectangles/engine/map/ui/minimap.js (renamed from public/assets/javascripts/rectangles/engine/map/ui_minimap.js)0
-rw-r--r--public/assets/javascripts/rectangles/engine/map/ui/ortho.js107
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/_rooms.js41
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/_scenery.js30
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/move.js8
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/resize.js5
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/sound.js16
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/_object.js4
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/audio.js4
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/video.js11
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/undo.js65
-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.js39
-rw-r--r--public/assets/javascripts/rectangles/models/rect.js33
-rw-r--r--public/assets/javascripts/rectangles/models/room.js7
-rw-r--r--public/assets/javascripts/rectangles/models/vec2.js18
-rw-r--r--public/assets/javascripts/rectangles/models/vec3.js25
-rw-r--r--public/assets/javascripts/rectangles/models/wall.js12
-rw-r--r--public/assets/javascripts/rectangles/util/constants.js5
-rw-r--r--public/assets/javascripts/rectangles/util/coords.js2
-rw-r--r--public/assets/javascripts/rectangles/util/measurement.js4
-rw-r--r--public/assets/javascripts/rectangles/util/minotaur.js6
-rw-r--r--public/assets/javascripts/rectangles/util/mouse.js25
-rw-r--r--public/assets/javascripts/rectangles/util/wheel.js10
-rw-r--r--public/assets/javascripts/ui/_router.js86
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintEditor.js129
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintInfo.js92
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintNotice.js62
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintScaler.js158
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintSettings.js123
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintToolbar.js97
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintUploader.js147
-rw-r--r--public/assets/javascripts/ui/blueprint/BlueprintView.js106
-rw-r--r--public/assets/javascripts/ui/builder/BuilderInfo.js4
-rw-r--r--public/assets/javascripts/ui/builder/BuilderSettings.js2
-rw-r--r--public/assets/javascripts/ui/builder/BuilderToolbar.js3
-rw-r--r--public/assets/javascripts/ui/editor/EditorSettings.js17
-rw-r--r--public/assets/javascripts/ui/editor/EditorView.js17
-rw-r--r--public/assets/javascripts/ui/editor/SculptureEditor.js237
-rw-r--r--public/assets/javascripts/ui/lib/AnimatedView.js31
-rw-r--r--public/assets/javascripts/ui/lib/ConfirmModal.js24
-rw-r--r--public/assets/javascripts/ui/lib/FormView.js9
-rw-r--r--public/assets/javascripts/ui/lib/LabColorPicker.js26
-rw-r--r--public/assets/javascripts/ui/lib/ModalView.js5
-rw-r--r--public/assets/javascripts/ui/lib/ToggleableView.js19
-rw-r--r--public/assets/javascripts/ui/lib/Toolbar.js22
-rw-r--r--public/assets/javascripts/ui/reader/MediaPlayer.js2
-rw-r--r--public/assets/javascripts/ui/reader/ReaderView.js22
-rw-r--r--public/assets/javascripts/ui/reader/Tracker.js14
-rw-r--r--public/assets/javascripts/ui/reader/_router.js24
-rw-r--r--public/assets/javascripts/ui/site/EditSubscriptionModal.js290
-rw-r--r--public/assets/javascripts/ui/site/HomeView.js2
-rw-r--r--public/assets/javascripts/ui/site/LayoutsIndex.js85
-rw-r--r--public/assets/javascripts/ui/site/LayoutsModal.js172
-rw-r--r--public/assets/javascripts/ui/site/NewProjectModal.js118
-rw-r--r--public/assets/javascripts/ui/site/StaffView.js47
-rw-r--r--public/assets/javascripts/util.js1
-rw-r--r--public/assets/javascripts/vendor/polyfill.js3
92 files changed, 4662 insertions, 316 deletions
diff --git a/public/assets/javascripts/app.js b/public/assets/javascripts/app.js
index a146325..3cafeca 100644
--- a/public/assets/javascripts/app.js
+++ b/public/assets/javascripts/app.js
@@ -21,44 +21,12 @@ app.init = function () {
app.launch = function () {
if ($.browser.msie || ! has3d()) { return app.fallback() }
- var movements
-
- app.devicePixelRatio = is_mobile ? devicePixelRatio : 1
-
- scene = new MX.Scene().addTo('#scene')
- scene.width = window.innerWidth
- scene.height = window.innerHeight
- scene.perspective = window.innerHeight
-
- window.onresize = function () {
- scene.width = window.innerWidth
- scene.height = window.innerHeight
- scene.perspective = window.innerHeight
- scene.update()
- }
-
- cam = scene.camera
- cam.y = viewHeight
-
- if (MX.Map) map = app.map = new MX.Map()
-
- if (is_mobile) {
- app.movements = new MX.MobileMovements(cam, viewHeight)
- }
- else {
- app.movements = new MX.Movements(cam, viewHeight)
- }
- app.movements.init()
-
var last_t = 0
function animate (t) {
var dt = t - last_t
last_t = t
requestAnimationFrame(animate)
- environment.update(t)
- window.path && path.update(t)
- app.movements.update(dt || 0)
- scene.update()
+ environment.update(t, dt)
}
var loader = new Loader(function(){
diff --git a/public/assets/javascripts/defaults.js b/public/assets/javascripts/defaults.js
index 12aed62..1e1ac5b 100644
--- a/public/assets/javascripts/defaults.js
+++ b/public/assets/javascripts/defaults.js
@@ -1,5 +1,7 @@
+app = window.app || {}
app.defaults = {
viewHeight: window.viewHeight = 186,
+ wallHeight: 397,
units: app.units = "ft",
footResolution: 36,
meterResolution: 100,
@@ -13,7 +15,7 @@ app.defaults = {
},
}
-marked.setOptions({
+window.marked && marked.setOptions({
sanitize: true,
smartypants: true,
})
diff --git a/public/assets/javascripts/mx/extensions/mx.movements.js b/public/assets/javascripts/mx/extensions/mx.movements.js
index 9af2c8d..3e34c1b 100644
--- a/public/assets/javascripts/mx/extensions/mx.movements.js
+++ b/public/assets/javascripts/mx/extensions/mx.movements.js
@@ -20,7 +20,7 @@ MX.Movements = function (cam) {
rotationX_max = PI/6
var v = 12,
- vr = Math.PI * 0.012,
+ vr = Math.PI * 0.018,
jumpV = 23,
vx = vy = vz = 0,
creepFactor = 0.3
@@ -39,7 +39,7 @@ MX.Movements = function (cam) {
})
function clampRotation( vr ) {
- if (Rooms.mover.noclip) {
+ if (window.Rooms && Rooms.mover.noclip) {
return clamp(vr, PI/-2, PI/2 )
}
else {
@@ -155,6 +155,9 @@ MX.Movements = function (cam) {
app.controller.presets.hide()
$(".inuse").removeClass("inuse")
}
+ else if (Rooms.shapesMode) {
+ // don't show map in editor for now..
+ }
else {
app.controller.toolbar.toggleMap()
}
@@ -162,10 +165,13 @@ MX.Movements = function (cam) {
case 8: // backspace
e.preventDefault()
- if (app.controller.mediaEditor.scenery) {
+ if (app.controller.sculptureEditor && app.controller.sculptureEditor.sculpture) {
+ app.controller.sculptureEditor.sculpture.remove()
+ }
+ else if (app.controller.mediaEditor && app.controller.mediaEditor.scenery) {
app.controller.mediaEditor.scenery.remove()
}
- else if (app.controller.textEditor.scenery) {
+ else if (app.controller.textEditor && app.controller.textEditor.scenery) {
app.controller.textEditor.scenery.remove()
}
}
@@ -221,15 +227,19 @@ MX.Movements = function (cam) {
/*
case 48: // 0
- cam.rotationX = 0
- cam.rotationY = 0
- cam.x = 0
- cam.y = viewHeight
- cam.z = 0
+ movements.center()
break
*/
}
},
+
+ center: function(){
+ cam.rotationX = 0
+ cam.rotationY = 0
+ cam.x = 0
+ cam.y = viewHeight
+ cam.z = 0
+ },
mousedown: function (e) {
if (locked) return;
@@ -341,7 +351,7 @@ MX.Movements = function (cam) {
jumping = false
}
- var ceiling = (Rooms.mover.room ? Rooms.mover.room.height : 5000)
+ var ceiling = ((window.Rooms && Rooms.mover.room) ? Rooms.mover.room.height : 5000)
if (pos.y >= ceiling-5) {
vy = 0
diff --git a/public/assets/javascripts/mx/extensions/mx.orbitCamera.js b/public/assets/javascripts/mx/extensions/mx.orbitCamera.js
new file mode 100644
index 0000000..a936cef
--- /dev/null
+++ b/public/assets/javascripts/mx/extensions/mx.orbitCamera.js
@@ -0,0 +1,97 @@
+MX.OrbitCamera = function(opt){
+ var exports = {}, bound = false
+ exports.opt = opt = defaults(opt, {
+ el: window, // object to bind events on
+ camera: scene.camera, // camera object we'll be moving
+ radius: 100,
+ radiusRange: [ 10, 1000 ],
+ rotationX: PI/2,
+ rotationY: 0,
+ center: { x: 0, y: 0, z: 0 },
+ sensitivity: 10, // moving 1 pixel is like moving N radians
+ wheelSensitivity: 10,
+ ease: 10,
+ })
+ var rx, ry, radius, px, py, epsilon = 1e-10, dragging = false
+ exports.init = function(){
+ ry = opt.rotationY
+ rx = opt.rotationX
+ radius = opt.radius
+ exports.wheel = new wheel({
+ el: opt.el,
+ update: function(e, delta){
+ opt.radius = clamp( opt.radius + delta * opt.wheelSensitivity, opt.radiusRange[0], opt.radiusRange[1] )
+ },
+ })
+ exports.bind()
+ }
+ exports.toggle = function(state){
+ if (state) exports.bind()
+ else exports.unbind()
+ }
+ exports.bind = function(){
+ if (bound) return;
+ bound = true
+ opt.el.addEventListener("mousedown", down)
+ window.addEventListener("mousemove", move)
+ window.addEventListener("mouseup", up)
+ exports.wheel.unlock()
+ }
+ exports.unbind = function(){
+ if (! bound) return;
+ bound = false
+ opt.el.removeEventListener("mousedown", down)
+ window.removeEventListener("mousemove", move)
+ window.removeEventListener("mouseup", up)
+ exports.wheel.lock()
+ }
+ function down (e) {
+ px = e.pageX
+ py = e.pageY
+ dragging = true
+ }
+ function move (e) {
+ if (! dragging) return
+ exports.delta(px- e.pageX, py - e.pageY)
+ px = e.pageX
+ py = e.pageY
+ }
+ function up (e) {
+ dragging = false
+ }
+ exports.delta = function(x,y){
+ opt.rotationY += x/window.innerWidth * opt.sensitivity
+ opt.rotationX = clamp( opt.rotationX + y/window.innerHeight * opt.sensitivity, 0, PI)
+ }
+ exports.move = function(y, x){
+ opt.rotationY = y
+ if (typeof x == "number") { opt.rotationX = x }
+ }
+ exports.update = function(){
+ if (! bound) return
+ if (abs(ry - opt.rotationY) > epsilon) {
+ ry = avg(ry, opt.rotationY, opt.ease)
+ }
+ else {
+ ry = opt.rotationY
+ }
+ if (abs(rx - opt.rotationX) > epsilon) {
+ rx = avg(rx, opt.rotationX, opt.ease)
+ }
+ else {
+ rx = opt.rotationX
+ }
+ if (abs(radius - opt.radius) > epsilon) {
+ radius = avg(radius, opt.radius, opt.ease)
+ }
+ else {
+ radius = opt.radius
+ }
+ opt.camera.x = opt.center.x + radius * sin(rx) * cos(ry)
+ opt.camera.y = opt.center.y + radius * cos(rx)
+ opt.camera.z = opt.center.z + radius * sin(rx) * sin(ry)
+ opt.camera.rotationX = PI/2 - rx
+ opt.camera.rotationY = ry + PI/2
+ }
+ return exports
+}
diff --git a/public/assets/javascripts/mx/mx.js b/public/assets/javascripts/mx/mx.js
index ab9a9a0..60651eb 100644
--- a/public/assets/javascripts/mx/mx.js
+++ b/public/assets/javascripts/mx/mx.js
@@ -25,7 +25,7 @@ var MX = MX || (function (undefined) {
var MX = {
version: '0.1.0',
prefix: undefined,
- rotationUnit: 'rad'
+ rotationUnit: 'rad',
}
var floatPrecision = 5
@@ -162,24 +162,24 @@ var MX = MX || (function (undefined) {
Object.defineProperty(this, 'width', {
get: function () {
return width
- || parseInt(self.el.style.width, 10) * app.devicePixelRatio
+ || parseInt(self.el.style.width, 10) * app_devicePixelRatio
|| 0
},
set: function (val) {
width = val
- this.el.style.width = (width/app.devicePixelRatio) + 'px'
+ this.el.style.width = (width/app_devicePixelRatio) + 'px'
}
})
Object.defineProperty(this, 'height', {
get: function () {
return height
- || parseInt(self.el.style.height, 10) * app.devicePixelRatio
+ || parseInt(self.el.style.height, 10) * app_devicePixelRatio
|| 0
},
set: function (val) {
height = val
- this.el.style.height = (height/app.devicePixelRatio) + 'px'
+ this.el.style.height = (height/app_devicePixelRatio) + 'px'
}
})
}
@@ -302,9 +302,9 @@ var MX = MX || (function (undefined) {
+ (-this.y).toFixed(floatPrecision) + 'px,'
+ (-this.z).toFixed(floatPrecision) + 'px) '
+ 'scale3d('
- + (app.devicePixelRatio * this.scaleX).toFixed(floatPrecision) + ','
- + (app.devicePixelRatio * this.scaleY).toFixed(floatPrecision) + ','
- + (app.devicePixelRatio * this.scaleZ).toFixed(floatPrecision) + ') '
+ + (app_devicePixelRatio * this.scaleX).toFixed(floatPrecision) + ','
+ + (app_devicePixelRatio * this.scaleY).toFixed(floatPrecision) + ','
+ + (app_devicePixelRatio * this.scaleZ).toFixed(floatPrecision) + ') '
if (rotationTranslation) {
transformString += rotationTranslation.before
diff --git a/public/assets/javascripts/mx/primitives/mx.grid.js b/public/assets/javascripts/mx/primitives/mx.grid.js
new file mode 100644
index 0000000..a765c89
--- /dev/null
+++ b/public/assets/javascripts/mx/primitives/mx.grid.js
@@ -0,0 +1,70 @@
+MX.Grid = MX.Object3D.extend({
+ init: function (ops) {
+
+ this.type = "Grid"
+
+ var ops = this.ops = defaults(ops, {
+ x: 0,
+ y: 0,
+ z: 0,
+ space: 20,
+ cells: 20,
+ })
+
+ ops.side = ops.space * ops.cells
+
+ var ctx, canvas = document.createElement("canvas")
+
+ this.el = canvas
+ this.width = this.height = canvas.width = canvas.height = ops.side + 4
+
+ ctx = canvas.getContext('2d')
+
+ this.x = ops.x || 0
+ this.y = ops.y || 0
+ this.z = ops.z || 0
+ this.rotationX = PI/2
+ this.scale = ops.scale || 1
+ this.backface = ops.backface || false
+
+ ops.className && this.el.classList.add(ops.className)
+ this.backface && this.el.classList.add("backface-visible")
+ this.el.classList.add("mx-grid")
+
+ this.draw(ctx)
+ },
+
+ draw: function(ctx, recenter){
+ ctx = ctx || this.ctx
+
+ if (recenter) {
+ ctx.save()
+ ctx.translate( -grid.width/2, -grid.height/2 )
+ }
+
+ var cells = this.ops.cells,
+ space = this.ops.space,
+ side = this.ops.side
+
+ ctx.strokeStyle = "#444"
+ ctx.lineWidth = 1
+ ctx.beginPath()
+ ctx.translate(1,1)
+ for (var i = 0; i <= cells; i++) {
+ ctx.moveTo(i*space, 0)
+ ctx.lineTo(i*space, side)
+ ctx.moveTo(0, i*space)
+ ctx.lineTo(side, i*space)
+ }
+ ctx.closePath()
+ ctx.stroke()
+
+ if (recenter) {
+ ctx.restore()
+ }
+
+ },
+
+})
+
+
diff --git a/public/assets/javascripts/mx/primitives/mx.image.js b/public/assets/javascripts/mx/primitives/mx.image.js
index b8557bf..ce99592 100644
--- a/public/assets/javascripts/mx/primitives/mx.image.js
+++ b/public/assets/javascripts/mx/primitives/mx.image.js
@@ -1,6 +1,7 @@
MX.Image = MX.Object3D.extend({
init: function (ops) {
-
+ ops = ops || {}
+
this.type = "Image"
this.media = ops.media
this.width = 0
@@ -18,7 +19,7 @@ MX.Image = MX.Object3D.extend({
this.el.style.backgroundRepeat = 'no-repeat'
- this.load(ops)
+ ops.src && this.load(ops)
},
load: function(ops){
@@ -41,8 +42,41 @@ MX.Image = MX.Object3D.extend({
layer.el.classList.add('image')
layer.dirty = true
layer.update()
+ layer.ops.onload
+
+ if (ops.keepImage) {
+ layer.image = image
+ }
+ }
+
+ if (ops.src) {
+ image.src = ops.src
+ }
+ else if (ops.media) {
+ image.src = ops.media.url
+ }
+ else if (ops.url) {
+ image.src = ops.url
+ }
+ },
+
+ draw: function(ctx, recenter){
+ if (! this.image) { return }
+
+ if (recenter) {
+ ctx.save()
+ ctx.scale(-1, 1)
+ ctx.translate( -this.width/2 * this.scale, -this.height/2 * this.scale )
+ }
+
+ ctx.drawImage(this.image,
+ 0, 0, this.image.naturalWidth, this.image.naturalHeight,
+ 0, 0, this.image.width * this.scale * devicePixelRatio, this.image.height * this.scale * devicePixelRatio
+ )
+
+ if (recenter) {
+ ctx.restore()
}
- image.src = ops.src;
},
})
diff --git a/public/assets/javascripts/mx/primitives/mx.point.js b/public/assets/javascripts/mx/primitives/mx.point.js
new file mode 100644
index 0000000..41a7732
--- /dev/null
+++ b/public/assets/javascripts/mx/primitives/mx.point.js
@@ -0,0 +1,17 @@
+MX.Point = MX.Object3D.extend({
+ init: function(p){
+ this.updateChildren = false
+ this.move({
+ x: p.a,
+ y: 11,
+ z: p.b,
+ width: 20,
+ height: 20,
+ rotationX: PI/2,
+ })
+ this.el.style.backgroundColor = 'rgb(' + [abs(floor(p.a*30)), 0, abs(floor(p.b*30))] + ')'
+ this.el.style.backfaceVisibility = "visible"
+ this.el.style.borderRadius = "50%"
+ scene.add(this)
+ }
+})
diff --git a/public/assets/javascripts/mx/primitives/mx.polyline.js b/public/assets/javascripts/mx/primitives/mx.polyline.js
new file mode 100644
index 0000000..e549150
--- /dev/null
+++ b/public/assets/javascripts/mx/primitives/mx.polyline.js
@@ -0,0 +1,57 @@
+MX.Polyline = MX.Object3D.extend({
+ init: function(polyline){
+ this.faces = []
+ this.points = polyline.points
+ for (var i = 1; i < this.points.length; i++) {
+ var mx = new MX.Object3D()
+ var head = this.points[i-1]
+ var tail = this.points[i]
+ this.move_face(mx, head, tail)
+ this.faces.push(mx)
+ scene.add(mx)
+ }
+ },
+
+ rebuild: function(){
+ for (var i = 1; i < this.points.length; i++) {
+ var mx = this.faces[i-1]
+ var head = this.points[i-1]
+ var tail = this.points[i]
+ this.move_face(mx, head, tail)
+ }
+ },
+
+ move_face: function (mx, head, tail){
+ var mid_x = (head.a + tail.a)
+ var mid_z = (head.b + tail.b)
+ var len = head.distanceTo( tail )
+ var angle = atan2( head.b - tail.b, head.a - tail.a )
+ mx.move({
+ x: mid_x / 2,
+ y: wallHeight/2 + 1,
+ z: mid_z / 2,
+ width: ceil(len),
+ height: wallHeight,
+ rotationY: angle
+ })
+ // var hue = abs(round( angle / PI * 90 + 300))
+ // mx.el.style.backgroundColor = 'hsl(' + [hue, "100%", "50%"] + ')'
+ var lum = abs(round( angle / PI * 20 + 70))
+ mx.el.style.backgroundColor = 'hsla(' + ["0", "0%", lum + "%", "0.99"] + ')'
+ },
+
+ set_height: function(height){
+ for (var i = 0; i < this.faces.length; i++) {
+ this.faces[i].height = height
+ this.faces[i].y = height / 2 + 1
+ }
+ },
+
+ destroy: function(){
+ this.faces.forEach(function(mx){
+ scene.remove(mx)
+ })
+ this.faces = null
+ this.points = null
+ },
+})
diff --git a/public/assets/javascripts/mx/primitives/mx.soundcloud.js b/public/assets/javascripts/mx/primitives/mx.soundcloud.js
index 75286d9..fecb2f4 100644
--- a/public/assets/javascripts/mx/primitives/mx.soundcloud.js
+++ b/public/assets/javascripts/mx/primitives/mx.soundcloud.js
@@ -102,6 +102,11 @@ MX.Soundcloud = MX.Object3D.extend({
setLoop: function(state){
this.media.loop = state
},
+
+ setVolume: function(n){
+ if (this.muted || ! this.player) return
+ this.player.setVolume(floor( n * 100 ))
+ },
didPlay: function(){
this.paused = false
diff --git a/public/assets/javascripts/mx/primitives/mx.text.js b/public/assets/javascripts/mx/primitives/mx.text.js
index 6b5681b..3095b67 100644
--- a/public/assets/javascripts/mx/primitives/mx.text.js
+++ b/public/assets/javascripts/mx/primitives/mx.text.js
@@ -47,7 +47,7 @@ MX.Text = MX.Object3D.extend({
if (! font.color || font.color[0] == "#") { font.color = [0,0,0] }
this.inner.style.fontFamily = "'" + font.family + "',sans-serif"
- this.el.style.fontSize = (2 * font.size / devicePixelRatio) + "pt"
+ this.el.style.fontSize = (font.size / devicePixelRatio) + "pt"
this.el.style.textAlign = font.align
this.el.style.color = rgb_string(font.color)
},
diff --git a/public/assets/javascripts/mx/primitives/mx.video.js b/public/assets/javascripts/mx/primitives/mx.video.js
index c281f02..53ccf2e 100644
--- a/public/assets/javascripts/mx/primitives/mx.video.js
+++ b/public/assets/javascripts/mx/primitives/mx.video.js
@@ -21,6 +21,7 @@ MX.Video = MX.Object3D.extend({
this.el.classList.add("video")
this.el.classList.add("mx-scenery")
this.paused = !! this.media.autoplay
+ this.playing = false
this.muted = app.muted || !! this.media.mute
},
@@ -60,11 +61,13 @@ MX.Video = MX.Object3D.extend({
play: function(){
this.paused = false
+ this.playing = true
this.player.play()
},
pause: function(){
this.paused = true
+ this.playing = false
this.player.pause()
},
@@ -87,6 +90,11 @@ MX.Video = MX.Object3D.extend({
this.muted = false
},
+ setVolume: function(n){
+ if (this.muted || ! this.player) return
+ this.player.volume = n
+ },
+
setLoop: function(state){
this.media.loop = state
},
diff --git a/public/assets/javascripts/mx/primitives/mx.vimeo.js b/public/assets/javascripts/mx/primitives/mx.vimeo.js
index fe5ce86..af138ae 100644
--- a/public/assets/javascripts/mx/primitives/mx.vimeo.js
+++ b/public/assets/javascripts/mx/primitives/mx.vimeo.js
@@ -20,6 +20,7 @@ MX.Vimeo = MX.Object3D.extend({
this.backface && this.el.classList.add("backface-visible")
this.el.classList.add("video")
this.el.classList.add("mx-scenery")
+ this.playing = false
this.paused = !! this.media.autoplay
this.muted = app.muted || !! this.media.mute
this.started = false
@@ -99,7 +100,7 @@ MX.Vimeo = MX.Object3D.extend({
return
}
- if (! this.started || n === 0) {
+ if (! this.started) {
return
}
@@ -130,6 +131,11 @@ MX.Vimeo = MX.Object3D.extend({
this.player.api('setVolume', 0.8)
this.muted = false
},
+
+ setVolume: function(n){
+ if (this.muted || ! this.player) return
+ this.player.api('setVolume', n)
+ },
setLoop: function(state){
this.media.loop = state
@@ -140,19 +146,26 @@ MX.Vimeo = MX.Object3D.extend({
if (this.paused) {
this.pause()
}
+ else {
+ this.playing = true
+ }
},
onPause: function(){
if (! this.paused) {
this.play()
}
+ else {
+ this.playing = false
+ }
},
finished: function(){
-// if (this.media.loop) {
-// this.seek(0)
-// this.play()
-// }
+ console.log("vimeo finished")
+ if (this.media.loop) {
+ this.seek(0)
+ this.play()
+ }
// else if (this.bound) {
if (! this.media.loop && this.bound) {
$(".playButton").removeClass('playing')
diff --git a/public/assets/javascripts/mx/primitives/mx.youtube.js b/public/assets/javascripts/mx/primitives/mx.youtube.js
index 5c92378..8cd9f59 100644
--- a/public/assets/javascripts/mx/primitives/mx.youtube.js
+++ b/public/assets/javascripts/mx/primitives/mx.youtube.js
@@ -21,6 +21,7 @@ MX.Youtube = MX.Object3D.extend({
this.el.classList.add("video")
this.el.classList.add("mx-scenery")
this.paused = !! this.media.autoplay
+ this.playing = false
this.muted = app.muted || !! this.media.mute
},
@@ -143,11 +144,13 @@ MX.Youtube = MX.Object3D.extend({
play: function(){
this.paused = false
+ this.playing = true
this.player.playVideo()
},
pause: function(){
this.paused = true
+ this.playing = false
this.player.pauseVideo()
},
@@ -173,6 +176,11 @@ MX.Youtube = MX.Object3D.extend({
this.player.setVolume(80)
this.muted = false
},
+
+ setVolume: function(n){
+ if (this.muted || ! this.player || ! this.player.setVolume) return
+ this.player.setVolume( floor(n * 100) )
+ },
setLoop: function(state){
this.media.loop = state
diff --git a/public/assets/javascripts/rectangles/_env.js b/public/assets/javascripts/rectangles/_env.js
index 3221bac..b3c7d66 100644
--- a/public/assets/javascripts/rectangles/_env.js
+++ b/public/assets/javascripts/rectangles/_env.js
@@ -1,6 +1,22 @@
var environment = new function(){}
environment.init = function(){
+ scene = new MX.Scene().addTo('#scene')
+ scene.width = window.innerWidth
+ scene.height = window.innerHeight
+ scene.perspective = window.innerHeight
+
+ cam = scene.camera
+ cam.y = viewHeight
+
+ if (is_mobile) {
+ app.movements = new MX.MobileMovements(cam, viewHeight)
+ }
+ else {
+ app.movements = new MX.Movements(cam, viewHeight)
+ }
+ app.movements.init()
+
map = new Map ()
if (window.scene) {
@@ -16,10 +32,18 @@ environment.init = function(){
scene.camera.radius = 20
}
-
+
+ window.onresize = function () {
+ scene.width = window.innerWidth
+ scene.height = window.innerHeight
+ scene.perspective = window.innerHeight
+ scene.update()
+ }
+
Rooms.init()
Walls.init()
Scenery.init()
+ Sculpture.init()
scene.update()
environment.update()
@@ -51,8 +75,13 @@ environment.init = function(){
}
})
}
-environment.update = function(t){
+environment.minimal = function(){
+ environment.update = function(t){}
+}
+environment.update = function(t, dt){
+ app.movements.update(dt || 0)
+ scene.update()
map.update()
- window.minimap && window.minimap.update && minimap.update()
+ window.minimap && minimap.update && minimap.update()
z = false
}
diff --git a/public/assets/javascripts/rectangles/engine/map/_map.js b/public/assets/javascripts/rectangles/engine/map/_map.js
index 202803a..ba3ec92 100644
--- a/public/assets/javascripts/rectangles/engine/map/_map.js
+++ b/public/assets/javascripts/rectangles/engine/map/_map.js
@@ -16,6 +16,7 @@ var Map = function(opt){
var base = this
base.el = opt.el
base.$el = $(base.el)
+ base.opt = opt
if (! base.el) return
@@ -33,7 +34,6 @@ var Map = function(opt){
scene.camera.x + sides.a, scene.camera.z + sides.b )
}
-
base.set_zoom = function (n) {
n = clamp(n, opt.zoom_min, opt.zoom_max)
base.zoom_exponent = n
@@ -41,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)
@@ -57,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(state){
- return $(base.el).toggle(state).is(':visible')
- }
-
}
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 eceda3c..4f4759f 100644
--- a/public/assets/javascripts/rectangles/engine/map/draw.js
+++ b/public/assets/javascripts/rectangles/engine/map/draw.js
@@ -5,24 +5,32 @@ Map.Draw = function(map, opt){
var draw = this
- var ctx = map.canvas.getContext("2d")
+ var ctx = draw.ctx = map.canvas.getContext("2d")
draw.animate = function(){
ctx.save()
draw.clear()
draw.fill("rgba(255,255,255,0.98)")
- if (opt.minimap) {
+ if (opt.ortho) {
+ }
+ else if (opt.minimap) {
ctx.translate( map.dimensions.a * 1/2, map.dimensions.b * 1/2)
ctx.scale( map.zoom, map.zoom )
+ if (map.opt.pivot) { ctx.rotate(scene.camera.rotationY + PI) }
ctx.translate( opt.center.x, - opt.center.z )
ctx.scale( -1, 1 )
draw.coords()
- draw.regions(Rooms.regions, [ "#fff" ], "#000")
+ if (Rooms.shapesMode) {
+ shapes.draw(draw.ctx, "#fff", null)
+ shapes.draw(draw.ctx, null, "#000")
+ }
+ else {
+ draw.regions(Rooms.regions, [ "#fff" ], "#000")
+ }
draw.camera(scene.camera)
}
-
else {
ctx.translate( map.dimensions.a * 1/2, map.dimensions.b * 1/2)
ctx.scale( map.zoom, map.zoom )
@@ -31,12 +39,19 @@ Map.Draw = function(map, opt){
draw.regions(Rooms.regions, [ "#f8f8f8" ], "#000")
draw.mouse(map.ui.mouse.cursor)
+ draw.mouse_dimensions(map.ui.mouse.cursor)
draw.coords()
scene && draw.camera(scene.camera)
}
ctx.restore()
}
+ draw.translate = function(){
+ ctx.translate( map.dimensions.a * 1/2, map.dimensions.b * 1/2)
+ ctx.scale( map.zoom, map.zoom )
+ ctx.translate( map.center.a, map.center.b )
+ ctx.scale( -1, 1 )
+ }
// changes the ctx temporarily
draw.render = function(){
@@ -56,9 +71,9 @@ Map.Draw = function(map, opt){
}
var canvas = document.createElement("canvas")
- ctx = canvas.getContext('2d')
canvas.width = thumbnail_width
canvas.height = thumbnail_height
+ ctx = canvas.getContext('2d')
draw.clear()
@@ -87,7 +102,7 @@ Map.Draw = function(map, opt){
ctx.putImageData(pixelData, 0, 0)
// reset the ctx
- ctx = map.canvas.getContext("2d")
+ ctx = draw.ctx
return canvas
}
@@ -123,7 +138,8 @@ Map.Draw = function(map, opt){
ctx.beginPath();
ctx.arc(mouse.x.b, mouse.y.b, radius, 0, 2*Math.PI, false);
ctx.fill();
-
+ }
+ draw.mouse_dimensions = function(mouse){
if (mouse.width() != 0 && mouse.height() != 0) {
if (map.ui.dragging) {
stroke_rect(mouse)
@@ -159,36 +175,39 @@ Map.Draw = function(map, opt){
}
draw.coords = function(){
- /*
- ctx.fillStyle = "#888";
- dot_at(0,0)
- ctx.fillStyle = "#bbb";
- dot_at(100,0)
- dot_at(0,100)
- ctx.fillStyle = "#ddd";
- dot_at(200,0)
- dot_at(0,200)
- ctx.fillStyle = "#eee";
- dot_at(300,0)
- dot_at(0,300)
- */
-
ctx.strokeStyle = "rgba(0,0,0,0.1)"
ctx.lineWidth = 1/map.zoom
var sides = map.sides()
- var quant = sides.clone().quantize(MAP_GRID_SIZE)
- for (var x = quant.x.a - MAP_GRID_SIZE; x <= quant.x.b; x += MAP_GRID_SIZE) {
- line(x, sides.y.a, x, sides.y.b)
- }
- for (var y = quant.y.a - MAP_GRID_SIZE; y <= quant.y.b; y += MAP_GRID_SIZE) {
- line(sides.x.a, y, sides.x.b, y)
+ var quant = sides.clone().quantize(draw.grid_size)
+ for (var x = quant.x.a - draw.grid_size; x <= quant.x.b; x += draw.grid_size) {
+ if (Math.round(x) % 360 == 0) {
+ ctx.strokeStyle = "rgba(0,0,0,0.3)"
+ ctx.lineWidth = 1/map.zoom
+ }
+ else {
+ ctx.strokeStyle = "rgba(0,0,0,0.05)"
+ ctx.lineWidth = 1/map.zoom
+ }
+ line(x, sides.y.a, x, sides.y.b)
}
+
+ for (var y = quant.y.a - draw.grid_size; y <= quant.y.b; y += draw.grid_size) {
+ if (Math.round(y) % 360 == 0) {
+ ctx.strokeStyle = "rgba(0,0,0,0.3)"
+ ctx.lineWidth = 1/map.zoom
+ }
+ else {
+ ctx.strokeStyle = "rgba(0,0,0,0.05)"
+ ctx.lineWidth = 1/map.zoom
+ }
+ line(sides.x.a, y, sides.x.b, y)
+ }
}
//
- function line (x,y,a,b,translation){
+ var line = draw.line = function (x,y,a,b,translation){
if (translation) {
x += translation.a
a += translation.a
@@ -219,11 +238,11 @@ Map.Draw = function(map, opt){
line(r.x.a, r.y.a, r.x.b, r.y.b, r.translation)
}
- function dot_at (x,z){
+ draw.dot_at = function dot_at (x, z, radius){
ctx.save()
ctx.translate(x,z)
- var radius = 2 / map.zoom
+ radius = (radius || 2) / map.zoom
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2*Math.PI, false);
@@ -231,4 +250,29 @@ Map.Draw = function(map, opt){
ctx.restore()
}
+
+ draw.x_at = function x_at (x, z, length){
+ ctx.save()
+ if (x && 'x' in x) {
+ length = z
+ ctx.translate(x.x,x.z)
+ }
+ else {
+ ctx.translate(x,z)
+ }
+
+ var len = (length/2 || 4) / map.zoom
+
+ ctx.lineCap = "square"
+ ctx.lineWidth = 2/map.zoom
+ ctx.beginPath()
+ ctx.moveTo( -len, -len);
+ ctx.lineTo( len, len);
+ ctx.moveTo( -len, len);
+ ctx.lineTo( len, -len);
+ ctx.stroke();
+
+ ctx.restore()
+ }
+
} \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/_base.js b/public/assets/javascripts/rectangles/engine/map/tools/_base.js
new file mode 100644
index 0000000..17b247d
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/_base.js
@@ -0,0 +1,11 @@
+var MapTool = Fiber.extend(function(base){
+ var exports = {
+ recenterCursor: true,
+ down: function(e, cursor){},
+ move: function(e, cursor){},
+ drag: function(e, cursor){},
+ up: function(e, cursor, new_cursor){},
+ cancel: function(){},
+ }
+ return exports
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/arrow.js b/public/assets/javascripts/rectangles/engine/map/tools/arrow.js
new file mode 100644
index 0000000..0b0557e
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/arrow.js
@@ -0,0 +1,83 @@
+// Tool used to move corners of polylines
+
+var ArrowTool = MapTool.extend(function(base){
+ var exports = {}
+
+ var selected_point = null, selected_segment = null, original_point = null, selected_shape = null
+ var src_points, dest_points
+
+ exports.down = function(e, cursor){
+ last_point.a = cursor.x.a
+ last_point.b = cursor.y.a
+ var p = shapes.findClosestPoint(last_point)
+ if (p && p.shape.type() !== "ortho") {
+ selected_shape = p.shape
+ selected_point = p.point
+ original_point = selected_point.clone()
+ return
+ }
+ var segment = shapes.findClosestSegment(last_point)
+ if (segment) {
+ document.body.style.cursor = "pointer"
+
+ selected_segment = segment
+ console.log(segment.head, segment.tail)
+ selected_shape = segment.shape
+ src_points = segment.shape.cloneSegment( segment )
+ dest_points = segment.shape.getSegment( segment )
+
+ last_point.a = segment.x
+ last_point.b = segment.y
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ }
+ else {
+ map.ui.set_drag_tool("position")
+ }
+ }
+
+ exports.move = function(e, cursor){
+ last_point.a = cursor.x.a
+ last_point.b = cursor.y.a
+ var p = shapes.findClosestPoint(last_point)
+ if (p) {
+ document.body.style.cursor = "pointer"
+ last_point.assign(p.point)
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ return
+ }
+ var segment = shapes.findClosestSegment(last_point)
+ if (segment) {
+ document.body.style.cursor = "pointer"
+ last_point.a = segment.x
+ last_point.b = segment.y
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ }
+ else {
+ document.body.style.cursor = "crosshair"
+ }
+ }
+
+ exports.drag = function(e, cursor){
+ if (selected_point) {
+ selected_point.a = original_point.a + cursor.x.magnitude()
+ selected_point.b = original_point.b + cursor.y.magnitude()
+ selected_shape.rebuild()
+ }
+ else if (selected_segment) {
+ selected_shape.translateSegment(
+ src_points, dest_points,
+ cursor.x.magnitude(), cursor.y.magnitude()
+ )
+ selected_shape.rebuild()
+ }
+ }
+
+ exports.up = function(e, cursor){
+ selected_point = selected_shape = selected_segment = original_point = null
+ }
+
+ return exports
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/eraser.js b/public/assets/javascripts/rectangles/engine/map/tools/eraser.js
new file mode 100644
index 0000000..8fc3687
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/eraser.js
@@ -0,0 +1,29 @@
+// Tool used to delete lines
+
+var EraserTool = MapTool.extend(function(base){
+ var exports = {}
+ exports.down = function(e, cursor){
+ last_point.a = cursor.x.a
+ last_point.b = cursor.y.a
+ var segment = shapes.findClosestSegment(last_point)
+ if (segment) {
+ shapes.removeSegment(segment)
+ }
+ }
+ exports.move = function(e, cursor){
+ last_point.a = cursor.x.a
+ last_point.b = cursor.y.a
+ var segment = shapes.findClosestSegment(last_point)
+ if (segment) {
+ document.body.style.cursor = "pointer"
+ last_point.a = segment.x
+ last_point.b = segment.y
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ }
+ else {
+ document.body.style.cursor = "crosshair"
+ }
+ }
+ return exports
+})
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/line.js b/public/assets/javascripts/rectangles/engine/map/tools/line.js
new file mode 100644
index 0000000..8175d66
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/line.js
@@ -0,0 +1,63 @@
+// Tool is used to define a very simple line between two points.
+// It is used by the BlueprintScaler to specify a sample distance to scale.
+
+var LineTool = MapTool.extend(function(base){
+ var exports = {}
+
+ var selected_point = null
+
+ var line = exports.line = []
+
+ var can_drag, dragging
+
+ exports.down = function(e, cursor){
+
+ // rightclick?
+ if (e.ctrlKey || e.which === 3) {
+ cursor.quantize(1/map.zoom)
+ app.router.blueprintView.map.center.a = cursor.x.a
+ app.router.blueprintView.map.center.b = -cursor.y.a
+ cursor.x.b = cursor.x.a
+ cursor.y.b = cursor.y.a
+ return
+ }
+
+ this.cursor = cursor
+ switch (line.length) {
+ case 0:
+ line[0] = cursor.x_component()
+ can_drag = true
+ break
+ case 1:
+ line[1] = cursor.x_component()
+ can_drag = false
+ break
+ case 2:
+ line[0] = cursor.x_component()
+ line.pop()
+ can_drag = true
+ break
+ }
+ }
+
+ exports.move = function(e, cursor){
+ this.cursor = cursor
+ }
+
+ exports.drag = function(e, cursor){
+ if (dragging) {
+ line[1].a = cursor.x.b
+ line[1].b = cursor.y.b
+ }
+ else if (can_drag && cursor.magnitude() > 10/map.zoom) {
+ line[1] = cursor.y_component()
+ dragging = true
+ }
+ }
+
+ exports.up = function(e, cursor){
+ can_drag = dragging = false
+ }
+
+ return exports
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/ortho.js b/public/assets/javascripts/rectangles/engine/map/tools/ortho.js
new file mode 100644
index 0000000..374c822
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/ortho.js
@@ -0,0 +1,118 @@
+// Tool to make a polyline where all walls are orthogonal
+
+var OrthoPolylineTool = MapTool.extend(function (base) {
+
+ var prev_point, horizontal = false, first_edge_is_horizontal = false
+
+ var exports = {}
+ exports.down = function(e, cursor){
+ // rightclick?
+ if (e.ctrlKey || e.which === 3) {
+ e.preventDefault()
+ e.stopPropagation()
+ if (map.ui.placing) {
+ // close polyline or cancel
+ map.ui.placing = false
+ if (shapes.workline.points.length > 2) {
+ shapes.workline.build()
+ shapes.add(shapes.workline)
+ }
+ else {
+ shapes.workline.reset()
+ }
+ return
+ }
+ else {
+ map.ui.tools.position.rightclick(e, cursor)
+ }
+ return
+ }
+
+ // compare to initial point
+ var p = last_point.clone()
+ if (map.ui.placing) {
+ if (shapes.workline.lastPoint().eq(p)) {
+ return
+ }
+ else if (shapes.workline.canCloseWith(p)) {
+ shapes.workline.close()
+ shapes.workline.build()
+ shapes.add(shapes.workline)
+ map.ui.placing = false
+ }
+ else {
+ shapes.workline.add(p)
+ prev_point = p
+ horizontal = ! horizontal
+ }
+ }
+ else {
+ map.ui.placing = true
+ shapes.workline = new OrthoPolyline ()
+ shapes.workline.add(p)
+ first_point = prev_point = p
+ horizontal = false
+ }
+ }
+ exports.move = function(e, cursor){
+ last_point.a = cursor.x.a
+ last_point.b = cursor.y.a
+ if (map.ui.placing) {
+ if (shapes.workline.points.length == 1) {
+ var x = abs(prev_point.a - last_point.a)
+ var y = abs(prev_point.b - last_point.b)
+ if (x > y) {
+ last_point.b = prev_point.b
+ first_edge_is_horizontal = horizontal = true
+ }
+ else {
+ last_point.a = prev_point.a
+ first_edge_is_horizontal = horizontal = false
+ }
+ }
+ else {
+ if (horizontal) {
+ last_point.b = prev_point.b
+ }
+ else {
+ last_point.a = prev_point.a
+ }
+ if (horizontal == first_edge_is_horizontal) {
+ // check if this point is within N pixels of the normal
+ // and lock it into place if so
+ if (horizontal && abs( first_point.a - last_point.a ) < 10/map.zoom) {
+ last_point.a = first_point.a
+ }
+ else if (! horizontal && abs( first_point.b - last_point.b ) < 10/map.zoom) {
+ last_point.b = first_point.b
+ }
+ }
+ }
+
+ if (shapes.workline.canCloseWith(last_point)) {
+ document.body.style.cursor = "pointer"
+ last_point.assign(first_point)
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ }
+ return
+ }
+ var end_point = shapes.findClosestEndPoint(last_point)
+ if (end_point) {
+ document.body.style.cursor = "pointer"
+ last_point.assign(end_point.point)
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ return
+ }
+ else {
+ document.body.style.cursor = "crosshair"
+ }
+ }
+ exports.cancel = function(){
+ if (map.ui.placing) { shapes.workline.reset() }
+ first_point = null
+ map.ui.placing = false
+ }
+ return exports
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/polyline.js b/public/assets/javascripts/rectangles/engine/map/tools/polyline.js
new file mode 100644
index 0000000..445ae26
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/polyline.js
@@ -0,0 +1,73 @@
+// Tool used to draw polylines with arbitrary angles
+
+var PolylineTool = MapTool.extend(function (base) {
+ var exports = {}
+ exports.down = function(e, cursor){
+
+ // rightclick?
+ if (e.ctrlKey || e.which === 3) {
+ e.preventDefault()
+ e.stopPropagation()
+ if (map.ui.placing) {
+ // close polyline or cancel
+ map.ui.placing = false
+ if (shapes.workline.points.length > 2) {
+ shapes.workline.build()
+ shapes.add(shapes.workline)
+ }
+ else {
+ shapes.workline.reset()
+ }
+ return
+ }
+ map.ui.tools.position.rightclick(e, cursor)
+ return
+ }
+
+ // compare to initial point
+ var p = last_point.clone()
+ if (map.ui.placing) {
+ if (shapes.workline.canCloseWith(p)) {
+ shapes.workline.close()
+ shapes.workline.build()
+ shapes.add(shapes.workline)
+ map.ui.placing = false
+ }
+ else {
+ shapes.workline.add(p)
+ }
+ }
+ else {
+ map.ui.placing = true
+ shapes.workline = new Polyline ()
+ shapes.workline.add(p)
+ }
+ }
+ exports.move = function(e, cursor){
+ last_point.a = cursor.x.a
+ last_point.b = cursor.y.a
+ if (map.ui.placing && shapes.workline.canCloseWith(last_point)) {
+ document.body.style.cursor = "pointer"
+ last_point.assign(shapes.workline.points[0])
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ return
+ }
+ var end_point = shapes.findClosestEndPoint(last_point)
+ if (end_point) {
+ document.body.style.cursor = "pointer"
+ last_point.assign(end_point.point)
+ cursor.x.a = cursor.x.b = last_point.a
+ cursor.y.a = cursor.y.b = last_point.b
+ return
+ }
+ else {
+ document.body.style.cursor = "crosshair"
+ }
+ }
+ exports.cancel = function(){
+ if (map.ui.placing) { shapes.workline.reset() }
+ map.ui.placing = false
+ }
+ return exports
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/position.js b/public/assets/javascripts/rectangles/engine/map/tools/position.js
new file mode 100644
index 0000000..f8365bc
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/position.js
@@ -0,0 +1,19 @@
+// Tool used to set position on the map and let you change the view by dragging.
+
+var PositionTool = MapTool.extend(function(base){
+ var exports = {
+ recenterCursor: false,
+ drag: function(e, cursor){
+ map.center.a = -cursor.x.magnitude()
+ map.center.b = cursor.y.magnitude()
+ },
+ rightclick: function(e, cursor){
+ cursor.quantize(1/map.zoom)
+ map.center.a = cursor.x.a
+ map.center.b = -cursor.y.a
+ cursor.x.b = cursor.x.a
+ cursor.y.b = cursor.y.a
+ }
+ }
+ return exports
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/map/tools/start.js b/public/assets/javascripts/rectangles/engine/map/tools/start.js
new file mode 100644
index 0000000..203a85f
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/tools/start.js
@@ -0,0 +1,29 @@
+// Tool is used to set the start position on the map.
+
+var StartPositionTool = MapTool.extend(function(base){
+ var exports = {}
+
+ var selected_point = null
+
+ var line = exports.line = []
+
+ var can_drag, dragging
+
+ exports.down = function(e, cursor){
+ // rightclick?
+ if (e.ctrlKey || e.which === 3) {
+ cursor.quantize(1/map.zoom)
+ app.router.blueprintView.map.center.a = cursor.x.a
+ app.router.blueprintView.map.center.b = -cursor.y.a
+ cursor.x.b = cursor.x.a
+ cursor.y.b = cursor.y.a
+ return
+ }
+
+ cam.x = app.controller.startPosition.x = cursor.x.a
+ cam.z = app.controller.startPosition.z = cursor.y.a
+ }
+
+ return exports
+
+})
diff --git a/public/assets/javascripts/rectangles/engine/map/ui_editor.js b/public/assets/javascripts/rectangles/engine/map/ui/editor.js
index 7308344..699597a 100644
--- a/public/assets/javascripts/rectangles/engine/map/ui_editor.js
+++ b/public/assets/javascripts/rectangles/engine/map/ui/editor.js
@@ -73,7 +73,7 @@ Map.UI.Editor = function(map){
app.tube("builder-destroy-room", room)
// TODO: watch individual scenery object here
- Minotaur.watch( app.router.editorView.settings )
+ Minotaur.watch( (app.router.builderView || app.router.editorView).settings )
return
}
else if (intersects.length) {
@@ -205,13 +205,13 @@ Map.UI.Editor = function(map){
Rooms.rebuild()
// TODO: watch individual scenery object here
- Minotaur.watch( app.router.editorView.settings )
+ 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 (! intersects.length) {
+ if (! base.dragging && ! intersects.length) {
app.tube("builder-pick-nothing")
}
@@ -247,7 +247,7 @@ Map.UI.Editor = function(map){
Rooms.rebuild()
// TODO: watch individual scenery object here
- Minotaur.watch( app.router.editorView.settings )
+ Minotaur.watch( (app.router.builderView || app.router.editorView).settings )
wheelState = null
}, 250)
diff --git a/public/assets/javascripts/rectangles/engine/map/ui_minimap.js b/public/assets/javascripts/rectangles/engine/map/ui/minimap.js
index 0fdd336..0fdd336 100644
--- a/public/assets/javascripts/rectangles/engine/map/ui_minimap.js
+++ b/public/assets/javascripts/rectangles/engine/map/ui/minimap.js
diff --git a/public/assets/javascripts/rectangles/engine/map/ui/ortho.js b/public/assets/javascripts/rectangles/engine/map/ui/ortho.js
new file mode 100644
index 0000000..5be7446
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/map/ui/ortho.js
@@ -0,0 +1,107 @@
+Map.UI = Map.UI || {}
+Map.UI.Ortho = function(map){
+
+ var base = this
+ var last_event = null
+
+ base.creating = base.dragging = base.resizing = false
+
+ base.mouse = new mouse({
+ el: map.el,
+ down: function(e, cursor){
+ last_event = e
+ cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom)
+ cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom)
+ if (tool.recenterCursor) {
+ cursor.x.add(map.center.a)
+ cursor.y.sub(map.center.b)
+ base.tools[currentTool].down(e, cursor)
+ }
+ else {
+ base.tools[currentTool].down(e, cursor)
+ cursor.x.add(map.center.a)
+ cursor.y.sub(map.center.b)
+ }
+ },
+ move: function(e, cursor){
+ last_event = e
+ cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom)
+ cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom)
+ if (tool.recenterCursor) {
+ cursor.x.add(map.center.a)
+ cursor.y.sub(map.center.b)
+ base.tools[currentTool].move(e, cursor)
+ }
+ else {
+ base.tools[currentTool].move(e, cursor)
+ cursor.x.add(map.center.a)
+ cursor.y.sub(map.center.b)
+ }
+ },
+ drag: function(e, cursor){
+ last_event = e
+ cursor.x.b = ((cursor.x.b/map.dimensions.a)+0.5) * map.dimensions.a / map.zoom
+ cursor.y.b = ((cursor.y.b/map.dimensions.b)-0.5) * map.dimensions.b / map.zoom
+ if (tool.recenterCursor) {
+ cursor.x.b += map.center.a
+ cursor.y.b -= map.center.b
+ base.tools[currentTool].drag(e, cursor)
+ }
+ else {
+ base.tools[currentTool].drag(e, cursor)
+ cursor.x.b += map.center.a
+ cursor.y.b -= map.center.b
+ }
+ },
+ up: function(e, cursor, new_cursor){
+ last_event = e
+ new_cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom)
+ new_cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom)
+ if (tool.recenterCursor) {
+ new_cursor.x.add(map.center.a)
+ new_cursor.y.sub(map.center.b)
+ base.tools[currentTool].up(e, cursor, new_cursor)
+ }
+ else {
+ base.tools[currentTool].up(e, cursor, new_cursor)
+ new_cursor.x.add(map.center.a)
+ new_cursor.y.sub(map.center.b)
+ }
+ if (nextTool) {
+ console.log('found nextTool')
+ base.set_tool(nextTool)
+ nextTool = null
+ }
+ }
+ })
+
+ var currentTool = "polyline", nextTool, tool
+ base.add_tool = function(name, tool){
+ base.tools[name] = tool
+ }
+ base.set_tool = function(s){
+ console.log("set tool to", s)
+ if (base.tools[currentTool]) {
+ base.tools[currentTool].cancel()
+ }
+ currentTool = s
+ tool = base.tools[currentTool]
+ }
+ base.set_drag_tool = function(s){
+ console.log('set drag tool to', s)
+ nextTool = currentTool
+ currentTool = s
+ tool = base.tools[currentTool]
+ base.tools[currentTool].down(last_event, base.mouse.cursor)
+ }
+ base.tools = {}
+
+ base.wheel = new wheel({
+ el: map.el,
+ update: mousewheel,
+ })
+
+ function mousewheel (e, deltaY, deltaX){
+ map.set_zoom(map.zoom_exponent - deltaY/20)
+ }
+}
diff --git a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
index 46c1d7f..9aff33f 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
@@ -37,6 +37,7 @@
base.list = {}
base.regions = []
+ base.shapesMode = false
base.uid = new UidGenerator(base.list)
@@ -95,6 +96,7 @@
}
base.rebuild = function(walls_data){
+ if (base.shapesMode) return
walls_data = walls_data || Walls.serialize()
Rooms.clipper.update()
Rooms.builder.rebuild()
@@ -124,6 +126,45 @@
})
Rooms.rebuild(walls_data)
}
+
+ base.deserializeFromShapes = function(data, walls_data) {
+ walls_data = walls_data || Walls.serialize()
+ base.shapesMode = true
+ window.viewHeight = data.viewHeight || app.defaults.viewHeight
+ window.wallHeight = data.wallHeight || app.defaults.wallHeight
+ $(".units").val( data.units )
+
+ Rooms.builder.clear()
+
+ shapes.deserialize( data.shapes )
+ // shapes.build()
+ var regions = RegionList.build()
+
+ regions.forEach(function(region){
+ var room = new Room({
+ rect: region,
+ regions: [region],
+ height: wallHeight,
+ })
+
+ room.sides = region.sides
+ region.id = Rooms.uid("room_")
+ Rooms.list[ region.id ] = room
+ var mx_walls = Rooms.builder.build_walls(region)
+ room.mx_floor = Rooms.builder.make_floor(room, region)
+ room.mx_ceiling = Rooms.builder.make_ceiling(room, region)
+
+ mx_walls.forEach(function(mx){ scene.add(mx) })
+ scene.add(room.mx_floor)
+ scene.add(room.mx_ceiling)
+ })
+
+ Rooms.grouper.build()
+
+ Walls.paint()
+ Walls.deserialize(walls_data)
+ app.tube("rooms-built")
+ }
base.report = function(){
var data = []
diff --git a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
index d52fe21..6203c20 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
@@ -11,6 +11,7 @@ var Scenery = new function(){
base.init = function(){
base.resize.init()
+ base.sound.init()
}
base.add = function(opt){
@@ -113,6 +114,35 @@ var Scenery = new function(){
return added
}
+ base.rewindAll = function(){
+ base.forEach(function(scenery){
+ if (scenery.type == "video") scenery.seek(0)
+ })
+ }
+ base.playAll = function(){
+ base.forEach(function(scenery){
+ if (scenery.type == "video") {
+ scenery.unmute()
+ scenery.play()
+ }
+ })
+ }
+ base.pauseAll = function(){
+ base.forEach(function(scenery){
+ if (scenery.type == "video") scenery.pause()
+ })
+ }
+ base.muteAll = function(){
+ base.forEach(function(scenery){
+ if (scenery.type == "video") scenery.mx.mute()
+ })
+ }
+ base.unmuteAll = function(){
+ base.forEach(function(scenery){
+ if (scenery.type == "video") scenery.mx.unmute()
+ })
+ }
+
return base
}
diff --git a/public/assets/javascripts/rectangles/engine/scenery/move.js b/public/assets/javascripts/rectangles/engine/scenery/move.js
index f57ddba..94c6281 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/move.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/move.js
@@ -28,7 +28,7 @@ Scenery.move = function(base){
base.remove()
return
}
-
+
// load the modal
app.controller.pick(base)
@@ -75,13 +75,14 @@ Scenery.move = function(base){
switch (base.wall.side) {
case FRONT:
case BACK:
- base.mx.x = position.a + delta.a * cos(wall_rotation[base.wall.side]) + dimension.a / 2
+ base.mx.x = position.a + delta.a * cos(base.wall.rotationY) + dimension.a / 2
break
case LEFT:
case RIGHT:
- base.mx.z = position.a + delta.a * sin(wall_rotation[base.wall.side]) + dimension.a / 2
+ base.mx.z = position.a + delta.a * sin(base.wall.rotationY) + dimension.a / 2
break
}
+
if (editor.permissions.resize) {
Scenery.resize.move_dots()
}
@@ -104,7 +105,6 @@ Scenery.move = function(base){
dragging = moved = false
oldState = null
document.body.classList.remove("dragging")
-
}
function switch_wall (e, target, cursor){
diff --git a/public/assets/javascripts/rectangles/engine/scenery/resize.js b/public/assets/javascripts/rectangles/engine/scenery/resize.js
index 5af7f3f..252af74 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/resize.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/resize.js
@@ -8,6 +8,7 @@ Scenery.resize = new function(){
var dragging = false
var naturalDimension, naturalDimensionCopy, dimension, position, scale
var oldState
+ var rotationY
var dots = [], dot, selected_dot
@@ -49,7 +50,8 @@ Scenery.resize = new function(){
// rotate the dots as appropriate
base.rotate_dots = function(){
- rotationY = wall_rotation[obj.wall.side]
+ // console.trace()
+ rotationY = obj.wall.rotationY
dots.forEach(function(dot){
dot.rotationY = rotationY
})
@@ -93,6 +95,7 @@ Scenery.resize = new function(){
base.add_dots()
base.rotate_dots()
base.move_dots()
+ Sculpture.resize.hide()
}
// dismiss the dots on blur
diff --git a/public/assets/javascripts/rectangles/engine/scenery/sound.js b/public/assets/javascripts/rectangles/engine/scenery/sound.js
new file mode 100644
index 0000000..5a783d6
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/scenery/sound.js
@@ -0,0 +1,16 @@
+
+Scenery.sound = {}
+Scenery.sound.max_distance = 1500
+Scenery.sound.init = function(){
+ app.tube.on("move", Scenery.sound.move)
+}
+Scenery.sound.move = function(){
+ Scenery.forEach(function(scenery){
+ if ((scenery.type == "video" || scenery.type == "audio")) { // && ! scenery.muted()) {
+ var distance = dist(cam.x, cam.z,
+ scenery.mx.x, scenery.mx.z)
+ var volume = 1 - (clamp( distance, 0, Scenery.sound.max_distance ) / Scenery.sound.max_distance)
+ scenery.setVolume(volume)
+ }
+ })
+}
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/_object.js b/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
index cd3f981..e3b9b4d 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
@@ -90,11 +90,11 @@ Scenery.types.base = Fiber.extend(function(base){
switch (this.wall.side) {
case FRONT:
case BACK:
- this.position.a += delta.a * cos(wall_rotation[this.wall.side])
+ this.position.a += delta.a * cos(this.wall.rotationY)
break
case LEFT:
case RIGHT:
- this.position.a += delta.a * sin(wall_rotation[this.wall.side])
+ this.position.a += delta.a * sin(this.wall.rotationY)
break
}
},
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/audio.js b/public/assets/javascripts/rectangles/engine/scenery/types/audio.js
index 82f984e..fdd221d 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/types/audio.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/audio.js
@@ -39,6 +39,10 @@ Scenery.types.audio = Scenery.types.base.extend(function(base){
this.dimensions.deserialize(data.dimensions)
},
+ setVolume: function(n){
+ this.mx.setVolume(n)
+ },
+
play: function(){
this.mx.play()
},
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/video.js b/public/assets/javascripts/rectangles/engine/scenery/types/video.js
index d1b1763..163e19e 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/types/video.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/video.js
@@ -38,7 +38,8 @@ Scenery.types.video = Scenery.types.base.extend(function(base){
backface: false,
})
scene.add(this.mx)
- this.mx.load()
+
+ this.mx.load()
},
play: function(){
@@ -90,6 +91,14 @@ Scenery.types.video = Scenery.types.base.extend(function(base){
}
},
+ unmute: function(){
+ this.mx.unmute()
+ },
+
+ setVolume: function(n){
+ this.mx.setVolume(n)
+ },
+
serialize: function(){
var data = base.serialize.call(this)
return data
diff --git a/public/assets/javascripts/rectangles/engine/scenery/undo.js b/public/assets/javascripts/rectangles/engine/scenery/undo.js
index 1232780..b976ea2 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/undo.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/undo.js
@@ -195,5 +195,70 @@
},
},
+ //
+ {
+ type: "create-sculpture",
+ undo: function(state){
+ Sculpture.remove(state.id)
+ Sculpture.resize.hide()
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ redo: function(state){
+ var scenery = Sculpture.deserialize([ state ])
+ Sculpture.resize.show( sculpture )
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ },
+ {
+ type: "update-sculpture",
+ undo: function(state){
+ var sculpture = Sculpture.find(state.id)
+
+ sculpture.deserialize(state)
+
+ if (editor.permissions.resize) {
+ Sculpture.resize.show(sculpture)
+ }
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ redo: function(state){
+ var sculpture = Sculpture.find(state.id)
+
+ sculpture.deserialize(state)
+
+ if (editor.permissions.resize) {
+ Sculpture.resize.show(sculpture)
+ Sculpture.resize.move_dots()
+ Sculpture.resize.rotate_dots()
+ }
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ },
+ {
+ type: "destroy-sculpture",
+ undo: function(state){
+ var sculpture = Sculpture.deserialize([ state ])
+ Sculpture.resize.show( sculpture )
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ redo: function(state){
+ Sculpture.resize.hide()
+ Sculpture.remove(state.id)
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+ },
+ },
+
])
})()
diff --git a/public/assets/javascripts/rectangles/engine/sculpture/_sculpture.js b/public/assets/javascripts/rectangles/engine/sculpture/_sculpture.js
new file mode 100644
index 0000000..888b925
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/sculpture/_sculpture.js
@@ -0,0 +1,105 @@
+
+var Sculpture = new function(){
+
+ var base = this;
+
+ base.list = {}
+
+ base.mouse = new mouse ({ use_offset: false, mousedownUsesCapture: true })
+
+ base.init = function(){
+ app.on("move", base.updateBillboards)
+ base.resize.init()
+ }
+
+ base.updateBillboards = function(){
+ base.forEach(function(sculpture){
+ if (sculpture.billboard) {
+ sculpture.mx.rotationY = cam.rotationY
+ }
+ })
+ if (Sculpture.resize.obj && Sculpture.resize.obj.billboard) {
+ Sculpture.resize.move_dots()
+ }
+ }
+
+ base.add = function(opt){
+ var sculpture
+ switch (opt.media.type) {
+ case 'image':
+ sculpture = new Sculpture.types.image (opt)
+ break
+ }
+ base.list[sculpture.id] = sculpture
+ return sculpture
+ }
+
+ base.addNext = function(opt){
+ opt.newMedia = true
+ opt.media = Scenery.nextMedia
+ var sculpture = base.add(opt)
+
+ // test if sculpture was placed here
+ if (! sculpture) {
+ return null
+ }
+ else {
+ Scenery.nextMedia = null
+ return sculpture
+ }
+ }
+
+ base.find = function(id){
+ return base.list[id] || null
+ }
+
+ base.remove = function(id){
+ var scene_media = base.list[id]
+ delete base.list[id]
+ scene_media && scene_media.destroy()
+ }
+
+ base.removeAll = function(){
+ base.forEach(function(scene_media){
+ base.remove(scene_media.id)
+ })
+ }
+
+ base.uid = new UidGenerator(base.list)
+
+ base.forEach = function(f){
+ return base.values().forEach(f)
+ }
+
+ base.map = function(f){
+ return base.values().map(f)
+ }
+
+ base.values = function(){
+ return _.values(base.list)
+ }
+
+ base.serialize = function(){
+ var sculptures = base.map(function(sculpture){
+ return sculpture.serialize()
+ })
+ return sculptures
+ }
+
+ base.deserialize = function(sculpture_data){
+ var added = []
+ sculpture_data.forEach(function(data){
+ var scene_media = base.add({
+ id: data.id,
+ data: data,
+ media: data.media,
+ })
+ added.push(scene_media)
+ })
+ return added
+ }
+
+ return base
+}
+
+Sculpture.types = {}
diff --git a/public/assets/javascripts/rectangles/engine/sculpture/move.js b/public/assets/javascripts/rectangles/engine/sculpture/move.js
new file mode 100644
index 0000000..0cbeccd
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/sculpture/move.js
@@ -0,0 +1,111 @@
+
+Sculpture.move = function(base){
+
+ var x, y, z, position, dimension, bounds
+ var rotationY
+ var dragging = false, altPressed = false, shiftPressed = false, moved = false
+ var oldState
+ var height, width
+
+ this.bind = function(){
+ Sculpture.mouse.bind_el(base.mx.el)
+ Sculpture.mouse.on("down", down)
+ Sculpture.mouse.on("drag", drag)
+ Sculpture.mouse.on("up", up)
+ }
+
+ this.unbind = function(){
+ base.focused = false
+ Sculpture.mouse.unbind_el(base.mx.el)
+ Sculpture.mouse.off("down", down)
+ Sculpture.mouse.off("drag", drag)
+ Sculpture.mouse.off("up", up)
+ }
+
+ function down (e, cursor){
+ if (e.target != base.mx.el && (e.target != base.mx.overlay)) return;
+ if (editor.permissions.destroy) {
+ base.remove()
+ return
+ }
+
+ // load the modal
+ app.controller.pick(base)
+
+ if (! base.focused) {
+ e.clickAccepted = false
+ base.focused = true
+ return
+ }
+ if (! (editor.permissions.move || editor.permissions.resize) ) {
+ e.clickAccepted = false
+ return
+ }
+ altPressed = (e.altKey && ! base.billboard)
+ shiftPressed = e.shiftKey
+ dragging = true
+ moved = false
+ x = base.mx.x
+ y = base.mx.y
+ z = base.mx.z
+ rotationY = base.mx.rotationY
+ height = base.mx.height * base.mx.scale
+
+ bounds = base.bounds
+ dimension = base.dimensions
+
+ oldState = base.serialize()
+ document.body.classList.add("dragging")
+ }
+
+ function drag (e, cursor){
+ if (! dragging) return
+
+ moved = true
+
+ var delta = cursor.delta()
+ delta.mul( cursor_amp ) // TODO: this should be proportional to your distance from the wall
+ delta.b *= -1
+
+ // here we need to move the element based on the XY coords, as
+ // base.mx.y = position.b + delta.b + dimension.b / 2
+ if (shiftPressed) {
+ base.mx.y = clamp( y + delta.b, height/2 + sculpture_distance_from_floor, Infinity )
+ }
+ else if (altPressed) {
+ base.mx.rotationY = mod( rotationY + delta.a / (window.innerWidth / 2) , TWO_PI )
+ Sculpture.resize.move_dots()
+ }
+ else {
+ base.mx.x = x + delta.a * cos(cam.rotationY) - delta.b * sin(cam.rotationY)
+ base.mx.z = z + delta.a * sin(cam.rotationY) + delta.b * cos(cam.rotationY)
+ }
+
+ if (editor.permissions.resize) {
+ Sculpture.resize.move_dots()
+ base.updateOutline()
+ }
+ }
+
+ function up (e, cursor){
+ if (! dragging || ! oldState) return
+
+ if (moved) {
+ UndoStack.push({
+ type: 'update-sculpture',
+ undo: oldState,
+ redo: base.serialize(),
+ })
+
+ // TODO: watch individual sculpture object here
+ Minotaur.watch( app.router.editorView.settings )
+ }
+
+ dragging = moved = false
+ oldState = null
+ document.body.classList.remove("dragging")
+ }
+
+ return this
+
+} \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/sculpture/resize.js b/public/assets/javascripts/rectangles/engine/sculpture/resize.js
new file mode 100644
index 0000000..5f21d66
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/sculpture/resize.js
@@ -0,0 +1,208 @@
+Sculpture.resize = new function(){
+
+ var base = this
+
+ var obj
+ var x, y, z, bounds
+ var dragging = false
+ var naturalDimension, naturalDimensionCopy, dimension, position, scale
+ var oldState
+ var rotationY
+
+ var dots = [], dot, selected_dot
+
+ base.init = function(){
+ base.build()
+ base.bind()
+ }
+
+ // create 9 dots at the corners of the div
+ base.build = function(){
+ [ TOP,
+ TOP_RIGHT,
+ RIGHT,
+ BOTTOM_RIGHT,
+ BOTTOM,
+ BOTTOM_LEFT,
+ LEFT,
+ TOP_LEFT ].forEach(base.build_dot)
+ }
+
+ // generate a dot element
+ base.build_dot = function(side) {
+ var dot = new MX.Object3D('.dot')
+ dot.width = dot.height = dot_side * 2
+ dot.scale = 0.5
+ dot.side = side
+ $(dot.el).on({
+ mouseenter: function(){ base.hovering = true },
+ mouseleave: function(){ base.hovering = false },
+ })
+ dots.push(dot)
+ }
+
+ base.add_dots = function(){
+ dots.forEach(function(dot){
+ scene.add(dot)
+ })
+ }
+
+ // move all the dots to the object's current position
+ base.move_dots = function(){
+ rotationY = obj.mx.rotationY
+
+ var x = obj.mx.x + sin(rotationY) * dot_distance_from_picture
+ var y = obj.mx.y
+ var z = obj.mx.z - cos(rotationY) * dot_distance_from_picture
+
+ dots.forEach(function(dot){
+ base.move_dot(dot, { x: x, y: y, z: z, rotationY: rotationY })
+ })
+ }
+
+ // move a dot .. to the initial position of the image
+ base.move_dot = function(dot, pos){
+ if (dot.side & TOP) {
+ pos.y += obj.dimensions.b / 2
+ }
+ if (dot.side & BOTTOM) {
+ pos.y -= obj.dimensions.b / 2
+ }
+ if (dot.side & LEFT) {
+ pos.x -= cos(rotationY) * (obj.dimensions.a) / 2
+ pos.z -= sin(rotationY) * (obj.dimensions.a) / 2
+ }
+ if (dot.side & RIGHT) {
+ pos.x += cos(rotationY) * (obj.dimensions.a) / 2
+ pos.z += sin(rotationY) * (obj.dimensions.a) / 2
+ }
+ dot.move(pos)
+ }
+
+ // pick a new object to focus on and show the dots
+ base.show = function(new_object) {
+ // if (obj === new_object) return
+ if (! new_object) return
+ base.obj = obj = new_object
+ base.add_dots()
+ base.move_dots()
+ }
+
+ // dismiss the dots on blur
+ var dotsHideTimeout;
+ base.defer_hide = function(){
+ clearTimeout(dotsHideTimeout)
+
+ dotsHideTimeout = setTimeout(function(){
+ if (Scenery.hovering || Scenery.resize.hovering || Scenery.mouse.down) return
+ Scenery.resize.hide()
+ }, dot_hide_delay)
+ }
+
+ base.hide = function () {
+ if (! obj) return
+ base.obj = obj = null
+ dots.forEach(function(dot){
+ scene.remove(dot)
+ })
+ }
+
+ base.bind = function(){
+ dots.forEach(function(dot){
+ Sculpture.mouse.bind_el(dot.el)
+ })
+ Sculpture.mouse.on("down", down)
+ Sculpture.mouse.on("drag", drag)
+ Sculpture.mouse.on("up", up)
+ }
+
+ base.unbind = function(){
+ dots.forEach(function(dot){
+ Sculpture.mouse.unbind_el(dot.el)
+ })
+ Sculpture.mouse.off("down", down)
+ Sculpture.mouse.off("drag", drag)
+ Sculpture.mouse.off("up", up)
+ }
+
+ function down (e, cursor){
+ var selection = dots.filter(function(dot){return e.target == dot.el})
+ if (! selection.length) return
+
+ selected_dot = selection[0]
+ dragging = true
+
+ naturalDimension = obj.naturalDimensions
+ dimension = obj.dimensions
+ position = new vec3(obj.mx.x, obj.mx.y, obj.mx.z)
+ oldState = obj.serialize()
+
+ if (obj.type == "text") {
+ naturalDimensionCopy = naturalDimension.clone()
+ positionCopy = position.clone()
+ }
+
+ document.body.classList.add("dragging")
+ }
+
+ function drag (e, cursor){
+ if (! dragging) return
+
+ var x_sign = selected_dot.side & LEFT ? -1 : selected_dot.side & RIGHT ? 1 : 0
+ var y_sign = selected_dot.side & TOP ? -1 : selected_dot.side & BOTTOM ? 1 : 0
+ var width = cursor.x.magnitude()
+ var height = cursor.y.magnitude()
+ var mag = cursor.magnitude()
+
+ if (abs(width) > abs(height)) {
+ mag = x_sign * mag * sign(width)
+ }
+ else {
+ mag = y_sign * mag * sign(height)
+ }
+
+ if (obj.type == "text") {
+ obj.mx.width = obj.media.width = naturalDimension.a = naturalDimensionCopy.a + (mag * 2)
+ obj.mx.height = obj.media.height = naturalDimension.b = naturalDimensionCopy.b + (mag * 2)
+ dimension.a = naturalDimension.a * obj.scale
+ dimension.b = naturalDimension.b * obj.scale
+ }
+ else {
+ obj.set_scale( ( dimension.a + mag ) / naturalDimension.a )
+ }
+
+ if (selected_dot.side & LEFT_RIGHT) {
+ obj.mx.x = position.a + cos(rotationY) * mag/2 * (x_sign)
+ obj.mx.z = position.c + sin(rotationY) * mag/2 * (x_sign)
+ }
+ if (selected_dot.side & TOP_BOTTOM) {
+ obj.mx.y = position.b - mag/2 * y_sign
+ }
+
+ base.move_dots()
+ obj.updateOutline()
+
+ app.controller.sculptureEditor.setDimensions()
+ }
+
+ function up (e, cursor){
+ if (! dragging) return
+ dragging = false
+ if (! editor.permissions.resize) { return }
+
+ obj.scale = obj.mx.ops.scale = obj.mx.scale
+ obj.dimensions.assign(obj.naturalDimensions).mul(obj.scale)
+
+ UndoStack.push({
+ type: 'update-sculpture',
+ undo: oldState,
+ redo: obj.serialize(),
+ })
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+
+ document.body.classList.remove("dragging")
+ selected_dot = null
+ }
+} \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/sculpture/types/_object.js b/public/assets/javascripts/rectangles/engine/sculpture/types/_object.js
new file mode 100644
index 0000000..2f593e8
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/sculpture/types/_object.js
@@ -0,0 +1,176 @@
+Sculpture.types.base = Fiber.extend(function(base){
+
+ var exports = {
+
+ init: function(opt){
+ this.id = opt.id || Sculpture.uid("sculpture")
+ // this.move = new Sculpture.move (this)
+ this.media = opt.media
+ this.naturalDimensions = new vec3(this.media.width, this.media.height, this.media.width)
+ this.dimensions = new vec3(this.media.width, this.media.height, this.media.width)
+
+ this.move = new Sculpture.move (this)
+ this.billboard = true
+ this.backface = true
+
+ this.set_scale( opt.scale || this.media.scale || 1.0 )
+ this.position = new vec2(0,0)
+
+ this.isSculpture = true
+ },
+
+ set_scale: function(scale){
+ this.scale = scale || 1.0
+ if (this.mx) {
+ this.mx.scale = this.mx.ops.scale = this.scale
+ }
+ this.dimensions = this.naturalDimensions.clone().mul(this.scale)
+ },
+ set_depth: function(depth){
+ console.log(this.dimensions.c, this.naturalDimensions.c, depth)
+ this.dimensions.c = depth
+ this.naturalDimensions.c = depth / this.scale
+ },
+
+ place: function(opt){
+ if (opt.data) {
+ this.deserialize(opt.data)
+ }
+ else {
+ this.mx.move(opt.position)
+ }
+ },
+
+ bind: function(){
+ this.move.bind()
+ },
+
+ unbind: function(){
+ this.move.unbind()
+ },
+
+ setOutline: function(val){
+ this.outline = val
+ if (val && ! this.outlineEl) {
+ this.buildOutline()
+ }
+ this.outlineEl.el.style.display = val ? "block" : "none"
+ },
+ setOutlineColor: function(val){
+ this.outlineColor = val
+ if (this.outlineEl) {
+ this.outlineColor = this.outlineEl.el.style.borderColor = val
+ }
+ },
+ setBillboard: function(val){
+ this.billboard = val
+ if (this.billboard) {
+ this.mx.el.classList.add("backface-hidden")
+ this.mx.el.classList.remove("backface-visible")
+ this.mx.rotationY = cam.rotationY
+ }
+ else {
+ this.mx.el.classList.add("backface-visible")
+ this.mx.el.classList.remove("backface-hidden")
+ this.mx.rotationY = quantize(cam.rotationY, PI/2)
+ }
+ Sculpture.resize.move_dots()
+ },
+
+ buildOutline: function(){
+ this.outlineEl = new MX.Object3D(".mx-outline")
+ this.outlineEl.width = this.naturalDimensions.a
+ this.outlineEl.height = this.naturalDimensions.c
+ this.outlineEl.scale = this.mx.scale
+ this.outlineEl.rotationX = -PI/2
+ this.outlineEl.x = this.mx.x
+ this.outlineEl.y = sculpture_distance_from_floor
+ this.outlineEl.z = this.mx.z
+ this.outlineEl.el.style.borderColor = this.outlineColor || "#000000"
+ scene.add(this.outlineEl)
+ },
+ updateOutline: function(){
+ if (! this.outline) { return }
+ if (! this.outlineEl) {
+ this.buildOutline()
+ }
+ this.outlineEl.x = this.mx.x
+ this.outlineEl.y = sculpture_distance_from_floor
+ this.outlineEl.z = this.mx.z
+ this.outlineEl.width = this.naturalDimensions.a
+ this.outlineEl.height = this.naturalDimensions.c
+
+ this.outlineEl.scale = this.mx.scale
+ },
+
+ remove: function(){
+ if (this.removed) return
+ this.removed = true
+
+ UndoStack.push({
+ type: 'destroy-sculpture',
+ undo: this.serialize(),
+ redo: { id: this.id },
+ })
+
+ // TODO: watch individual scenery object here
+ Minotaur.watch( app.router.editorView.settings )
+
+ Sculpture.remove(this.id)
+
+ Sculpture.resize.hide()
+ if (app.controller.sculptureEditor) {
+ app.controller.sculptureEditor.tainted = false
+ app.controller.sculptureEditor.hide()
+ }
+ },
+
+ destroy: function(){
+ this.unbind()
+ scene.remove(this.mx)
+ this.mx.media = null
+ this.mx.ops = null
+ this.mx = null
+ this.move = null
+ this.media = null
+ this.dimensions = null
+ this.naturalDimensions = null
+ this.wall = null
+ this.bounds = null
+ this.center = null
+ },
+
+ serialize: function(){
+ var data = {
+ id: this.id,
+ dimensions: this.dimensions.serialize(),
+ position: app.position(this.mx),
+ scale: this.scale,
+ media: this.media,
+ billboard: this.billboard,
+ outline: this.outline,
+ outlineColor: this.outlineColor || "#000000",
+ backface: this.backface,
+ }
+ return data
+ },
+
+ deserialize: function(data){
+ this.mx.move(data.position)
+ this.mx.ops.width = data.dimensions.a
+ this.mx.ops.height = data.dimensions.b
+ this.billboard = data.billboard
+ this.outline = data.outline
+ this.outlineColor = data.outlineColor || "#000000"
+ this.backface = data.backface
+ if (! this.backface) this.mx.el.classList.remove("backface-visible")
+ this.dimensions.deserialize(data.dimensions)
+ if (this.outline) {
+ this.buildOutline()
+ }
+ },
+ }
+
+ return exports
+
+})
diff --git a/public/assets/javascripts/rectangles/engine/sculpture/types/image.js b/public/assets/javascripts/rectangles/engine/sculpture/types/image.js
new file mode 100644
index 0000000..1a53f5b
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/sculpture/types/image.js
@@ -0,0 +1,38 @@
+
+Sculpture.types.image = Sculpture.types.base.extend(function(base){
+
+ var exports = {
+
+ type: 'image',
+
+ init: function(opt){
+
+ opt.scale = opt.scale || (opt.data && opt.data.scale) || DEFAULT_PICTURE_WIDTH / max(DEFAULT_PICTURE_WIDTH, opt.media.width)
+
+ base.init.call(this, opt)
+
+ this.build(opt)
+ this.bind()
+ this.place(opt)
+ },
+
+ build: function(opt){
+ this.mx = new MX.Image({
+ src: this.media.url,
+ scale: this.scale,
+ media: this.media,
+ backface: true,
+ })
+
+ if (opt.position) {
+ opt.position.y = opt.position.y || this.scale * this.media.height/2 + sculpture_distance_from_floor
+ opt.position.rotationY = opt.position.rotationY || scene.camera.rotationY
+ }
+
+ scene.add( this.mx )
+ },
+
+ }
+
+ return exports
+})
diff --git a/public/assets/javascripts/rectangles/engine/shapes/ortho.js b/public/assets/javascripts/rectangles/engine/shapes/ortho.js
new file mode 100644
index 0000000..163f646
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/shapes/ortho.js
@@ -0,0 +1,66 @@
+// An OrthoPolyline is a Polyline where all angles are 90 degrees.
+
+if (! ('window' in this) ) {
+ var Polyline = require("./polyline.js")
+}
+
+var OrthoPolyline = Polyline.extend(function(base){
+ var exports = {}
+ exports.type = function(){
+ return "ortho"
+ }
+ exports.instantiate = function(){
+ return new OrthoPolyline
+ }
+ exports.canCloseWith = function(p){
+ return (this.points.length > 2 && this.points[0].distanceTo( p ) < 10/map.zoom)
+ }
+ exports.draw = function(ctx, fillStyle, strokeStyle){
+ var points = this.points
+ if (! points.length) return
+ if (points.length == 1) {
+ ctx.fillStyle = "#f80"
+ map.draw.dot_at(this.points[0].a, points[0].b, 5)
+ }
+ if (points.length > 1) {
+ ctx.fillStyle = fillStyle
+ ctx.strokeStyle = strokeStyle
+ ctx.lineWidth = 2 / map.zoom
+ ctx.beginPath()
+ ctx.moveTo(points[0].a, points[0].b)
+ points.forEach(function(point, i){
+ i && ctx.lineTo(point.a, point.b)
+ })
+ strokeStyle && ctx.stroke()
+ if (! map.ui.placing || this.closed) {
+ fillStyle && ctx.fill()
+ }
+ }
+ }
+ exports.translateSegment = function(src, dest, dx, dy) {
+ if (src[0].a == src[1].a) {
+ dest[0].a = src[0].a + dx
+ dest[1].a = src[1].a + dx
+ if (src.length == 3) {
+ dest[2].a = src[2].a + dx
+ }
+ }
+ else {
+ dest[0].b = src[0].b + dy
+ dest[1].b = src[1].b + dy
+ if (src.length == 3) {
+ dest[2].b = src[2].b + dy
+ }
+ }
+ }
+ exports.close = function(){
+ this.points[this.points.length] = this.points[0]
+ this.closed = true
+ }
+ return exports
+})
+
+
+if (! ('window' in this) ) {
+ module.exports = OrthoPolyline
+}
diff --git a/public/assets/javascripts/rectangles/engine/shapes/polyline.js b/public/assets/javascripts/rectangles/engine/shapes/polyline.js
new file mode 100644
index 0000000..b2cd92f
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/shapes/polyline.js
@@ -0,0 +1,212 @@
+// A Polyline is a set of points inputted by the user using the V2 editor to trace a blueprint.
+// Additionally, it manages a set of MX objects which correspond to the walls in 3D.
+// In this way, it attempts to bridge the 2D (canvas, imperative) and 3D (css, declarative) views.
+
+if (! ('window' in this) ) {
+ var Fiber = require("../../../vendor/bower_components/fiber/src/fiber.js")
+ var vec2 = require("../../models/vec2")
+}
+
+var Polyline = Fiber.extend(function(base){
+ var exports = {}
+ exports.init = function(){
+ this.points = []
+ this.mx_points = []
+ this.closed = false
+ }
+ exports.type = function(){
+ return "polyline"
+ }
+ exports.instantiate = function(){
+ return new Polyline
+ }
+ exports.add = function(p){
+ this.points.push( p )
+ this.mx_points.push( new MX.Point(p) )
+ }
+ exports.firstPoint = function(){
+ return this.points[0]
+ }
+ exports.lastPoint = function(){
+ return this.points[this.points.length-1]
+ }
+ exports.canCloseWith = function(p){
+ return (this.points.length > 2 && this.points[0].distanceTo( p ) < 10/map.zoom)
+ }
+ exports.getHeadAtIndex = function(index){
+ if (index == 0) { return null }
+ if (index == this.points.length-1) { return this.clone() }
+ var head = this.instantiate()
+ head.points = this.points.slice(0, index+1)
+ return head
+ }
+ exports.getTailAtIndex = function(index){
+ if (index == this.points.length-1) { return null }
+ if (index == 0) { return this.clone() }
+ var tail = this.instantiate()
+ tail.points = this.points.slice(index, this.points.length)
+ return tail
+ }
+ exports.clone = function(){
+ var clone = this.instantiate()
+ clone.points = this.points.concat()
+ }
+ exports.getSegment = function(segment){
+ var seg = [
+ this.points[segment.head],
+ this.points[segment.tail],
+ ]
+ if (segment.head == 0) {
+ seg.push( this.lastPoint() )
+ }
+ else if (segment.tail == this.points.length-1) {
+ seg.push( this.firstPoint() )
+ }
+ return seg
+ }
+ exports.cloneSegment = function(segment){
+ return this.getSegment(segment).map(function(point){ return point.clone() })
+ }
+ exports.translateSegment = function(src, dest, dx, dy){
+ dest[0].a = src[0].a + dx
+ dest[0].b = src[0].b + dy
+ dest[1].a = src[1].a + dx
+ dest[1].b = src[1].b + dy
+ if (src.length == 3) {
+ dest[2].a = src[2].a + dx
+ dest[2].b = src[2].b + dy
+ }
+ }
+ exports.hasPointNear = function(p){
+ var point
+ for (var i = 0; i < this.points.length; i++){
+ point = this.points[i]
+ if (point.distanceTo( p ) < 10/map.zoom) {
+ return point
+ }
+ }
+ return null
+ }
+ exports.hasEndPointNear = function(p){
+ if (this.closed || ! this.points.length) return null
+ if (this.firstPoint().distanceTo( p ) < 10/map.zoom) {
+ return this.firstPoint()
+ }
+ if (this.lastPoint().distanceTo( p ) < 10/map.zoom) {
+ return this.lastPoint()
+ }
+ return null
+ }
+ exports.hasSegmentNear = function(p, min_dist){
+ var p1, p2, d1, d2, sum, rat
+ var dx, dy, new_x, new_y, x, y, closest_distance = min_dist || Infinity
+ var closest_i = -1
+ var points = this.points
+ var p1, p2 = points[0]
+ for (var i = 1; i < points.length; i++) {
+ p1 = p2
+ p2 = points[i]
+ d1 = p2.a - p1.a
+ d2 = p2.b - p1.b
+ sum = d1*d1 + d2*d2
+ rat = ((p.a - p1.a) * d1 + (p.b - p1.b) * d2) / sum
+ rat = rat < 0 ? 0 : rat < 1 ? rat : 1
+ new_x = p1.a + rat * d1
+ new_y = p1.b + rat * d2
+ dx = new_x - p.a
+ dy = new_y - p.b
+ sum2 = sqrt(dx*dx+dy*dy)
+ if (sum2 < closest_distance) {
+ x = new_x
+ y = new_y
+ closest_distance = sum2
+ closest_i = i
+ }
+ }
+ if (closest_i == -1) return null
+ return {
+ x: x,
+ y: y,
+ distance: closest_distance,
+ head: closest_i-1,
+ tail: closest_i,
+ }
+ }
+ exports.draw = function(ctx, fillStyle, strokeStyle){
+ var points = this.points
+ if (! points.length) return
+ if (points.length == 1) {
+ ctx.fillStyle = "#f80"
+ map.draw.dot_at(this.points[0].a, points[0].b, 5)
+ }
+ if (points.length > 1) {
+ ctx.fillStyle = fillStyle
+ ctx.strokeStyle = strokeStyle
+ ctx.lineWidth = 2 / map.zoom
+ ctx.beginPath()
+ ctx.moveTo(points[0].a, points[0].b)
+ points.forEach(function(point, i){
+ i && ctx.lineTo(point.a, point.b)
+ })
+ strokeStyle && ctx.stroke()
+ if (! map.ui.placing || this.closed) {
+ fillStyle && ctx.fill()
+ }
+ }
+ }
+ exports.draw_line = function (ctx, p){
+ var last = this.points[this.points.length-1]
+ ctx.strokeStyle = "#f80"
+ ctx.lineWidth = 2 / map.zoom
+ ctx.beginPath()
+ ctx.moveTo(last.a, last.b)
+ ctx.lineTo(p.a, p.b)
+ ctx.stroke()
+ }
+ exports.close = function(){
+ this.points[this.points.length] = this.points[0]
+ this.closed = true
+ }
+ exports.build = function(){
+ this.mx_points && this.mx_points.forEach(function(mx){ scene.remove(mx) })
+ this.mx = new MX.Polyline(this)
+ }
+ exports.rebuild = function(){
+ this.mx.rebuild()
+ }
+ exports.getSegments = function(){
+ if (this.points.length == 1) {
+ return []
+ }
+ var segments = []
+ for (var i = 1; i < this.points.length; i++) {
+ segments.push( [ this.points[i-1], this.points[i] ] )
+ }
+ return segments
+ }
+ exports.serialize = function(){
+ return {
+ type: this.type(),
+ closed: this.closed,
+ points: this.points.map(function(point){ return [point.a, point.b] }),
+ }
+ }
+ exports.deserialize = function(data){
+ this.closed = data.closed || false
+ this.points = (data.points || data).map(function(point){ return new vec2(point[0], point[1]) })
+ }
+ exports.reset = function(){
+ this.mx_points.forEach(function(mx){ scene.remove(mx) })
+ this.mx_points.length = 0
+ this.points.length = 0
+ }
+ exports.destroy = function(){
+ this.reset()
+ this.mx && this.mx.destroy()
+ }
+ return exports
+})
+
+if (! ('window' in this) ) {
+ module.exports = Polyline
+}
diff --git a/public/assets/javascripts/rectangles/engine/shapes/regionlist.js b/public/assets/javascripts/rectangles/engine/shapes/regionlist.js
new file mode 100644
index 0000000..8c9e732
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/shapes/regionlist.js
@@ -0,0 +1,240 @@
+// This algorithm takes the polylines from ShapeList as input and produces
+// a set of Rooms which can be used by the existing V1 mover and editor.
+
+// The algorithm assumes that
+// 1) all angles are orthogonal
+// 2) all polylines are closed
+
+if (! ('window' in this) ) {
+ var Fiber = require("../../../vendor/bower_components/fiber/src/fiber.js")
+ var vec2 = require("../../models/vec2")
+ var Rect = require("../../models/rect")
+ var sort = require("../../util/sort")
+}
+
+var RegionList = (function(){
+
+ var RegionList = {}
+ var regions = RegionList.regions
+
+ // Build a list of regions from the existing shapes.
+ // Operates on all the shapes at once.
+ RegionList.build = function(){
+ var segments = RegionList.getSortedSegments()
+ return RegionList.buildRoomsFromSegments(segments)
+ }
+
+ // Build a list of regions from the individual shapes.
+ // Same, but operates on the shapes individually
+ RegionList.buildByShape = function(){
+ var shapes = RegionList.getSortedSegmentsByShape()
+ var region_lists = shapes.map(function(shape){
+ return RegionList.buildRoomsFromSegments(shape.segments)
+ })
+ var regions = []
+ return regions.concat.apply(regions, region_lists);
+ }
+
+ RegionList.buildRoomsFromSegments = function(segments){
+
+ var rooms = []
+ var seen_rooms = {}
+ var open_segments = []
+
+ var segment, open_segment, x_segment, y_segments, overlapped, seen_segments
+
+ // first pass: generate rooms from the vertical segments only
+ for (var i = 0; i < segments.length; i++) {
+ segment = segments[i]
+ if (! segment.isVertical()) {
+ continue
+ }
+
+ // check all the "open segments" we know about, i.e. rooms where we've only found
+ // the right wall.
+ overlapped = false
+ for (var j = 0; j < open_segments.length; j++) {
+ open_segment = open_segments[j]
+
+ // if these two segments overlap each other, then there is a room between them.
+ if (segment.y.overlaps(open_segment.y)) {
+ overlapped = true
+ open_segments.splice(j--, 1)
+
+ // the X part of the room will be the span between these two vertical segments'
+ // X components. the Y part of the room is the "split" or subdivision between
+ // the two horizontal vectors.
+
+ // the split function is non-commutative,
+ // so we need to call A split B and B split A,
+ // then dedupe the segments we got back..
+ x_segment = new vec2( open_segment.x.a, segment.x.b )
+ y_segments = open_segment.y.split(segment.y, 0, 0)
+
+ seen_segments = {}
+
+ // check each of the splits.. if the two segments overlap, then we definitely
+ // have a room here.
+ y_segments.forEach(function(seg){
+ seen_segments[ seg[0]+"" ] = true
+ var room = new Rect( x_segment, seg[0] )
+
+ if (seen_rooms[ room+"" ]) return
+ seen_rooms[ room+"" ] = true
+
+ room.sides = 0
+
+ rooms.push(room)
+ var new_seg = new Rect( segment.x, seg[0] )
+ open_segments.unshift(new_seg)
+ j++
+ })
+
+ // splitting the other way..
+ y_segments = segment.y.split(open_segment.y, 0, 0)
+ y_segments.forEach(function(seg){
+ if (seen_segments[ seg[0]+"" ]) return;
+ var new_seg = new Rect( segment.x, seg[0] )
+ open_segments.unshift(new_seg)
+ j++
+ })
+ }
+ }
+
+ // if we have overlap, then re-sort the open segments Y-wise
+ // and (again) dedupe..
+ if (overlapped) {
+ open_segments = open_segments.sort(function(a,b){
+ if (a.y.a < b.y.a) { return -1 }
+ if (a.y.a == b.y.a) { return 0 }
+ if (a.y.a > b.y.a) { return 1 }
+ })
+
+ if (open_segments.length < 2) { continue }
+
+ for (var k = 1; k < open_segments.length; k++) {
+ if (open_segments[k-1].y.containsVec(open_segments[k].y)) {
+ open_segments.splice(k--, 1)
+ }
+ else if (open_segments[k-1].y.overlaps(open_segments[k].y)) {
+ open_segments[k].y = open_segments[k].y.clone()
+ open_segments[k].y.a = open_segments[k-1].y.b
+ }
+ }
+ }
+ // if we don't have any overlap, then this is a new open segment.
+ else {
+ open_segments.push(segment)
+ }
+ }
+
+ var splits, splitter
+
+ // second pass: now that we have a bunch of rooms, assign sides to all of them.
+ // sides are used in the "mover" script to do bounds checking.
+ for (var i = 0; i < segments.length; i++) {
+ segment = segments[i]
+ var horizontal = segment.isHorizontal(), vertical = segment.isVertical()
+ for (var r = 0; r < rooms.length; r++){
+ room = rooms[r]
+
+ // vertical segments determine the left and right edges of the room, fairly simply.
+ if (vertical) {
+ if (segment.y.containsVec(room.y)) {
+ if (segment.x.a == room.x.a) {
+ room.sides |= LEFT
+ }
+ if (segment.x.a == room.x.b) {
+ room.sides |= RIGHT
+ }
+ }
+ }
+
+ // horizontal segments determine the front and back edges of the room.
+ if (horizontal) {
+ if (segment.x.containsVec(room.x)) {
+ if (segment.y.a == room.y.a) {
+ room.sides |= FRONT
+ }
+ if (segment.y.a == room.y.b) {
+ room.sides |= BACK
+ }
+ }
+
+ // however, since we did not split on horizontal segments, our rooms may
+ // only have partial overlap with these segments, in which case we need to
+ // split the rooms apart.
+ else if ((segment.y.a == room.y.a || segment.y.a == room.y.b) && room.x.overlaps(segment.x)) {
+
+ // split the room across the segment. preserve whether or not we know the
+ // room borders a segment on the left or right.
+ splits = room.x.split(segment.x, room.sides & LEFT, room.sides & RIGHT)
+ rooms.splice(r--, 1)
+ for (var k = 0; k < splits.length; k++) {
+ splitter = splits[k]
+ var new_room = new Rect( splitter[0], room.y )
+ new_room.sides = splitter[1] | ( room.sides & FRONT_BACK )
+ if (segment.x.overlaps( splitter[0] )) {
+ if (segment.y.a == new_room.y.a) {
+ new_room.sides |= FRONT
+ }
+ if (segment.y.a == new_room.y.b) {
+ new_room.sides |= BACK
+ }
+ }
+ rooms.unshift(new_room)
+ r++
+ }
+ }
+
+ }
+ }
+ }
+
+ return rooms
+ }
+
+ // Gets a list of polylines from the ShapeList and sorts the segments.
+ RegionList.getSortedSegments = function(){
+ // get a list of all segments from these polylines
+ var segments = shapes.getAllSegments()
+
+ segments = segments.map(RegionList.segmentsToRects)
+
+ return sort.rects_by_position(segments)
+ }
+
+ RegionList.getSortedSegmentsByShape = function(){
+ return shapes.getAllShapeSegments().map(function(shape){
+ var segments = shape.map(RegionList.segmentsToRects)
+ return {
+ shape: shape,
+ segments: sort.rects_by_position(segments)
+ }
+ })
+ }
+
+ // re-orient a segment so it's either facing up or right and make it into a rect
+ RegionList.segmentsToRects = function(segment){
+ // vertical
+ if (segment[0].a == segment[1].a) {
+ if (segment[0].b > segment[1].b) {
+ segment.push(segment.shift())
+ }
+ }
+ // horizontal
+ else if (segment[0].b == segment[1].b) {
+ if (segment[0].a > segment[1].a) {
+ segment.push(segment.shift())
+ }
+ }
+ return new Rect( segment[0].a, segment[0].b, segment[1].a, segment[1].b )
+ }
+
+ if (! ('window' in this) ) {
+ module.exports = RegionList
+ }
+
+ return RegionList
+
+})() \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/engine/shapes/shapelist.js b/public/assets/javascripts/rectangles/engine/shapes/shapelist.js
new file mode 100644
index 0000000..21beb76
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/shapes/shapelist.js
@@ -0,0 +1,124 @@
+// The ShapeList manages the list of polylines which form a V2 layout.
+
+if (! ('window' in this) ) {
+ var Fiber = require("../../../vendor/bower_components/fiber/src/fiber.js")
+ var Polyline = require("./polyline.js")
+ var OrthoPolyline = require("./ortho.js")
+}
+
+var ShapeList = Fiber.extend(function(base){
+ var exports = {}
+ exports.init = function(){
+ this.shapes = []
+ this.workline = null
+ }
+ exports.add = function(shape){
+ this.shapes.push(shape)
+ }
+ exports.remove = function(shape){
+ var index = this.shapes.indexOf(shape)
+ if (index !== -1) {
+ this.shapes.splice(index, 1)
+ }
+ }
+ exports.destroy = function(){
+ this.shapes.forEach(function(shape){
+ shape.destroy()
+ })
+ this.shapes = []
+ }
+ exports.count = function(){
+ return this.shapes.length
+ }
+ exports.removeSegment = function (segment){
+ var shape = segment.shape
+ var head = shape.getHeadAtIndex(segment.head)
+ var tail = shape.getTailAtIndex(segment.tail)
+ this.remove(shape)
+ shape.destroy()
+ if (head) {
+ this.add(head)
+ head.build()
+ }
+ if (tail) {
+ this.add(tail)
+ tail.build()
+ }
+ }
+ exports.findClosestPoint = function (p){
+ var point
+ for (var i = 0; i < this.shapes.length; i++) {
+ point = this.shapes[i].hasPointNear(p)
+ if (point) return { point: point, shape: this.shapes[i] }
+ }
+ return null
+ }
+ exports.findClosestEndPoint = function (p){
+ var point
+ for (var i = 0; i < this.shapes.length; i++) {
+ point = this.shapes[i].hasEndPointNear(p)
+ if (point) return { point: point, shape: this.shapes[i] }
+ }
+ return null
+ }
+ exports.findClosestSegment = function (p){
+ var segment = null, closest_segment = null
+ for (var i = 0; i < this.shapes.length; i++) {
+ segment = this.shapes[i].hasSegmentNear(p, 10)
+ if (segment && (! closest_segment || segment.distance < closest_segment.distance)) {
+ closest_segment = segment
+ closest_segment.shape = this.shapes[i]
+ }
+ }
+ return closest_segment
+ }
+ exports.forEach = function(fn){
+ this.shapes.forEach(fn)
+ }
+ exports.getAllShapeSegments = function(){
+ return this.shapes.map(function(shape){ return shape.getSegments() })
+ }
+ exports.getAllSegments = function(){
+ var segments = []
+ this.shapes.forEach(function(shape){
+ segments = segments.concat( shape.getSegments() )
+ })
+ return segments
+ }
+ exports.draw = function(ctx, fillStyle, strokeStyle) {
+ this.shapes.forEach(function(shape){
+ shape.draw(ctx, fillStyle, strokeStyle)
+ })
+ }
+ exports.serialize = function(){
+ return this.shapes.map(function(shape){
+ return shape.serialize()
+ })
+ }
+ exports.deserialize = function(data){
+ data && data.forEach(function(shape_data){
+ var line
+ switch (shape_data.type) {
+ case 'ortho':
+ line = new OrthoPolyline()
+ break
+ default:
+ line = new Polyline()
+ break
+ }
+ line.deserialize(shape_data)
+ shapes.add(line)
+ }.bind(this))
+ }
+ exports.build = function(){
+ this.shapes.forEach(function(shape){
+ shape.build()
+ })
+ }
+ return exports
+})
+
+if (! ('window' in this) ) {
+ shapes = new ShapeList
+ module.exports = shapes
+}
diff --git a/public/assets/javascripts/rectangles/models/floor.js b/public/assets/javascripts/rectangles/models/floor.js
index 51537f3..417494b 100644
--- a/public/assets/javascripts/rectangles/models/floor.js
+++ b/public/assets/javascripts/rectangles/models/floor.js
@@ -68,8 +68,43 @@
}
return
}
+
- if (Scenery.nextWallpaper) {
+ var offset = offsetFromPoint(e, mx.el)
+ if (! offset) { return }
+
+ var x = mx.x + mx.width * (offset.left-0.5)
+ var z = mx.z + mx.height * (0.5-offset.top)
+
+ if (Scenery.nextMedia) {
+ e.preventDefault()
+
+ var sculpture = Sculpture.addNext({
+ position: { x: x, y: 0, z: z },
+ })
+
+ // scenery was not placed
+ if (! sculpture) {
+ e.stopPropagation()
+ return
+ }
+
+ app.controller.toolbar.resetPermissions()
+ Sculpture.resize.show(sculpture)
+ Sculpture.hovering = true
+
+ // app.controller.pick(sculpture)
+
+ UndoStack.push({
+ type: 'create-sculpture',
+ undo: { id: sculpture.id },
+ redo: sculpture.serialize(),
+ })
+
+ // TODO: watch individual sculpture object here
+ Minotaur.watch( app.router.editorView.settings )
+ }
+ else if (Scenery.nextWallpaper) {
var oldState = base.serialize()
base.wallpaper(Scenery.nextWallpaper)
// Scenery.nextWallpaper = null
@@ -98,7 +133,7 @@
this.mx.reverse()
}
}
-
+
Floor.prototype.color = function(color){
this.$els.css("background-color", color)
}
diff --git a/public/assets/javascripts/rectangles/models/rect.js b/public/assets/javascripts/rectangles/models/rect.js
index c667cf5..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)
diff --git a/public/assets/javascripts/rectangles/models/room.js b/public/assets/javascripts/rectangles/models/room.js
index 26bf055..1abe2ba 100644
--- a/public/assets/javascripts/rectangles/models/room.js
+++ b/public/assets/javascripts/rectangles/models/room.js
@@ -31,10 +31,14 @@
var Room = function(opt){
this.id = opt.id || Rooms.uid("room_")
this.rect = opt.rect
- this.regions = []
+ this.regions = opt.regions || []
this.height = opt.height || 200
this.focused = false
+
+ this.mx_walls = []
+ this.mx_floor = []
+ this.mx_ceiling = []
}
Room.prototype.copy = function(){
@@ -125,6 +129,7 @@
Room.prototype.collidesDisc = function(src, dest, radius){
var x = dest.x, y = dest.z
var collision = 0, wall_collision, contains_x, contains_y
+
this.regions.forEach(function(r){
if (! r.sides) return
diff --git a/public/assets/javascripts/rectangles/models/vec2.js b/public/assets/javascripts/rectangles/models/vec2.js
index 14d0e6b..8942d92 100644
--- a/public/assets/javascripts/rectangles/models/vec2.js
+++ b/public/assets/javascripts/rectangles/models/vec2.js
@@ -43,6 +43,9 @@
vec2.prototype.eq = function(v){
return this.a == v.a && this.b == v.b
}
+ vec2.prototype.isPoint = function(){
+ return this.a == this.b
+ }
vec2.prototype.add = function(n){
this.a += n
this.b += n
@@ -80,6 +83,11 @@
this.a = Math.round(this.a)
this.b = Math.round(this.b)
}
+ vec2.prototype.distanceTo = function(v){
+ var va = (this.a - v.a)
+ var vb = (this.b - v.b)
+ return Math.sqrt( va*va + vb*vb )
+ }
vec2.prototype.setPosition = function(n){
var len = this.length()
this.a = n
@@ -103,6 +111,12 @@
vec2.prototype.containsDisc = function(n,r){
return this.a <= n-r && n+r <= this.b
}
+ vec2.prototype.containsVec = function(v){
+ return this.a <= v.a && v.b <= this.b
+ }
+ vec2.prototype.containsCenterVec = function(v){
+ return this.a < v.a && v.b < this.b
+ }
vec2.prototype.clamp = function(n){
return clamp(n, this.a, this.b)
}
@@ -200,13 +214,13 @@
}
vec2.prototype.toString = function(){
- return "[" + round(this.a) + " " + round(this.b) + "]"
+ return "[" + Math.round(this.a) + " " + Math.round(this.b) + "]"
}
vec2.prototype.exactString = function(){
return "[" + this.a + " " + this.b + "]"
}
vec2.prototype.serialize = function(){
- return [ round(this.a), round(this.b) ]
+ return [ Math.round(this.a), Math.round(this.b) ]
}
vec2.prototype.deserialize = function(data){
this.a = data[0]
diff --git a/public/assets/javascripts/rectangles/models/vec3.js b/public/assets/javascripts/rectangles/models/vec3.js
index c44dfe6..b3825a9 100644
--- a/public/assets/javascripts/rectangles/models/vec3.js
+++ b/public/assets/javascripts/rectangles/models/vec3.js
@@ -32,3 +32,28 @@ vec3.prototype.apply_projection = function (m) {
return this;
}
+
+vec3.prototype.serialize = function(){
+ return [ round(this.a), round(this.b), round(this.c) ]
+}
+vec3.prototype.deserialize = function(data){
+ this.a = data[0]
+ this.b = data[1]
+ this.c = data[2] || data[0]
+ return this
+}
+vec3.prototype.clone = function(){
+ return new vec3(this.a, this.b, this.c)
+}
+vec3.prototype.assign = function(v){
+ this.a = v.a
+ this.b = v.b
+ this.c = v.c
+ return this
+}
+vec3.prototype.mul = function(n) {
+ this.a *= n
+ this.b *= n
+ this.c *= n
+ return this
+} \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/models/wall.js b/public/assets/javascripts/rectangles/models/wall.js
index fc1ccbe..dadcd53 100644
--- a/public/assets/javascripts/rectangles/models/wall.js
+++ b/public/assets/javascripts/rectangles/models/wall.js
@@ -10,6 +10,11 @@
vec2 = require('./vec2')
Rect = require('./rect')
UidGenerator = require('../util/uid')
+ wall_rotation = {}
+ wall_rotation[FRONT] = PI
+ wall_rotation[BACK] = 0
+ wall_rotation[LEFT] = HALF_PI
+ wall_rotation[RIGHT] = -HALF_PI
}
var Wall = function(opt){
@@ -18,6 +23,7 @@
this.edge = opt.edge
this.side = opt.side
this.surface = opt.surface
+ this.rotationY = ('rotationY' in opt) ? opt.rotationY : wall_rotation[opt.side]
this.mx = opt.mx
this.background = { src: "none" }
}
@@ -89,7 +95,7 @@
mx_dot.move(mx_pos)
mx_dot.width = 5
mx_dot.height = 5
- mx_dot.rotationY = wall_rotation[base.side]
+ mx_dot.rotationY = base.rotationY
mx_dot.el.style.backgroundColor = "red"
scene.add(mx_dot)
}
@@ -192,7 +198,7 @@
}
Wall.prototype.serialize = function(){
- return {
+ return {
id: this.id,
background: this.background,
}
@@ -236,7 +242,7 @@
x: x,
y: position.b + dimension.b / 2,
z: z,
- rotationY: wall_rotation[ this.side ],
+ rotationY: this.rotationY,
}
}
Wall.prototype.mxToPosition = function(mx, dimension) {
diff --git a/public/assets/javascripts/rectangles/util/constants.js b/public/assets/javascripts/rectangles/util/constants.js
index 3bc314c..522689b 100644
--- a/public/assets/javascripts/rectangles/util/constants.js
+++ b/public/assets/javascripts/rectangles/util/constants.js
@@ -22,10 +22,11 @@ var height_min = 200,
resize_margin = 8,
cursor_amp = 1.5,
DEFAULT_PICTURE_WIDTH = 350,
- MAP_GRID_SIZE = 360 // 10 feet
+ MAP_GRID_SIZE = 36 // 10 feet
var painting_distance_from_wall = 10,
- dot_distance_from_picture = 3
+ dot_distance_from_picture = 3,
+ sculpture_distance_from_floor = 3
var dot_hide_delay = 50, // ms
dot_side = 20
diff --git a/public/assets/javascripts/rectangles/util/coords.js b/public/assets/javascripts/rectangles/util/coords.js
index 74b7fda..ff56199 100644
--- a/public/assets/javascripts/rectangles/util/coords.js
+++ b/public/assets/javascripts/rectangles/util/coords.js
@@ -30,4 +30,4 @@ function offsetFromPoint(event, element) {
return 'left: ' + l + '%, top: ' + t + '%';
}
} : null;
-}
+} \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/util/measurement.js b/public/assets/javascripts/rectangles/util/measurement.js
index d6a0b35..6346eac 100644
--- a/public/assets/javascripts/rectangles/util/measurement.js
+++ b/public/assets/javascripts/rectangles/util/measurement.js
@@ -28,6 +28,10 @@ function measurementToString( n ) {
case 'ft':
ft = floor(n / 36)
inch = abs(round((n % 36) / 3))
+ if (inch == 12) {
+ inch = 0
+ ft += 1
+ }
s = ft + "'"
if (inch > 0) {
s += " " + inch + '"'
diff --git a/public/assets/javascripts/rectangles/util/minotaur.js b/public/assets/javascripts/rectangles/util/minotaur.js
index d165ccc..8b1abfe 100644
--- a/public/assets/javascripts/rectangles/util/minotaur.js
+++ b/public/assets/javascripts/rectangles/util/minotaur.js
@@ -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/wheel.js b/public/assets/javascripts/rectangles/util/wheel.js
index 712d470..4155a70 100644
--- a/public/assets/javascripts/rectangles/util/wheel.js
+++ b/public/assets/javascripts/rectangles/util/wheel.js
@@ -3,8 +3,8 @@
base.wheel = new wheel({
el: document.querySelector("#map"),
- update: function(e, val, delta){
- // do something with val
+ update: function(e, delta){
+ // do something with delta
},
})
@@ -13,7 +13,7 @@
function wheel (opt) {
opt = defaults(opt, {
el: document,
- fn: function(e, val, delta){},
+ update: function(e, delta){},
propagate: false,
locked: false,
reversible: true,
@@ -22,7 +22,7 @@ function wheel (opt) {
})
opt.el.addEventListener('wheel', onMouseWheel, false);
-// opt.el.addEventListener('mousewheel', onMouseWheel, false);
+ // opt.el.addEventListener('mousewheel', onMouseWheel, false);
opt.el.addEventListener('DOMMouseScroll', onMouseWheel, false);
function onMouseWheel (e) {
@@ -58,6 +58,8 @@ function wheel (opt) {
// opt.val = clamp(opt.val + delta, opt.min, opt.max)
+ // deltaX is also passed, but these values tend to be unusable
+ // try http://vvalls.com/assets/test/wheel.html with a trackpad
opt.update(e, deltaY, deltaX)
}
diff --git a/public/assets/javascripts/ui/_router.js b/public/assets/javascripts/ui/_router.js
index 3532428..61b1d1b 100644
--- a/public/assets/javascripts/ui/_router.js
+++ b/public/assets/javascripts/ui/_router.js
@@ -9,6 +9,7 @@ var SiteRouter = Router.extend({
"click [data-role='new-project-modal']": 'newProject',
"click [data-role='edit-project-modal']": 'editProject',
"click [data-role='edit-profile-modal']": 'editProfile',
+ "click [data-role='edit-subscription-modal']": 'editSubscription',
"click [data-role='new-document-modal']": 'newDocument',
"click [data-role='edit-document-modal']": 'editDocument',
"click [data-role='destroy-document-modal']": 'destroyDocument',
@@ -17,31 +18,38 @@ var SiteRouter = Router.extend({
},
routes: {
- "/": 'home',
- "/home": 'home',
- "/login": 'signin',
- "/signin": 'signin',
- "/signup": 'signup',
+ "/": 'home',
+ "/home": 'home',
+ "/login": 'signin',
+ "/signin": 'signin',
+ "/signup": 'signup',
- "/auth/usernameTaken": 'usernameTaken',
- "/auth/password": 'passwordReset',
- "/auth/forgotPassword": 'passwordForgot',
+ "/auth/usernameTaken": 'usernameTaken',
+ "/auth/password": 'passwordReset',
+ "/auth/forgotPassword": 'passwordForgot',
- "/profile": 'profile',
- "/profile/edit": 'editProfile',
- "/profile/:name": 'profile',
- "/about/:name/edit": 'editDocument',
- "/about/new": 'newDocument',
+ "/profile": 'profile',
+ "/profile/edit": 'editProfile',
+ "/profile/billing": 'editSubscription',
+ "/profile/:name": 'profile',
+ "/about/:name/edit": 'editDocument',
+ "/about/new": 'newDocument',
- "/layout": 'layoutPicker',
- "/layout/:name": 'layoutEditor',
+ "/layout": 'layoutPicker',
+ "/layout/:name": 'layoutEditor',
- "/project": 'projectPicker',
- "/project/new": 'newProject',
- "/project/new/:layout": 'projectNewWithLayout',
- "/project/:name": 'projectViewer',
- "/project/:name/edit": 'projectEditor',
- "/project/:name/view": 'projectViewer',
+ "/blueprint": 'blueprintEditor',
+ "/blueprint/:name": 'blueprintEditor',
+
+ "/project": 'projectPicker',
+ "/project/new": 'newProject',
+ "/project/blueprint/:blueprint": 'projectNewWithBlueprint',
+ "/project/new/:layout": 'projectNewWithLayout',
+ "/project/:name": 'projectViewer',
+ "/project/:name/edit": 'projectEditor',
+ "/project/:name/view": 'projectViewer',
+
+ "/test/blueprint": 'blueprintEditor',
},
mobileRoutes: {
@@ -56,6 +64,7 @@ var SiteRouter = Router.extend({
"/profile": 'profile',
"/profile/edit": 'editProfile',
+ "/profile/billing": 'editSubscription',
"/profile/:name": 'profile',
"/project/:name": 'projectViewer',
@@ -69,6 +78,7 @@ var SiteRouter = Router.extend({
this.newProjectModal = new NewProjectModal()
this.editProjectModal = new EditProjectModal()
this.editProfileModal = new EditProfileModal()
+ this.editSubscriptionModal = new EditSubscriptionModal()
this.passwordForgotModal = new PasswordForgot()
this.documentModal = new DocumentModal()
this.profileView = new ProfileView()
@@ -83,7 +93,9 @@ var SiteRouter = Router.extend({
})
}
- $("body").removeClass("loading")
+ setTimeout(function(){
+ $("body").removeClass("loading")
+ }, 200)
},
layoutEditor: function(e, name){
@@ -112,6 +124,22 @@ var SiteRouter = Router.extend({
window.history.pushState(null, document.title, "/project/new")
this.newProjectModal.load()
},
+
+ projectNewWithBlueprint: function(e, blueprint){
+ e && e.preventDefault()
+
+ Rooms.shapesMode = true
+
+ app.mode.editor = true
+ app.launch()
+ if (app.unsupported) return
+
+ blueprint = slugify(blueprint)
+
+ window.history.pushState(null, document.title, "/project/blueprint/" + blueprint)
+ this.editorView = app.controller = new EditorView()
+ this.editorView.loadBlueprint(blueprint)
+ },
projectNewWithLayout: function(e, layout){
e && e.preventDefault()
@@ -153,6 +181,15 @@ var SiteRouter = Router.extend({
this.readerView = app.controller = new ReaderView()
this.readerView.load(name)
},
+
+ blueprintEditor: function(e, name){
+ environment.init = environment.minimal
+ app.launch()
+ if (app.unsupported) return
+
+ this.blueprintView = app.controller = new BlueprintView ()
+ this.blueprintView.load(name)
+ },
signup: function(e){
e && e.preventDefault()
@@ -195,7 +232,12 @@ var SiteRouter = Router.extend({
this.editProfileModal.load()
},
+ editSubscription: function(e){
+ e && e.preventDefault()
+ window.history.pushState(null, document.title, "/profile/billing")
+ this.editSubscriptionModal.load()
+ },
newDocument: function(e){
e && e.preventDefault()
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintEditor.js b/public/assets/javascripts/ui/blueprint/BlueprintEditor.js
new file mode 100644
index 0000000..7704689
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintEditor.js
@@ -0,0 +1,129 @@
+
+var wallHeight = 180
+var shapes = new ShapeList
+var last_point = new vec2 (0,0)
+
+var BlueprintEditor = View.extend(AnimatedView.prototype).extend({
+
+ regions: [],
+
+ initialize: function(opt){
+ this.parent = opt.parent
+
+ $(window).resize(this.resize.bind(this))
+
+ scene = new MX.Scene().addTo("#perspective")
+ scene.camera.radius = 20
+ cam = scene.camera
+
+ scene.width = window.innerWidth/2
+ scene.height = window.innerHeight
+ scene.perspective = window.innerHeight
+
+ movements = new MX.Movements(cam, viewHeight)
+ movements.init()
+ movements.lock()
+
+ app.on("move", function(pos){
+ cam.x = pos.x
+ cam.y = pos.y
+ cam.z = pos.z
+ })
+
+ var floorplan = this.floorplan = new MX.Image({
+ backface: true,
+ })
+ scene.add(this.floorplan)
+
+ // recenter perspective view by rightclicking map
+ this.floorplan.el.addEventListener("contextmenu", function(e){
+ e.preventDefault()
+ var offset = offsetFromPoint(e, this)
+ var x = (offset.left - 0.5) * floorplan.width * floorplan.scale
+ var z = (offset.top - 0.5) * floorplan.height * floorplan.scale
+ controls.opt.center.x = -x
+ controls.opt.center.y = 0
+ controls.opt.center.z = z
+ }, true)
+
+ scene.update()
+
+ controls = new MX.OrbitCamera({
+ el: scene.el,
+ radius: 3000,
+ radiusRange: [ 10, 10000 ],
+ rotationX: PI/4,
+ rotationY: PI/2,
+ })
+ controls.init()
+ },
+
+ resize: function(){
+ if (this.parent.orbiting) {
+ scene.width = window.innerWidth/2
+ scene.height = window.innerHeight
+ this.parent.map.resize( window.innerWidth/2, window.innerHeight )
+ this.parent.map.canvas.style.display = "block"
+ }
+ else {
+ scene.width = window.innerWidth
+ scene.height = window.innerHeight
+ this.parent.map.canvas.style.display = "none"
+ }
+ },
+
+ loadFloorplan: function(media){
+ // console.log(media)
+ this.floorplan.load({
+ media: media,
+ keepImage: true,
+ rotationX: -PI/2,
+ rotationY: PI,
+ scale: media.scale,
+ })
+ this.startAnimating()
+ this.regions = RegionList.buildByShape()
+ },
+
+ animate: function(t, dt){
+ map.update(t)
+
+ movements.update(dt)
+ controls.update()
+ scene.update()
+
+ map.draw.ctx.save()
+ map.draw.translate()
+
+ this.floorplan.draw(map.draw.ctx, true)
+
+ map.draw.coords()
+
+ if (shapes.workline) {
+ shapes.workline.draw(map.draw.ctx, "rgba(255,255,0,0.1)", "#f80")
+ if (map.ui.placing && last_point) {
+ shapes.workline.draw_line( map.draw.ctx, last_point )
+ }
+ }
+
+ shapes.draw(map.draw.ctx, "rgba(255,255,0,0.1)", "#f80")
+
+ map.draw.ctx.strokeStyle = "#f00";
+ map.draw.x_at( this.parent.startPosition )
+ map.draw.mouse(map.ui.mouse.cursor)
+ map.draw.camera(scene.camera)
+
+// var colors = ["rgba(0,0,0,0.1)"]
+// var colors = ["rgba(255,255,255,1)"]
+
+// map.draw.regions(this.regions, colors, "#000")
+
+// this.regions.forEach(function(room,i){
+// map.draw.ctx.fillStyle = colors[i % colors.length]
+// map.draw.ctx.fillRect( room.x.a, room.y.a, room.width(), room.height() )
+// })
+
+ map.draw.ctx.restore()
+ },
+
+})
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintInfo.js b/public/assets/javascripts/ui/blueprint/BlueprintInfo.js
new file mode 100644
index 0000000..51b310e
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintInfo.js
@@ -0,0 +1,92 @@
+
+var BlueprintInfo = View.extend({
+ el: "#blueprintInfo",
+
+ events: {
+ "mousedown": "stopPropagation",
+ "keydown": 'stopPropagation',
+ "change [name=height]": 'changeHeight',
+ "keydown [name=height]": 'enterHeight',
+ "change [name=units]": 'changeUnits',
+ "keydown [name=viewHeight]": 'enterViewHeight',
+ "change [name=viewHeight]": 'changeViewHeight',
+ "click .openScaler": 'openScaler',
+ },
+
+ initialize: function(opt){
+ this.parent = opt.parent
+ this.$height = this.$("[name=height]")
+ this.$units = this.$("[name=units]")
+ this.$viewHeight = this.$("[name=viewHeight]")
+ this.$unitName = this.$(".unitName")
+ this.$blueprintScaleDisplay = this.$("#blueprintScaleDisplay")
+ },
+
+ load: function(data){
+ this.$viewHeight.unitVal( window.viewHeight = data.viewHeight || app.defaults.viewHeight )
+ this.$height.unitVal( window.wallHeight = data.wallHeight || app.defaults.wallHeight )
+ this.$units.val( data.units )
+ this.$('span.units').html( data.units )
+ this.$unitName.html( data.units )
+
+ var resolution
+ switch (data.units) {
+ case 'ft':
+ resolution = app.defaults.footResolution
+ break
+ case 'm':
+ resolution = app.defaults.meterResolution
+ break
+ case 'px':
+ default:
+ resolution = 1
+ break
+ }
+ this.$blueprintScaleDisplay.html( ((1/data.scale) * resolution).toFixed(1) )
+ this.show()
+ },
+
+ toggle: function(state){
+ this.$el.toggleClass("active", state)
+ this.$viewHeight.unitVal( window.viewHeight )
+ },
+
+ openScaler: function(){
+ this.parent.scaler.pick( this.parent.data, true )
+ this.parent.scaler.show()
+ },
+
+ show: function(){
+ this.toggle(true)
+ },
+
+ hide: function(){
+ this.toggle(false)
+ },
+
+ deselect: function(){
+ this.toggle(true)
+ },
+
+ enterHeight: function(e){
+ if (e.keyCode == 13) this.changeHeight(e)
+ },
+ changeHeight: function(e){
+ e.stopPropagation()
+ window.wallHeight = this.$height.unitVal()
+ shapes.forEach(function(line){
+ line.mx.set_height( window.wallHeight )
+ })
+ },
+ changeUnits: function(){
+ app.units = this.$units.val()
+ this.$('.units').resetUnitVal()
+ },
+ enterViewHeight: function(e){
+ if (e.keyCode == 13) this.changeViewHeight(e)
+ },
+ changeViewHeight: function(){
+ window.viewHeight = this.$viewHeight.unitVal()
+ }
+
+})
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintNotice.js b/public/assets/javascripts/ui/blueprint/BlueprintNotice.js
new file mode 100644
index 0000000..4b799a6
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintNotice.js
@@ -0,0 +1,62 @@
+var BlueprintNotice = View.extend(ToggleableView.prototype).extend({
+
+ el: "#blueprintNotice",
+
+ events: {
+ "click .next": "next",
+ },
+
+ initialize: function(opt){
+ this.parent = opt.parent
+ this.$notice = this.$(".notice")
+ this.$next = this.$(".next")
+ },
+
+ notice: function(msg){
+ this.$notice.html(msg)
+ },
+
+ showCreateProjectNotice: function(){
+ this.notice("<a href='/project/blueprint/" + this.parent.data.slug +
+ "'>Start a new project</a> with this blueprint.")
+ this.$next.hide()
+ this.nextFn = null
+ this.show()
+ },
+
+ showStartPositionNotice: function(){
+ this.parent.settings.hide()
+ this.notice("First, click the map to set the starting location.")
+ this.$next.show().html("Next")
+ this.show()
+ this.nextFn = this.showStartAngleNotice.bind(this)
+ },
+
+ showStartAngleNotice: function(){
+ this.parent.settings.hide()
+ this.notice("Next, rotate the camera to the desired orientation.")
+ this.$next.show().html("Done")
+ this.show()
+ this.nextFn = this.doneSettingPosition.bind(this)
+ this.parent.toolbar.toggleOrbitMode(false)
+ },
+
+ doneSettingPosition: function(){
+ this.nextFn = null
+ this.$next.hide()
+ this.hide()
+ this.parent.settings.show()
+ this.parent.startPosition.rotationX = cam.rotationX
+ this.parent.startPosition.rotationY = cam.rotationY
+ this.parent.toolbar.toggleOrbitMode(true)
+ this.parent.toolbar.orthoPolylineMode()
+ },
+
+ nextFn: null,
+ next: function(){
+ if (this.nextFn) {
+ this.nextFn()
+ }
+ },
+
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintScaler.js b/public/assets/javascripts/ui/blueprint/BlueprintScaler.js
new file mode 100644
index 0000000..cd370ef
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintScaler.js
@@ -0,0 +1,158 @@
+
+var BlueprintScaler = ModalFormView.extend(AnimatedView.prototype).extend({
+ el: ".blueprintScaler",
+
+ fixedClose: true,
+
+ action: "/api/blueprint/scale",
+
+ events: {
+ "change [name=blueprint-dimensions]": "changeDimensions",
+ "change [name=blueprint-units]": "changeUnits",
+ "click .uploadNewBlueprint": "showUploader",
+ },
+
+ initialize: function(opt){
+ this.parent = opt.parent
+
+ this.$blueprintMap = this.$("#blueprintMap")
+ this.$blueprintDimensionsRapper = this.$("#blueprintDimensions")
+ this.$dimensions = this.$("[name=blueprint-dimensions]")
+ this.$pixels = this.$("[name=blueprint-pixels]")
+ this.$units = this.$("[name=blueprint-units]")
+ this.$save = this.$("#saveBlueprint")
+
+ this.map = new Map ({
+ type: "ortho",
+ el: this.$blueprintMap.get(0),
+ width: window.innerWidth,
+ height: window.innerHeight,
+ zoom: -2,
+ zoom_min: -7.0,
+ zoom_max: 2,
+ })
+ this.lineTool = new LineTool
+ this.map.ui.add_tool("line", this.lineTool)
+ this.map.ui.set_tool("line")
+
+ scene = scene || { camera: { x: 0, y: 0, z: 0 } }
+
+ this.floorplan = new MX.Image ()
+ },
+
+ showUploader: function(){
+ this.parent.uploader.show()
+ },
+
+ pick: function(media, shouldEdit){
+ this.media = media
+
+ this.floorplan.load({ media: media, scale: 1, keepImage: true })
+
+ if (!! media.units && ! shouldEdit) {
+ this.parent.ready(media)
+ this.hide()
+ this.stopAnimating()
+ return
+ }
+
+ if (media.units && media.line && media.scale) {
+ var points = media.line.split(",")
+ this.lineTool.line[0] = new vec2( +points[0], +points[1] )
+ this.lineTool.line[1] = new vec2( +points[2], +points[3] )
+
+ app.units = media.units
+ this.$units.val( media.units )
+ this.$dimensions.unitVal( media.scale * this.lineLength() )
+ }
+
+ this.startAnimating()
+ },
+
+ animate: function(t, dt){
+ this.map.update(t)
+
+ this.map.draw.ctx.save()
+ this.map.draw.translate()
+
+ this.floorplan.draw(this.map.draw.ctx, true)
+
+ this.map.draw.ctx.save()
+ this.map.draw.ctx.strokeStyle = "#f00"
+ this.map.draw.ctx.lineWidth = 1/map.zoom
+ switch (this.lineTool.line.length) {
+ case 1:
+ this.map.draw.line(
+ this.lineTool.line[0].a,
+ this.lineTool.line[0].b,
+ this.lineTool.cursor.x.a,
+ this.lineTool.cursor.y.a
+ )
+ break
+ case 2:
+ this.map.draw.line(
+ this.lineTool.line[0].a,
+ this.lineTool.line[0].b,
+ this.lineTool.line[1].a,
+ this.lineTool.line[1].b
+ )
+ break
+ }
+ this.map.draw.ctx.restore()
+
+ this.map.draw.coords()
+
+ this.map.draw.mouse(this.map.ui.mouse.cursor)
+
+ this.map.draw.ctx.restore()
+ },
+
+ changeDimensions: function(){
+ app.units = this.$units.val()
+ this.$dimensions.unitVal()
+ },
+ changeUnits: function(){
+ app.units = this.$units.val()
+ this.$dimensions.resetUnitVal()
+ },
+ lineLength: function(){
+ if (this.lineTool.line.length !== 2) return 0
+ var line = this.lineTool.line
+ return dist( line[0].a, line[0].b, line[1].a, line[1].b )
+ },
+
+ validate: function(){
+ var val = this.$dimensions.unitVal()
+ var errors = []
+ if (! this.lineLength()) {
+ errors.push("no line")
+ this.$dimensions.val("")
+ alert("Please click two corners of a wall and then specify how long it is in feet or meters.")
+ }
+ else if (val == 0) {
+ errors.push("no measurement")
+ alert("Please tell us how long the wall is in feet or meters.")
+ }
+ return errors
+ },
+
+ showErrors: function(){},
+
+ serialize: function(){
+ var fd = new FormData(), line = this.lineTool.line
+ fd.append( "_id", this.media._id)
+ fd.append( "units", this.$units.val() )
+ fd.append( "scale", this.$dimensions.unitVal() / this.lineLength() )
+ fd.append( "line", [line[0].a,line[0].b,line[1].a,line[1].b].join(",") )
+ fd.append( "_csrf", $("[name=_csrf]").val())
+ return fd
+ },
+
+ success: function(){
+ this.media.scale = this.$dimensions.unitVal() / this.lineLength()
+ this.stopAnimating()
+ this.parent.ready(this.media)
+ this.hide()
+ },
+
+})
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintSettings.js b/public/assets/javascripts/ui/blueprint/BlueprintSettings.js
new file mode 100644
index 0000000..8addb9c
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintSettings.js
@@ -0,0 +1,123 @@
+
+var BlueprintSettings = FormView.extend(ToggleableView.prototype).extend({
+ el: "#blueprintSettings",
+
+ action: "/api/blueprint/edit",
+ destroyAction: "/api/blueprint/destroy",
+
+ events: {
+ "mousedown": "stopPropagation",
+ "keydown": 'stopPropagation',
+ "keydown [name=name]": 'enterSubmit',
+ "click [data-role='save-layout']": 'clickSave',
+ "click [data-role='clear-layout']": 'clear',
+ "click [data-role='destroy-layout']": 'destroy',
+ },
+
+ initialize: function(opt){
+ this.parent = opt.parent
+ this.__super__.initialize.call(this)
+
+ this.$id = this.$("[name=_id]")
+ this.$csrf = this.$("[name=_csrf]")
+ this.$name = this.$("[name=name]")
+ },
+
+ load: function(data){
+ this.$id.val(data._id)
+ if (data.name) {
+ this.$name.val(data.name)
+ this.hide()
+ }
+ else {
+ this.$name.val("")
+ }
+ if (data.shapes) {
+ shapes.destroy()
+ shapes.deserialize( data.shapes )
+ shapes.build()
+ }
+ },
+
+ clear: function(){
+ shapes.destroy()
+ },
+
+ destroy: function(){
+ var msg = "Are you sure you want to delete the blueprint " + sanitize(this.$name.val()) + "?"
+ ConfirmModal.confirm(msg, function(){
+ $.ajax({
+ url: this.destroyAction,
+ type: "delete",
+ data: { _id: this.$id.val(), _csrf: this.$csrf.val() },
+ success: function(data){
+ window.location.href = "/layout"
+ }
+ })
+ }.bind(this))
+ },
+
+ enterSubmit: function (e) {
+ e.stopPropagation()
+ var base = this
+ if (e.keyCode == 13) {
+ setTimeout(function(){ base.save(e) }, 100)
+ }
+ },
+
+ validate: function(){
+ var errors = []
+ var name = this.$name.val()
+ if (! name || ! name.length) {
+ errors.push("Blueprint needs a name.")
+ }
+ if (shapes.count() == 0) {
+ errors.push("Please add some walls.")
+ }
+ return errors
+ },
+
+ showErrors: function(errors){
+ var $errors = $("<span>")
+ errors.forEach(function(err){
+ var $row = $("<div>")
+ $row.html(err)
+ $errors.append( $row )
+ })
+ ErrorModal.alert($errors)
+ },
+
+ serialize: function(){
+ var fd = new FormData()
+ fd.append( "_csrf", this.$csrf.val() )
+ fd.append( "_id", this.$id.val() )
+ fd.append( "name", this.$name.val() )
+ fd.append( "shapes", JSON.stringify( shapes.serialize() ) )
+ fd.append( "startPosition", JSON.stringify( this.parent.quantizeStartPosition() ) )
+ fd.append( "wallHeight", this.parent.info.$height.unitVal() )
+ fd.append( "units", this.parent.info.$units.val() )
+ return fd
+ },
+
+ clickSave: function(){
+ this.toggle(false)
+ this.save()
+ },
+
+ success: function(data){
+ this.parent.data = data
+
+ this.$id.val(data._id)
+ this.$name.val(data.name)
+ this.action = this.updateAction
+
+ this.hide()
+ this.parent.notice.showCreateProjectNotice()
+
+ Minotaur.unwatch(this)
+ Minotaur.hide()
+
+ window.history.pushState(null, document.title, "/blueprint/" + data.slug)
+ },
+
+})
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintToolbar.js b/public/assets/javascripts/ui/blueprint/BlueprintToolbar.js
new file mode 100644
index 0000000..458357d
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintToolbar.js
@@ -0,0 +1,97 @@
+var BlueprintToolbar = View.extend({
+
+ el: "#blueprintToolbar",
+
+ events: {
+ "click [data-role=upload-floorplan]": 'showUploader',
+ "click [data-role=toggle-orbit-mode]": 'toggleOrbitMode',
+ "click [data-role=arrow-mode]": 'arrowMode',
+ "click [data-role=polyline-mode]": 'polylineMode',
+ "click [data-role=ortho-polyline-mode]": 'orthoPolylineMode',
+ "click [data-role=eraser-mode]": 'eraserMode',
+ "click [data-role=start-position-mode]": 'startPositionMode',
+ "click [data-role=toggle-layout-settings]": 'toggleSettings',
+ },
+
+ initialize: function(opt){
+ this.parent = opt.parent
+
+ this.$modes = this.$('.mode')
+ this.$toggleOrbitMode = this.$('[data-role=toggle-orbit-mode]')
+ this.$arrowMode = this.$('[data-role=arrow-mode]')
+ this.$polylineMode = this.$('[data-role=polyline-mode]')
+ this.$orthoPolylineMode = this.$('[data-role=ortho-polyline-mode]')
+ this.$eraserMode = this.$('[data-role=eraser-mode]')
+ this.$startPositionMode = this.$('[data-role=start-position-mode]')
+
+ keys.on('escape', function(){
+ app.controller.toolbar.toggleOrbitMode()
+ })
+
+ this.arrowMode()
+ },
+
+ showUploader: function(){
+ this.parent.scaler.show()
+ this.parent.uploader.show()
+ },
+
+ toggleOrbitMode: function(state){
+ this.parent.orbiting = typeof state == "boolean" ? state : ! this.parent.orbiting
+ this.$toggleOrbitMode.toggleClass("inuse", ! this.parent.orbiting)
+ this.parent.editor.resize()
+ if (this.parent.orbiting) {
+ controls.toggle(true)
+ movements.lock()
+ }
+ else {
+ controls.toggle(false)
+ movements.unlock()
+ movements.gravity(true)
+ var pos = this.parent.quantizeStartPosition()
+ cam.rotationX = pos.rotationX
+ cam.rotationY = pos.rotationY
+ cam.x = pos.x
+ cam.y = viewHeight
+ cam.z = pos.z
+ }
+ },
+
+ toggleSettings: function(){
+ this.parent.settings.toggle()
+ this.parent.notice.toggle( ! this.parent.data.isNew && ! this.parent.settings.visible() )
+ },
+
+ setActiveMode: function( $el ) {
+ this.$modes.removeClass('active')
+ $el.addClass('active')
+ },
+
+ arrowMode: function(){
+ this.setActiveMode( this.$arrowMode )
+ this.parent.map.ui.set_tool("arrow")
+ },
+
+ polylineMode: function(){
+ this.setActiveMode( this.$polylineMode )
+ this.parent.map.ui.set_tool("polyline")
+ },
+
+ orthoPolylineMode: function(){
+ this.setActiveMode( this.$orthoPolylineMode )
+ this.parent.map.ui.set_tool("ortho-polyline")
+ },
+
+ eraserMode: function(){
+ this.setActiveMode( this.$eraserMode )
+ this.parent.map.ui.set_tool("eraser")
+ },
+
+ startPositionMode: function(){
+ this.setActiveMode( this.$startPositionMode )
+ this.parent.map.ui.set_tool("start-position")
+ this.parent.settings.hide()
+ this.parent.notice.showStartPositionNotice()
+ },
+
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintUploader.js b/public/assets/javascripts/ui/blueprint/BlueprintUploader.js
new file mode 100644
index 0000000..aa62a4c
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintUploader.js
@@ -0,0 +1,147 @@
+
+var BlueprintUploader = UploadView.extend({
+ el: ".blueprintUploader",
+
+ mediaTag: "blueprint",
+ createAction: "/api/blueprint/new",
+ uploadAction: "/api/blueprint/upload",
+ listAction: "/api/blueprint/user",
+ destroyAction: "/api/blueprint/destroy",
+
+ events: {
+ "mousedown": 'stopPropagation',
+ "change .url": "enterUrl",
+ "keydown .url": "enterSetUrl",
+
+ "click .blueprint": "pick",
+ "click .remove": "destroy",
+ },
+
+ initialize: function(opt){
+ this.parent = opt.parent
+ this.__super__.initialize.call(this)
+
+ this.$url = this.$(".url")
+ this.$blueprints = this.$(".blueprints")
+ },
+
+ loaded: false,
+ nameToShow: null,
+ load: function(name){
+ this.nameToShow = name || ""
+ $.get(this.listAction, { tag: this.mediaTag }, this.populate.bind(this))
+ },
+
+ populate: function(data){
+ this.loaded = true
+ if (data && data.length) {
+ this.$blueprints.show()
+ data.forEach(this.append.bind(this))
+ if (this.nameToShow === "new") {
+ // don't pick anything..
+ this.show()
+ }
+ else if (! this.nameToShow) {
+ this.hide()
+ data.some(function(el){
+ if (el.slug == this.nameToShow) {
+ this.parent.scaler.pick(el)
+ return true
+ }
+ }.bind(this))
+ }
+ else {
+ this.hide()
+ this.parent.scaler.pick(data[0])
+ }
+ }
+ else {
+ this.parent.scaler.hideClose()
+ this.show()
+ }
+ },
+
+ pick: function(e){
+ var $el = $(e.currentTarget)
+ var media = $el.data("media")
+ this.hide()
+ this.parent.scaler.pick(media)
+ if (media.slug) {
+ window.history.pushState(null, document.title, "/blueprint/" + media.slug)
+ }
+ },
+
+ destroy: function(e){
+ e.stopPropagation()
+ var $el = $(e.currentTarget)
+ var _id = $el.closest(".blueprint").data("id")
+ $el.remove()
+ $.ajax({
+ type: "delete",
+ url: this.destroyAction,
+ data: { _id: _id, _csrf: $("[name=_csrf]").val() }
+ }).complete(function(){
+ })
+ },
+
+ show: function(){
+ this.toggle(true)
+ },
+ hide: function(){
+ this.toggle(false)
+ },
+ toggle: function (state) {
+ this.$el.toggleClass("active", state)
+ },
+
+ addUrl: function (url){
+ Parser.loadImage(url, function(media){
+ if (! media) return
+ media._csrf = $("[name=_csrf]").val()
+ media.tag = this.mediaTag
+
+ var request = $.ajax({
+ type: "post",
+ url: this.createAction,
+ data: media,
+ })
+ request.done(this.add.bind(this))
+
+ }.bind(this))
+ },
+ enterUrl: function(){
+ var url = this.$url.sanitize()
+ this.addUrl(url)
+ this.$url.val("")
+ },
+ enterSetUrl: function (e) {
+ e.stopPropagation()
+ if (e.keyCode == 13) {
+ setTimeout(this.enterUrl.bind(this), 100)
+ }
+ },
+
+ add: function(media){
+ this.$blueprints.show()
+ this.append(media)
+ this.hide()
+ this.parent.scaler.pick(media, true)
+ },
+
+ append: function(media){
+ var $el = $("<span>")
+ var img = new Image ()
+ img.src = media.url
+ var remove = document.createElement("span")
+ remove.className = "remove"
+ remove.innerHTML = "<span>x</span>"
+
+ $el.data("id", media._id)
+ $el.data("media", media)
+ $el.append(img)
+ $el.append(remove)
+ $el.addClass("blueprint")
+ this.$blueprints.append($el)
+ },
+
+})
diff --git a/public/assets/javascripts/ui/blueprint/BlueprintView.js b/public/assets/javascripts/ui/blueprint/BlueprintView.js
new file mode 100644
index 0000000..1858c3d
--- /dev/null
+++ b/public/assets/javascripts/ui/blueprint/BlueprintView.js
@@ -0,0 +1,106 @@
+
+var BlueprintView = View.extend({
+ el: "#blueprintView",
+
+ action: "/api/blueprint/show/",
+
+ events: {
+ },
+
+ initialize: function(){
+// this.colorControl = new ColorControl ({ parent: this })
+// this.cursor = new HelpCursor({ parent: this })
+ this.map = this.buildMap()
+ this.editor = new BlueprintEditor ({ parent: this })
+ this.toolbar = new BlueprintToolbar ({ parent: this })
+ this.uploader = new BlueprintUploader ({ parent: this })
+ this.scaler = new BlueprintScaler ({ parent: this })
+ this.info = new BlueprintInfo ({ parent: this })
+ this.settings = new BlueprintSettings ({ parent: this })
+ this.notice = new BlueprintNotice ({ parent: this })
+ Rooms.shapesMode = true
+ },
+
+ load: function(name){
+ name = sanitize(name) || "new"
+ this.uploader.load(name)
+// name = sanitize(name)
+// $.get(this.action + name, this.ready.bind(this))
+ },
+
+ orbiting: true,
+ startPosition: {},
+ quantizeStartPosition: function(){
+ //
+ var regions = RegionList.build()
+ var pos = this.startPosition
+ var startPositionIsInARoom = regions.some(function(region){
+ return region.contains(pos.x, pos.z)
+ })
+ if (startPositionIsInARoom) {
+ return this.startPosition
+ }
+ else if (! regions.length) {
+ return {
+ x: 0,
+ y: viewHeight,
+ z: 0,
+ rotationX: 0,
+ rotationY: Math.PI/2,
+ }
+ }
+ else {
+ var center = regions[0].center()
+ return {
+ x: center.a,
+ y: viewHeight,
+ z: center.b,
+ rotationX: 0,
+ rotationY: Math.PI/2,
+ }
+ }
+ },
+
+ buildMap: function(){
+ // i forget if this has to be global
+ map = new Map ({
+ type: "ortho",
+ el: document.querySelector("#orthographic"),
+ width: window.innerWidth/2,
+ height: window.innerHeight,
+ zoom: -2,
+ zoom_min: -6.2,
+ zoom_max: 1,
+ })
+ map.ui.add_tool("arrow", new ArrowTool)
+ map.ui.add_tool("polyline", new PolylineTool)
+ map.ui.add_tool("ortho-polyline", new OrthoPolylineTool)
+ map.ui.add_tool("eraser", new EraserTool)
+ map.ui.add_tool("position", new PositionTool)
+ map.ui.add_tool("start-position", new StartPositionTool)
+ map.ui.placing = false
+ return map
+ },
+
+ ready: function(data){
+ this.data = data
+ this.info.load(data)
+ this.settings.load(data)
+ this.editor.loadFloorplan(data)
+ if (! data.shapes || data.shapes.length == 0) {
+ this.startPosition = { x: 0, y: 0, z: 0, rotationX: 0, rotationY: Math.PI/2 }
+ }
+ else {
+ this.startPosition = data.startPosition
+ }
+ this.notice.hide()
+ this.settings.show()
+ },
+
+ hideExtras: function(){
+ },
+
+ pickWall: function(wall, pos){
+ },
+
+})
diff --git a/public/assets/javascripts/ui/builder/BuilderInfo.js b/public/assets/javascripts/ui/builder/BuilderInfo.js
index 9a7dbf9..aa58d6e 100644
--- a/public/assets/javascripts/ui/builder/BuilderInfo.js
+++ b/public/assets/javascripts/ui/builder/BuilderInfo.js
@@ -40,8 +40,8 @@ var BuilderInfo = View.extend({
load: function(data){
this.$viewHeight.unitVal( window.viewHeight = data.viewHeight || app.defaults.viewHeight )
- this.$units.val( "ft" )
- this.$unitName.html( "ft" )
+ this.$units.val( data.units || "ft" )
+ this.$unitName.html( data.units || "ft" )
if (Rooms.regions.length == 0) {
this.changeHeightGlobal(true)
diff --git a/public/assets/javascripts/ui/builder/BuilderSettings.js b/public/assets/javascripts/ui/builder/BuilderSettings.js
index c8c8880..256bffe 100644
--- a/public/assets/javascripts/ui/builder/BuilderSettings.js
+++ b/public/assets/javascripts/ui/builder/BuilderSettings.js
@@ -52,7 +52,7 @@ var BuilderSettings = FormView.extend({
this.$name.val( names.join(" ") )
this.action = this.createAction
- window.history.pushState(null, document.title, "/builder/new")
+ window.history.pushState(null, document.title, "/layout/new")
},
clear: function(){
diff --git a/public/assets/javascripts/ui/builder/BuilderToolbar.js b/public/assets/javascripts/ui/builder/BuilderToolbar.js
index 6c218be..e9dcce3 100644
--- a/public/assets/javascripts/ui/builder/BuilderToolbar.js
+++ b/public/assets/javascripts/ui/builder/BuilderToolbar.js
@@ -60,6 +60,9 @@ var BuilderToolbar = View.extend({
var state = map.ui.permissions.toggle("destroy")
$(".inuse").removeClass("inuse")
$(e.currentTarget).toggleClass("inuse", state)
+ if (! state) {
+ this.resetPermissions()
+ }
},
})
diff --git a/public/assets/javascripts/ui/editor/EditorSettings.js b/public/assets/javascripts/ui/editor/EditorSettings.js
index b319404..5aa88e9 100644
--- a/public/assets/javascripts/ui/editor/EditorSettings.js
+++ b/public/assets/javascripts/ui/editor/EditorSettings.js
@@ -41,7 +41,12 @@ var EditorSettings = FormView.extend({
this.action = data.isNew ? this.createAction : this.updateAction
this.parent.data = data
- data.rooms && Rooms.deserialize(data.rooms, data.walls)
+ if (data.shapes && data.shapes.length) {
+ Rooms.deserializeFromShapes(data, data.walls)
+ }
+ else if (data.rooms) {
+ Rooms.deserialize(data.rooms, data.walls)
+ }
if (data.startPosition) {
scene.camera.move(data.startPosition)
this.startPosition = data.startPosition
@@ -77,6 +82,7 @@ var EditorSettings = FormView.extend({
data.privacy && this.$privacy.find("[value=" + data.privacy + "]").prop("checked", "checked")
data.media && Scenery.deserialize(data.media)
+ data.sculpture && Sculpture.deserialize(data.sculpture)
}
},
@@ -112,6 +118,7 @@ var EditorSettings = FormView.extend({
clear: function(e){
e.preventDefault()
Scenery.removeAll()
+ Sculpture.removeAll()
},
destroy: function(){
@@ -187,10 +194,16 @@ var EditorSettings = FormView.extend({
fd.append( "description", this.$description.val() )
fd.append( "privacy", this.$privacy.filter(":checked").val() == "private" )
fd.append( "viewHeight", window.viewHeight )
- fd.append( "rooms", JSON.stringify( Rooms.serialize() ) )
+ if (Rooms.shapesMode) {
+ fd.append( "shapes", JSON.stringify( shapes.serialize() ) )
+ }
+ else {
+ fd.append( "rooms", JSON.stringify( Rooms.serialize() ) )
+ }
fd.append( "walls", JSON.stringify( Walls.serialize() ) )
fd.append( "colors", JSON.stringify( Walls.colors ) )
fd.append( "media", JSON.stringify( Scenery.serialize() ) )
+ fd.append( "sculpture", JSON.stringify( Sculpture.serialize() ) )
fd.append( "startPosition", JSON.stringify( this.startPosition || false ) )
fd.append( "lastPosition", JSON.stringify( app.position(scene.camera) ) )
diff --git a/public/assets/javascripts/ui/editor/EditorView.js b/public/assets/javascripts/ui/editor/EditorView.js
index 50d3650..c05b373 100644
--- a/public/assets/javascripts/ui/editor/EditorView.js
+++ b/public/assets/javascripts/ui/editor/EditorView.js
@@ -2,6 +2,7 @@
var EditorView = View.extend({
el: "#editorView",
+ blueprintAction: "/api/blueprint/user/",
projectAction: "/api/project/",
layoutAction: "/api/layout/",
@@ -17,6 +18,7 @@ var EditorView = View.extend({
this.mediaUpload = new MediaUpload ({ parent: this })
this.mediaTumblr = new MediaTumblr ({ parent: this })
this.mediaEditor = new MediaEditor ({ parent: this })
+ this.sculptureEditor = new SculptureEditor ({ parent: this })
this.wallpaperPicker = new WallpaperPicker ({ parent: this })
this.colorControl = new ColorControl ({ parent: this })
this.textEditor = new TextEditor ({ parent: this })
@@ -40,6 +42,10 @@ var EditorView = View.extend({
$.get(this.layoutAction + layout, this.readyLayout.bind(this))
},
+ loadBlueprint: function(blueprint){
+ $.get(this.blueprintAction + blueprint, this.readyLayout.bind(this))
+ },
+
ready: function(data){
$("#map").hide()
@@ -56,12 +62,19 @@ var EditorView = View.extend({
},
pick: function(scenery){
- if (scenery.type == "text") {
+ if (scenery.isSculpture) {
+ this.mediaEditor.hide()
+ this.textEditor.hide()
+ this.sculptureEditor.pick(scenery)
+ }
+ else if (scenery.type == "text") {
this.mediaEditor.hide()
+ this.sculptureEditor.hide()
this.textEditor.pick(scenery)
}
else {
this.textEditor.hide()
+ this.sculptureEditor.hide()
this.mediaEditor.pick(scenery)
}
},
@@ -72,9 +85,11 @@ var EditorView = View.extend({
},
hideExtras: function(){
+ this.sculptureEditor.hide()
this.mediaEditor.hide()
this.textEditor.hide()
this.share.hide()
+ Sculpture.resize.hide()
Scenery.resize.hide()
Scenery.hovering = false
}
diff --git a/public/assets/javascripts/ui/editor/SculptureEditor.js b/public/assets/javascripts/ui/editor/SculptureEditor.js
new file mode 100644
index 0000000..953260c
--- /dev/null
+++ b/public/assets/javascripts/ui/editor/SculptureEditor.js
@@ -0,0 +1,237 @@
+
+var SculptureEditor = FormView.extend({
+ el: "#sculptureEditor",
+
+ events: {
+ "keydown": 'taint',
+ "focus [name]": "clearMinotaur",
+ "click [data-role=play-media]": "togglePaused",
+ "mousedown [name=keyframe]": "stopPropagation",
+ "mousedown": "stopPropagation",
+ "change [name=keyframe]": "seek",
+ "change [name=autoplay]": "setAutoplay",
+ "change [name=billboard]": "setBillboard",
+ "change [name=outline]": "setOutline",
+ "change [name=outlineColor]": "setOutlineColor",
+ "change [name=loop]": "setLoop",
+ "change [name=mute]": "setMute",
+ "change [name=width]": 'changeWidth',
+ "change [name=height]": 'changeHeight',
+ "change [name=depth]": 'changeDepth',
+ "change [name=units]": 'changeUnits',
+ "click [data-role=destroy-sculpture]": "destroy",
+ },
+
+ initialize: function(opt){
+ this.parent = opt.parent
+ this.__super__.initialize.call(this)
+
+ this.$name = this.$("[name=name]")
+ this.$description = this.$("[name=description]")
+
+ this.$billboard = this.$("[name=billboard]")
+ this.$outline = this.$("[name=outline]")
+ this.$outlineColor = this.$("[name=outlineColor]")
+
+ // image fields
+ this.$width = this.$("[name=width]")
+ this.$height = this.$("[name=height]")
+ this.$depth = this.$("[name=depth]")
+ this.$units = this.$("[name=units]")
+
+ // video fields
+ this.$playButton = this.$("[data-role=play-media]")
+ this.$autoplay = this.$("[name=autoplay]")
+ this.$loop = this.$("[name=loop]")
+ this.$mute = this.$("[name=mute]")
+ this.$keyframe = this.$("[name=keyframe]")
+ },
+
+ toggle: function(state) {
+ if (state) {
+ this.parent.settings.toggle()
+ }
+ this.$el.toggleClass("active", state);
+ },
+
+ togglePaused: function(state){
+ var state = this.sculpture.toggle(state)
+ this.$playButton.toggleClass("paused", ! state)
+ },
+
+ pick: function(sculpture) {
+ if (this.sculpture && sculpture !== this.sculpture) {
+ this.unbind()
+ }
+
+ this.bind(sculpture)
+ this.$el.addClass("active")
+
+// app.controller.toolbar.resetMode()
+ app.controller.toolbar.resetControls()
+ Sculpture.resize.show(sculpture)
+ Sculpture.hovering = true
+
+ var media = sculpture.media
+
+ // console.log(media)
+ this.$name.val(media.title || "") // || filenameFromUrl(media.url) )
+ this.$description.val(media.description || "")
+ this.setDimensions()
+ this.$units.val( "ft" )
+
+ this.$outline.prop( 'checked', !! sculpture.outline )
+ this.$outlineColor.val( sculpture.outlineColor || "#000000" )
+ this.$billboard.prop( 'checked', !! sculpture.billboard )
+
+ switch (media.type) {
+ case "image":
+ this.$(".video").hide()
+ this.$(".audio").hide()
+ this.$(".image").show()
+ break
+
+ case "youtube":
+ case "vimeo":
+ case "video":
+ this.$(".image").hide()
+ this.$(".audio").hide()
+ this.$(".video").show()
+
+ this.$playButton.toggleClass("paused", ! this.sculpture.paused())
+ this.$autoplay.prop('checked', !! media.autoplay)
+ this.$loop.prop('checked', !! media.loop)
+ this.$mute.prop('checked', !! media.mute)
+ this.$keyframe.val( Number(media.keyframe || 0) )
+ break
+
+ case "soundcloud":
+ this.$(".image").hide()
+ this.$(".video").hide()
+ this.$(".audio").show()
+ this.$playButton.toggleClass("paused", ! this.sculpture.paused())
+ this.$autoplay.prop('checked', !! media.autoplay)
+ this.$loop.prop('checked', !! media.loop)
+ break
+ }
+ },
+
+ hide: function(sculpture){
+ if (this.sculpture) {
+ this.unbind()
+ }
+ this.toggle(false)
+ },
+
+ seek: function(){
+ var n = parseFloat( this.$keyframe.val() )
+ this.sculpture.seek(n)
+ this.tainted = true
+
+ this.sculpture.media.keyframe = n
+ },
+ setAutoplay: function(){
+ var checked = this.$autoplay.prop('checked')
+ this.sculpture.media.autoplay = checked
+ this.tainted = true
+ if (checked && this.sculpture.paused()) {
+ this.togglePaused()
+ }
+ },
+ setLoop: function(){
+ var checked = this.$loop.prop('checked')
+ this.sculpture.setLoop(checked)
+ this.tainted = true
+ },
+ setMute: function(){
+ var checked = this.$mute.prop('checked')
+ this.sculpture.media.mute = checked
+ this.sculpture.mute(checked)
+ this.tainted = true
+ },
+
+ setBillboard: function(){
+ var checked = this.$billboard.prop('checked')
+ this.sculpture.setBillboard(checked)
+ this.tainted = true
+ },
+ setOutline: function(){
+ var checked = this.$outline.prop('checked')
+ this.sculpture.setOutline(checked)
+ this.tainted = true
+ },
+ setOutlineColor: function(){
+ var color = this.$outlineColor.val()
+ this.sculpture.setOutlineColor(color)
+ this.tainted = true
+ },
+
+ setDimensions: function(){
+ if (! this.sculpture) return
+ this.$width.unitVal( Number(this.sculpture.naturalDimensions.a * this.sculpture.scale) || "" )
+ this.$height.unitVal( Number(this.sculpture.naturalDimensions.b * this.sculpture.scale) || "" )
+ this.$depth.unitVal( Number(this.sculpture.naturalDimensions.c * this.sculpture.scale) || "" )
+ this.tainted = true
+ },
+ changeWidth: function(e){
+ e.stopPropagation()
+ this.sculpture.set_scale( this.$width.unitVal() / this.sculpture.naturalDimensions.a )
+ this.setDimensions()
+ this.sculpture.updateOutline()
+ },
+ changeHeight: function(e){
+ e.stopPropagation()
+ this.sculpture.set_scale( this.$height.unitVal() / this.sculpture.naturalDimensions.b )
+ this.setDimensions()
+ this.sculpture.updateOutline()
+ },
+ changeDepth: function(e){
+ e.stopPropagation()
+ this.sculpture.set_depth( this.$depth.unitVal() )
+ this.$depth.unitVal( Number(this.sculpture.naturalDimensions.c * this.sculpture.scale) || "" )
+ this.sculpture.updateOutline()
+ },
+ changeUnits: function(){
+ app.units = this.$units.val()
+ this.$('.units').resetUnitVal()
+ },
+
+ taint: function(e){
+ e.stopPropagation()
+ this.tainted = true
+ },
+
+ bind: function(sculpture){
+ this.sculpture = sculpture
+ this.sculpture.mx.bound = true
+ this.sculpture.mx.el.classList.add("picked")
+ },
+
+ unbind: function(){
+ if (this.sculpture) {
+ this.sculpture.focused = false
+ if (this.tainted && this.sculpture.media) {
+ this.sculpture.media.title = this.$name.val()
+ this.sculpture.media.description = this.$description.val()
+ Minotaur.watch( app.router.editorView.settings )
+ }
+ if (this.sculpture.mx) {
+ this.sculpture.mx.bound = false
+ this.sculpture.mx.el.classList.remove("picked")
+ }
+ }
+ this.tainted = false
+ this.sculpture = null
+ },
+
+ destroy: function(){
+ var sculpture = this.sculpture
+ this.hide()
+
+ sculpture.remove()
+
+ this.tainted = false
+ this.sculpture = null
+ },
+
+})
diff --git a/public/assets/javascripts/ui/lib/AnimatedView.js b/public/assets/javascripts/ui/lib/AnimatedView.js
new file mode 100644
index 0000000..3c50b0a
--- /dev/null
+++ b/public/assets/javascripts/ui/lib/AnimatedView.js
@@ -0,0 +1,31 @@
+var AnimatedView = View.extend({
+
+ _animating: false,
+ last_t: 0,
+
+ startAnimating: function(){
+ if (this._animating) return
+ this._animating = true
+ this._animate()
+ },
+
+ stopAnimating: function(){
+ this._animating = false
+ },
+
+ _animate: function(t){
+ if (! this._animating) return
+
+ requestAnimationFrame(this._animate.bind(this))
+
+ var dt = t - this.last_t
+ this.last_t = t
+
+ if (! t) return
+
+ this.animate(t, dt)
+ },
+
+ animate: function(t, dt){},
+
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/ui/lib/ConfirmModal.js b/public/assets/javascripts/ui/lib/ConfirmModal.js
index a72b31e..7d9da67 100644
--- a/public/assets/javascripts/ui/lib/ConfirmModal.js
+++ b/public/assets/javascripts/ui/lib/ConfirmModal.js
@@ -4,21 +4,31 @@ var ConfirmModal = new( ModalFormView.extend({
el: ".mediaDrawer.confirm",
events: {
- "click .yes": "advance",
- "click .no": "hide",
+ "click .yes": "agree",
+ "click .no": "cancel",
},
- confirm: function(question, callback){
+ confirm: function(question, agreeCallback, cancelCallback){
this.$(".question").empty().append(question)
- this.callback = callback
+ this.agreeCallback = agreeCallback
+ this.cancelCallback = cancelCallback
this.show()
},
- advance: function(e){
+ agree: function(e){
e && e.preventDefault()
this.hide()
- this.callback && this.callback()
- this.callback = null
+ this.agreeCallback && this.agreeCallback()
+ this.agreeCallback = null
+ this.cancelCallback = null
+ },
+
+ cancel: function(e){
+ e && e.preventDefault()
+ this.hide()
+ this.cancelCallback && this.cancelCallback()
+ this.agreeCallback = null
+ this.cancelCallback = null
}
}) ) \ No newline at end of file
diff --git a/public/assets/javascripts/ui/lib/FormView.js b/public/assets/javascripts/ui/lib/FormView.js
index f5845e7..a952ecb 100644
--- a/public/assets/javascripts/ui/lib/FormView.js
+++ b/public/assets/javascripts/ui/lib/FormView.js
@@ -63,13 +63,15 @@ var FormView = View.extend({
save: function(e, successCallback, errorCallback){
e && e.preventDefault()
- this.$errors.hide().css("opacity", 0.0);
+ this.$errors && this.$errors.hide().css("opacity", 0.0);
if (this.validate) {
var errors = this.validate()
if (errors && errors.length) {
if (errorCallback) {
- errorCallback(errors)
+ setTimeout(function(){
+ errorCallback(errors)
+ })
}
else {
this.showErrors(errors)
@@ -77,7 +79,6 @@ var FormView = View.extend({
return
}
}
-
var action = typeof this.action == "function" ? this.action() : this.action
if (! action) return
@@ -112,7 +113,9 @@ var FormView = View.extend({
return
}
else {
+ console.log("ok")
if (successCallback) {
+ console.log("use cb")
successCallback(response)
}
if (this.success) {
diff --git a/public/assets/javascripts/ui/lib/LabColorPicker.js b/public/assets/javascripts/ui/lib/LabColorPicker.js
index 7ddcdd5..2c8fb90 100644
--- a/public/assets/javascripts/ui/lib/LabColorPicker.js
+++ b/public/assets/javascripts/ui/lib/LabColorPicker.js
@@ -1,9 +1,12 @@
var LabColorPicker = function (parent, w, h) {
var base = this
var canvas = this.canvas = document.createElement('canvas')
- var ctx = this.ctx = canvas.getContext('2d')
- var imageData = ctx.createImageData(w,h)
- var data = imageData.data
+ canvas.width = w
+ canvas.height = h
+ var ctx = this.ctx = canvas.getContext('2d-lodpi')
+// canvas.className = "colorPicker"
+// var imageData = ctx.createImageData(w, h)
+// var data = imageData.data
var cursor = this.cursor = document.createElement("div")
cursor.className = "colorPickerCursor"
@@ -15,10 +18,6 @@ var LabColorPicker = function (parent, w, h) {
brightnessControl.setAttribute("max", "110")
brightnessControl.setAttribute("value", "0")
- canvas.width = w
- canvas.height = h
- canvas.className = "colorPicker"
-
var ww = w-1
var hh = h-1
@@ -84,11 +83,14 @@ var LabColorPicker = function (parent, w, h) {
}
this.paint = function() {
val = clamp(val, L_range[0], L_range[1])
- var x, y, t
- for (var i = 0; i < w; i++) {
- for (var j = 0; j < h; j++) {
- x = mix( i/ww, a_range[0], a_range[1] )
- y = mix( j/hh, b_range[0], b_range[1] )
+ var imageData = ctx.createImageData(canvas.width, canvas.height)
+ var data = imageData.data
+ var x, y, t, cw = imageData.width, ch = imageData.height
+ var cww = cw-1, chh = ch-1
+ for (var i = 0; i < cw; i++) {
+ for (var j = 0; j < ch; j++) {
+ x = mix( i/cww, a_range[0], a_range[1] )
+ y = mix( j/chh, b_range[0], b_range[1] )
t = (j*w + i) * 4
rgb = xyz2rgb(hunterlab2xyz(val, x, y))
data[t] = Math.round( rgb[0] )
diff --git a/public/assets/javascripts/ui/lib/ModalView.js b/public/assets/javascripts/ui/lib/ModalView.js
index 6f1c729..e0070ce 100644
--- a/public/assets/javascripts/ui/lib/ModalView.js
+++ b/public/assets/javascripts/ui/lib/ModalView.js
@@ -35,6 +35,11 @@ var ModalView = View.extend({
$("body").removeClass("noOverflow");
},
+ hideClose: function(){
+ $("#fixed_close").removeClass("active")
+ $("#fixed_close").unbind("click", this.hide.bind(this))
+ },
+
close: function(){
if (window.isModalView) {
window.location.pathname = "/"
diff --git a/public/assets/javascripts/ui/lib/ToggleableView.js b/public/assets/javascripts/ui/lib/ToggleableView.js
new file mode 100644
index 0000000..371629f
--- /dev/null
+++ b/public/assets/javascripts/ui/lib/ToggleableView.js
@@ -0,0 +1,19 @@
+var ToggleableView = View.extend({
+
+ toggle: function(state){
+ this.$el.toggleClass("active", state)
+ },
+
+ show: function(){
+ this.toggle(true)
+ },
+
+ hide: function(){
+ this.toggle(false)
+ },
+
+ visible: function(){
+ return this.$el.hasClass("active")
+ }
+
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/ui/lib/Toolbar.js b/public/assets/javascripts/ui/lib/Toolbar.js
new file mode 100644
index 0000000..a9ce51c
--- /dev/null
+++ b/public/assets/javascripts/ui/lib/Toolbar.js
@@ -0,0 +1,22 @@
+var Toolbar = Fiber.extend(function(base){
+ var exports = {}
+ exports.init = function(rapper){
+ this.rapper = (typeof rapper == "string") ? $(rapper)[0] : rapper
+ this.tools = {}
+ this.els = {}
+ }
+ exports.add = function(role, fn){
+ var self = this
+ this.tools[role] = fn
+ this.els[role] = $("[data-role=" + role + "]", self.rapper)
+ this.els[role].click(function(){
+ $(".active", self.rapper).removeClass('active')
+ $(this).addClass('active')
+ fn()
+ })
+ }
+ exports.pick = function(role){
+ this.els[role].trigger("click")
+ }
+ return exports
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/ui/reader/MediaPlayer.js b/public/assets/javascripts/ui/reader/MediaPlayer.js
index 8424d9c..8e65976 100644
--- a/public/assets/javascripts/ui/reader/MediaPlayer.js
+++ b/public/assets/javascripts/ui/reader/MediaPlayer.js
@@ -1,5 +1,5 @@
-var MediaPlayer = FormView.extend({
+var MediaPlayer = View.extend({
el: "#mediaPlayer",
events: {
diff --git a/public/assets/javascripts/ui/reader/ReaderView.js b/public/assets/javascripts/ui/reader/ReaderView.js
index 617a145..43e81d8 100644
--- a/public/assets/javascripts/ui/reader/ReaderView.js
+++ b/public/assets/javascripts/ui/reader/ReaderView.js
@@ -31,7 +31,12 @@ var ReaderView = View.extend({
this.tracker = new Tracker ({ mode: mode })
- $.get(this.projectAction + name, this.ready.bind(this))
+ if ('vvalls_data' in window) {
+ this.ready(window.vvalls_data)
+ }
+ else {
+ $.get(this.projectAction + name, this.ready.bind(this))
+ }
},
getQS: function(){
@@ -71,9 +76,15 @@ var ReaderView = View.extend({
},
build: function(data){
- data.rooms && Rooms.deserialize(data.rooms)
+ if (data.shapes.length) {
+ Rooms.deserializeFromShapes(data)
+ }
+ else {
+ Rooms.deserialize(data.rooms)
+ }
data.walls && Walls.deserialize(data.walls)
data.media && Scenery.deserialize(data.media)
+ data.sculpture && Sculpture.deserialize(data.sculpture)
data.startPosition && scene.camera.move(data.startPosition)
cam.y = window.viewHeight = data.viewHeight || app.defaults.viewHeight
@@ -84,9 +95,12 @@ var ReaderView = View.extend({
Walls.setColor[mode](colors[mode])
})
- editor.permissions.clear()
+ window.editor && editor.permissions.clear()
- this.listen()
+ // disable until we start using spinning again
+ // this.listen()
+
+ app.tube("site-ready")
},
listen: function(){
diff --git a/public/assets/javascripts/ui/reader/Tracker.js b/public/assets/javascripts/ui/reader/Tracker.js
index ce32c59..d2dec39 100644
--- a/public/assets/javascripts/ui/reader/Tracker.js
+++ b/public/assets/javascripts/ui/reader/Tracker.js
@@ -1,8 +1,12 @@
-(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
+if (window.location.host.indexOf("lvh.me") === -1) {
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+}
+else {
+ ga = function(){}
+}
ga('create', 'UA-56883705-1', 'auto');
ga('send', 'pageview');
diff --git a/public/assets/javascripts/ui/reader/_router.js b/public/assets/javascripts/ui/reader/_router.js
new file mode 100644
index 0000000..f3b121b
--- /dev/null
+++ b/public/assets/javascripts/ui/reader/_router.js
@@ -0,0 +1,24 @@
+
+var SiteRouter = Router.extend({
+ el: "body",
+
+ initialize: function(){
+ app.launch()
+ if (app.unsupported) return
+
+ this.readerView = app.controller = new ReaderView()
+ this.readerView.load()
+
+ $("body").removeClass("loading")
+ }
+
+})
+
+var editor = {
+ permissions: new Permissions({
+ 'pick': true,
+ 'move': true,
+ 'resize': true,
+ 'destroy': false,
+ })
+}
diff --git a/public/assets/javascripts/ui/site/EditSubscriptionModal.js b/public/assets/javascripts/ui/site/EditSubscriptionModal.js
new file mode 100644
index 0000000..c1dc9f8
--- /dev/null
+++ b/public/assets/javascripts/ui/site/EditSubscriptionModal.js
@@ -0,0 +1,290 @@
+
+var EditSubscriptionModal = ModalView.extend({
+ el: ".mediaDrawer.editSubscription",
+ action: "/api/subscription",
+ syncAction: "/api/subscription/sync",
+ updateAction: "/api/subscription",
+ destroyAction: "/api/subscription/destroy",
+
+ fixedClose: true,
+ editing: false,
+ subscriber: null,
+ tempSubscriber: null,
+
+ events: {
+ "click [data-role='addLayouts']": 'addLayouts',
+ "click [data-role='changePlan']": 'changePlan',
+ "click [data-role='cancelSubscription']": 'destroy',
+ "click .gear": 'sync',
+ "click .planList button": 'followLink',
+
+ "click [data-role=showEditMenu]": "editMode",
+
+ "click [data-role=closeMenu]": "resetMode",
+
+ "input [data-role=basicLayoutInput]": "updateQuantity",
+ "input [data-role=proLayoutInput]": "updateQuantity",
+ "click [data-role=saveChanges]": "saveChanges",
+
+ "change [name=planRadio]": "updatePlan",
+ "click [data-role=savePlan]": "savePlan",
+
+ "submit form": "preventDefault",
+ },
+
+ initialize: function(){
+ // this.parent = opt.parent
+ this.__super__.initialize.call(this)
+
+ // two sections
+ this.$freePlan = this.$(".freePlan")
+ this.$paidPlan = this.$(".paidPlan")
+
+ // subscription table
+ this.$planInfo = this.$(".planInfo")
+ this.$planRow = this.$(".planRow")
+ this.$basicLayoutRow = this.$(".basicLayoutRow")
+ this.$proLayoutRow = this.$(".proLayoutRow")
+ this.$totalRow = this.$(".totalRow")
+ this.$planList = this.$(".planList")
+
+ this.$billingInterval = this.$("[data-role=billingInterval]")
+
+ // plan stuff
+ this.$planName = this.$("[data-role=planName]")
+ this.$planCost = this.$("[data-role=planCost]")
+ this.$planTotal = this.$("[data-role=planTotal]")
+
+ this.$basicPlanName = this.$("[data-role=basicPlanName]")
+ this.$basicPlanCost = this.$("[data-role=basicPlanCost]")
+
+ this.$proPlanName = this.$("[data-role=proPlanName]")
+ this.$proPlanCost = this.$("[data-role=proPlanCost]")
+
+ // basic + pro layout stuff
+ this.$basicLayoutCost = this.$("[data-role=basicLayoutCost]")
+ this.$basicLayoutQuantity = this.$("[data-role=basicLayoutQuantity]")
+ this.$basicLayoutTotal = this.$("[data-role=basicLayoutTotal]")
+
+ this.$proLayoutCost = this.$("[data-role=proLayoutCost]")
+ this.$proLayoutQuantity = this.$("[data-role=proLayoutQuantity]")
+ this.$proLayoutTotal = this.$("[data-role=proLayoutTotal]")
+
+ // menus.. main menu
+ this.$showEditMenu = this.$("[data-role=showEditMenu]")
+ this.$cancelSubscription = this.$("[data-role=cancelSubscription]")
+
+ // three submenus
+ this.$editMenu = this.$("[data-role=editMenu]")
+ this.$planMenu = this.$("[data-role=planMenu]")
+
+ this.$buyLayouts = this.$("[data-role=buyLayouts]")
+ this.$closeMenu = this.$("[data-role=closeMenu]")
+ this.$changePlan = this.$("[data-role=changePlan]")
+
+ // input fields
+ this.$basicLayoutInput = this.$("[data-role=basicLayoutInput]")
+ this.$proLayoutInput = this.$("[data-role=proLayoutInput]")
+ this.$planRadio = this.$("[name=planRadio]")
+ this.$basicPlanInput = this.$("[data-role=basicPlanInput]")
+ this.$proPlanInput = this.$("[data-role=proPlanInput]")
+
+ this.$gear = this.$(".gear")
+ },
+
+ plan_levels: {
+ free: 0,
+ basic: 1,
+ pro: 2,
+ custom: 3,
+ artist: 4,
+ },
+
+ loaded: false,
+ load: function(){
+ if (this.loaded) { return this.show() }
+ $.get(this.action, this.didLoad.bind(this))
+ },
+ didLoad: function(data){
+ this.loaded = true
+ this.plans = data.plans
+ if (data.subscription) {
+ this.subscriber = data.subscription
+ }
+ else if (data.error) {
+ // ...no subscription found
+ this.subscriber = null
+ }
+ return this.show()
+ },
+ followLink: function(e){
+ e.preventDefault();
+ window.location.href = $(e.target).closest("a").attr("href")
+ },
+
+ show: function(){
+ this.$gear.removeClass("turning")
+ if (! this.subscriber) {
+ this.$freePlan.show()
+ this.$paidPlan.hide()
+ this.$planList.load("/partials/plans", function(){
+ this.$(".free_plan_info").remove()
+ this.__super__.show.call(this)
+ }.bind(this))
+ return
+ }
+
+ this.$freePlan.hide()
+ this.$paidPlan.show()
+
+ this.resetMode()
+
+ this.__super__.show.call(this)
+ },
+ reset: function(){
+ var subscriber = this.subscriber
+ var plan = this.getPlan(subscriber.plan_type)
+ this.displayTotals(subscriber, plan)
+ },
+ getPlan: function(plan_type){
+ return this.plans[ this.plan_levels[ plan_type ] ]
+ },
+ calculateTotals: function(subscriber, plan){
+ var t = {}
+ t.is_pro = subscriber.plan_type == "pro"
+ t.is_monthly = subscriber.plan_period == "monthly"
+ t.plan_price = t.is_monthly ? plan.monthly_price : plan.yearly_price
+ t.basic_layout_price = t.is_monthly ? plan.basic_layout_monthly_price : plan.basic_layout_yearly_price
+ t.basic_layout_total = subscriber.basic_layouts * t.basic_layout_price
+ t.pro_layout_price = t.is_monthly ? plan.pro_layout_monthly_price : plan.pro_layout_yearly_price
+ t.pro_layout_total = t.is_pro ? subscriber.pro_layouts * t.pro_layout_price : 0
+ t.plan_total = t.plan_price + t.basic_layout_total + t.pro_layout_total
+ return t
+ },
+ displayTotals: function(subscriber, plan){
+ var totals = this.calculateTotals(subscriber, plan)
+
+ this.$basicPlanName.html ( this.plans[1].name )
+ this.$proPlanName.html ( this.plans[2].name )
+ this.$basicPlanCost.toDollars ( totals.is_monthly ? this.plans[1].monthly_price : this.plans[2].yearly_price)
+ this.$proPlanCost.toDollars ( totals.is_monthly ? this.plans[2].monthly_price : this.plans[2].yearly_price)
+
+ this.$planName.html ( plan.name )
+ this.$planCost.toDollars ( totals.plan_price )
+
+ this.$billingInterval.html ( totals.is_monthly ? "mo." : "yr." )
+ this.$basicLayoutRow.toggle ( subscriber.basic_layouts > 0 )
+ this.$proLayoutRow.toggle ( totals.is_pro && subscriber.pro_layouts > 0)
+
+ this.$basicLayoutCost.toDollars ( totals.basic_layout_price )
+ this.$basicLayoutQuantity.html ( subscriber.basic_layouts )
+ this.$basicLayoutTotal.toDollars ( totals.basic_layout_total )
+
+ this.$proLayoutCost.toDollars ( totals.pro_layout_price )
+ this.$proLayoutQuantity.html ( subscriber.pro_layouts )
+ this.$proLayoutTotal.toDollars ( totals.pro_layout_total )
+
+ this.$planTotal.toDollars ( totals.plan_total )
+ },
+
+ editMode: function(e){
+ e && e.preventDefault()
+
+ this.editing = true
+ this.$el.addClass("editing")
+ this.tempSubscriber = defaults({}, this.subscriber)
+ this.$basicLayoutInput.val( this.subscriber.basic_layouts )
+ this.$proLayoutInput.val( this.subscriber.pro_layouts )
+ this.$basicLayoutRow.show()
+ this.$proLayoutRow.toggle(this.subscriber.plan_type == "pro")
+ switch (this.subscriber.plan_type) {
+ case 'basic': this.$basicPlanInput.prop('checked', true); break;
+ case 'pro': this.$proPlanInput.prop('checked', true); break;
+ }
+ },
+ resetMode: function(e){
+ e && e.preventDefault()
+ this.editing = false
+ this.$el.removeClass("editing")
+ this.reset()
+ },
+
+ updateQuantity: function(e){
+ e && e.preventDefault()
+ var plan = this.getPlan( this.tempSubscriber.plan_type )
+ this.tempSubscriber.basic_layouts = clamp( this.$basicLayoutInput.int() || 0, 0, 100)
+ this.tempSubscriber.pro_layouts = clamp( this.$proLayoutInput.int() || 0, 0, 100)
+
+ this.$basicLayoutInput.val(this.tempSubscriber.basic_layouts)
+ this.$proLayoutInput.val(this.tempSubscriber.pro_layouts)
+ this.displayTotals(this.tempSubscriber, plan)
+ this.$basicLayoutRow.show()
+ this.$proLayoutRow.toggle(this.tempSubscriber.plan_type == "pro")
+ },
+ saveChanges: function(e){
+ e && e.preventDefault()
+ var is_changed = false
+ var diff = {}
+ "plan_type basic_layouts pro_layouts".split(" ").forEach(function(field){
+ diff[field] = this.tempSubscriber[field]
+ if (this.tempSubscriber[field] != this.subscriber[field]) {
+ is_changed = true
+ }
+ }.bind(this))
+
+ if (is_changed) {
+ this.update(diff)
+ }
+ this.subscriber = this.tempSubscriber
+ this.resetMode()
+ },
+
+ updatePlan: function(e){
+ e && e.preventDefault()
+ this.tempSubscriber.plan_type = this.$("[name=planRadio]:checked").val()
+ this.updateQuantity()
+ },
+
+ sync: function(){
+ this.$gear.addClass("turning")
+ $.ajax({
+ url: this.syncAction,
+ type: "put",
+ data: { _csrf: $("[name=_csrf]").val() },
+ success: this.didLoad.bind(this)
+ })
+ },
+
+ update: function(data){
+ data['_csrf'] = $("[name=_csrf]").val()
+ this.$gear.addClass("turning")
+ $.ajax({
+ url: this.updateAction,
+ type: "put",
+ data: data,
+ success: function(data){
+ this.$gear.removeClass("turning")
+ }.bind(this)
+ })
+ },
+
+ destroy: function(e){
+ e.preventDefault()
+ var msg = "Are you sure you want to cancel your subscription?"
+ ConfirmModal.confirm(msg, function(){
+ $.ajax({
+ url: this.destroyAction,
+ type: "delete",
+ data: { _csrf: $("[name=_csrf]").val() },
+ success: function(data){
+ this.subscriber = null
+ this.didLoad(data)
+ }.bind(this)
+ })
+ }.bind(this),
+ function(){
+ this.show()
+ }.bind(this))
+ },
+
+})
diff --git a/public/assets/javascripts/ui/site/HomeView.js b/public/assets/javascripts/ui/site/HomeView.js
index a04bac1..20452bd 100644
--- a/public/assets/javascripts/ui/site/HomeView.js
+++ b/public/assets/javascripts/ui/site/HomeView.js
@@ -22,7 +22,7 @@ var HomeView = View.extend({
player.api('play')
})
- $('.videoModal .ion-ios7-close-empty').click( function(){
+ $('.videoModal .ion-ios-close-empty').click( function(){
player.api('pause')
hide()
})
diff --git a/public/assets/javascripts/ui/site/LayoutsIndex.js b/public/assets/javascripts/ui/site/LayoutsIndex.js
new file mode 100644
index 0000000..f7272bb
--- /dev/null
+++ b/public/assets/javascripts/ui/site/LayoutsIndex.js
@@ -0,0 +1,85 @@
+
+var LayoutsIndex = View.extend({
+
+ initialize: function(){
+ this.$templates = this.$(".templates")
+ this.$templatesList = this.$(".templates-list")
+ this.$noTemplates = this.$(".no-templates")
+ this.$form = this.$("form")
+
+ this.$userTemplatesList = this.$(".userTemplatesList")
+ this.$blueprintsList = this.$(".blueprintsList")
+ this.$newBlueprintButton = this.$("[data-role='create-new-blueprint']")
+ },
+
+ load: function(type){
+ this.$templates.children("span").remove()
+
+ $.get(this.action, this.populate.bind(this))
+ },
+
+ populate: function(data){
+ if (! data.layouts.length) {
+ this.$templates.hide()
+ this.$form.hide()
+ this.$noTemplates.show()
+ }
+ this.$templatesList.empty()
+ data.layouts.forEach(function(room){
+ var $span = $("<span>")
+ $span.data("slug", room.slug)
+
+ var $label = $("<label>")
+ $label.html( room.name )
+
+ var $image = $("<span>")
+ $image.addClass("image").css("background-image", "url(" + room.photo + ")")
+
+ $span.append( $image )
+ $span.append( $label )
+
+ this.$templatesList.append($span)
+ }.bind(this))
+ this.show()
+ }
+
+})
+
+
+
+var ProjectsModal = ModalView.extend(LayoutsIndex.prototype).extend({
+ el: ".mediaDrawer.projects",
+
+ action: "/api/project",
+
+ events: {
+ "click .templates span": 'toggleActive',
+ "submit form": 'newProject',
+ },
+
+ populate: function(data){
+ if (! data.length) {
+ app.router.newProject()
+ }
+ else {
+ this.__super__.populate.call(this, data)
+ }
+ },
+
+ toggleActive: function(e){
+ e.preventDefault()
+ this.$(".templates .active").removeClass("active")
+ var $layout = $(e.currentTarget)
+ $layout.addClass("active")
+
+ // actually do
+ window.location.pathname = "/project/" + $layout.data("slug") + "/edit"
+ },
+
+ newProject: function(e){
+ e && e.preventDefault()
+ window.location.pathname = "/project/new"
+ }
+
+})
+
diff --git a/public/assets/javascripts/ui/site/LayoutsModal.js b/public/assets/javascripts/ui/site/LayoutsModal.js
index 5974fc3..0222077 100644
--- a/public/assets/javascripts/ui/site/LayoutsModal.js
+++ b/public/assets/javascripts/ui/site/LayoutsModal.js
@@ -1,27 +1,52 @@
+var LayoutsModal = ModalView.extend(LayoutsIndex.prototype).extend({
+ el: ".mediaDrawer.layouts",
-var LayoutsIndex = View.extend({
+ action: "/api/layout",
- initialize: function(){
- this.$templates = this.$(".templates")
- this.$templatesList = this.$(".templates-list")
- this.$noTemplates = this.$(".no-templates")
- this.$form = this.$("form")
+ events: {
+ "click [data-role='create-new-layout']": 'createNewLayout',
+ "click [data-role='create-new-blueprint']": 'createNewBlueprint',
+ "click .templates span": 'pick',
},
+
+ pick: function(e){
+ e.preventDefault()
+ var layout = $(e.currentTarget).data("slug")
+ var isBlueprint = $(e.currentTarget).data("blueprint")
- load: function(type){
- this.$templates.children("span").remove()
+ if (! layout || ! layout.length) return
- $.get(this.action, this.populate.bind(this))
+ if (isBlueprint) {
+ window.location.pathname = "/blueprint/" + layout
+ }
+ else {
+ window.location.pathname = "/layout/" + layout
+ }
+ },
+
+ createNewLayout: function(e){
+ e.preventDefault()
+ window.location.pathname = "/layout/new"
+ },
+
+ createNewBlueprint: function(e){
+ e.preventDefault()
+ window.location.pathname = "/blueprint/new"
},
populate: function(data){
- if (! data.length) {
+/*
+ if (data.user.plan_level < 1 && data.projectCount == 1) {
+ // show lockout message
+ }
+*/
+ if (! data.layouts.length) {
this.$templates.hide()
this.$form.hide()
this.$noTemplates.show()
}
this.$templatesList.empty()
- data.forEach(function(room){
+ data.layouts.forEach(function(room){
var $span = $("<span>")
$span.data("slug", room.slug)
@@ -36,103 +61,46 @@ var LayoutsIndex = View.extend({
this.$templatesList.append($span)
}.bind(this))
- this.show()
- }
-
-})
-var ProjectsModal = ModalView.extend(LayoutsIndex.prototype).extend({
- el: ".mediaDrawer.projects",
-
- action: "/api/project",
-
- events: {
- "click .templates span": 'toggleActive',
- "submit form": 'newProject',
- },
-
- populate: function(data){
- if (! data.length) {
- app.router.newProject()
- }
- else {
- this.__super__.populate.call(this, data)
- }
- },
-
- toggleActive: function(e){
- e.preventDefault()
- this.$(".templates .active").removeClass("active")
- var $layout = $(e.currentTarget)
- $layout.addClass("active")
-
- // actually do
- window.location.pathname = "/project/" + $layout.data("slug") + "/edit"
- },
-
- newProject: function(e){
- e && e.preventDefault()
- window.location.pathname = "/project/new"
- }
-
-})
-
-
-var LayoutsModal = ModalView.extend(LayoutsIndex.prototype).extend({
- el: ".mediaDrawer.layouts",
-
- action: "/api/layout",
-
- events: {
- "click .templates span": 'toggleActive',
- "submit form": 'newLayout',
- },
-
- toggleActive: function(e){
- e.preventDefault()
- this.$(".templates .active").removeClass("active")
- var $layout = $(e.currentTarget)
- $layout.addClass("active")
+ data.user_layouts.forEach(function(room){
+ var $span = $("<span>")
+ $span.data("slug", room.slug)
+
+ var $label = $("<label>")
+ $label.html( room.name )
+
+ var $image = $("<span>")
+ $image.addClass("image").css("background-image", "url(" + room.photo + ")")
+
+ $span.append( $image )
+ $span.append( $label )
+
+ this.$templatesList.append($span)
+ }.bind(this))
+
+ data.blueprints.forEach(function(blueprint){
+ var $span = $("<span>")
+ $span.data("slug", blueprint.slug)
+ $span.data("blueprint", true)
+
+ var $label = $("<label>")
+ $label.html( blueprint.name )
+
+ var $image = $("<span>")
+ $image.addClass("image").css("background-image", "url(" + blueprint.url + ")")
+
+ $span.append( $image )
+ $span.append( $label )
+
+ this.$templatesList.append($span)
+ }.bind(this))
- // actually do
- window.location.pathname = "/layout/" + $layout.data("slug")
+ this.show()
},
-
+
newLayout: function(e){
e && e.preventDefault()
window.location.pathname = "/layout/new"
}
})
-
-
-var NewProjectModal = ModalView.extend(LayoutsIndex.prototype).extend({
- el: ".mediaDrawer.newProject",
-
- action: "/api/layout",
-
- events: {
- "click [data-role='create-new-layout']": 'createNewLayout',
- "click .templates span": 'choose',
- "submit form": 'choose',
- },
-
- toggleActive: function(e){
- e.preventDefault()
- this.$(".templates .active").removeClass("active")
- $(e.currentTarget).addClass("active")
- },
-
- choose: function(e){
- e && e.preventDefault()
-// var layout = this.$(".templates .active").data("slug")
- var layout = $(e.currentTarget).data("slug")
- if (! layout || ! layout.length) return
- window.location.pathname = "/project/new/" + layout
- },
-
- createNewLayout: function(){
- window.location.pathname = "/project/new/empty"
- },
-
-})
diff --git a/public/assets/javascripts/ui/site/NewProjectModal.js b/public/assets/javascripts/ui/site/NewProjectModal.js
new file mode 100644
index 0000000..31675ba
--- /dev/null
+++ b/public/assets/javascripts/ui/site/NewProjectModal.js
@@ -0,0 +1,118 @@
+var NewProjectModal = ModalView.extend(LayoutsIndex.prototype).extend({
+ el: ".mediaDrawer.newProject",
+
+ action: "/api/layout",
+
+ events: {
+ "click [data-role='create-new-layout']": 'createNewLayout',
+ "click [data-role='create-new-blueprint']": 'createNewBlueprint',
+ "click .templates span": 'pick',
+ },
+
+ toggleActive: function(e){
+ e.preventDefault()
+ this.$(".templates .active").removeClass("active")
+ $(e.currentTarget).addClass("active")
+ },
+
+ pick: function(e){
+ e && e.preventDefault()
+// var layout = this.$(".templates .active").data("slug")
+ var layout = $(e.currentTarget).data("slug")
+ var isBlueprint = $(e.currentTarget).data("blueprint")
+ if (! layout || ! layout.length) return
+
+ if (isBlueprint) {
+ window.location.pathname = "/project/blueprint/" + layout
+ }
+ else {
+ window.location.pathname = "/project/new/" + layout
+ }
+ },
+
+ createNewLayout: function(e){
+ e.preventDefault()
+ window.location.pathname = "/project/new/empty"
+ },
+
+ createNewBlueprint: function(e){
+ e.preventDefault()
+ window.location.pathname = "/blueprint/new"
+ },
+
+ populate: function(data){
+/*
+ if (data.user.plan_level < 1 && data.projectCount == 1) {
+ // show lockout message
+ this.$newBlueprintButton.hide()
+ }
+*/
+ if (! data.layouts.length) {
+ this.$templates.hide()
+ this.$form.hide()
+ this.$noTemplates.show()
+ }
+ if (! data.blueprints.length) {
+ this.$blueprintsList.parent().hide()
+ }
+ if (! data.user_layouts.length) {
+ this.$userTemplatesList.parent().hide()
+ }
+
+ this.$templatesList.empty()
+ data.layouts.forEach(function(room){
+ var $span = $("<span>")
+ $span.data("slug", room.slug)
+
+ var $label = $("<label>")
+ $label.html( room.name )
+
+ var $image = $("<span>")
+ $image.addClass("image").css("background-image", "url(" + room.photo + ")")
+
+ $span.append( $image )
+ $span.append( $label )
+
+ this.$templatesList.append($span)
+ }.bind(this))
+
+ data.user_layouts.forEach(function(room){
+ var $span = $("<span>")
+ $span.data("slug", room.slug)
+
+ var $label = $("<label>")
+ $label.html( room.name )
+
+ var $image = $("<span>")
+ $image.addClass("image").css("background-image", "url(" + room.photo + ")")
+
+ $span.append( $image )
+ $span.append( $label )
+
+ this.$templatesList.append($span)
+ }.bind(this))
+
+ data.blueprints.forEach(function(blueprint){
+ if (! blueprint.slug) { return }
+
+ var $span = $("<span>")
+ $span.data("blueprint", true)
+ $span.data("slug", blueprint.slug)
+
+ var $label = $("<label>")
+ $label.html( blueprint.name )
+
+ var $image = $("<span>")
+ $image.addClass("image").css("background-image", "url(" + blueprint.url + ")")
+
+ $span.append( $image )
+ $span.append( $label )
+
+ this.$templatesList.append($span)
+ }.bind(this))
+
+ this.show()
+ },
+
+
+}) \ No newline at end of file
diff --git a/public/assets/javascripts/ui/site/StaffView.js b/public/assets/javascripts/ui/site/StaffView.js
index 0398f71..97f86c2 100644
--- a/public/assets/javascripts/ui/site/StaffView.js
+++ b/public/assets/javascripts/ui/site/StaffView.js
@@ -4,11 +4,15 @@ var StaffView = View.extend({
events: {
"click #toggle-staff": "toggleStaff",
"click #toggle-featured": "toggleFeatured",
+ "click #toggle-stock": "toggleStock",
+ "click #toggle-artist": "toggleArtist",
},
initialize: function() {
this.$toggleStaff = $("#toggle-staff")
this.$toggleFeatured = $("#toggle-featured")
+ this.$toggleStock = $("#toggle-stock")
+ this.$toggleArtist = $("#toggle-artist")
this.$mediaEmbed = $("#media-embed")
if (this.$toggleStaff.length && this.$toggleStaff.data().isstaff) {
this.$toggleStaff.html("Is Staff")
@@ -16,6 +20,12 @@ var StaffView = View.extend({
if (this.$toggleFeatured.length && this.$toggleFeatured.data().featured) {
this.$toggleFeatured.html("Featured Project")
}
+ if (this.$toggleStock.length && this.$toggleStock.data().stock) {
+ this.$toggleStock.html("Layout is Stock")
+ }
+ if (this.$toggleArtist.length && this.$toggleArtist.data().isartist) {
+ this.$toggleArtist.html("Is Artist")
+ }
if (this.$mediaEmbed.length) {
var media = this.$mediaEmbed.data()
this.$mediaEmbed.html( Parser.tag( media ) )
@@ -67,6 +77,41 @@ var StaffView = View.extend({
$("#isFeaturedProject").html(data.state ? "yes" : "no")
}.bind(this)
})
- },
+ },
+
+ toggleStock: function(){
+ var state = ! this.$toggleStock.data().stock
+ $.ajax({
+ type: "put",
+ dataType: "json",
+ url: window.location.href + "/stock",
+ data: {
+ state: state,
+ _csrf: $("#_csrf").val(),
+ },
+ success: function(data){
+ this.$toggleStock.data("stock", data.state)
+ this.$toggleStock.html(data.state ? "Stock Layout" : "Make this layout Stock")
+ $("#isStockLayout").html(data.state ? "yes" : "no")
+ }.bind(this)
+ })
+ },
+ toggleArtist: function(){
+ var state = ! this.$toggleArtist.data().isartist
+ $.ajax({
+ type: "put",
+ dataType: "json",
+ url: window.location.href + "/artist",
+ data: {
+ state: state,
+ _csrf: $("#_csrf").val(),
+ },
+ success: function(data){
+ this.$toggleArtist.data("stock", data.state)
+ this.$toggleArtist.html(data.state ? "Is Artist" : "Make Artist")
+ $("#isArtist").html(data.state ? "yes" : "no")
+ }.bind(this)
+ })
+ },
})
diff --git a/public/assets/javascripts/util.js b/public/assets/javascripts/util.js
index 2cfe0de..0f5c6ed 100644
--- a/public/assets/javascripts/util.js
+++ b/public/assets/javascripts/util.js
@@ -6,6 +6,7 @@ if (window.$) {
$.fn.disable = function() { return $(this).attr("disabled","disabled") }
$.fn.sanitize = function(s) { return trim(sanitize($(this).val())) }
$.fn.htmlSafe = function(s) { return $(this).html(sanitize(s)) }
+ $.fn.toDollars = function(i) { return $(this).html((i/100).toFixed(2)) }
}
function trim (s){ return s.replace(/^\s+/,"").replace(/\s+$/,"") }
diff --git a/public/assets/javascripts/vendor/polyfill.js b/public/assets/javascripts/vendor/polyfill.js
index 499cbc5..2139618 100644
--- a/public/assets/javascripts/vendor/polyfill.js
+++ b/public/assets/javascripts/vendor/polyfill.js
@@ -48,7 +48,7 @@ function has3d(){
} else if ( browser.webkit ) {
browser.safari = true;
}
- $.browser = browser;
+ if (window.$) $.browser = browser;
return browser;
})( navigator.userAgent );
@@ -58,6 +58,7 @@ var is_ipad = (navigator.userAgent.match(/iPad/i))
var is_android = (navigator.userAgent.match(/Android/i))
var is_mobile = is_iphone || is_ipad || is_android
var is_desktop = ! is_mobile;
+var app_devicePixelRatio = is_mobile ? devicePixelRatio : 1;
// rAF shim