Skip to content

Commit

Permalink
BitECS hover visual effect
Browse files Browse the repository at this point in the history
  • Loading branch information
keianhzo committed Jan 22, 2024
1 parent c003c76 commit bf62039
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 1 deletion.
7 changes: 7 additions & 0 deletions src/bit-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ export const HoveredHandRight = defineComponent();
export const HoveredHandLeft = defineComponent();
export const HoveredRemoteRight = defineComponent();
export const HoveredRemoteLeft = defineComponent();
export const HoverableVisuals = defineComponent({
geometryRadius: Types.f32
});
/**
* @type {Map<EntityId, Uniform[]}>}
*/
export const HoverableVisualsUniforms = new Map();
export const HeldHandRight = defineComponent();
export const HeldHandLeft = defineComponent();
export const HeldRemoteRight = defineComponent();
Expand Down
124 changes: 124 additions & 0 deletions src/bit-systems/hoverable-visuals-system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { defineQuery, enterQuery, exitQuery, hasComponent } from "bitecs";
import { HubsWorld } from "../app";
import {
HandLeft,
HandRight,
Held,
HoverableVisuals,
HoverableVisualsUniforms,
HoveredHandLeft,
HoveredHandRight,
HoveredRemoteLeft,
HoveredRemoteRight,
RemoteLeft,
RemoteRight
} from "../bit-components";
import { injectCustomShaderChunks } from "../utils/media-utils";
import { anyEntityWith, findChildWithComponent } from "../utils/bit-utils";
import { EntityID } from "../utils/networking-types";

export const updateHoverableVisuals = (function () {
const boundingBox = new THREE.Box3();
const boundingSphere = new THREE.Sphere();
return function (world: HubsWorld, eid: EntityID) {
const obj = world.eid2obj.get(eid)!;

HoverableVisualsUniforms.set(eid, injectCustomShaderChunks(obj));

boundingBox.setFromObject(obj);
boundingBox.getBoundingSphere(boundingSphere);
HoverableVisuals.geometryRadius[eid] = boundingSphere.radius / obj.scale.y;
};
})();

