Three.js doesn't play animation in React - three.js

I load GLTF model downloaded from Sketchfab in React app. Model loads perfectly well, but animation doesn`t play at all. I tried different approaches. Any ideas?
function Model({ url }) {
const model = useRef()
const { scene, animations } = useLoader(GLTFLoader, url)
const [mixer] = useState(() => new THREE.AnimationMixer())
useEffect(() => void mixer.clipAction(animations[0], group.current).play(), [])
return(
<primitive
ref={model}
object={scene}
/>
)
}

Solution
I had to use node instead of scene and select "RootNode" (console.log node and choose what seemed to me the main node, the model contained a dozen of nodes)
Update mixer by frames with useFrame
Apply animation to the model (a bit updated)
Working code:
function Model({ url }) {
const group = useRef()
const { nodes, scene, materials, animations } = useLoader(GLTFLoader, url)
const actions = useRef()
const [mixer] = useState(() => new THREE.AnimationMixer())
useFrame((state, delta) => mixer.update(delta))
useEffect(() => {
actions.current = { idle: mixer.clipAction(animations[0], group.current) }
actions.current.idle.play()
return () => animations.forEach((clip) => mixer.uncacheClip(clip))
}, [])
return(
<group ref={group} dispose={null}>
<primitive
ref={group}
name="Object_0"
object={nodes["RootNode"]}
/>
</group>
)
}
Now it works

You need to call mixer.update(delta) in useFrame inside your component:
import { useFrame } from 'react-three-fiber'
function Model({url}) {
.
.
.
useFrame((scene, delta) => {
mixer?.update(delta)
})
.
.
.

Put your glb file inside public filer.
import { Suspense, useEffect, useRef } from "react";
import { useAnimations, useGLTF } from "#react-three/drei";
export const Character = (props: any) => {
const ref = useRef() as any;
const glf = useGLTF("assets/Soldier.glb");
const { actions } = useAnimations(glf.animations, ref);
useEffect(() => {
actions.Run?.play();
});
return (
<Suspense fallback={null}>
<primitive
ref={ref}
object={glf.scene}
/>
</Suspense>
);
};

Related

React Three Fiber / Orbit Controls

Hello
I made a component for orbitControls which doesn't work :
extend({ OrbitControls })
const OrbitA = () => {
const { camera, gl } = useThree()
return (
<OrbitControls
args={[camera, gl.domElement]}
/>
)
}
I have found a workaroud with useEffect :
extend({ OrbitControls })
const Orbit = () => {
const { camera, gl } = useThree()
useEffect(() => {
const controls = new OrbitControls(camera, gl.domElement)
controls.minDistance = 3
controls.maxDistance = 20
return () => {
controls.dispose()
}
}, [camera, gl])
return null
}
But in the last case, i don't know how to pass the attach parameter like I would have done it in the first case :
return (
<OrbitControls
attach='orbitControls'
args={[camera, gl.domElement]}
/>
)
}
Any help ?

how to do simple collision detection in react three fiber

I don't want to use any libraries. I just want simple collision detection. If it's a raycast in the direction the object is moving I am happy with that.
I don't know where to begin to write code for this.
This is called a Raycaster. It can be implemented in react-three-fiber by using a hook:
import { useThree } from '#react-three/fiber'
import { useMemo } from 'react'
import { Object3D, Raycaster, Vector3 } from 'three'
export const useForwardRaycast = (obj: {current: Object3D|null}) => {
const raycaster = useMemo(() => new Raycaster(), [])
const pos = useMemo(() => new Vector3(), [])
const dir = useMemo(() => new Vector3(), [])
const scene = useThree(state => state.scene)
return () => {
if (!obj.current)
return []
raycaster.set(
obj.current.getWorldPosition(pos),
obj.current.getWorldDirection(dir))
return raycaster.intersectObjects(scene.children)
}
}
Here's a codesandbox, check out the console for when the array contains hits.
TLDR implementation:
const Comp = () => {
const ref = useRef(null)
const raycast = useForwardRaycast(ref)
useFrame((state, delta) => {
ref.current.rotation.y += 1 * delta
const intersections = raycast()
console.log(intersections.length)
//...do something here
})
return (
<mesh ref={ref}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
)
}

Implement force-directed graph in next js

I'm trying to create a force-directed graph for mapping the interactions between courses in an institution. Using Next JS + TypeScript for my frontend.
Have tried several attempts at charting this out using react-flow, dagre, vis-network but am getting either a window : undefined error or just the damn alignment of nodes not being force-directed inside the box I have defined.
Before I move on with implementing d3-force right out of the box, can someone please recommend any alternative solution to this ?
Here's what my nodes & edges look like :
Here's my attempt with reactflow & dagre :
import React, { useCallback, useEffect, useState } from 'react';
import ReactFlow, {
addEdge,
useNodesState,
useEdgesState,
Edge,
Node,
Position,
ConnectionLineType,
ReactFlowProvider,
MiniMap,
Controls,
Background,
} from 'react-flow-renderer';
import dagre from 'dagre';
import { NodeData, useCourseNodes } from 'src/hooks/useCourseNodes';
import { useDepartment } from '#contexts/ActiveDepartmentContext';
import {
useUpdateActiveCourse,
} from '#contexts/ActiveCourseContext';
import { useDrawerOpen, useUpdateDrawerOpen } from '#contexts/DrawerContext';
const dagreGraph = new dagre.graphlib.Graph({directed:true});
dagreGraph.setDefaultEdgeLabel(() => ({}));
const nodeWidth = 10.2;
const nodeHeight = 6.6;
const getLayoutedElements = (
nodes: Node[],
edges:Edge[],
) => {
// const isHorizontal = direction === 'LR';
dagreGraph.setGraph( {width:900, height:900, nodesep:20, ranker:'longest-path' });
nodes.forEach((node: Node) => {
dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
});
edges.forEach((edge: Edge) => {
dagreGraph.setEdge(edge.source, edge.target);
});
dagre.layout(dagreGraph);
nodes.forEach((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
// node.targetPosition = isHorizontal ? Position.Left : Position.Top;
// node.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
node.targetPosition = Position.Top;
node.sourcePosition = Position.Bottom;
// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
node.position = {
x: nodeWithPosition.x - nodeWidth / 2,
y: nodeWithPosition.y - nodeHeight / 2,
};
console.log(nodeWithPosition)
return node;
})
return { layoutedNodes:nodes, layoutedEdges:edges };
};
const LayoutFlow = () => {
const activeDept = useDepartment();
const setActiveCourse = useUpdateActiveCourse();
const setDrawerOpen = useUpdateDrawerOpen()
const drawerOpen = useDrawerOpen();
const {courseList, edgeList} = useCourseNodes()
const { layoutedNodes, layoutedEdges } = getLayoutedElements(courseList, edgeList)
const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes);
const [edges, setEdges,onEdgesChange] = useEdgesState(layoutedEdges);
console.log(layoutedNodes)
const onConnect = useCallback(
(params) =>
setEdges((eds) =>
addEdge({ ...params, type: ConnectionLineType.SimpleBezier, animated: true }, eds),
),
[],
);
// ? For switching between layouts (horizontal & vertical) for phone & desktop
// const onLayout = useCallback(
// (direction) => {
// const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
// nodes,
// edges,
// direction
// );
// setNodes([...layoutedNodes]);
// setEdges([...layoutedEdges]);
// },
// [nodes, edges]
// );
// ? M1 - for force re-rendering react flow graph on state change - https://github.com/wbkd/react-flow/issues/1168
// ? M2 - (Applied currently in useEffect block below)for force re-rendering react flow graph on state change - https://github.com/wbkd/react-flow/issues/1168
useEffect(() => {
const {layoutedNodes, layoutedEdges} = getLayoutedElements(courseList, edgeList)
setNodes([...layoutedNodes]);
setEdges([...layoutedEdges]);
}, [activeDept, drawerOpen]);
return (
<div style={{ width: '100%', height: '100%' }} className="layoutflow">
<ReactFlowProvider>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
onNodeClick={(e: React.MouseEvent, node: Node<NodeData>) => {
e.preventDefault();
// created a copy of the node since we're only deleting the "label" property from the node object to conveniently map the rest of the data to the "data" property of the active course
const nodeCopy = JSON.parse(JSON.stringify(node))
const { data } = nodeCopy;
const { label } = data
delete data.label
setActiveCourse({
courseId: label,
data
});
setDrawerOpen(true);
}}
connectionLineType={ConnectionLineType.SimpleBezier}
fitView
>
<MiniMap />
<Controls />
{/* <Background /> */}
</ReactFlow>
</ReactFlowProvider>
<div className="controls">
{/* <button onClick={() => onLayout('TB')}>vertical layout</button>
<button onClick={() => onLayout('LR')}>horizontal layout</button> */}
</div>
</div>
);
};
export default LayoutFlow;
Here's my attempt with vis-network : (note : I did slightly modify edges to have from-to instead of source-target when working with this)
import { useCourseNodes } from "#hooks/useCourseNodes";
import React, { useEffect, useRef } from "react";
import { Network } from "vis-network";
const GraphLayoutFour: React.FC = () => {
const {courseList:nodes, edgeList:edges} = useCourseNodes()
// Create a ref to provide DOM access
const visJsRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const network =
visJsRef.current &&
new Network(visJsRef.current, { nodes, edges } );
// Use `network` here to configure events, etc
}, [visJsRef, nodes, edges]);
return typeof window !== "undefined" ? <div ref={visJsRef} /> : <p>NOT AVAILABLE</p>;
};
export default GraphLayoutFour;
Here's my attempt with react-sigma
import React, { ReactNode, useEffect, useState } from "react";
import ReactDOM from "react-dom";
import { UndirectedGraph } from "graphology";
import erdosRenyi from "graphology-generators/random/erdos-renyi";
import randomLayout from "graphology-layout/random";
import chroma from "chroma-js";
import { Attributes } from "graphology-types";
import { ControlsContainer, ForceAtlasControl, SearchControl, SigmaContainer, useLoadGraph, useRegisterEvents, useSetSettings, useSigma, ZoomControl } from "react-sigma-v2/lib/esm";
interface MyCustomGraphProps {
children?: ReactNode;
}
export const MyCustomGraph: React.FC<MyCustomGraphProps> = ({ children }) => {
const sigma = useSigma();
const registerEvents = useRegisterEvents();
const loadGraph = useLoadGraph();
const setSettings = useSetSettings();
const [hoveredNode, setHoveredNode] = useState<any>(null);
useEffect(() => {
// Create the graph
const graph = erdosRenyi(UndirectedGraph, { order: 100, probability: 0.2 });
randomLayout.assign(graph);
graph.nodes().forEach(node => {
graph.mergeNodeAttributes(node, {
label: "label",
size: Math.max(4, Math.random() * 10),
color: chroma.random().hex(),
});
});
loadGraph(graph);
// Register the events
registerEvents({
enterNode: event => setHoveredNode(event.node),
leaveNode: () => setHoveredNode(null),
});
}, []);
useEffect(() => {
setSettings({
nodeReducer: (node, data) => {
const graph = sigma.getGraph();
const newData: Attributes = { ...data, highlighted: data.highlighted || false };
if (hoveredNode) {
//TODO : add type safety
if (node === hoveredNode || (graph as any).neighbors(hoveredNode).includes(node)) {
newData.highlighted = true;
} else {
newData.color = "#E2E2E2";
newData.highlighted = false;
}
}
return newData;
},
edgeReducer: (edge, data) => {
const graph = sigma.getGraph();
const newData = { ...data, hidden: false };
//TODO : add type safety
if (hoveredNode && !(graph as any).extremities(edge).includes(hoveredNode)) {
newData.hidden = true;
}
return newData;
},
});
}, [hoveredNode]);
return <>{children}</>;
};
ReactDOM.render(
<React.StrictMode>
<SigmaContainer>
<MyCustomGraph />
<ControlsContainer position={"bottom-right"}>
<ZoomControl />
<ForceAtlasControl autoRunFor={2000} />
</ControlsContainer>
<ControlsContainer position={"top-right"}>
<SearchControl />
</ControlsContainer>
</SigmaContainer>
</React.StrictMode>,
document.getElementById("root"),
);
import { useCourseNodes } from '#hooks/useCourseNodes'
import dynamic from 'next/dynamic';
import React from 'react'
import { useSigma } from 'react-sigma-v2/lib/esm';
const GraphLayoutThree = () => {
const isBrowser = () => typeof window !== "undefined"
const { courseList, edgeList } = useCourseNodes()
const sigma = useSigma();
if(isBrowser) {
const SigmaContainer = dynamic(import("react-sigma-v2").then(mod => mod.SigmaContainer), {ssr: false});
const MyGraph = dynamic(import("./CustomGraph").then(mod => mod.MyCustomGraph), {ssr: false});
return (
<SigmaContainer style={{ height: "500px", width: "500px" }} >
<MyGraph/>
</SigmaContainer>
)
}
else return (<p>NOT AVAILABLE</p>)
}
export default GraphLayoutThree
Here's my attempt with react-force-graph (note : I did slightly modify edges to have from-to instead of source-target when working with this)
import dynamic from "next/dynamic";
const GraphLayoutTwo = () => {
const isBrowser = () => typeof window !== "undefined"
if(isBrowser) {
const MyGraph = dynamic(import("./CustomGraphTwo").then(mod => mod.default), {ssr: false});
return (
<MyGraph/>
)
}
else return (<p>NOT AVAILABLE</p>)
}
export default GraphLayoutTwo
import dynamic from "next/dynamic";
const GraphLayoutTwo = () => {
const isBrowser = () => typeof window !== "undefined"
if(isBrowser) {
const MyGraph = dynamic(import("./CustomGraphTwo").then(mod => mod.default), {ssr: false});
return (
<MyGraph/>
)
}
else return (<p>NOT AVAILABLE</p>)
}
export default GraphLayoutTwo
To implement something similar we use react-graph-vis inside a nextjs application.
If you have the window is not defined error, just wrap the component and import it with dynamic
// components/graph.tsx
export const Graph = ({data, options, events, ...props}) => {
return (
<GraphVis
graph={transformData(data)}
options={options}
events={events}
/>
)
}
then in your page
// pages/index.ts
const Graph = dynamic(() => (import("../components/graph").then(cmp => cmp.Graph)), { ssr: false })
const Index = () => {
return (
<>
<Graph data={...} .... />
</>
)
}
export default Index;

React-Redux Maximum call stack size exceeded when adding object to list

I am creating a simple game react app and when I try to add a player to my players list it seems to be creating an infinite loop and I'm not sure why. I tried to use useEffect to render the player list on initial load but that didn't help so I removed it for now to simplify. Any ideas what I could be doing differently?
App.js
import React, { useEffect } from 'react'
import {useDispatch, useSelector} from 'react-redux';
import './App.css';
import {setPlayerName, increaseCurrentPlayerId, decreaseCurrentPlayerId, addPlayerToList} from './redux/reducers/playerReducer';
function App() {
const dispatch = useDispatch()
const playerName = useSelector(state => state.playerName);
const playerList = useSelector(state => state.playerList);
const currentPlayerId = useSelector(state => state.currentPlayerId)
// dispatch(addPlayerToList('Test'))
const addPlayer = (player) => {
dispatch(addPlayer(player))
dispatch(setPlayerName(''))
}
const renderPlayerList = () => {
if (playerList.length < 1) {
return (
<div>
No Players
</div>
)
} else {
return (
playerList.map(p =>
<p>p.name</p>
)
)
}
}
return (
<div className="App">
<input
type='text'
name='playerName'
onChange={({ target }) => dispatch(setPlayerName(target.value))}
required
/>
Name<br/>
<button type='button'
onClick={() => addPlayer(playerName)}
>
Add Player</button> <br />
<br />
</div>
);
}
export default App;
playerReducer.js
export const playerNameReducer = (state = '', action) => {
switch (action.type) {
case 'SET_PLAYER_NAME':
return action.data;
default:
return state;
}
};
export const playerListReducer = (state = null, action) => {
switch (action.type) {
case 'ADD_PLAYER':
return [...state, action.data];
default:
return state;
}
};
Action Creators
export const setPlayerName = playerName => {
return {
type: 'SET_PLAYER_NAME',
data: playerName,
};
};
export const addPlayerToList = player => {
return {
type: 'ADD_PLAYER',
data: player,
};
};
addPlayer calls itself
const addPlayer = (player) => {
dispatch(addPlayer(player))
}

HOC is triggering while I use it on container

Could anyone can help me, because i can't understand why HOC is causing ifnite loop while I am using it on container. That's my container:
class UserContainer extends Component {
componentDidMount() {
const { onValuePassedThroughParams, match } = this.props;
const { user } = match.params;
if (user !== '') {
onValuePassedThroughParams(user);
}
}
render() {
const { user } = this.props;
return (
<UserView user={user} />
);
}
}
const UserContainerWithLoading = LoaderHOC(UserContainer);
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({
onValuePassedThroughParams: val => dispatch(takeUserNameAndFetchData(val)),
});
export default
withRouter(
connect(mapStateToProps, mapDispatchToProps)(UserContainerWithLoading),
);
my HOC:
const LoaderHOC = WrappedComponent => props =>{
return(
props.user.isLoading
? <div className={styles.ldsHourglass} />
: <WrappedComponent {...props} />
)};
and also a thunk:
function fetchData(url) {
return (
fetch(url)
.then(result => result.json())
);
}
export default function takeUserNameAndFetchData(name) {
const userInfoUrl = `https://api.github.com/users/${name}`;
const userRepoUrl = `https://api.github.com/users/${name}/repos`;
return (dispatch) => {
dispatch(fetchUserBegin());
Promise.all([
fetchData(userInfoUrl),
fetchData(userRepoUrl),
])
.then(([info, repos]) => {
dispatch(fetchUserInfoSucces(info));
dispatch(fetchUserReposSuccess(repos));
dispatch(fetchUserLoadingEnd());
})
.catch((err) => {
console.log(`ERROR!${err}`);
dispatch(fetchUserError());
});
};
}
When I use an HOC on my View component everything is fine and it's stop geting data from the server, but when I am using it on container there is always an infinite loop. Do you have any advice for me?

Resources