Unable to use Pointer Lock with PerspectiveCamera and PointerLockControls - three.js

I like react-three and I am trying to make an FPV character with a body:
https://codesandbox.io/s/fpv-player-ers63
So the goal is that the camera is placed at the head level and the character moves around like in original example https://codesandbox.io/s/minecraft-vkgi6 by Paul Henschel
drcmda
I have added PerspectiveCamera to change the camera position but now PointerLockControls doesn't work any more and I do not understand why and how to handle it.
Thank you
PerspectiveCamera usage
export const Player = (props) => {
const axe = useRef()
const [ref, api] = useSphere(() => ({ mass: 1, type: "Dynamic", position: [0, 10, 0], ...props }))
const { forward, backward, left, right, jump } = usePlayerControls()
const { camera } = useThree()
const velocity = useRef([0, 0, 0])
useEffect(() => api.velocity.subscribe((v) => (velocity.current = v)), [])
useFrame((state) => {
ref.current.getWorldPosition(camera.position)
frontVector.set(0, 0, Number(backward) - Number(forward))
sideVector.set(Number(left) - Number(right), 0, 0)
direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(SPEED).applyEuler(camera.rotation)
speed.fromArray(velocity.current)
axe.current.children[0].rotation.x = THREE.MathUtils.lerp(
axe.current.children[0].rotation.x,
Math.sin((speed.length() > 1) * state.clock.elapsedTime * 10) / 20,
0.01,
)
axe.current.rotation.copy(camera.rotation)
axe.current.position.copy(camera.position).add(camera.getWorldDirection(rotation).multiplyScalar(1))
api.velocity.set(direction.x, velocity.current[1], direction.z)
if (jump && Math.abs(velocity.current[1].toFixed(2)) < 0.05) api.velocity.set(velocity.current[0], 10, velocity.current[2])
})
return (
<>
<mesh ref={ref} />
<group ref={axe} onPointerMissed={(e) => (axe.current.children[0].rotation.x = -0.5)}>
<group position={[0, 7, 0]}>
<PerspectiveCamera makeDefault={true} fov={75}>
<Body position={[0, -4, 1]} />
</PerspectiveCamera>
</group>
</group>
</>
)
}
PointerLockControls usage
export default function App() {
return (
<Canvas
shadows
gl={{ alpha: false }}
camera={{ fov: 45 }}
raycaster={{
computeOffsets: (e) => ({ offsetX: e.target.width / 2, offsetY: e.target.height / 2 }),
}}>
<Sky sunPosition={[100, 20, 100]} />
<ambientLight intensity={0.3} />
<pointLight castShadow intensity={0.8} position={[100, 100, 100]} />
<Physics gravity={[0, -30, 0]}>
<Ground />
<Player />
<Reflector
blur={[0, 0]} // Blur ground reflections (width, heigt), 0 skips blur
mixBlur={0.1} // How much blur mixes with surface roughness
mixStrength={0.25} // Strength of the reflections
resolution={1024} // Off-buffer resolution, lower=faster, higher=better quality
args={[10, 30]} // PlaneBufferGeometry arguments
position={[0, 0, -20]}
rotation={[0, 0, 2 * Math.PI]}
mirror={0.5} // Mirror environment, 0 = texture colors, 1 = pick up env colors
minDepthThreshold={0.25}
maxDepthThreshold={1}
depthScale={50}>
{(Material, props) => <Material metalness={0} roughness={0} {...props} />}
</Reflector>
</Physics>
<PointerLockControls />
</Canvas>
)
}
Browser: Firefox 94.0.2 (64-bit), macOS Monterey
"dependencies": {
"#pmndrs/branding": "0.0.8",
"#react-three/cannon": "4.0.1",
"#react-three/drei": "7.17.2",
"#react-three/fiber": "7.0.17",
"#types/three": "0.133.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-scripts": "4.0.3",
"simplex-noise": "3.0.0",
"three": "0.133.1",
"zustand": "3.6.1"
},
https://codesandbox.io/s/fpv-player-ers63

