React Three Fiber, How to track mouse movement - reactjs

I am trying to use three for the first time to level up my webpages a little so I'm following a guide on YouTube here so you can see what I'm trying to accomplish. https://www.youtube.com/watch?v=YK1Sw_hnm58 I'm at around 1:12:20. the only difference is I'm using react instead of plain JavaScript so I'm struggling to convert it since I've never used it before.
/* eslint-disable no-unused-vars */
import React, { useRef } from "react";
import { useFrame, useLoader } from "#react-three/fiber";
import { OrbitControls } from "#react-three/drei";
import * as THREE from "three";
import { useEffect } from "react";
import { useState } from "react";
const PlaneMesh = (params) => {
const paneRef = useRef();
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(false)
const { array } = paneRef.current.geometry.attributes.position
for (let i = 0; i < array.length; i += 3){
array[i + 2] = array[i + 2] + Math.random()
}
}, [loading])
const [MousePosition, setMousePosition] = useState({
left: 0,
top: 0
})
useFrame( state => {
let mouseX = state.mouse.x;
let mouseY = state.mouse.y;
console.log(mouseX)
});
return (
<>
{/* <ambientLight intensity={1} /> */}
<directionalLight color={0xFFFFFF} position={[0, 0, 1]} intensity={1} />
{/* <pointLight color="#FFFFFF" position={[0, 0, 2]} intensity={1} /> */}
<mesh ref={paneRef} position={[0,0, -5]}>
<planeGeometry args={[100, 100, 50, 50]} />
<meshPhongMaterial
color={0x000044}
side={THREE.DoubleSide}
flatShading={true}
/>
{/* <OrbitControls
enableZoom={true}
enablePan={true}
enableRotate={true}
zoomSpeed={0.6}
panSpeed={0.5}
rotateSpeed={0.4}
/> */}
</mesh>
</>
);
};
export default PlaneMesh;
This is my full file right now. I cant work out how to add the mouse move event listener and have it work as any method I have tried either only runs once or only runs when I click on the page rather than when I move the mouse. This is the closest I have been able to get it as it constantly prints the value but the mouse coordinates don't update unless I click. Any advice is greatly appreciated.

I ended up fixing it by adding a window event listener and subtracting the bounding box to get the mouse position over the element.
const section = document.getElementById("planeSection");
section.addEventListener("mousemove", (event) => {
var rect = container.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / window.innerWidth) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / window.innerHeight) * 2 + 1;
});

Related

React Three Fiber not appearing as background in mobile browsers

I'm attempting to use a design built with React-Three-Fiber as the background of my application.
The canvas appears to show and run the animation on chrome and firefox dev tools in the desktop browsers but is failing to show on mobile. I've tried firefox, chrome, and safari using my iPhone and a few other phones but still no prevail.
It is a fairly basic animation so it shouldn't be too much for the browser to handle.
I've included the proper meta tag in the index.html, found within the documentation.
The code below that is contained within its own component.
<meta
name="viewport"
content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
/>
import * as THREE from "three";
import React, { Suspense, useMemo, useRef } from "react";
import styled from "styled-components";
import { Canvas, useFrame } from "#react-three/fiber";
import vertexShader from "./Shaders/VertexShader";
import fragmentShader from "./Shaders/FragmentShader";
export default function Animation() {
// universal points ref
const points = useRef();
const radius = 2;
const Planet = () => {
const distance = 2;
const count = 20000;
const particlesPosition = useMemo(() => {
// create the float32 array to store the point positions.
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = THREE.MathUtils.randFloatSpread(360);
const phi = THREE.MathUtils.randFloatSpread(360);
// Generate random values for x, y, z
let x = distance * Math.sin(theta) * Math.cos(phi);
let y = distance * Math.sin(theta) * Math.sin(phi);
let z = distance * Math.cos(theta);
// add each value to the array
positions.set([x, y, z], i * 3);
}
return positions;
}, [count]);
const uniforms = useMemo(
() => ({
uTime: {
value: 0.8,
},
uRadius: {
value: radius,
},
}),
[]
);
useFrame((state) => {
const { clock } = state;
// adding 100 starts the animation at 100s ahead of animation start time.
points.current.material.uniforms.uTime.value = clock.elapsedTime + 110;
});
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute // bufferAttribute will allow you to set the position attribute of the geometry.
attach="attributes-position" // this will allow the data being fed to bufferAttribute to be accessable under the position attribute.
count={particlesPosition.length / 3} //number of particles
array={particlesPosition}
itemSize={3} // number of values from the particlesPosition array associated with one item/vertex. It is set to 3 becuase position is based on X, Y, Z
/>
</bufferGeometry>
<shaderMaterial
depthWrite={false}
fragmentShader={fragmentShader}
vertexShader={vertexShader}
uniforms={uniforms}
/>
</points>
);
};
return (
//Canvas takes place of all the boilerplate to get three.js running with react.
<Wrapper>
<Canvas className="canvas">
<ambientLight intensity={0.5} />
<Suspense>
<Planet />
</Suspense>
</Canvas>
</Wrapper>
);
}
const Wrapper = styled.div`
position: relative;
background: inherit;
height: 100vh;
width: 100vw;
`;

