From 9ab12a798ff6681f40ad067a7f7f495236128695 Mon Sep 17 00:00:00 2001
From: netpro2k <netpro2k@gmail.com>
Date: Tue, 5 Jun 2018 20:30:46 -0700
Subject: [PATCH] First pass at basic image interactables

---
 src/components/image-plus.js | 92 ++++++++++++++++++++++++++++++++++++
 src/hub.html                 | 16 +++++++
 src/hub.js                   | 14 ++++++
 src/network-schemas.js       |  5 ++
 4 files changed, 127 insertions(+)
 create mode 100644 src/components/image-plus.js

diff --git a/src/components/image-plus.js b/src/components/image-plus.js
new file mode 100644
index 000000000..f23be48bb
--- /dev/null
+++ b/src/components/image-plus.js
@@ -0,0 +1,92 @@
+AFRAME.registerComponent("image-plus", {
+  dependencies: ["geometry", "material"],
+
+  schema: {
+    src: { type: "string" },
+
+    freezeSpeedLimit: { default: 1 },
+
+    initialOffset: { default: { x: 0, y: 0, z: -1.5 } },
+    reorientOnGrab: { default: false }
+  },
+
+  _fit(w, h) {
+    const ratio = (h || 1.0) / (w || 1.0);
+    const geo = this.el.geometry;
+    let width, height;
+    if (geo && geo.width) {
+      if (geo.height && ratio > 1) {
+        width = geo.width / ratio;
+      } else {
+        height = geo.height * ratio;
+      }
+    } else if (geo && geo.height) {
+      width = geo.width / ratio;
+    } else {
+      width = Math.min(1.0, 1.0 / ratio);
+      height = Math.min(1.0, ratio);
+    }
+    this.el.setAttribute("geometry", { width, height });
+    this.el.setAttribute("shape", {
+      halfExtents: {
+        x: width / 2,
+        y: height / 2,
+        z: 0.05
+      }
+    });
+  },
+
+  _onMaterialLoaded(e) {
+    const src = e.detail.src;
+    const w = src.videoWidth || src.width;
+    const h = src.videoHeight || src.height;
+    if (w || h) {
+      this._fit(w, h);
+    }
+  },
+
+  _sleepIfStill(e) {
+    if (
+      e.target === this.el &&
+      this.el.body.velocity.lengthSquared() < this.data.freezeSpeedLimit * this.data.freezeSpeedLimit
+    ) {
+      this.el.body.sleep();
+    }
+  },
+
+  _onGrab: (function() {
+    const q = new THREE.Quaternion();
+    return function() {
+      this.el.body.wakeUp();
+      if (this.data.reorientOnGrab) {
+        this.billboardTarget.getWorldQuaternion(q);
+        this.el.body.quaternion.copy(q);
+      }
+    };
+  })(),
+
+  init() {
+    this._onMaterialLoaded = this._onMaterialLoaded.bind(this);
+    this._sleepIfStill = this._sleepIfStill.bind(this);
+    this._onGrab = this._onGrab.bind(this);
+
+    this.billboardTarget = document.querySelector("#player-camera").object3D;
+
+    const el = this.el;
+    el.addEventListener("materialtextureloaded", this._onMaterialLoaded);
+    el.addEventListener("materialvideoloadeddata", this._onMaterialLoaded);
+
+    el.addEventListener("grab-start", this._onGrab);
+    el.addEventListener("grab-end", this._sleepIfStill);
+    el.addEventListener("body-loaded", this._sleepIfStill);
+
+    const worldPos = new THREE.Vector3().copy(this.data.initialOffset);
+    this.billboardTarget.localToWorld(worldPos);
+    this.el.object3D.position.copy(worldPos);
+    this.billboardTarget.getWorldQuaternion(this.el.object3D.quaternion);
+  },
+
+  update() {
+    this.el.setAttribute("material", "src", this.data.src);
+  }
+});
diff --git a/src/hub.html b/src/hub.html
index a7f615b37..d24077252 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -182,6 +182,21 @@
                 ></a-entity>
             </template>
 
+            <template id="interactable-image">
+                <a-entity
+                    class="interactable"
+                    super-networked-interactable="counter: #media-counter; mass: 1;"
+                    body="type: dynamic; shape: none; mass: 1;"
+                    grabbable="uusePhysics: never;"
+                    stretchable="useWorldPosition: true;"
+                    hoverable
+                    geometry="primitive: plane"
+                    material="side: double;"
+                    image-plus
+                >
+                </a-entity>
+            </template>
+
             <a-mixin id="super-hands"
                 super-hands="
                     colliderEvent: collisions; colliderEventProperty: els;
@@ -196,6 +211,7 @@
 
         <!-- Interactables -->
         <a-entity id="counter" networked-counter="max: 3; ttl: 120"></a-entity>
+        <a-entity id="media-counter" networked-counter="max: 3; ttl: 120"></a-entity>
 
         <a-entity
             id="cursor-controller"
diff --git a/src/hub.js b/src/hub.js
index a9f63cc3c..a62c2f650 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -63,6 +63,7 @@ import "./components/networked-avatar";
 import "./components/css-class";
 import "./components/scene-shadow";
 import "./components/avatar-replay";
+import "./components/image-plus";
 
 import ReactDOM from "react-dom";
 import React from "react";
@@ -292,6 +293,19 @@ const onReady = async () => {
       NAF.connection.entities.completeSync(ev.detail.clientId);
     });
 
+    document.addEventListener("paste", e => {
+      const scene = AFRAME.scenes[0];
+      const imgUrl = e.clipboardData.getData("text");
+      console.log("Pasted: ", imgUrl);
+
+      const image = document.createElement("a-entity");
+      image.id = "interactable-image-" + Date.now();
+      image.setAttribute("position", { x: 0, y: 2, z: 1 });
+      image.setAttribute("image-plus", "src", imgUrl);
+      image.setAttribute("networked", { template: "#interactable-image" });
+      scene.appendChild(image);
+    });
+
     if (!qsTruthy("offline")) {
       document.body.addEventListener("connected", () => {
         if (!isBotMode) {
diff --git a/src/network-schemas.js b/src/network-schemas.js
index 9e20a17ca..4ae8e1839 100644
--- a/src/network-schemas.js
+++ b/src/network-schemas.js
@@ -90,6 +90,11 @@ function registerNetworkSchemas() {
       "scale"
     ]
   });
+
+  NAF.schemas.add({
+    template: "#interactable-image",
+    components: ["position", "rotation", "image-plus"]
+  });
 }
 
 export default registerNetworkSchemas;
-- 
GitLab