I'm pretty new to React and am currently trying to visualize a point cloud (.ply file) with the help of three.js and react.
When trying to load the file though, I don't get any errors, the point cloud doesn't appear at all. Everything else does though...
Only thing I receive via the console is Infinty% loaded.
Click here to download the .ply file used in this example
Here's my code:
import React from "react";
import * as THREE from "three";
import { PLYLoader } from "three/examples/jsm/loaders/PLYLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
var container;
var camera, scene, renderer, controls, loader;
export default function Visualization() {
return <div></div>;
}
init();
animate();
function init() {
//Creating the container for the ply
container = document.createElement("div");
document.body.appendChild(container);
//initializing the camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 2000);
camera.position.z = 2;
camera.position.set(0, 9, 1500);
//initializing the scene
scene = new THREE.Scene();
scene.add(new THREE.AxesHelper(30));
//initializing renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding;
//adding renderer to DOM
container.appendChild(renderer.domElement);
//initializing interactive controls
controls = new OrbitControls(camera, renderer.domElement);
controls.update();
//rendering ply file
const plyLoader = new PLYLoader();
plyLoader.load(
"./Crankcase.ply",
function (geometry) {
const mesh = new THREE.Points(geometry);
mesh.rotateX(-Math.PI / 2);
scene.add(mesh);
},
// called when loading is in progress
function (xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
// called when loading has errors
function (error) {
console.log("An error happened");
console.log(error);
}
);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
controls.update();
}
A late reply to your question.
All you need to do is move your *.ply file to the public folder of your react application and in loader just use it like this:
plyLoader.load(
"Crankcase.ply",
function (geometry) {
const mesh = new THREE.Points(geometry);
mesh.rotateX(-Math.PI / 2);
scene.add(mesh);
},
// called when loading is in progress
function (xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
// called when loading has errors
function (error) {
console.log("An error happened");
console.log(error);
}
);
This worked for me
Related
Hello everyone I am creating my own website using reactjs and I want to include 3d object in my website.
To do that I am using three js with a WebGL renderer, it usually works fine but SOMETIMES my 3d object does not render and I get a black frame instead (see the pictures). Now the issue is that this bug happens sometimes and I can't understand what produces it. The bug is only happening on firefox, chrome works fine.
3d object bug
3d object working
I know how to solve it for my computer (I have to enable graphic performances for firefox in config panel), yet it was working perfectly fine sometimes without graphic performances enabled. My fear is that if I deploy my website this bug occurs for other user.
(Edit after some research this is EXACTLY the issue https://bugzilla.mozilla.org/show_bug.cgi?id=1712486 but i don't get how to solve the issue)
I am pretty sure that this is due to the WebGL renderer which seems to sometimes have trouble to load textures with my graphic card... not sure why.
Can anyone help me to find a fix (for every user not only for my computer) or is there a way to check if this rendering bug happens in order to display an image instead ?
Here is the code to generate my object :
import React from 'react';
import './object3d.css';
import { useState, useEffect, useRef } from "react";
import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
var sat;
function loadGLTFModel(scene, glbPath) {
return new Promise((resolve, reject) => {
const loader = new RGBELoader();
//loader.load(
loader.load( 'royal_esplanade_1k.hdr', ( texture ) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
const loader2 = new GLTFLoader();
loader2.load( glbPath, function ( gltf ) {
sat = gltf.scene
scene.add( sat );
resolve(sat);
} );
},
undefined,
function (error) {
console.log(error);
reject(error);
} );
});
}
const Object = ({path_file}) => {
const refContainer = useRef();
const [loading, setLoading] = useState(true);
const [renderer, setRenderer] = useState();
useEffect(() => {
const { current: container } = refContainer;
if (container && !renderer) {
const scW = container.clientWidth;
const scH = container.clientHeight;
//console.log(scW);
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
premultipliedAlpha : false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(scW, scH);
renderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild(renderer.domElement);
setRenderer(renderer);
const scene = new THREE.Scene();
const scale = 25;
const camera = new THREE.OrthographicCamera(
-scale,
scale,
scale,
-scale,
0.01,
50000
);
camera.position.y = 25;
camera.position.x = 2;
camera.position.z = 0;
camera.rotation.y = 45* Math.PI /180;
camera.rotation.z = 45* Math.PI /180;
const ambientLight = new THREE.AmbientLight(0xcccccc, 1);
scene.add(ambientLight);
loadGLTFModel(scene, path_file, {
}).then((sat) => {
animate(sat);
setLoading(false);
});
let req = null;
const animate = () => {
req = requestAnimationFrame(animate);
sat.rotation.y+=0.01;
renderer.render(scene, camera);
};
return () => {
cancelAnimationFrame(req);
renderer.dispose();
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className='container_3d'
ref={refContainer}
>
{loading && (
<span style={{ left: "50%", top: "50%" }}>
Loading...
</span>
)}
</div>
);
};
const Object3dsat = ({path_file}) => {
return (
<Object path_file={path_file}/>
)
}
export default Object3dsat
I notted that when the bug occurs I got multiple lines in the Fail Logs :
"RenderDXGITextureHost Failed to open shared texture, hr=0x80070057"
More Over my config :
GPU 1 : AMD radeon RX Vega 10
GPU 2 : Nvidia GeForce RTX 2060
CPU : AMD Ryzen 7 3750H with Radeon Vega Mobile Gfx
and I am using Firefox Version: 105.0.1
(I also notted that the GPU used for Web rendering is the AMD Radeon GPU but it was working even with this GPU)
Hope you guys can help me
Thank you for your help !
I am currently using the iFrame API from my project and I am trying to have custom volume controls so that the user is able to change the volume outside of the iframe video. I have set up the player, but it is always undefined when it's being called outside an eventHandler.
const { youtubeDetails, volume } = useContext(GlobalContext);
useYoutube(loadVideo);
let player: any;
function loadVideo() {
(window as any).YT.ready(function () {
player = new window.YT.Player("player", {
events: {
onStateChange: onStateChange
}
});
});
}
useEffect(() => {
console.log(player) // never defined
changeVolume()
}, [volume]);
function onStateChange() {
console.log(player) // always defined
}
function changeVolume() {
player.setVolume(volume * 100);
}
This is because loadVideo() is never called again after the very first rerender. Is there a work around this structure so that the goal functionality is achieved?
The custom useYoutube Hooks is as follows:
import React from "react";
export const useYoutube = (callback: any) => {
React.useEffect(() => {
if (!(window as any).YT) {
var tag = document.createElement("script");
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag?.parentNode?.insertBefore(tag, firstScriptTag);
tag.onload = callback;
} else {
callback();
}
}, []);
};
I know this is an old post but I ran in to the same problem and the problem seems to be that YT is loaded first and then with a delay the properties 'ready' and 'Player' is loaded.
So I had to wrap my callback in a setTimeout with aproximatly 200ms delay to be able to call the methods 'ready' and 'Player'.
I am using react js boiler plate (create-react-app) and imported three.js. I am trying to use TrackballControls for a particular project but it isn't working. It throws an error like "Attempted import error: 'TrackballControls' is not exported from 'three' (imported as 'THREE')" . Now I understand that it is in the examples folder and it's not an official export if I correctly understood from the forum. Some one please help me with this, how do I import TrackballControls in a react component? Help will be highly appreciated!
import React, { useState, useEffect } from 'react'
import * as THREE from 'three'
const OrbitControls = require('three-orbit-controls')(THREE)
import "../src/assets/sass/home.scss"
const X = () => {
const [y, setY] = useState(0)
const [ masses, setMasses ] = useState([])
let parent, renderer, scene, camera, controls
useEffect(() => {
// renderer
renderer = new THREE.WebGLRenderer()
renderer.setSize( window.innerWidth, window.innerHeight )
document.body.appendChild( renderer.domElement )
// scene
scene = new THREE.Scene()
// camera
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 )
camera.position.set( 20, 20, 20 )
// controls
controls = new THREE.TrackballControls( camera, sphere )
controls.minDistance = 5
controls.maxDistance = 250
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.05;
// axes
// scene.add(new THREE.AxisHelper( 20 ))
// geometry
let geometry = new THREE.SphereGeometry( 2, 8, 6, 0, 6.3, 0, 3.1)
// material
let material = new THREE.MeshBasicMaterial({
wireframe: true,
wireframeLinewidth: 1
})
let sphere = new THREE.Mesh( geometry, material )
// parent
parent = new THREE.Object3D()
scene.add( parent )
scene.add( sphere )
function animate() {
requestAnimationFrame( animate )
parent.rotation.z += 0.01
controls.update()
renderer.render( scene, camera )
}
animate()
}
,[])
return <div></div>
}
export default X
All JavaScript files from the examples directory are now available as ES6 modules in the three npm package. There is actually a guide that explains how you can import them in your application:
https://threejs.org/docs/index.html#manual/en/introduction/Import-via-modules
For your particular case, it would look like so:
import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js';
Notice that third-party packages like three-orbit-controls are not necessary anymore. When using the modules from the three package, it's guaranteed that you work with the latest (and supported) versions.
three.js R109
I know there's no one here to see this but I'm posting my solution if someone comes across this issue in the future. So here's what I figured out -
I haven't mentioned in my question that I was using a SSR (server side render) react framework called next.js. The import works perfectly fine with a pure react app. But in server side frameworks, the import related stuff should be done inside the useEffect (or componentDidMount) along with the rest of the threejs stuff. So I dynamically imported it like this -
let dynamicallyImportPackage = async () => {
let TrackballControls
await import('three/examples/jsm/controls/TrackballControls')
// you can now use the package in here
.then(module => {
TrackballControls = module.TrackballControls
})
.catch(e => console.log(e))
return TrackballControls
}
then, I used it in my useEffect like so -
let TrackbackControls = await dynamicallyImportPackage()
// controls
controls = new TrackbackControls(camera, container)
controls.minDistance = 5
controls.maxDistance = 250
Arigato Gosaimasu!
I am trying to load a glTF file in my reactjs application using gltf loader. I manage to display a basic cube on the canvas, but using the gltf loader I only get the blank canvas without the 3D object.
I have tried several paths to the file but it seems to sit in the correct position (same folder as component). I have also tried other glTF files without luck. Do i need to include the bin file somewhere?
Here is my code:
import React, { Component } from 'react';
import * as THREE from 'three';
import GLTFLoader from 'three-gltf-loader';
const OrbitControls = require("three-orbit-controls") (THREE);
class Viewer extends Component{
componentDidMount(){
const width = this.mount.clientWidth
const height = this.mount.clientHeight
//ADD SCENE
this.scene = new THREE.Scene()
//ADD CAMERA
this.camera = new THREE.PerspectiveCamera(
75,
width / height,
0.1,
1000
)
this.camera.position.z = 4
//ADD RENDERER
this.renderer = new THREE.WebGLRenderer({ antialias: true })
this.renderer.setClearColor('#888888')
this.renderer.setSize(width, height)
this.mount.appendChild(this.renderer.domElement)
//ADD ORBIT CONTROL
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
//ADD CUBE
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: '#433F81' })
this.cube = new THREE.Mesh(geometry, material)
this.scene.add(this.cube)
//ADD OBJECT
const loader = new GLTFLoader();
loader.load('model.gltf', (object) => {
this.scene.add(object.scene);
});
this.animate();
}
componentWillUnmount(){
this.stop()
this.mount.removeChild(this.renderer.domElement)
}
animate = () => {
this.renderScene()
this.frameId = window.requestAnimationFrame(this.animate)
}
renderScene = () => {
this.renderer.render(this.scene, this.camera)
}
render(){
return(
<div
style={{ width: '400px', height: '400px' }}
ref={(mount) => { this.mount = mount }}
/>
)
}
}
export default Viewer;
The application should show the 3D file on the canvas.
NOTE: I'm new to three.js, so layman terms would be appreciated.
You can turn gltf files to JSX and use react-three-fiber to reconcile threejs. Here's an example: https://github.com/react-spring/react-three-fiber/tree/3.x/tools three-fiber wraps threejs similar to how react-dom wraps html, reconcilers don't necessarily change the rules, which makes it less of an abstraction as it seems.
I found following code for zoom in a simple D3 solution which works well with Javascript:
var zoom = d3.behavior.zoom()
.on("zoom", function() {
projection.translate(d3.event.translate).scale(d3.event.scale);
feature.attr("d", path);
circle.attr("transform", ctr);
})
;
zoom.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([h / 6, h])
;
When I try to convert this code in D3 + ReactJS, I get issues. There have been many since I've tried so many different solutions. The latest one zooms my world map once but it's all a mess.
This is my code:
import React, { Component } from 'react';
import 'd3';
import * as d3Z from 'd3-zoom'
import * as d3 from 'd3-selection';
//import d3Scale from 'd3-scale'
import { geoMercator, geoPath } from 'd3-geo';
import './WorldMap.css';
import countries from '../resources/world-countries.json';
//
export default class WorldMap extends Component {
componentDidUpdate() {
const width = 1300, //document.body.clientWidth,
height = 600;//document.body.clientHeight;
const circleRadius = 2;
const lstRadius = [7, circleRadius];
const lstColor = ["green", "white"];
const duration = 500;
const lstShots = [];
const projection = geoMercator();
const path = geoPath()
.projection(projection);
const d3Zoom = d3Z.zoom();
for (var i = 0; i < this.props.data.length; i++) {
lstShots.push(JSON.parse(JSON.stringify(this.props.data[i])));
}
const getCirclePos = (d) => {
return "translate(" + projection([d.long, d.lat]) + ")";
}
const svgObj = d3.select(this.refs.svgMap);
const feature = svgObj
.selectAll("path.feature")
.data(countries.features)
.enter().append("path")
.attr("class", "feature");
projection
.scale(width / 6.5)
.translate([width / 2, height / 1.6]);
const zoom = d3Zoom
.on("zoom", function() {
console.log(d3.event);
projection.translate([d3.event.transform.x, d3.event.transform.y]).scale(d3.event.scale);
feature.attr("d", path);
// circle.attr("transform", getCirclePos);
})
;
svgObj.call(zoom);
// console.log(zoom);
// zoom.translateTo(projection.translate())
// .scale(projection.scale())
// .scaleExtent([height / 6, height])
// ;
feature.attr("d", path);
// eslint-disable-next-line
const circle = svgObj.selectAll("circle")
.data(lstShots)
.enter()
.append("circle")
.attr("r", circleRadius)
.attr("fill", 'white')
.attr("transform", getCirclePos)
.attr("node", function(d) { return JSON.stringify(d); })
.style("cursor", "pointer");
}
render() {
return (
<svg ref="svgMap"></svg>
);
}
}
The problem is that d3.event does not contain fields of translate or scale which I have seen people use in other example of d3-zoom. While I have replaced translate with transform in zoom function, I don't know what to replace d3.event.scale with.
If you port code from d3v3 to d3v4 you need to do it all, d3.event.scale does not exist in d3v4
projection.translate([d3.event.transform.x, d3.event.transform.y]).scale(d3.event.transform.k);
You need to initialise the zoom too
svgObj.call(zoom);
svgObj.call(zoom.transform, d3.zoomIdentity.translate(projection.translate()[0], projection.translate()[1]).scale(projection.scale()));
Don't use ref strings but Callback Refs
I needed to use componentDidMount() to see anything.