can primitive tag react three fiber only be used once for the same model

I have an iphone model and i want to load it to my react project. I use primitive tag from react three fiber R3F which is not a real object that we will be able to see in the scene, but it’s a container supported by R3F. I want to display 2 iphone that'w why i use primitive tag twice for the same model but the first primitive tag doesn't load the model and only the second one. Is is because i only can use it once for the same model? Should i copy the model and
import { useGLTF, OrbitControls, useHelper, useScroll, Html } from '#react-three/drei'
import * as THREE from 'three'
import { useFrame } from '#react-three/fiber'
import { useRef, forwardRef } from 'react'
export default function App() {
const model = useGLTF('/iphone_14_pro_max.glb');
const model2 = useGLTF('/iphone_14_pro_max.glb');
const scroll = useScroll()
const iphone = useRef()
const first = useRef()
useFrame((state, delta) => {
//kapan pake range kapan pake visible?
const r1 = scroll.range(0 / 4, 1 / 4)
const r2 = scroll.range(1 / 4, 1 / 4)
const r3 = scroll.range(1 / 4, 1 / 4)
iphone.current.rotation.y = (Math.PI * 0.67) + r1 + r2 + r3 * 3
})
return(
<>
<directionalLight position={ [ 1, 2, 3 ] } intensity={ 1.5 } />
<ambientLight intensity={ 0.5 } />
<primitive ref={iphone} object={model.scene} scale={20} position={ [ 0, -1.5, 0] }>
<Tagline ref={first} left="PRO" right="BEYOND"></Tagline>
</primitive>
<primitive object={model2.scene} scale={15} position={ [ 1, 1, 1] } rotation-y={-45}/>
</>
)
}
const Tagline = forwardRef(({ left, right, ...props }, ref) => {
return (
<Html occlude ref={ref} className="homepage" center {...props}>
<h1>{left}</h1>
<h1>{right}</h1>
</Html>
)
})
I already have an answer that primitive tag can only load one model for each tag. If you want to load the same model then you need to copy it with different name and load it agaian.

What should I add/change to make the model look like the sample?

I just started learning ThreeJS and Fiber.
I loaded the 3D model and added the lights, but it still doesn't look the way I want it to.
Could you tell me what needs to be added so the model looks like the example (please see below)?
Current result:
What I need:
Code:
import {Canvas, useFrame, useThree} from '#react-three/fiber';
import {useGLTF} from '#react-three/drei';
import {useRef} from 'react';
const Cell = () => {
const {nodes, materials} = useGLTF(`${process.env.PUBLIC_URL}/cell.glb`);
const model = useRef();
const {clock} = useThree();
useFrame(() => {
const t = clock.getElapsedTime() / 2;
model.current.rotation.set(t, t, t);
});
return (
<group scale={1} ref={model}>
<mesh
name="Sphere001"
geometry={nodes.Sphere001.geometry}
material={materials.red}
morphTargetDictionary={nodes.Sphere001.morphTargetDictionary}
morphTargetInfluences={nodes.Sphere001.morphTargetInfluences}
/>
<mesh
name="Sphere001_1"
geometry={nodes.Sphere001_1.geometry}
material={materials['GLB GLASS']}
morphTargetDictionary={nodes.Sphere001_1.morphTargetDictionary}
morphTargetInfluences={nodes.Sphere001_1.morphTargetInfluences}
/>
</group>
);
};
const CellScene = () => {
return (
<Canvas orthographic camera={{zoom: 75, position: [0, 0, 500]}}>
<hemisphereLight intensity={0.4} />
<spotLight position={[7, 13, 15]} angle={0.3} penumbra={1} intensity={4} />
<Cell />
</Canvas>
);
};
Added some new lights to the scene
Updated materials colors & opacity (directly in the useMemo block before useFrame)

Simple testing example of canvas element using Jest

