Skip to content

v2.6.1 #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions examples/ideas/Wings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect, useState } from "react";
import { usePlayer, isTyping } from "spacesvr";
import { Vector3 } from "three";

export default function Wings() {
const { velocity } = usePlayer();

const [dummy] = useState(() => new Vector3());

useEffect(() => {
const jump = (e: KeyboardEvent) => {
if (e.key.toLowerCase() === " " && !isTyping() && !e.metaKey) {
dummy.copy(velocity.get());
dummy.y = 5;
velocity.set(dummy);
}
};

document.addEventListener("keypress", jump);
return () => {
document.removeEventListener("keypress", jump);
};
}, [dummy, velocity]);

return null;
}
2 changes: 2 additions & 0 deletions examples/worlds/Workshop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Title from "../ideas/Title";
import Link from "../ideas/Link";
import Analytics from "../ideas/Analytics";
import Bloom from "../ideas/Bloom";
import Wings from "../ideas/Wings";

export default function Workshop() {
const [value, setValue] = useState("hello world");
Expand Down Expand Up @@ -45,6 +46,7 @@ export default function Workshop() {
<StandardReality
environmentProps={{ dev: process.env.NODE_ENV === "development" }}
>
<Wings />
<Image
src="https://uploads.codesandbox.io/uploads/user/b3e56831-8b98s-4fee-b941-0e27f39883ab/I9vI-RoNmD7W.png"
position={[-8, 2, 6.4]}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spacesvr",
"version": "2.6.0",
"version": "2.6.1",
"private": true,
"description": "A standardized reality for future of the 3D Web",
"keywords": [
Expand Down
1 change: 1 addition & 0 deletions src/ideas/basis/VisualSite/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function VisualSite(props: VisualSiteProps) {
color="white"
outlineWidth={0.025}
rotation-y={-Math.PI / 2}
renderOrder={2}
>
/{site.slug}
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ export default function MobileDesktopInteractable(props: InteractableProps) {

const getIntersection = useCallback(() => {
if (!group.current) return undefined;
RAYCASTER.firstHitOnly = true;
const intersects = RAYCASTER.intersectObject(group.current, true);
RAYCASTER.firstHitOnly = false;
return intersects.length > 0 ? intersects[0] : undefined;
}, [RAYCASTER]);

// continuously update the hover state if we have a hover handler
useLimitedFrame(20, () => {
useLimitedFrame(17, () => {
if (!group.current || !DETECT_HOVER) return;

const inter = getIntersection();
Expand All @@ -62,7 +64,7 @@ export default function MobileDesktopInteractable(props: InteractableProps) {

group.current.traverse((obj) => {
const mesh = obj as Mesh;
if (mesh.isMesh) enableBVHRaycast(mesh, 500);
if (mesh.isMesh) enableBVHRaycast(mesh, 50);
});
}, []);

Expand Down
12 changes: 11 additions & 1 deletion src/ideas/modifiers/Tool/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type ToolProps = {
pinY?: boolean;
range?: number;
orderIndex?: number;
bobStrength?: number;
onSwitch?: (enabled: boolean) => void;
};

Expand All @@ -35,6 +36,7 @@ export function Tool(props: ToolProps) {
face = true,
pinY = false,
range,
bobStrength,
orderIndex,
onSwitch,
} = props;
Expand All @@ -59,11 +61,19 @@ export function Tool(props: ToolProps) {
if (onSwitch) onSwitch(toolbelt.activeTool?.name === name);
}, [toolbelt.activeTool, onSwitch, name]);

if (!visible) return null;

return (
<>
{createPortal(
<group name={`tool-${name}`} visible={visible}>
<HUD pos={pos} pinY={pinY} distance={DISTANCE} range={range}>
<HUD
pos={pos}
pinY={pinY}
distance={DISTANCE}
range={range}
bobStrength={bobStrength}
>
<OnScreen distance={DISTANCE} name={name} pos={pos}>
<FacePlayer enabled={face}>{visible && children}</FacePlayer>
</OnScreen>
Expand Down
100 changes: 100 additions & 0 deletions src/ideas/modifiers/Tool/logic/quat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Quaternion } from "three";

const dummy = new Quaternion();

// taken from https://gist.github.com/sketchpunk/3568150a04b973430dfe8fd29bf470c8
export class QuaterionSpring {
velocity: Float32Array;
stiffness: number;
damping: number;

constructor(damping = 5, stiffness = 30) {
this.velocity = new Float32Array(4);
this.stiffness = stiffness;
this.damping = damping;
}

_velLenSqr() {
return (
this.velocity[0] ** 2 +
this.velocity[1] ** 2 +
this.velocity[2] ** 2 +
this.velocity[3] ** 2
);
}

// Harmonic oscillation
// https://stackoverflow.com/questions/44688112/spring-physics-applied-to-quaternions-using-python
oscillationStep(cq: Quaternion, target: Quaternion, dt: number) {
// Check when the spring is done.
const dot = cq.dot(target);
if (dot >= 0.9999 && this._velLenSqr() < 0.000001) {
cq.copy(target);
return;
}

const tq = dummy;
if (dot < 0) {
// Use the closest rotation
tq.x = -target.x;
tq.y = -target.y;
tq.z = -target.z;
tq.w = -target.w;
} else {
tq.copy(target);
}

this.velocity[0] +=
(-this.stiffness * (cq.x - tq.x) - this.damping * this.velocity[0]) * dt;
this.velocity[1] +=
(-this.stiffness * (cq.y - tq.y) - this.damping * this.velocity[1]) * dt;
this.velocity[2] +=
(-this.stiffness * (cq.z - tq.z) - this.damping * this.velocity[2]) * dt;
this.velocity[3] +=
(-this.stiffness * (cq.w - tq.w) - this.damping * this.velocity[3]) * dt;

cq.x += this.velocity[0] * dt;
cq.y += this.velocity[1] * dt;
cq.z += this.velocity[2] * dt;
cq.w += this.velocity[3] * dt;
cq.normalize();
}

// Critically Damped Spring
criticallyStep(cq: Quaternion, target: Quaternion, dt: number) {
// Check when the spring is done.
const dot = cq.dot(target);
if (dot >= 0.9999 && this._velLenSqr() < 0.000001) {
cq.copy(target);
return;
}

const tq = dummy;
if (dot < 0) {
// Use the closest rotation
tq.x = -target.x;
tq.y = -target.y;
tq.z = -target.z;
tq.w = -target.w;
} else {
tq.copy(target);
}

const dSqrDt = this.damping * this.damping * dt,
n2 = 1 + this.damping * dt,
n2Sqr = n2 * n2;

this.velocity[0] = (this.velocity[0] - (cq.x - tq.x) * dSqrDt) / n2Sqr;
this.velocity[1] = (this.velocity[1] - (cq.y - tq.y) * dSqrDt) / n2Sqr;
this.velocity[2] = (this.velocity[2] - (cq.z - tq.z) * dSqrDt) / n2Sqr;
this.velocity[3] = (this.velocity[3] - (cq.w - tq.w) * dSqrDt) / n2Sqr;

cq.x += this.velocity[0] * dt;
cq.y += this.velocity[1] * dt;
cq.z += this.velocity[2] * dt;
cq.w += this.velocity[3] * dt;
cq.normalize();

return cq;
}
}
127 changes: 99 additions & 28 deletions src/ideas/modifiers/Tool/modifiers/HUD.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,97 @@
import { ReactNode, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { Group, PerspectiveCamera, Quaternion, Vector2 } from "three";
import { ReactNode, useMemo, useRef, useState } from "react";
import { useFrame } from "@react-three/fiber";
import {
Euler,
Group,
MathUtils,
PerspectiveCamera,
Quaternion,
Vector2,
} from "three";
import { getHudPos } from "../../../../logic/hud";
import { QuaterionSpring } from "../logic/quat";
import { config, useSpring } from "@react-spring/three";
import { usePlayer } from "../../../../layers/Player/";

type HUDProps = {
children?: ReactNode | ReactNode[];
pos: [number, number];
distance: number;
pinY?: boolean;
range?: number;
bobStrength?: number;
};

/**
* Place the children in front of the camera
* Place the children in front of the camera with some sway
* 1. I tried springing the quaternion, but it's not continuous and causes a jump
* 2. I found a continuous quaternion spring, but it was not tight enough https://gist.github.com/sketchpunk/3568150a04b973430dfe8fd29bf470c8
* 3. solution was to move tool in screen spacing by springing pos value offsets based on rotation velocity
* springed quat rotation still used for range
*
* @param props
* @constructor
*/
export default function HUD(props: HUDProps) {
const { children, pos, pinY = false, distance, range = 0 } = props;
const {
children,
pos,
pinY = false,
distance,
range = 0,
bobStrength,
} = props;

const t = 0.01;
const { velocity } = usePlayer();

const camera = useThree((state) => state.camera);
const size = useThree((state) => state.size);
const t = 0.00001;

const group = useRef<Group>(null);
const [vecPos] = useState(new Vector2());
const [lerpPos] = useState(new Vector2().fromArray(pos));
const [targetPos] = useState(new Vector2());
const [lerpedPos] = useState(new Vector2().fromArray(pos));
const [lerpedQuat] = useState(new Quaternion());
const [targetQuat] = useState(new Quaternion());
const [lastQuat] = useState(new Quaternion());
const [lastEuler] = useState(new Euler(0, 0, 0, "YXZ"));
const [thisEuler] = useState(new Euler(0, 0, 0, "YXZ"));
const [dummy1] = useState(new Quaternion());
const [dummy2] = useState(new Quaternion());

useFrame((_, delta) => {
const [spring, set] = useSpring(() => ({
offset: [0, 0],
config: config.stiff,
}));

const qs = useMemo(() => new QuaterionSpring(50, 100), []);

useFrame(({ camera, clock }, delta) => {
if (!group.current) return;

const alpha = 1 - Math.pow(t * 0.0005 * size.width, delta);
const alpha = 1 - Math.pow(t, delta);

vecPos.fromArray(pos);
lerpPos.lerp(vecPos, alpha);
// apply passes pos and offset pos
const off = spring.offset.get();
targetPos.fromArray(pos);
targetPos.x += off[0];
targetPos.y += off[1];
lerpedPos.lerp(targetPos, alpha);

// calculate x position based on camera and screen width
const { x, y } = getHudPos(
lerpPos.toArray(),
camera as PerspectiveCamera,
distance
);
const hud = getHudPos(lerpedPos, camera as PerspectiveCamera, distance);
const { x, y } = hud;
group.current.position.set(x, y, -distance);

const RANGE_SET = range > 0;
// calculate rotation velocities about the respective ROTATION axis (not screen space)
dummy1.copy(lastQuat);
dummy2.copy(camera.quaternion);
thisEuler.setFromQuaternion(camera.quaternion);
let y_axis_vel = dummy1.multiply(dummy2.invert()).y / delta;
let x_axis_vel = (thisEuler.x - lastEuler.x) / delta;

// implement range
const RANGE_SET = range > 0;
if (!RANGE_SET) {
lerpedQuat.slerp(camera.quaternion, alpha);
lerpedQuat.copy(camera.quaternion);
} else {
// find angle along y axis
dummy1.copy(lerpedQuat);
Expand All @@ -63,19 +104,49 @@ export default function HUD(props: HUDProps) {
dummy2.normalize();
const angle = dummy1.angleTo(dummy2);

if (angle > range) lerpedQuat.slerp(camera.quaternion, alpha);
}

if (!pinY) {
lerpedQuat.x = 0;
lerpedQuat.z = 0;
// if out of range, move it back
if (angle > range) {
const diff = angle - range;
targetQuat.copy(lerpedQuat);
targetQuat.rotateTowards(camera.quaternion, diff);
if (!pinY) {
targetQuat.x = 0;
targetQuat.z = 0;
targetQuat.normalize();
}
} else {
// disable offsets if moving camera within range
x_axis_vel = 0;
y_axis_vel = 0;
}
qs.criticallyStep(lerpedQuat, targetQuat, delta);
}

// bob a bit based on player velocity
const vel_len = velocity.get().length() > 1 ? 1 : 0;
const strength = bobStrength || Math.max(lerpedPos.length(), 0.05);
x_axis_vel +=
Math.sin(clock.getElapsedTime() * 15) * vel_len * 0.2 * strength;
y_axis_vel +=
Math.cos(clock.getElapsedTime() * 20 + 12) * vel_len * 0.1 * strength;

// set spring targets based on velocities
const scale_ang = 0.1;
const max_ang = 0.3;
const x_off = MathUtils.clamp(-y_axis_vel * scale_ang, -max_ang, max_ang);
const y_off = MathUtils.clamp(-x_axis_vel * scale_ang, -max_ang, max_ang);
set({ offset: [x_off, y_off] });

// range dependent, move items to camera quat
group.current.position.applyQuaternion(lerpedQuat);

// needed to that children positions are applied in screen space
// needed so that children positions are applied in screen space
// should probably be moved to draggable .. ? idk, maybe the supposition is that children of hud are in screen space
group.current.quaternion.copy(camera.quaternion);

// update last values
lastQuat.copy(camera.quaternion);
lastEuler.setFromQuaternion(camera.quaternion);
});

return (
Expand Down
Loading