diff options
| author | Jules Laplace <jules@okfoc.us> | 2016-10-28 18:07:56 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2016-10-28 18:07:56 -0400 |
| commit | a9c9d6adf470d0966e6c6bef0803e298fd2d4117 (patch) | |
| tree | 6ccec2a448992a5f43226532051a6df09afbc203 /public/assets/javascripts | |
| parent | 343b0b3dc5bb7dbe762182a486e63a4aff6ef8fc (diff) | |
| parent | 9e7bacd46c1e5d0e1c24433690d421ab3f3a11f2 (diff) | |
merge
Diffstat (limited to 'public/assets/javascripts')
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 |
