diff --git a/src/components/heightfield.js b/src/components/heightfield.js new file mode 100644 index 0000000000000000000000000000000000000000..b1ed8b46bee36c243544476a7f59fb33b575a5fb --- /dev/null +++ b/src/components/heightfield.js @@ -0,0 +1,67 @@ +/* global CANNON */ +AFRAME.registerComponent("heightfield", { + init() { + this.el.addEventListener("componentinitialized", e => { + if (e.detail.name === "static-body") { + this.generateAndAddHeightfield(this.el.components["static-body"]); + } + }); + this.el.setAttribute("static-body", { shape: "none", mass: 0 }); + }, + generateAndAddHeightfield(body) { + const mesh = this.el.object3D.getObjectByProperty("type", "Mesh"); + mesh.geometry.computeBoundingBox(); + const size = new THREE.Vector3(); + mesh.geometry.boundingBox.getSize(size); + + const minDistance = 0.25; + const resolution = (size.x + size.z) / 2 / minDistance; + const distance = Math.max(minDistance, (size.x + size.z) / 2 / resolution); + + const data = []; + const down = new THREE.Vector3(0, -1, 0); + const position = new THREE.Vector3(); + const raycaster = new THREE.Raycaster(); + const intersections = []; + const meshPos = new THREE.Vector3(); + mesh.getWorldPosition(meshPos); + const offsetX = -size.x / 2 + meshPos.x; + const offsetZ = -size.z / 2 + meshPos.z; + let min = Infinity; + for (let z = 0; z < resolution; z++) { + data[z] = []; + for (let x = 0; x < resolution; x++) { + position.set(offsetX + x * distance, size.y / 2, offsetZ + z * distance); + raycaster.set(position, down); + intersections.length = 0; + raycaster.intersectObject(mesh, false, intersections); + let val; + if (intersections.length) { + val = -intersections[0].distance + size.y / 2; + } else { + val = -size.y / 2; + } + data[z][x] = val; + if (val < min) { + min = data[z][x]; + } + } + } + // Cannon doesn't like heightfields with negative heights. + for (let z = 0; z < resolution; z++) { + for (let x = 0; x < resolution; x++) { + data[z][x] -= min; + } + } + + const orientation = new CANNON.Quaternion(); + orientation.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); + const rotation = new CANNON.Quaternion(); + rotation.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), -Math.PI / 2); + rotation.mult(orientation, orientation); + const offset = new CANNON.Vec3(-size.x / 2, min, -size.z / 2); + + const shape = new CANNON.Heightfield(data, { elementSize: distance }); + body.addShape(shape, offset, orientation); + } +}); diff --git a/src/components/skybox.js b/src/components/skybox.js index e5f26b12fe93d006fa15920378223dc1d566243b..08566b88c0388b99d1d4de4db8c79ea9f85e1863 100644 --- a/src/components/skybox.js +++ b/src/components/skybox.js @@ -271,6 +271,8 @@ AFRAME.registerComponent("skybox", { const z = distance * Math.sin(phi) * Math.cos(theta); uniforms.sunPosition.value.set(x, y, z).normalize(); + + this.sky.scale.set(distance, distance, distance); } }, diff --git a/src/gltf-component-mappings.js b/src/gltf-component-mappings.js index f39748d390bbbd0ab7fe0b17ce45a15bd00125f3..73c7d628ee70e5b1182f6a035a0aeef701488221 100644 --- a/src/gltf-component-mappings.js +++ b/src/gltf-component-mappings.js @@ -5,6 +5,9 @@ AFRAME.GLTFModelPlus.registerComponent("quack", "quack"); AFRAME.GLTFModelPlus.registerComponent("sound", "sound"); AFRAME.GLTFModelPlus.registerComponent("collision-filter", "collision-filter"); AFRAME.GLTFModelPlus.registerComponent("css-class", "css-class"); +AFRAME.GLTFModelPlus.registerComponent("interactable", "css-class", (el, componentName) => { + el.setAttribute(componentName, "interactable"); +}); AFRAME.GLTFModelPlus.registerComponent("scene-shadow", "scene-shadow"); AFRAME.GLTFModelPlus.registerComponent("super-spawner", "super-spawner"); AFRAME.GLTFModelPlus.registerComponent("gltf-model-plus", "gltf-model-plus"); @@ -25,6 +28,7 @@ AFRAME.GLTFModelPlus.registerComponent("scale-audio-feedback", "scale-audio-feed AFRAME.GLTFModelPlus.registerComponent("animation-mixer", "animation-mixer"); AFRAME.GLTFModelPlus.registerComponent("loop-animation", "loop-animation"); AFRAME.GLTFModelPlus.registerComponent("shape", "shape"); +AFRAME.GLTFModelPlus.registerComponent("heightfield", "heightfield"); AFRAME.GLTFModelPlus.registerComponent( "box-collider", "shape", @@ -44,7 +48,13 @@ AFRAME.GLTFModelPlus.registerComponent( }; })() ); -AFRAME.GLTFModelPlus.registerComponent("visible", "visible"); +AFRAME.GLTFModelPlus.registerComponent("visible", "visible", (el, componentName, componentData) => { + if (typeof componentData === "object") { + el.setAttribute(componentName, componentData.visible); + } else { + el.setAttribute(componentName, componentData); + } +}); AFRAME.GLTFModelPlus.registerComponent("spawn-point", "spawn-point"); AFRAME.GLTFModelPlus.registerComponent("hoverable", "hoverable"); AFRAME.GLTFModelPlus.registerComponent("sticky-zone", "sticky-zone"); @@ -58,4 +68,7 @@ AFRAME.GLTFModelPlus.registerComponent("nav-mesh", "nav-mesh", (el, _componentNa nav.loadMesh(node, zone); } }); + // There isn't actually an a-frame nav-mesh component, but we want to tag this el as a nav-mesh since + // nav-mesh-helper will query for it later. + el.setAttribute("nav-mesh"); }); diff --git a/src/hub.js b/src/hub.js index 6bc7382c84b8ec3cf3af71914a592ca853f76d1b..40c4f178e94b448c379d2d5136a01ea2b141ac5d 100644 --- a/src/hub.js +++ b/src/hub.js @@ -133,6 +133,7 @@ import "./components/cardboard-controls"; import "./components/cursor-controller"; +import "./components/heightfield"; import "./components/nav-mesh-helper"; import "./systems/tunnel-effect";