Well, I found that it is sufficient to offset the camera in useFrame update
https://codesandbox.io/s/fpv-player-ers63
export const Player = (props) => {
const axe = useRef()
const [ref, api] = useSphere(() => ({ args: [1], mass: 1, type: "Dynamic", position: [0, 10, 0], ...props }))
const { forward, backward, left, right, jump } = usePlayerControls()
const { camera } = useThree()
const velocity = useRef([0, 0, 0])
useEffect(() => api.velocity.subscribe((v) => (velocity.current = v)), [])
useFrame((state) => {
ref.current.getWorldPosition(camera.position)
frontVector.set(0, 0, Number(backward) - Number(forward))
sideVector.set(Number(left) - Number(right), 0, 0)
direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(SPEED).applyEuler(camera.rotation)
speed.fromArray(velocity.current)
axe.current.children[0].rotation.x = THREE.MathUtils.lerp(
axe.current.children[0].rotation.x,
Math.sin((speed.length() > 1) * state.clock.elapsedTime * 10) / 10,
0.1,
)
axe.current.rotation.copy(camera.rotation)
axe.current.position.copy(camera.position).add(camera.getWorldDirection(rotation).multiplyScalar(1))
camera.position.setY(camera.position.y + 8)
api.velocity.set(direction.x, velocity.current[1], direction.z)
if (jump && Math.abs(velocity.current[1].toFixed(2)) < 0.05) api.velocity.set(velocity.current[0], 10, velocity.current[2])
})
return (
<>
<mesh ref={ref} />
<group ref={axe} onPointerMissed={(e) => (axe.current.children[0].rotation.x = -0.5)}>
<Body position={[0, 4, 0]} />
</group>
</>
)
}

Related

React Three Fiber | Lines are not casting shadows

