是否可以在 A 帧场景中使用 HTML 图像映射?

Is it possible to use HTML image maps within an A-Frame scene?

场景如下:

我有一张世界地图的 360º 全景图,运行 在 A 帧上

我希望每个大陆都可以点击,重定向到相关的维基百科页面。

我试过的:

我的问题:

我的目标有可能实现吗?如果可以,怎么做?

我的代码:

注:目前只有非洲大陆有坐标。

JSFiddle

<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>

<a-scene>
  <a-entity camera look-controls="reverseMouseDrag: true"></a-entity>
  <a-sky id="image-360" radius="10" src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Equirectangular_projection_SW.jpg/1920px-Equirectangular_projection_SW.jpg" usemap="#image_map"></a-sky>
  
  <map name="image_map">
    <area alt="Africa" title="Africa" href="https://en.wikipedia.org/wiki/Africa" coords="920,296 871,351 873,424 916,461 979,449 1014,463 1005,490 1032,540 1020,579 1056,663 1081,667 1114,655 1146,600 1175,561 1165,504 1220,451 1228,422 1191,426 1124,314 1063,308 1054,324 1014,302 1016,284 963,290" shape="polygon">
  </map>
  
</a-scene>

<map> 标签将区域放置在图像上方。它不会像那样将区域投影到三维对象上。

我建议您 re-use 在 a-frame 自定义组件中的 <map> 标记中定义的区域。

tldr:

我制作的 a component 似乎可以完成这项工作,而且使用起来相当简单:

<a-image material="src: #texture" asourcemap="#some-map"></a-image>
<!-- somewhere else -->
<map id="some-map">
   <area shape="rect" alt="rectangle" coords="0,0, 50,50" href="rect.html">
   <area shape="polygon" alt="poly" coords="0,0, 40,50, 25,0">
</map>

它应该与 href 属性配合使用,但 <a-image> 也会发出带有点击区域名称的信号。

您可以看到它使用 3D 平面和圆柱体 here

tldr 结束

0。收集 <map> 数据

简单解析。获取 <map> 元素,遍历所有子项,并使用 getAttribute():

收集他们的数据
var map = document.querySelector(selector);
for (let area of map.children) {
   // area.getAttribute("href") - href attribute
   // area.getAttribute("alt") - alt name
   // area.getAttribute("coords") - coordinates array. 
} 

保存起来以备后用。坐标是逗号分隔的字符串,因此您可能需要 parseInt() 它们,管理顺序(即 [[x1,x1], [x2,y2], [x3, y3]]

1.使 a-frame 实体可交互

对点击做出反应,更重要的是 - 检查点击发生的位置:

this.el.addEventListener("click", evt => {
   var UVPoint = evt.detail.intersection.uv
})

UV mapping 将帮助我们确定单击了纹理上的哪个点。 UV 范围从 <0, 1>,所以我们需要 re-scale UVPoint:

// may need waiting for "model-loaded"
let mesh = this.el.getObject3D("mesh")
// this may not be available immidiately
let image = mesh.material.map.image
let x_on_image = UVPoint * image.width
// the y axis goes from <1, 0>
let y_on_image = image.height - UVPoint * image.heigth  

嘿,我们得到了区域坐标和点坐标! 只剩下一件事了:

2。确定某个区域是否被点击

不需要re-invent这里的轮子。 This SO question on checking if a point is inside a polygon 有一个简单的 inside(point, polygon) 函数。其实我们什么都有,所以我们做的最后一件事是:

  • 遍历多边形
  • 检查点击点是否在任何多边形内
  • 如果肯定 - 做你的事

像这样:

var point = [x_on_texture, y_on_texture]
for (var i = 0; i < polygons.length; i++) {
   // polygons need to be [[x1, y1], [x2, y2],...[xn, yn]] here
   if (inside(point, polygons[i]) {
      console.log("polygon", i, "clicked!")
   }
}

如果您跳过了 tldr 部分 - 上述步骤合并在 this component and used in this example

3。老掉牙的尝试

另一种方法可以是:

  • 收到对 a-frame 实体的点击
  • 1
  • 一样抓取点击的坐标
  • 隐藏场景
  • 查看哪个<area>document.elementFromPoint(x, y);的坐标处。
  • 展示现场
  • 使用 document.createEvent("MouseEvent");
  • 创建鼠标事件
  • <area> 元素上发送它。

即使在我的手机上,隐藏/显示技巧也非常有效 phone。我真的很惊讶场景没有闪烁,没有冻结,甚至没有减速。

但是 document.elementFromPoint(x, y); 不能与 firefox 一起工作,并且可能任何让它工作的尝试都比 0-[=84= 更耗时]2 步。我也相信陷阱会变得更大 case-dependant.

无论如何,这是 old-answer 组件:

/* SETUP
<a-scene>
 <a-image press-map>
</a-scene>

<image id="image" sourcemap="map">
<map name="map">
  <area ...>
</map>
*/

AFRAME.registerComponent("press-map", {
  init: function() {
    // the underlying image
    this.img = document.querySelector("#image")
    // react on clicks
    this.el.addEventListener("click", evt => {
      // get the point on the UV
      let uvPoint = evt.detail.intersection.uv 
      // the y is inverted
      let pointOnImage = {
        x: uvPoint.x * this.img.width,
        y: this.img.height - uvPoint.y * this.img.height
      }
      
      // the ugly show-hide bits
      this.el.sceneEl.style.display = "none";
      this.img.style.display = "block";
      // !! grab the <area> at the (x,y) position
      var el = document.elementFromPoint(pointOnImage.x, pointOnImage.y);
      this.el.sceneEl.style.display="block"
      this.img.style.display="none"

      // create and dispatch the event
      var ev = document.createEvent("MouseEvent");
      ev.initMouseEvent(
            "click",
            true /* bubble */, false /* cancelable */,
            window, null,
            x, y, 0, 0, /* coordinates */
            false, false, false, false, /* modifier keys */
            0 /*left*/, null
      );
      el.dispatchEvent(ev);
    }
  }
})