I am having issues trying to make collision work properly with an imported glb file only used for collision.
There are two parts to this question:
I am currently getting a whole lot of faceNormal errors and vertices warnings in the console.
.faceNormals[767] = Vec3(0.999994684003945,-4.252105447140875e-10,0.003260669233505899) looks like it points into the shape? The vertices follow. Make sure they are ordered CCW around the normal, using the right hand rule.
.vertices[555] = Vec3(-18.135730743408203,9.071623802185059,-13.433568000793457)
I am not sure if this error has something to do with how I'm merging the geometry of the object or how the object is built inside blender.
When my sphere collides with the glb model it will clip through it sometimes with enough force. (holding down the W key to move the sphere forward). It clips through especially on the edges of the environment, against a flat wall it gives some resistance but still manages to clip through with enough time holding down a movement key.
Here is the code I'm using to import my glb model:
import React, { useMemo } from "react";
import { useConvexPolyhedron } from "#react-three/cannon";
import { Geometry } from "three-stdlib/deprecated/Geometry";
import {useGLTF} from "#react-three/drei";
import collision from "./Collision6.glb";
function toConvexProps(bufferGeometry) {
const geo = new Geometry().fromBufferGeometry(bufferGeometry);
geo.mergeVertices();
return [geo.vertices.map((v) => [v.x, v.y, v.z]), geo.faces.map((f) => [f.a, f.b, f.c]), []];
}
export default function Collision(props) {
const { nodes } = useGLTF(collision);
const geo = useMemo(() => toConvexProps(nodes.Collision001.geometry), [nodes]);
const [ref] = useConvexPolyhedron(() => ({ type: 'Static', mass: 100 ,position:[0,0,0], args: geo
}));
return (
<mesh
ref={ref}
geometry={nodes.Collision001.geometry}
>
<meshStandardMaterial wireframe color="#ff0000" opacity={0} transparent />
</mesh>
);
}
Related
I have a case of highly nested keys within a React component.
import * as React from "react";
import { Lg } from "./lg.jsx";
import { L } from "./l.jsx";
import { Caesura } from "./caesura.jsx";
export const Poem = ({ poem }) => {
return poem.map(stanza => <Lg>{
stanza.map(line => <L>{
line.map((segment, index) =>
<React.Fragment key={segment}>{
index === 0 ?
segment :
<><Caesura />{segment}</>
}</React.Fragment>)
}</L>)
}</Lg>);
};
export default Poem;
I am not at all sure how React will handle keys in this case. I might not be able to come up with a better solution.
I am rendering poetry in a highly regular format (for other cases I used custom mdx.)
And did those feet in ancient time
Walk upon Englands mountains green:
And was the holy Lamb of God,
On Englands pleasant pastures seen!
And did the Countenance Divine,
Shine forth upon our clouded hills?
And was Jerusalem builded here,
Among these dark Satanic Mills?
Ought to be result in a tree of components like
<Lg>
<L>And did those feet in ancient time</L>
<L>Walk upon Englands mountains green:</L>
<L>And was the holy Lamb of God,</L>
<L>On Englands pleasant pastures seen!</L>
</Lg>
<Lg>
<L>And did the Countenance Divine,</L>
<L>Shine forth upon our clouded hills?</L>
<L>And was Jerusalem builded here,</L>
<L>Among these dark Satanic Mills?</L>
</Lg>
I'm using #react-three/fiber and I'm implementing first person controls (WASD & cursor keys) with addition of OrbitControls to navigate a scene. I'm pretty much cloning PointerLockControls and I've got it working for the WASD controls as such (updating the target vector of the OrbitControls, passed as ref):
const moveForward = (distance) => {
vec.setFromMatrixColumn(camera.matrix, 0)
vec.crossVectors(camera.up, vec)
camera.position.addScaledVector(vec, distance)
orbitControls.current.target.addScaledVector(vec, distance)
}
const moveRight = (distance) => {
vec.setFromMatrixColumn(camera.matrix, 0)
camera.position.addScaledVector(vec, distance)
orbitControls.current.target.addScaledVector(vec, distance)
}
However, I'm not quite sure how to go about updating the target when the camera is rotated. Here's how I'm rotating the camera and its working just fine without OrbitControls:
const euler = new THREE.Euler(0, 0, 0, 'YXZ' );
euler.setFromQuaternion(camera.quaternion)
euler.y -= 0.25 * radians;
camera.quaternion.setFromEuler(euler)
Preview here: https://codesandbox.io/s/wasd-with-orbit-9edup7
OK, so you can see the working version here: https://yvod70.csb.app/
The idea is quite simple, attach the camera to your player/box and then move said object instead of the camera. The camera being a child of the player will be translated and rotated relative to the player.
To do this the first step is to get a reference to the mesh:
const scene = () => {
const mesh = useRef();
return (
<Canvas>
<mesh
ref={mesh}
>
<boxBufferGeometry args={[1, 1, 1]} />
<meshBasicMaterial wireframe color={"green"} />
</mesh>
<Controls mesh={mesh} />
</Canvas>
);
}
After setting this up, we just pass the mesh ref to whatever React component we want and use it however. In our case it's to replace the camera for movement and attach the camera to the box. Which can be done like so in your Controls component:
const Controls = ({ orbitControls, mesh }) => {
/** #type {THREE.Mesh} */
const box = mesh.current;
const { camera } = useThree();
const code = useCodes();
// ...your movement code here, remember to replace camera with box.
// FIXME: Move the Controls component lower down the component tree so that we don't end up with these race conditions.
if (!!box) {
box.add(camera);
}
// ...animation code
}
These were the steps I took to attach the orbit controls to the player
I'm trying to create 3d text using Threejs + react-three/fiber .
I loaded the font using font loader like this :
const font = new FontLoader().parse('/Microsoft Tai Le_Regular.json');
After that I tried to use component inside the mesh , for some reason it didn't work, any other type of Geometry would work .
<mesh>
<textGeometry /> // this can't even be compiled ( maybe it has to do with typescript
</mesh>
With that problem , I tried to create the textGeometry on js instead of jsx So I did this :
const textOptions = {
font: font,
size: props.size,
height: props.height,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 10,
bevelSize: 8,
bevelOffset: 0,
bevelSegments: 5
};
const textGeo = new TextGeometry(props.text, textOptions);
and Passed 'textGeo' to mesh geometry prop
<mesh
geometry={textGeo}
>
still didn't work and gave this error :
can't access property "yMax", data.boundingBox is undefined
Thanks for your help,
So I'll preface this by saying that I'm still a student so I can't explain exactly why all these steps need to be done but here's what worked for me. It seems that your issue is with the path that you are using to parse. The file name itself seems inaccurate but even if the file path is valid, it still will not work, it must be imported. Make sure that your json file is inside of your src folder and import the json file using the relative path.
import myFont from '../relative_path'
Make sure to import extend from r3f
import { extend } from '#react-three/fiber'
Next, textGeometry does not come standard with r3f, so it needs to be imported like so
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
Then extend TextGeometry
extend({ TextGeometry })
This should work to get the textgeometry to compile. Take a look below for the full snippet.
import { extend } from '#react-three/fiber'
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader'
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry'
import myFont from '../relative_path'
extend({ TextGeometry })
export default function Text() {
const font = new FontLoader().parse(myFont);
return(
<mesh position={[0,10,0]}>
<textGeometry args={['test', {font, size:5, height: 1}]}/>
<meshLambertMaterial attach='material' color={'gold'}/>
</mesh>
)
}
Obviously you can change the args and material to fit your needs.
In my app I want to add texture to the loaded .obj model. I have the same situation with my .fbx loaded models. Below is my example code, but this works only with something like sphereGeometry not with a loaded model.
Thanks in Advance!
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { useTexture } from '#react-three/drei'
const OBJModel = ({ file }: { file: string }) => {
const obj = useLoader(OBJLoader, file)
const texture = useTexture(textureFile)
return (
<mesh>
<primitive object={obj} />
<meshStandardMaterial map={texture} attach="material" />
</mesh>
)
}
primitive is not a subset of mesh. it can be a children of group.
primitive requires both geometry and material as props. Mesh requires both geometry and material as props. it's pretty obvious both cannot be used as subset of each other.
to implement your idea, you need to use only one Mesh or primitive. I'd suggest using Mesh which has abundant documentations. primitive is not documented enough.
the OBJ acquired through useLoader may have complex group inside it. Models usually contain larger sets such as group or scene. Group and Scenes can't have textures. Mesh can.
OBJ(result of useLoader) = scene => group => mesh => geometry, texture
traversing is required to acquire geometry from mesh.
// I've implemented this with Typescript,
// but it is not necessary to use 'as' for type conversion.
const obj = useLoader(OBJLoader, "/rock.obj");
const texture = useTexture("/guide.png");
const geometry = useMemo(() => {
let g;
obj.traverse((c) => {
if (c.type === "Mesh") {
const _c = c as Mesh;
g = _c.geometry;
}
});
return g;
}, [obj]);
// I've used meshPhysicalMaterial because the texture needs lights to be seen properly.
return (
<mesh geometry={geometry} scale={0.04}>
<meshPhysicalMaterial map={texture} />
</mesh>
);
I've implemented it in codesandbox. here's the working code:
https://codesandbox.io/s/crazy-dawn-i6vzb?file=/src/components/Rock.tsx:257-550
i believe obj loader returns a group, or a mesh, it wouldn't make sense to put that into a top-level mesh, and giving the top level a texture wont change the loaded obj mesh.
there are three possible solutions:
use obj.traverse(...) and change the model by mutation, this is what people do in vanilla three and it is problematic because mutation is bad, you are destroying the source data and you won't be able to re-use the model
i would suggest to convert your model to a gltf, then you can use gltfjsx https://github.com/pmndrs/gltfjsx which can lay out the full model declaratively. click the video, this is exactly what you want
if you must use obj files you can create the declarative graph by hand. there is a hook that gives you { nodes, materials } https://docs.pmnd.rs/react-three-fiber/API/hooks#use-graph
In my react app, I am trying to make a view page which shows 3d-mesh exported from pix4d. This mesh consists of three types of files, (.obj, .mtl, .jpg) pix4d.com.
I am new, with react-three-fiber, which I suppose is best way to achieve my solution to my problem.
Below is whole code of react component used to load and render 3D-Model.
code
Thanks in Advance!
I want to understand how to attach texture & material to my obj rendered model.
I was looking for this answer for a couple of weeks, finally I've found a way to make it work.
Material loader is not resolving by itself the import of remote files (it does on web, not in mobile, maybe in the future it will). So, I'm creating material and assigning it images by hand.
Something like this:
import { TextureLoader } from 'expo-three';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
// useThree is used to get scene reference
import { useThree } from 'react-three-fiber';
const textureLoader = new TextureLoader();
const mapImage = textureLoader.load(require('path/to/image1.png'))
const normalMapImage = textureLoader.load(require('path/to/image2.png'))
Note that TextureLoader from expo-three can handle the file resource returned from require()
const loaderObj = new OBJLoader();
const loaderMtl = new MTLLoader();
export default props => {
const { scene } = useThree();
loaderMtl.load(
'https://url_to/material.mtl',
mtl => {
mtl.preload();
loaderObj.setMaterials(mtl);
loaderObj.load(
'https://url_to/model.obj',
obj => {
// simple logic for an obj with single child
obj.children[0].material.map = mapImage;
obj.children[0].material.normalMap = normalMapImage;
scene.add(obj)
}
)
}
)
return null;
}
This is my first successful attempt to render an obj with mtl including a map and a normal map, so since it works, we can keep updating the code for improvements.
Another way to load model with texture is by specifying the path where your texture has been stored. In one case, mtl + obj + texture files are stored in your react project's directory 'public/models/'. So you can specify the path by calling setPath() function prior loading your material or object file and it should load your texture on the material. You may also want to make sure that in ./mtl file the texture name is correct. It should be called following by map_Kd in .mtl file.
const Model = () => {
const materialLoader = new MTLLoader().setPath('./model/').load(MaterialFile);
const objLoader = new OBJLoader().setMaterials(materialLoader).load(OojectFile);
return <primitive object={objLoader} />;
};