I am trying to create a jest test for the example found in this sandbox:
https://codesandbox.io/s/r3f-basic-demo-forked-297k3?file=/src/App.js
Basically I would like to test the onClick functionality on each box. I have already come across with the fact that jest tests don't run in a real browser. Jest uses jsdom for mocking the necessary parts of the DOM. Consequently I may need canvas support for jsdom, yet I am not quite sure exactly what to do.
App.js
import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from 'react-three-fiber'
function Box(props) {
// This reference will give us direct access to the mesh
const mesh = useRef()
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false)
const [active, setActive] = useState(false)
// Rotate mesh every frame, this is outside of React without overhead
useFrame(() => {
mesh.current.rotation.x = mesh.current.rotation.y += 0.01
})
return (
<mesh
{...props}
ref={mesh}
scale={active ? [1.5, 1.5, 1.5] : [1, 1, 1]}
onClick={(e) => setActive(!active)}
onPointerOver={(e) => setHover(true)}
onPointerOut={(e) => setHover(false)}>
<boxBufferGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
export const App = () => {
return (
<>
<Canvas>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<pointLight position={[-10, -10, -10]} />
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</Canvas>
</>
)
}
It's not much of an answer but I found this article useful: https://www.valentinog.com/blog/canvas/.
The suggestion is for proper testing of a canvas element one should not be using Jest (or any unit test) but instead, use some sort of e2e testing so that it can actually render the canvas.
I was able to get some smaller tests working in jest with the following:
Firstly render your page normally using jest. You can either give your canvas a test-id or (as in my case) put a div around your canvas and give the div a test-id.
I ended up with the following:
let canvasContainer = getByTestId("canvas-container");
let canvas = canvasContainer.getElementsByTagName("canvas")[0];
let canvasContext = canvas.getContext("2d");
In either case, your aim is to get the canvas' elements context.
From there you can inspect it with canvasContext.__getDrawCalls()
This will give you some information on all of the "drawCalls" that have been done so far (such as drawing lines or rectangles or what have you).
It takes a little of a bit of inspection into the __getDrawCalls but you should find that every call so far is included.
I wrote some funcitons to help me get the exact details I wanted:
const getLineInfo = (lineInfo) => {
if (lineInfo.type !== "stroke") {
return "not a line";
}
let lineInfoDetail = lineInfo.props.path[3];
return {
lineStart: {
x: lineInfoDetail.transform[4],
y: lineInfoDetail.transform[5],
},
lineEnd: {
x: lineInfoDetail.transform[4] + lineInfoDetail.props.x,
y: lineInfoDetail.transform[5] + lineInfoDetail.props.y,
},
};
};
const getRectInfo = (rectInfo) => {
if (rectInfo.type !== "fill") {
return "not a rect";
}
let rectInfoDetail = rectInfo.props.path[1];
return {
rectStart: {
x: rectInfoDetail.transform[4],
y: rectInfoDetail.transform[5],
},
rectEnd: {
x: rectInfoDetail.transform[4] + rectInfoDetail.props.width,
y: rectInfoDetail.transform[5] + rectInfoDetail.props.height,
},
};
};
So for example if I know that the second draw is a line I can do the following:
expect(getLineInfo(canvasContext.__getDrawCalls()[1])).toStrictEqual({
lineStart: { x: 150, y: 250 },
lineEnd: { x: 150, y: 350 },
});
This is testing that that element is a line with a start point of { x: 150, y: 250 } and an endpoint of { x: 150, y: 350 }
The issue with this though is that you can't trigger click and drag events of the canvas elements within jest and therefore using jest to test canvas seems to be very limited.
Edit:
It looks like https://yonatankra.com/how-to-test-html5-canvas-with-jest/ gives another explanation.

How to execute two animations sequentially, using react-spring?

I tried chaining two springs (using useChain), so that one only starts after the other finishes, but they are being animated at the same time. What am I doing wrong?
import React, { useRef, useState } from 'react'
import { render } from 'react-dom'
import { useSpring, animated, useChain } from 'react-spring'
function App() {
const [counter, setCounter] = useState(0)
const topRef = useRef()
const leftRef = useRef()
const { top } = useSpring({ top: (window.innerHeight * counter) / 10, ref: topRef })
const { left } = useSpring({ left: (window.innerWidth * counter) / 10, ref: leftRef })
useChain([topRef, leftRef])
return (
<div id="main" onClick={() => setCounter((counter + 1) % 10)}>
Click me!
<animated.div id="movingDiv" style={{ top, left }} />
</div>
)
}
render(<App />, document.getElementById('root'))
Here's a codesandbox demonstrating the problem:
https://codesandbox.io/s/react-spring-usespring-hook-m4w4t
I just found out that there's a much simpler solution, using only useSpring:
function App() {
const [counter, setCounter] = useState(0)
const style = useSpring({
to: [
{ top: (window.innerHeight * counter) / 5 },
{ left: (window.innerWidth * counter) / 5 }
]
})
return (
<div id="main" onClick={() => setCounter((counter + 1) % 5)}>
Click me!
<animated.div id="movingDiv" style={style} />
</div>
)
}
Example: https://codesandbox.io/s/react-spring-chained-animations-8ibpi
I did some digging as this was puzzling me as well and came across this spectrum chat.
I'm not sure I totally understand what is going on but it seems the current value of the refs in your code is only read once, and so when the component mounts, the chain is completed instantly and never reset. Your code does work if you put in hardcoded values for the two springs and then control them with turnaries but obviously you are looking for a dynamic solution.
I've tested this and it seems to do the job:
const topCurrent = !topRef.current ? topRef : {current: topRef.current};
const leftCurrent = !leftRef.current ? leftRef : {current: leftRef.current};
useChain([topCurrent, leftCurrent]);
It forces the chain to reference the current value of the ref each time. The turnary is in there because the value of the ref on mount is undefined - there may be a more elegant way to account for this.

Resources