diff --git a/src/components/hover-visuals.js b/src/components/hover-visuals.js index 775f51dcab7b986e8446a2673caf3f06695fa905..0e34b061ed514baa8359ca5467f4f4a4b98c3da8 100644 --- a/src/components/hover-visuals.js +++ b/src/components/hover-visuals.js @@ -4,6 +4,10 @@ * @component hover-visuals */ AFRAME.registerComponent("hover-visuals", { + schema: { + hand: { type: "string" }, + controller: { type: "selector" } + }, init: function() { // uniforms are set from the component responsible for loading the mesh. this.uniforms = null; @@ -12,15 +16,20 @@ AFRAME.registerComponent("hover-visuals", { remove() { this.interactorTransform = null; }, - tick(time) { + tick() { if (!this.uniforms) return; this.el.object3D.matrixWorld.toArray(this.interactorTransform); + const hovering = this.data.controller.components["super-hands"].state.has("hover-start"); for (const uniform of this.uniforms) { - uniform.hubs_HighlightInteractorOne.value = !!this.el.components["super-hands"].state.has("hover-start"); - uniform.hubs_InteractorOneTransform.value = this.interactorTransform; - uniform.hubs_Time.value = time; + if (this.data.hand === "left") { + uniform.hubs_HighlightInteractorOne.value = hovering; + uniform.hubs_InteractorOneTransform.value = this.interactorTransform; + } else { + uniform.hubs_HighlightInteractorTwo.value = hovering; + uniform.hubs_InteractorTwoTransform.value = this.interactorTransform; + } } } }); diff --git a/src/components/hoverable-visuals.js b/src/components/hoverable-visuals.js index 0728c95bd1825dbaf1dfd23b345c3c5ddf6d9a38..9f70e7b92d59e4350c2b18f8bc273c25c7a01108 100644 --- a/src/components/hoverable-visuals.js +++ b/src/components/hoverable-visuals.js @@ -8,12 +8,17 @@ AFRAME.registerComponent("hoverable-visuals", { cursorController: { type: "selector" } }, init: function() { - // uniforms are set from the component responsible for loading the mesh. + // uniforms and boundingSphere are set from the component responsible for loading the mesh. this.uniforms = null; + this.boundingSphere = new THREE.Sphere(); + this.interactorOneTransform = []; this.interactorTwoTransform = []; + this.sweepParams = []; }, remove() { + this.uniforms = null; + this.boundingBox = null; this.interactorOneTransform = null; this.interactorTwoTransform = null; }, @@ -42,7 +47,16 @@ AFRAME.registerComponent("hoverable-visuals", { interactorTwo.matrixWorld.toArray(this.interactorTwoTransform); } + if (interactorOne || interactorTwo) { + const worldY = this.el.object3D.matrixWorld.elements[13]; + const scaledRadius = this.el.object3D.scale.y * this.boundingSphere.radius; + this.sweepParams[0] = worldY - scaledRadius; + this.sweepParams[1] = worldY + scaledRadius; + } + for (const uniform of this.uniforms) { + uniform.hubs_EnableSweepingEffect.value = true; + uniform.hubs_SweepParams.value = this.sweepParams; uniform.hubs_HighlightInteractorOne.value = !!interactorOne; uniform.hubs_InteractorOneTransform.value = this.interactorOneTransform; uniform.hubs_HighlightInteractorTwo.value = !!interactorTwo; diff --git a/src/components/media-loader.js b/src/components/media-loader.js index aac0915769903d96776e49f8a0ab2dd67721f48a..6545ca9f7cdcdf09309c17cee6df4683a4d83be2 100644 --- a/src/components/media-loader.js +++ b/src/components/media-loader.js @@ -19,6 +19,8 @@ const fetchMaxContentIndex = url => { return fetch(url).then(r => parseInt(r.headers.get("x-max-content-index"))); }; +const boundingBox = new THREE.Box3(); + AFRAME.registerComponent("media-loader", { schema: { src: { type: "string" }, @@ -102,13 +104,19 @@ AFRAME.registerComponent("media-loader", { delete this.showLoaderTimeout; }, - onMediaLoaded() { - this.clearLoadingTimeout(); - if (this.el.components["hoverable-visuals"]) { - this.el.components["hoverable-visuals"].uniforms = injectCustomShaderChunks(this.el.object3D); + setupHoverableVisuals() { + const hoverableVisuals = this.el.components["hoverable-visuals"]; + if (hoverableVisuals) { + hoverableVisuals.uniforms = injectCustomShaderChunks(this.el.object3D); + boundingBox.setFromObject(this.el.object3DMap.mesh); + boundingBox.getBoundingSphere(hoverableVisuals.boundingSphere); } }, + onMediaLoaded() { + this.setupHoverableVisuals(); + }, + async update(oldData) { try { const { src } = this.data; @@ -174,9 +182,10 @@ AFRAME.registerComponent("media-loader", { this.el.addEventListener( "model-loaded", () => { - this.onMediaLoaded(); + this.clearLoadingTimeout(); this.hasBakedShapes = !!(this.el.body && this.el.body.shapes.length > (this.shapeAdded ? 1 : 0)); this.setShapeAndScale(this.data.resize); + this.setupHoverableVisuals(); addAnimationComponents(this.el); }, { once: true } diff --git a/src/components/player-info.js b/src/components/player-info.js index 612386b4182e22f98356c2f00894d90feecab9c1..7c5cbd89e1f97cd0a61cda24187d24a01858284e 100644 --- a/src/components/player-info.js +++ b/src/components/player-info.js @@ -35,8 +35,9 @@ AFRAME.registerComponent("player-info", { modelEl.setAttribute("gltf-model-plus", "src", this.data.avatarSrc); } + const uniforms = injectCustomShaderChunks(this.el.object3D); this.el.querySelectorAll("[hover-visuals]").forEach(el => { - el.components["hover-visuals"].uniforms = injectCustomShaderChunks(this.el.object3D); + el.components["hover-visuals"].uniforms = uniforms; }); } }); diff --git a/src/hub.html b/src/hub.html index 462512136f0cc2dc7c5d1d2755b91221cb6dbfe3..b3935f397b6d137f6f879796dd8ad8f3ffa6b438 100644 --- a/src/hub.html +++ b/src/hub.html @@ -377,7 +377,6 @@ missOpacity: 0.1; curveShootingSpeed: 12;" haptic-feedback - hover-visuals body="type: static; shape: none;" mixin="controller-super-hands" controls-shape-offset @@ -404,7 +403,6 @@ missOpacity: 0.1; curveShootingSpeed: 12;" haptic-feedback - hover-visuals body="type: static; shape: none;" mixin="controller-super-hands" controls-shape-offset @@ -438,11 +436,11 @@ </template> <template data-name="LeftHand"> - <a-entity bone-visibility></a-entity> + <a-entity bone-visibility hover-visuals="hand: left; controller: #player-left-controller"></a-entity> </template> <template data-name="RightHand"> - <a-entity bone-visibility></a-entity> + <a-entity bone-visibility hover-visuals="hand: right; controller: #player-right-controller"></a-entity> </template> </a-entity> diff --git a/src/utils/media-highlight-frag.glsl b/src/utils/media-highlight-frag.glsl index e47db1ac09a41506fe2efb290cc169bcb0de7e05..c953d39ab48c834b3c3f669bff977b1214e2a853 100644 --- a/src/utils/media-highlight-frag.glsl +++ b/src/utils/media-highlight-frag.glsl @@ -15,24 +15,22 @@ if (hubs_HighlightInteractorOne || hubs_HighlightInteractorTwo) { dist2 = distance(hubs_WorldPosition, ip); } + float size = hubs_SweepParams.t - hubs_SweepParams.s; + float line = mod(hubs_Time / 3000.0 * size, size * 2.0) + hubs_SweepParams.s - size / 2.0; + float ratio = 0.0; + if (hubs_EnableSweepingEffect && hubs_WorldPosition.y < line) { + // Highlight with an sweeping gradient + ratio = max(0.0, 1.0 - (line - hubs_WorldPosition.y) / size * 3.0); + } + float pulse = sin(hubs_Time / 1000.0) + 1.0; - float spacing = 0.5; - float line = spacing * pulse - spacing / 2.0; - float lineWidth= 0.01; - float mody = mod(hubs_WorldPosition.y, spacing); - - if (-lineWidth + line < mody && mody < lineWidth + line) { - // Highlight with an animated line effect - ratio = 0.5; - } else { - // Highlight with a gradient falling off with distance. - if (hubs_HighlightInteractorOne) { - ratio = -min(1.0, pow(dist1 * (9.0 + 3.0 * pulse), 3.0)) + 1.0; - } - if (hubs_HighlightInteractorTwo) { - ratio += -min(1.0, pow(dist2 * (9.0 + 3.0 * pulse), 3.0)) + 1.0; - } + // Highlight with a gradient falling off with distance. + if (hubs_HighlightInteractorOne) { + ratio += -min(1.0, pow(dist1 * (9.0 + 3.0 * pulse), 3.0)) + 1.0; + } + if (hubs_HighlightInteractorTwo) { + ratio += -min(1.0, pow(dist2 * (9.0 + 3.0 * pulse), 3.0)) + 1.0; } ratio = min(1.0, ratio); diff --git a/src/utils/media-utils.js b/src/utils/media-utils.js index c192c11107af1ada6bf184e164d9981c221bbcbc..7db272e7e61a07e067dd063aef29f06d71bc2ecb 100644 --- a/src/utils/media-utils.js +++ b/src/utils/media-utils.js @@ -140,7 +140,7 @@ export const addMedia = (src, template, contentOrigin, resolve = false, resize = }; export function injectCustomShaderChunks(obj) { - const vertexRegex = /\bbegin_vertex\b/; + const vertexRegex = /\bskinning_vertex\b/; const fragRegex = /\bgl_FragColor\b/; const materialsSeen = new Set(); @@ -157,6 +157,8 @@ export function injectCustomShaderChunks(obj) { shader.uniforms.hubs_InteractorOneTransform = { value: [] }; shader.uniforms.hubs_InteractorTwoTransform = { value: [] }; shader.uniforms.hubs_InteractorTwoPos = { value: [] }; + shader.uniforms.hubs_EnableSweepingEffect = { value: false }; + shader.uniforms.hubs_SweepParams = { value: [] }; shader.uniforms.hubs_HighlightInteractorOne = { value: false }; shader.uniforms.hubs_HighlightInteractorTwo = { value: false }; shader.uniforms.hubs_Time = { value: 0 }; @@ -182,6 +184,8 @@ export function injectCustomShaderChunks(obj) { const findex = flines.findIndex(line => fragRegex.test(line)); flines.splice(findex + 1, 0, mediaHighlightFrag); flines.unshift("varying vec3 hubs_WorldPosition;"); + flines.unshift("uniform bool hubs_EnableSweepingEffect;"); + flines.unshift("uniform vec2 hubs_SweepParams;"); flines.unshift("uniform bool hubs_HighlightInteractorOne;"); flines.unshift("uniform mat4 hubs_InteractorOneTransform;"); flines.unshift("uniform bool hubs_HighlightInteractorTwo;");