React-Three-Fiber / How to replace textures of gltf file? - react-three-fiber

Hello
I would like to replace all textures of my gltf file inside my react three fiber component.
What I did :
1/ I have used https://github.com/pmndrs/gltfjsx to generate a RTF file from my gltf
2/ I have passed the props (list of the files) to my react three fiber components.
export default function HeartGl(props) {
const group = useRef();
const { nodes, materials, animations } = useGLTF("/Heart.gltf");
const { actions } = useAnimations(animations, group);
return (
<group ref={group} {...props} dispose={null}>
{props.images.map((index, i) => {
console.log(`./images/${myjson.images[index].name}`)
} )}
<group name="Scene">
<mesh name="myPlane263Shape_Mash_heart_instancer_1" geometry=
{nodes.myPlane263Shape_Mash_heart_instancer_1.geometry} material= materials.aiStandardSurface3}
position={[-1.95, 11.86, -35.32]} rotation={[1.91, 0.91, -1.28]} scale={0.07} />
the tag <mesh ...> is duplicated 38 times (same number as the props.image ) as they are the ones I need to change.
I saw there is possibility to use gltf.scene.traverse( function( object ) { but I don't get how to use it with my configuration

Related

Scaling textures on a gltfjsx import model

I'm completely new to this, and I don't know how to scale my texture to make it look smaller. it is a seamless texture. The three methods that come from materials do work well but the ones that have to do with texture don't do anything, surely it will be silly, but I'm starting to program and I don't know how to do it.
<mesh geometry={nodes.BON_GRAVA.geometry}
material={materials.BON_GRAVA}
material-map={useTexture("Gravel_001_BaseColor.jpg")}(works)
material-metalness={0.0} (works)
material-roughness={1.0} (works)
material-roughnessMap={useTexture("Gravel_001_Roughness.jpg")} (works)
material-normalMap={useTexture("Gravel_001_Normal.jpg")} (works)
material-aoMap={useTexture("Gravel_001_AmbientOcclusion.jpg")} (works)
This is me trying to do something, sorry
I've been trying .repeat, .offset, .wrapS but I don't know how the syntax for THREE methods works since it's a file gltfjsx + react
Call the useTexture hook at the top, then you can modify the Texture's (when the component mounts) with a useEffect hook: (be sure to set the needsUpdate flag to true on the Texture after any changes)
export function Component() {
const color = useTexture("Gravel_001_BaseColor.jpg");
const roughness = useTexture("Gravel_001_Roughness.jpg");
const normal = useTexture("Gravel_001_Normal.jpg");
const ao = useTexture("Gravel_001_AmbientOcclusion.jpg");
useEffect(() => {
color.repeat = new Vector2(1, 1);
color.wrapS = RepeatWrapping;
color.wrapT = RepeatWrapping;
color.needsUpdate = true; // don't forget this!
}, [color])
return (
<mesh
geometry={nodes.BON_GRAVA.geometry}
material={materials.BON_GRAVA}
material-map={color}
material-metalness={0.0}
material-roughness={1.0}
material-roughnessMap={roughness}
material-normalMap={normal}
material-aoMap={ao}
/>
)
}

How to Perform Inplace Animations in React Three Fiber

I imported a 3D model from Mixamo with some animations and was wondering how to perform does animations in place?
The GLTF file I created from npx gltfjsx:
const { nodes, materials, animations } = useGLTF("/Mannequin.glb");
const { actions } = useAnimations(animations, heroRef);
return (
<>
<group ref={heroRef} dispose={null}>
<group rotation={[Math.PI / 2, 0, 0]} scale={0.01}>
<primitive object={nodes.mixamorig1Hips} />
<skinnedMesh
geometry={nodes.Ch36.geometry}
material={materials.Ch36_Body}
skeleton={nodes.Ch36.skeleton}
/>
</group>
{/* <gridHelper args={[25, 25]}/> */}
</group>
</>
);
}
useGLTF.preload("/Mannequin.glb");
For example, I would like the model to perform the run animation without changing locations (running on the spot) so that if I can perform user controls that translate the model and with the animation looping, it would look like they were actually running.
Are there any ways to do this? I have searched and could not find anything.
useEffect(() => {
console.log(actions) // find out the name of your action
actions.someAction.play()
});
This will make the animation play in place, if it was exported like that from mixamo, there is a setting.
Then you can put a around your mesh and make it move based on your control
I couldn't use console.log(actions) to see available actions as they were undefined and I had {} in console. My code was:
const { actions } = useAnimations(animations, group);
useEffect(() => {
console.log(actions);
}, [actions]);
So I digged inside into useAnimations source code and saw two things:
actions object is not being updated, only it's properties, so useEffect doesn't trigger when ref updates.
if ref.current is undefined (as for first render) properties are undefined. that's why I saw {} in console.
So I changed my code to:
const { actions } = useAnimations(animations, group);
useEffect(() => {
console.log(Object.keys(actions));
}, [actions]);
That's how I saw that my animation names were: ['Female_Idle', 'Female_Talk']
And
const { actions } = useAnimations(animations, group);
const action = actions['Female_Idle'];
useEffect(() => {
if (action) action.play();
}, [action]);
In order to play action, when ref updates. That did the trick.

React-Three-Fiber Animation Resets on Scroll

I created a low poly water animation using three.js and react-three-fiber. The animation begins to play when my webpage is loaded but as you start to scroll down to view the other content on my webpage, my animation resets and begins to start again.
PolyWater is just a component I created to make the low poly water using vertices.
The SeaScene is exported to a Home component that merges the rest of my components together.
My Home component is being Rendered in the App.js file in react using Router
SeaScene.js
import React, {useRef} from 'react'
import {Canvas, extend, useFrame, useThree} from "react-three-fiber"
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls"
import PolyWater from "./PolyWater/PolyWater";
import './SeaScene.css'
extend({OrbitControls})
const Controls = () => {
const orbitRef = useRef();
const {camera, gl} = useThree();
useFrame(() => {
orbitRef.current.update()
camera.position.set(25, 12.5, -20)
camera.rotation.set(-1.5, 0, 0)
})
return (
<orbitControls
args={[camera, gl.domElement]}
ref={orbitRef}
/>
)
}
const SeaScene = () => {
return (
<section id="home" className="home-section">
<Canvas>
<ambientLight intensity={0.2}/>
<directionalLight color={0xffffff} position={[0, 50, 30]}/>
<Controls/>
<PolyWater/>
</Canvas>
</section>
)
}
Home.js
class Home extends Component {
render() {
return (
<div>
<SeaScene/>
<About/>
<Work/>
<Footer/>
</div>
);
}
}
App.js
class App extends Component {
render() {
return (
<Router>
<div>
<section>
<NavBar/>
<Switch>
<Route exact path='/' component={Home}/>
</Switch>
</section>
</div>
</Router>
);
};
}
Link to my working code: https://github.com/NikAtNight/waterportfolio/blob/master/homepage/src/components/MainAnimation/PolyWater/PolyWater.js
I found the fix myself. It was on react-three-fibers github just didn't know that would be the fix. I changed my materials and geometry from the regular way you declare them to the way below.
const geom = useMemo(() => new BoxBufferGeometry(), [])
const mat = useMemo(() => new MeshBasicMaterial(), [])
A link to the page
https://github.com/react-spring/react-three-fiber/blob/master/pitfalls.md
While the answer of #astronik is much better for overall performance. It only took me ages to replace every geometry and material.
So after some digging, I found a quick fix thanks to this comment by drcmda. Apparently, the canvas is always listening for scroll interactions in case of renderings. You can disable this behavior by simply setting scroll: false on the Canvas element.
<Canvas resize={{ scroll: false }} >
⚠️ The only downside using this quick fix, is that you can not use hover/click/scroll elements in the canvas anymore.

How to change the SendIcon in WebChat

I'm trying to replace the send icon on the WebChat. I'm able to change the colors of the existing icon but how could I replace the SVG image by another one?
If you aren't looking to fork and build the repo, an alternative is to change the element directly in the html. Generally speaking, changing the DOM directly in a React environment is not best practice, however, in this case, the results appear to be stable.
There is also an open issue on the BotFramework-WebChat repo (#1839) that is discussing making the sendBox customizable. No ETA on when that might happen, but something to keep in mind.
To include this on your page, add the code starting at const parent. Be sure to set your image size to 28x28 if you want to maintain the same size as the current arrow.
<script>
( async function () {
const res = await fetch( 'http://somesite/directline/token', { method: 'POST' } );
const { token } = await res.json();
[...]
document.querySelector( '#webchat > *' ).focus();
const parent = document.getElementsByClassName( 'main' )
const child = parent[0].children[2].getElementsByTagName('svg');
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const img = document.createElement("img");
img.src= 'bot - small.png';
svg.setAttribute('height', '28');
svg.setAttribute('width', '28');
svg.appendChild(img);
child[0].replaceWith(img);
}
)().catch( err => console.error( err ) );
</script>
Hope of help!
It looks like the icon cannot be changed as a setting currently.
But you can still fork the repository and change the icon, its definition is located here: https://github.com/microsoft/BotFramework-WebChat/blob/master/packages/component/src/SendBox/Assets/SendIcon.js
import React from 'react';
const SendIcon = () => (
<svg height={28} viewBox="0 0 45.7 33.8" width={28}>
<path clipRule="evenodd" d="M8.55 25.25l21.67-7.25H11zm2.41-9.47h19.26l-21.67-7.23zm-6 13l4-11.9L5 5l35.7 11.9z" />
</svg>
);
export default SendIcon;

Re-rendering a single row of a list without re-rendering the entire list

we're trying to implement a contact list that works just like the new Material Design Google Contacts (you must enable the material design skin to see it) using material-ui.
Specifically we're trying to show a checkbox instead of the avatar on row hover.
We'd like to catch and re-render only the interested row (when hovered) and show the avatar/checkbox accordingly... this seems an easy task but we're not able to isolate the render to the hovered row (instead of re-rendering the entire list)
Do you have any suggestion on how to do something like this?
Our temporary solution uses a container component that handles the table:
When a row is hovered we capture it from onRowHover of the Table component and save it in the container state. This triggers a re-render of the entire list with really poor perfomance.
You can see a video of the issue here.
Here is a code sample:
import React from 'react'
import Avatar from 'material-ui/lib/avatar'
import Checkbox from 'material-ui/lib/checkbox'
import Table from 'material-ui/lib/table/table'
import TableHeaderColumn from 'material-ui/lib/table/table-header-column'
import TableRow from 'material-ui/lib/table/table-row'
import TableHeader from 'material-ui/lib/table/table-header'
import TableRowColumn from 'material-ui/lib/table/table-row-column'
import TableBody from 'material-ui/lib/table/table-body'
import R from 'ramda'
export default class ContactsList extends React.Component {
constructor (props) {
super(props)
this.state = { hoveredRow: 0 }
this.contacts = require('json!../../public/contacts.json').map((e) => e.user) // Our contact list array
}
_handleRowHover = (hoveredRow) => this.setState({ hoveredRow })
_renderTableRow = ({ hovered, username, email, picture }) => {
const checkBox = <Checkbox style={{ marginLeft: 8 }} />
const avatar = <Avatar src={picture} />
return (
<TableRow key={username}>
<TableRowColumn style={{ width: 24 }}>
{hovered ? checkBox : avatar}
</TableRowColumn>
<TableRowColumn>{username}</TableRowColumn>
<TableRowColumn>{email}</TableRowColumn>
</TableRow>
)
}
render = () =>
<Table
height='800px'
fixedHeader
multiSelectable
onRowHover={this._handleRowHover}
>
<TableHeader displaySelectAll enableSelectAll>
<TableRow>
<TableHeaderColumn>Nome</TableHeaderColumn>
<TableHeaderColumn>Email</TableHeaderColumn>
<TableHeaderColumn>Telefono</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody displayRowCheckbox={false} showRowHover>
{this.contacts.map((contact, index) => this._renderTableRow({
hovered: index === this.state.hoveredRow,
...contact }))
}
</TableBody>
</Table>
}
Thank you in advance.
You could wrap your rows into a new component implementing shouldComponentUpdate like so :
class ContactRow extends Component {
shouldComponentUpdate(nextProps) {
return this.props.hovered !== nextProps.hovered || ...; // check all props here
}
render() {
const { username, email, ...otherProps } = this.props;
return (
<TableRow { ...otherProps } >
<TableRowColumn style={{ width: 24 }}>
{this.props.hovered ? checkBox : avatar}
</TableRowColumn>
<TableRowColumn>{this.props.username}</TableRowColumn>
<TableRowColumn>{this.props.email}</TableRowColumn>
</TableRow>
);
}
}
Then you can use it in your ContactList component like so :
this.contacts.map((contact, index) => <ContactRow key={contact.username} {...contact} hovered={index === this.state.hoveredRow} />)
If you don't want to manually implement shouldComponentUpdate, you can use React's PureRenderMixin or check a lib like recompose which provides useful helpers like pure to do so.
EDIT
As pointed out by the OP and #Denis, the approach above doesn't play well with some features of the Table component. Specifically, TableBody does some manipulation on its children's children. A better approach would be to define your ContactRow component like so:
class ContactRow extends Component {
shouldComponentUpdate(nextProps) {
// do your custom checks here
return true;
}
render() {
const { username, email, ...otherProps } = this.props;
return <TableRow { ...otherProps } />;
}
}
and then to use it like this
<ContactRow { ...myProps }>
<TableRowColumn>...</TableRowColumn>
</ContactRow>
But I guess having TableRow re-render only when necessary is a feature everyone would benefit from, so maybe a PR would be in order :)

Resources