const sweepParams = [0, 0];
const interactorOneTransform = new Array();
const interactorTwoTransform = new Array();
const hoverableVisualsQuery = defineQuery([HoverableVisuals]);
const hoverableVisualsEnterQuery = enterQuery(hoverableVisualsQuery);
const hoverableVisualsExitQuery = exitQuery(hoverableVisualsQuery);
const isMobile = AFRAME.utils.device.isMobile();
const isMobileVR = AFRAME.utils.device.isMobileVR();
const isTouchscreen = isMobile && !isMobileVR;
export function hoverableVisualsSystem(world: HubsWorld) {
hoverableVisualsExitQuery(world).forEach(eid => HoverableVisualsUniforms.delete(eid));
hoverableVisualsEnterQuery(world).forEach(eid => updateHoverableVisuals(world, eid));
hoverableVisualsQuery(world).forEach(eid => {
const uniforms = HoverableVisualsUniforms.get(eid);
if (uniforms && uniforms.length) {
const obj = world.eid2obj.get(eid)!;
const isFrozen = APP.scene!.is("frozen");

let interactorOne, interactorTwo;

const hoveredHandLeft = findChildWithComponent(world, HoveredHandLeft, eid);
const leftHand = anyEntityWith(world, HandLeft);
if (leftHand) {
if (hoveredHandLeft && !hasComponent(world, Held, leftHand)) {
interactorOne = APP.world.eid2obj.get(leftHand);
}
}
const hoveredRemoteLeft = findChildWithComponent(world, HoveredRemoteLeft, eid);
const leftRemote = anyEntityWith(world, RemoteLeft);
if (leftRemote) {
if (hoveredRemoteLeft && !hasComponent(world, Held, hoveredRemoteLeft)) {
interactorOne = APP.world.eid2obj.get(leftRemote);
}
}
const hoveredHandRight = findChildWithComponent(world, HoveredHandRight, eid);
const rightHand = anyEntityWith(world, HandRight);
if (rightHand) {
if (hoveredHandRight && !hasComponent(world, Held, hoveredHandRight)) {
interactorTwo = APP.world.eid2obj.get(rightHand);
}
}
const hoveredRemoteRight = findChildWithComponent(world, HoveredRemoteRight, eid);
const rightRemote = anyEntityWith(world, RemoteRight);
if (rightRemote) {
if (hoveredRemoteRight && !hasComponent(world, Held, hoveredRemoteRight)) {
interactorTwo = APP.world.eid2obj.get(rightRemote);
}
}

if (interactorOne) {
interactorOne.matrixWorld.toArray(interactorOneTransform);
}
if (interactorTwo) {
interactorTwo.matrixWorld.toArray(interactorTwoTransform);
}

if (interactorOne || interactorTwo || isFrozen) {
const worldY = obj.matrixWorld.elements[13];
const ms1 = obj.matrixWorld.elements[4];
const ms2 = obj.matrixWorld.elements[5];
const ms3 = obj.matrixWorld.elements[6];
const worldScale = Math.sqrt(ms1 * ms1 + ms2 * ms2 + ms3 * ms3);
const scaledRadius = worldScale * HoverableVisuals.geometryRadius[eid];
sweepParams[0] = worldY - scaledRadius;
sweepParams[1] = worldY + scaledRadius;
}

const uniforms = HoverableVisualsUniforms.get(eid)! as any;
for (let i = 0, l = uniforms.length; i < l; i++) {
const uniform = uniforms[i];
uniform.hubs_EnableSweepingEffect.value = true;
uniform.hubs_IsFrozen.value = isFrozen;
uniform.hubs_SweepParams.value = sweepParams;

uniform.hubs_HighlightInteractorOne.value = !!interactorOne && !isTouchscreen;
uniform.hubs_InteractorOnePos.value[0] = interactorOneTransform[12];
uniform.hubs_InteractorOnePos.value[1] = interactorOneTransform[13];
uniform.hubs_InteractorOnePos.value[2] = interactorOneTransform[14];

uniform.hubs_HighlightInteractorTwo.value = !!interactorTwo && !isTouchscreen;
uniform.hubs_InteractorTwoPos.value[0] = interactorTwoTransform[12];
uniform.hubs_InteractorTwoPos.value[1] = interactorTwoTransform[13];
uniform.hubs_InteractorTwoPos.value[2] = interactorTwoTransform[14];

if (interactorOne || interactorTwo || isFrozen) {
uniform.hubs_Time.value = world.time.elapsed;
}
}
}
});
}
5 changes: 5 additions & 0 deletions src/bit-systems/media-loading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Box3, Group, Matrix4, Quaternion, Vector3 } from "three";
import { HubsWorld } from "../app";
import {
GLTFModel,
HoverableVisuals,
LoadedByMediaLoader,
MediaContentBounds,
MediaImageLoaderData,
Expand Down Expand Up @@ -47,6 +48,7 @@ import { inflateGrabbable } from "../inflators/grabbable";
import { findAncestorsWithComponent, findChildWithComponent } from "../utils/bit-utils";
import { setMatrixWorld } from "../utils/three-utils";
import { computeObjectAABB, getScaleCoefficient } from "../utils/auto-box-collider";
import { updateHoverableVisuals } from "./hoverable-visuals-system";

export function* waitForMediaLoaded(world: HubsWorld, eid: EntityID) {
while (hasComponent(world, MediaLoader, eid)) {
Expand Down Expand Up @@ -170,6 +172,9 @@ function* finish(world: HubsWorld, mediaLoaderEid: EntityID) {
type: Shape.HULL,
minHalfExtent: 0.04
});
if (hasComponent(world, HoverableVisuals, mediaLoaderEid)) {
updateHoverableVisuals(world, mediaLoaderEid);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/prefabs/camera-tool.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export function CubeMediaFramePrefab() {
physicsShape={{ fit: Fit.MANUAL, type: Shape.BOX, halfExtents: [0.5, 0.5, 0.5] }}
object3D={new THREE.Mesh(new THREE.BoxBufferGeometry(), new THREE.MeshStandardMaterial())}
deletable
hoverableVisuals
>
<entity mediaFrame position={[0, 1, 0]} />
</entity>
Expand Down
1 change: 1 addition & 0 deletions src/prefabs/duck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function DuckPrefab(): EntityDef {
scale={[1, 1, 1]}
inspectable
deletable
hoverableVisuals
/>
);
}
1 change: 1 addition & 0 deletions src/prefabs/media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function MediaPrefab(params: MediaLoaderParams): EntityDef {
}}
scale={[1, 1, 1]}
inspectable
hoverableVisuals
/>
);
}
3 changes: 3 additions & 0 deletions src/systems/hubs-systems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import { objectMenuTransformSystem } from "../bit-systems/object-menu-transform-
import { bitPenCompatSystem } from "./bit-pen-system";
import { sfxButtonSystem } from "../bit-systems/sfx-button-system";
import { sfxMediaSystem } from "../bit-systems/sfx-media-system";
import { hoverableVisualsSystem } from "../bit-systems/hoverable-visuals-system";

declare global {
interface Window {
Expand Down Expand Up @@ -209,6 +210,8 @@ export function mainTick(xrFrame: XRFrame, renderer: WebGLRenderer, scene: Scene
constraintsSystem(world, hubsSystems.physicsSystem);
floatyObjectSystem(world);

hoverableVisualsSystem(world);

// We run this earlier in the frame so things have a chance to override properties run by animations
hubsSystems.animationMixerSystem.tick(dt);

Expand Down
5 changes: 4 additions & 1 deletion src/utils/jsx-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import {
Quack,
MixerAnimatableInitialize,
Inspectable,
ObjectMenu
ObjectMenu,
HoverableVisuals
} from "../bit-components";
import { inflateMediaLoader } from "../inflators/media-loader";
import { inflateMediaFrame } from "../inflators/media-frame";
Expand Down Expand Up @@ -306,6 +307,7 @@ export interface JSXComponentData extends ComponentData {
networked?: any;
textButton?: any;
hoverButton?: any;
hoverableVisuals?: any;
rigidbody?: OptionalParams<RigidBodyParams>;
physicsShape?: OptionalParams<PhysicsShapeParams>;
floatyObject?: any;
Expand Down Expand Up @@ -444,6 +446,7 @@ const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = {
holdableButton: createDefaultInflator(HoldableButton),
textButton: createDefaultInflator(TextButton),
hoverButton: createDefaultInflator(HoverButton),
hoverableVisuals: createDefaultInflator(HoverableVisuals),
holdable: createDefaultInflator(Holdable),
deletable: createDefaultInflator(Deletable),
rigidbody: inflateRigidBody,
Expand Down

0 comments on commit bf62039

Please sign in to comment.