I recently started digging into ThreeJS, followed some tutorials and now I have a problem with my line / bufferGeometry not casting any shadows. Shadows are casted, as long as I use boxBufferGeometry inside a mesh. But if I use lines, nothing happens
import React from 'react'
import { Canvas } from '#react-three/fiber'
import Curve from './components/Curve';
import Controls from './components/Controls';
import Plane from './components/Plane';
import * as THREE from 'three';
const App = () => {
return (
<div className="App">
<Canvas shadows colorManagement camera={{position: [5,5,5]}} onCreated={({gl}) => {
gl.shadowMap.enabled = true;
gl.shadowMap.type = THREE.PCFShadowMap;
gl.setPixelRatio(window.devicePixelRatio || 2);
}}>
<spotLight
position={[0,70,0]}
color="white"
intensity={1.5}
castShadow
shadowMapHeight={1024}
shadowMapWidth={1024}
shadowCameraNear={200}
shadowCameraFar={2000}
shadowBias={ - 0.00022}
angle={0.01}
/>
<ambientLight color="#f0f0f0" />
<Controls />
<Curve />
<Plane />
<gridHelper args={[30, 100, 'white', 'white']} position={[0,-3.99,0]} receiveShadow />
</Canvas>
</div>
);
}
export default App;
import React, { useLayoutEffect, useRef } from 'react';
import * as THREE from 'three';
const Curve = () => {
const curve = new THREE.CubicBezierCurve3
(
new THREE.Vector3( -10, 0, 0 ),
new THREE.Vector3( -5, 15, 0 ),
new THREE.Vector3( 20, 15, 0 ),
new THREE.Vector3( 10, 0, 0 )
);
const points = curve.getPoints( 50 );
const ref = useRef()
useLayoutEffect(() => {void ref.current.setFromPoints(points); }, [points])
return (
<line>
<bufferGeometry ref={ref} />
<lineBasicMaterial color={0xd2452b} />
</line>
);
};
export default Curve;
import React from 'react';
const Plane = () =>
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0,-4,0]} receiveShadow>
<planeGeometry
attach="geometry"
args={[1024,1024]}
/>
<shadowMaterial attach="material" color="#000000" opacity="1" />
</mesh>
export default Plane;
Rendering this component (Box) instead of curve, would cast a shadow.
import React, { useState } from 'react';
import { useSpring, a } from '#react-spring/three';
const Box = () => {
const [hovered, setHovered] = useState(false);
const [active, setActive] = useState(false);
const props = useSpring({
scale: active ? 2 : 1,
color: hovered ? 'red' : 'gray',
});
return (
<a.mesh
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
onClick={() => setActive(!active)}
scale={props.scale}
castShadow
>
<boxBufferGeometry
attach="geometry"
args={[1,1,1]}
/>
<a.meshPhysicalMaterial attach="material" color={props.color} />
</a.mesh>
);
};
export default Box;
The only difference (I'm still a newbie), is that I am using lines instead of mesh. Mesh accepts attributes like "receiveShadow and castShadow", lines doesn't.
How do I manage to let my lines cast shadows, too?
Thanks a lot in advance

Cannot export Animations with React three Fiber

"#react-three/fiber": "^8.2.0",
"three": "^0.142.0",
Hello I am trying to export a model containing animations. My model uses the useFrame hook and manipulates the ref like so
const TopicBadge = forwardRef((props, ref) => {
let {playlistBody, hasBadge} = useSelector(s => s.playlist)
return (
<Canvas>
<ShapeModel title={playlistBody.title} ref={ref} />
</Canvas>
);
})
export const ShapeModel = React.forwardRef(({title}, ref ) => {
const meshTurnRef = useRef()
useFrame(() => {
meshTurnRef.current.rotation.y += 0.01
})
return (
<group ref={ref}>
<Html fullscreen>
<div className='text-right w-1/3 ml-16 mt-8'>
<h1 className='text-sm text-plum-green'>{title}</h1>
</div>
</Html>
<mesh matrixAutoUpdate={true} position={[0, 0, 2.4]} ref={meshTurnRef}>
<torusBufferGeometry args={[1, .48, 16, 32]} attach="geometry" />
<meshStandardMaterial roughness={1} fog={true} color={"#049ef4"} attach="material" />
</mesh>
<directionalLight position={[1, 1, 0]} />
<directionalLight position={[0, 0, 3]} />
</group>
)
})
I then export the model with the GLTFExporter in a parent component
const exporter = new GLTFExporter()
let saveRef = useRef()
const onSavePlaylist = async () => {
try {
exporter.parse( saveRef.current, async function ( gltf ) {
// saveRef.current does not have animations
}
}
I see the saveRef.current does not have the animations.
Is there documentation or a recommended way to save animations for importing into a model later? Seems all documentation shows this as the recommended way so I think this might be a bug

react-three, unable to get mouse position for html content

I'm trying to to get current mouse position on the canvas when html contents are clicked. With useFrame, I can successfully print out mouse position on any parts of canvas except when mouse is on html contents (it just shows the last mouse position before it enters html part and just stops there).
Are there any ways I can get mouse position when it hovers on html content?
Here are my codes:
#app.js
import React, {useRef, useState } from 'react';
import {Canvas, useFrame, useThree } from '#react-three/fiber';
import { Stars, Html } from '#react-three/drei'
function Sphere(props){
const mesh = useRef(null);
const { viewport, mouse } = useThree()
useFrame(({ mouse }) => {
const x_viewport = (mouse.x * viewport.height) / 2
console.log(x_viewport)
});
return(
<mesh
ref={mesh}
<sphereGeometry args={[1, 16, 16]} />
<meshStandardMaterial />
</mesh>
);
};
function Blocks (props) {
const {handlePost } = props;
let rows = [];
for (let i = 0; i < 30; i++) {
rows.push(<div onClick={handlePost} key={i} ></div>);
}
return (
<>
<div> { rows } </div>
</>
)
}
export default function Meshes() {
const [ hasRender, setRender ] = useState(false);
const handlePost = (e) => {
setRender(true);
}
return (
<>
<Canvas
orthographic
dpr={[1, 1.5]}
mode="concurrent"
camera={{ zoom: 10, position: [0, 0, 500] }}
style={{ width: c_width, color: "black" }}
>
<color attach="background" args={["black"]} />
<Html >
<Blocks handlePost={handlePost} />
</Html>
<ambientLight intensity={0.1} />
<directionalLight position={[0, 1, 1]} />
<Sphere days={days}/>
</Canvas>
{ hasRender && ( <PostItems /> )}
</>
);
}
function App() {
return (
<>
<Meshes />
</>
)
}

How to get OrbitControls ref

I am trying to access OrbitControls ref as follow:
const controlsRef = useRef<any>();
return (
<>
<Canvas
camera={{
position: [0, 0, 100],
up: [0, 0, 1],
}}
>
<OrbitControls makeDefault ref={controlsRef} />
<ambientLight />
<FakeSphere position={[0, 0, 0]} color="red" />
<FakeSphere position={[100, 100, 0]} color="blue" />
<Nav controls={controlsRef.current} />
</Canvas>
</>
);
Code:
https://codesandbox.io/s/cool-flower-f5qr0?file=/src/App.tsx:949-962
The ref is undefined. Any idea please?
Found a hint hehe (https://github.com/pmndrs/drei/issues/718). Cant get ref from outside Canvas.
For now, add a wrapper over OrbitControls to capture the ref. Not sure if any better solution or not.
const MyControls = (props: any) => {
const { setControls } = props;
const ref = useRef<any>();
useEffect(() => {
if (!ref.current) return;
setControls(ref.current);
}, ref.current);
return <OrbitControls makeDefault ref={ref} />;
};

Objects don't overlap when rotating

I'm trying to reproduce this animation (see below) with react-three-fiber. I'm still very new to this package and to three-js.
http://makesportmakebook.com/livres/.
I've been able to do create book shapes with meshLambertMaterial, as such:
function Book(props) {
const mesh = useRef();
useFrame(() => {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01
})
const bookCover = useLoader(TextureLoader, bookCoverImg)
const bookSpine = useLoader(TextureLoader, bookSpineImg)
const bookBack = useLoader(TextureLoader, bookBackImg)
const bookPages = useLoader(TextureLoader, bookPagesImg)
const bookPagesTexture = useLoader(TextureLoader, bookPagesTextureImg)
const bookPagesTopBottomTexture = useLoader(TextureLoader, bookPagesTopBottomTextureImg)
return (
<mesh
position={props.position}
ref={mesh}>
<boxBufferGeometry attach="geometry" args={
[
7, 10, 1.2, 4, 4, 1
]
} />
<meshLambertMaterial color={"0xffffff"} map={bookCover} />
<meshLambertMaterial map={bookSpine} />
<meshLambertMaterial map={bookBack} />
<meshLambertMaterial map={bookPages} />
<meshLambertMaterial map={texture_5} />
<meshLambertMaterial map={texture_6} />
</mesh>
)
};
Here's a code sandbox of my code so far: https://codesandbox.io/s/cocky-fast-61ndj
My question is: how can I avoid the overlap of these you can see here (below) but still keep the same parallel position we can see in the first example?
There are two ways that you could handle this. The first is to move the camera instead of the books. The second is that instead of moving the books separately, move them as a group. I suspect that this second method is what you want to do.
This was quick and dirty, but it works.
Create a function to house the two books. Put the two books in a group and rotate the group.
import React, { useRef, Suspense } from "react";
import { Canvas, useFrame, extend, useLoader } from "react-three-fiber";
import { OrbitControls, StandardEffects, draco } from "drei";
import { TextureLoader } from "three/src/loaders/TextureLoader.js";
extend({ OrbitControls });
function Bookshelf(props) {
const mesh = useRef();
useFrame(() => {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01;
});
return (
<group ref={mesh} position={[0, 0, 0]}>
<Suspense fallback={null}><Book position={[3, 0, 3]} /></Suspense>
<Suspense fallback={null}><Book position={[-3, 0, 0]} /></Suspense>
</group>
)
}
function Book(props) {
const bookCover = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732427/HEAD_PUBLISHING/book-cover.jpg"
);
const bookSpine = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732420/HEAD_PUBLISHING/book-back.jpg"
);
const bookBack = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732421/HEAD_PUBLISHING/book-side.jpg"
);
const bookPages = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732421/HEAD_PUBLISHING/book-side.jpg"
);
const texture_5 = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732427/HEAD_PUBLISHING/book-cover.jpg"
);
const texture_6 = useLoader(
TextureLoader,
"https://res.cloudinary.com/www-c-t-l-k-com/image/upload/v1607732421/HEAD_PUBLISHING/book-spine.jpg"
);
return (
<mesh position={props.position} >
<boxBufferGeometry attach="geometry" args={[7, 10, 1.2, 4, 4, 1]} />
<meshLambertMaterial color={"0xffffff"} map={bookCover} />
<meshLambertMaterial map={bookSpine} />
<meshLambertMaterial map={bookBack} />
<meshLambertMaterial map={bookPages} />
<meshLambertMaterial map={texture_5} />
<meshLambertMaterial map={texture_6} />
</mesh>
);
}
export default function App() {
let styling = {
width: "100vw",
height: "100vh",
position: "relative"
};
return (
<div style={{ position: "relative" }}>
<Canvas camera={{ position: [0, 0, 20] }} style={styling}>
<ambientLight intensity={0.3} />
<directionalLight intensity={1} />
<Suspense fallback={null}>
<Bookshelf>
</Bookshelf>
</Suspense>
<OrbitControls enableZoom={false} />
</Canvas>
</div>
);
}

Resources