diff options
Diffstat (limited to 'public/assets/javascripts/rectangles/engine/sculpture')
5 files changed, 638 insertions, 0 deletions
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 +}) |
