diff --git a/doc/image_orientations.gif b/doc/image_orientations.gif new file mode 100755 index 0000000000000000000000000000000000000000..89c4c3ea1444de6b32e1cecc2d2825e430c833e8 Binary files /dev/null and b/doc/image_orientations.gif differ diff --git a/src/components/offset-relative-to.js b/src/components/offset-relative-to.js index e00acd4e57a1842e5b8116c5448a3c2088443eab..877cfcf8a118a13eb94200925d1ac53053d9faa1 100644 --- a/src/components/offset-relative-to.js +++ b/src/components/offset-relative-to.js @@ -13,6 +13,9 @@ AFRAME.registerComponent("offset-relative-to", { on: { type: "string" }, + orientation: { + default: 1 // see doc/image_orientations.gif + }, selfDestruct: { default: false } @@ -27,6 +30,9 @@ AFRAME.registerComponent("offset-relative-to", { }, updateOffset: (function() { + const y = new THREE.Vector3(0, 1, 0); + const z = new THREE.Vector3(0, 0, -1); + const QUARTER_CIRCLE = Math.PI / 2; const offsetVector = new THREE.Vector3(); return function() { const obj = this.el.object3D; @@ -40,6 +46,38 @@ AFRAME.registerComponent("offset-relative-to", { this.el.body && this.el.body.position.copy(obj.position); target.getWorldQuaternion(obj.quaternion); this.el.body && this.el.body.quaternion.copy(obj.quaternion); + + // See doc/image_orientations.gif + switch (this.data.orientation) { + case 8: + obj.rotateOnAxis(z, 3 * QUARTER_CIRCLE); + break; + case 7: + obj.rotateOnAxis(z, 3 * QUARTER_CIRCLE); + obj.rotateOnAxis(y, 2 * QUARTER_CIRCLE); + break; + case 6: + obj.rotateOnAxis(z, QUARTER_CIRCLE); + break; + case 5: + obj.rotateOnAxis(z, QUARTER_CIRCLE); + obj.rotateOnAxis(y, 2 * QUARTER_CIRCLE); + break; + case 4: + obj.rotateOnAxis(z, 2 * QUARTER_CIRCLE); + obj.rotateOnAxis(y, 2 * QUARTER_CIRCLE); + break; + case 3: + obj.rotateOnAxis(z, 2 * QUARTER_CIRCLE); + break; + case 2: + obj.rotateOnAxis(y, 2 * QUARTER_CIRCLE); + break; + case 1: + default: + break; + } + if (this.data.selfDestruct) { if (this.data.on) { this.el.sceneEl.removeEventListener(this.data.on, this.updateOffset); diff --git a/src/components/super-spawner.js b/src/components/super-spawner.js index f760a29e25cc1fb680b55a0b504a42202948f0da..0c69e82da5cfe8c62e4aabafd2506fcf11613e3d 100644 --- a/src/components/super-spawner.js +++ b/src/components/super-spawner.js @@ -84,7 +84,7 @@ AFRAME.registerComponent("super-spawner", { const thisGrabId = nextGrabId++; this.heldEntities.set(hand, thisGrabId); - const entity = addMedia(this.data.src, ObjectContentOrigins.SPAWNER); + const entity = addMedia(this.data.src, ObjectContentOrigins.SPAWNER).entity; entity.object3D.position.copy( this.data.useCustomSpawnPosition ? this.data.spawnPosition : this.el.object3D.position ); diff --git a/src/hub.js b/src/hub.js index 8db01eaddef8c92fa5dd4a8da040fa2473615b56..5e347527cfd99e822cb0d6adf6c9cb3d0c7bca77 100644 --- a/src/hub.js +++ b/src/hub.js @@ -306,11 +306,14 @@ const onReady = async () => { const offset = { x: 0, y: 0, z: -1.5 }; const spawnMediaInfrontOfPlayer = (src, contentOrigin) => { - const entity = addMedia(src, contentOrigin, true); + const { entity, orientation } = addMedia(src, contentOrigin, true); - entity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset + orientation.then(or => { + entity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset, + orientation: or + }); }); }; diff --git a/src/react-components/2d-hud.js b/src/react-components/2d-hud.js index effa07f2983ee8752e01c0f2a6892d0e99a6bbb0..e50d295987189b5eb00827d6333e6312fa156acd 100644 --- a/src/react-components/2d-hud.js +++ b/src/react-components/2d-hud.js @@ -75,6 +75,7 @@ const BottomHUD = ({ onCreateObject, showImageOnlyButton, onMediaPicked }) => ( BottomHUD.propTypes = { onCreateObject: PropTypes.func, + showImageOnlyButton: PropTypes.bool, onMediaPicked: PropTypes.func }; diff --git a/src/utils/media-utils.js b/src/utils/media-utils.js index 83a5f788ea841c1f3343c2068293b97bf9f53f3e..825865dcac289bc2afa1f8f3ed878b485e682ee1 100644 --- a/src/utils/media-utils.js +++ b/src/utils/media-utils.js @@ -51,6 +51,45 @@ export const upload = file => { }).then(r => r.json()); }; +// https://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side/32490603#32490603 +function getOrientation(file, callback) { + const reader = new FileReader(); + reader.onload = function(e) { + const view = new DataView(e.target.result); + if (view.getUint16(0, false) != 0xffd8) { + return callback(-2); + } + const length = view.byteLength; + let offset = 2; + while (offset < length) { + if (view.getUint16(offset + 2, false) <= 8) return callback(-1); + const marker = view.getUint16(offset, false); + offset += 2; + if (marker == 0xffe1) { + if (view.getUint32((offset += 2), false) != 0x45786966) { + return callback(-1); + } + + const little = view.getUint16((offset += 6), false) == 0x4949; + offset += view.getUint32(offset + 4, little); + const tags = view.getUint16(offset, little); + offset += 2; + for (let i = 0; i < tags; i++) { + if (view.getUint16(offset + i * 12, little) == 0x0112) { + return callback(view.getUint16(offset + i * 12 + 8, little)); + } + } + } else if ((marker & 0xff00) != 0xff00) { + break; + } else { + offset += view.getUint16(offset, false); + } + } + return callback(-1); + }; + reader.readAsArrayBuffer(file); +} + let interactableId = 0; export const addMedia = (src, contentOrigin, resize = false) => { const scene = AFRAME.scenes[0]; @@ -61,6 +100,15 @@ export const addMedia = (src, contentOrigin, resize = false) => { entity.setAttribute("media-loader", { resize, src: typeof src === "string" ? src : "" }); scene.appendChild(entity); + const orientation = new Promise(function(resolve) { + if (src instanceof File) { + getOrientation(src, x => { + resolve(x); + }); + } else { + resolve(1); + } + }); if (src instanceof File) { upload(src) .then(response => { @@ -78,5 +126,5 @@ export const addMedia = (src, contentOrigin, resize = false) => { scene.emit("object_spawned", { objectType }); }); - return entity; + return { entity, orientation }; };