How to import TrackballControls from three.js module using react? - reactjs

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!

Related

react openlayers popup offsetWidth issue

I am trying to implement openlayers popup in react.
It is implemented in plain js here: https://openlayers.org/en/latest/examples/popup.html
Here is my code:
import {createRef, useEffect, useRef, useState} from 'react';
import './Map.css';
import Map from '../node_modules/ol/Map.js';
import Overlay from '../node_modules/ol/Overlay.js';
import TileLayer from '../node_modules/ol/layer/Tile.js';
import View from '../node_modules/ol/View.js';
import XYZ from '../node_modules/ol/source/XYZ.js';
import {toLonLat} from '../node_modules/ol/proj.js';
import {toStringHDMS} from '../node_modules/ol/coordinate.js';
const MapExample = () => {
const [popupContent, setPopupContent] = useState('');
const containerRef = createRef();
const contentRef = createRef();
const closerRef = createRef();
const key = 'CvOgKFhRDDHIDOwAPhLI';
const overlay = new Overlay({
element: containerRef.current,
autoPan: {
animation: {
duration: 250,
},
},
});
const handleCloser = () => {
overlay.setPosition(undefined);
closerRef.current.blur();
return false;
};
useEffect(() => {
const map = new Map({
layers: [
new TileLayer({
source: new XYZ({
url: 'https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=' + key,
tileSize: 512,
}),
}),
],
overlays: [overlay],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
}),
});
map.on('singleclick', (evt) => {
const coordinate = evt.coordinate;
const hdms = toStringHDMS(toLonLat(coordinate));
setPopupContent('You clicked here: ' + hdms)
overlay.setPosition(coordinate);
});
}, [])
return (
<div>
<div id="map" className="map"></div>
<div ref={containerRef} className="ol-popup">
<a ref={closerRef} href="#" className="ol-popup-closer" onClick={handleCloser}></a>
<div ref={contentRef}>{popupContent}</div>
</div>
</div>
)
}
export default MapExample;
I've got an issue with offset width:
picture with an issue: Cannot read properties of null (reading 'offsetWidth') The problem is popup appears on the left bottom corner, not in the clicked place of the map.
How I understand this bug appears, because popup's properties are null. I tried to fix this bug, but could not find a working solution. Will be really grateful for any help )))
There are a number of React lifecycle issues in your code. Do not use createRef in functional components as it will create a new reference at every render - while the useEffect code will run only once. This means that the overlay will lose its connection to the map - especially since you create a new Overlay at each render.
Also I strongly advise you to not use useEffect to create Openlayers components - the reasons are very subtle - but the main problem is that your components won't be reusable or even rerendable.
As the author of rlayers I can tell you that proper wrapping of Openlayers components is not easy - you must manually manage the components' lifecycle. This works best with class-based components on which you should override all lifecycle methods.

Threejs and WebGL not rendering 3d objects in firefox

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 !

Mobile Safari Not Showing My Video Texture

I have a question similar to this one, but in my case, it's iOS causing troubles (not macOS, which I haven't tried yet), so I hope it's OK to post this as well. I tried to create a video texture in Three.js and can't bring it to work on mobile Safari (iOS 15.4). Here is my code, which I tried to tidy up as much as possible:
import React, { useEffect, useRef } from "react";
import * as THREE from "three";
import { Canvas } from "#react-three/fiber";
import "./styles.css";
const Screen = () => {
const meshRef = useRef();
useEffect(() => {
const vid = document.createElement("video");
vid.src = "/test.mp4";
vid.crossOrigin = "Anonymous";
vid.loop = vid.muted = vid.playsInline = true;
vid.play();
meshRef.current.material.map = new THREE.VideoTexture(vid);
});
return (
<mesh ref={meshRef}>
<planeGeometry attach="geometry" />
</mesh>
);
};
const App = () => {
return (
<Canvas camera={{ fov: 25 }}>
<Screen />
</Canvas>
);
};
export default App;
Please tell me if I'm doing something wrong here. The test.mp4 is from this URL. I also tried to place the video as HTML element, instead of creating it dynamically, then the video itself plays fine, but not the video texture.
Also, just curious, but why isn't meshRef.current available in a useEffect in the main component, but useEffect inside of Screen, which is placed inside of Canvas, is OK?
Apparently it's a problem with video file formats. Tried an example video from Three.js and it worked.
To those of you looking for the solution , you need to add
vid.playsInline=true;
for mobile ios devices.
I had the same problem. I had to set the 'playsinline' attribute in a very specific way.
video.playsinline= true did not work but video.setAttribute('playsinline', true)
did work.
Hope this helps

Custom react-admin drag & drop list

It can't drag. What is wrong with it?
I'm using react-sortable-hoc with material-ui to custom react-admin list page with drag & drop sortable.
Demo : https://codesandbox.io/s/vibrant-visvesvaraya-4k3gs
Source code: https://github.com/tangbearrrr/poc-ra-sort-drag/tree/main
As I checked you are getting data from the props and in props there is no data field exists, so the error is coming from there
Here is the all props list
The sortable method you are using is from react-sortable-hoc, which adds huge complexity to react-admin.
Not so fast, I have run out of attempts trying to debug your code and come up with another solution works just fine but not so ideal, is to use sortablejs:
yarn add sortablejs
yarn add #types/sortablejs --dev
Do not mess up with react-sortablejs, this also applies the same complexity level as react-sortable-hoc.
Let's use your cmsLanguage as an example, with changes to use Datagrid instead.
Just be reminded that this working solution needs several retries on null el (e.g. your data is fetching, slow network speed, etc). The code below has 3 retries, 1500 milliseconds per each retry. The initialisation will stop after 3 attempts.
import {Datagrid, ShowButton, TextField} from "react-admin";
import * as React from "react";
import MenuIcon from '#mui/icons-material/Menu';
import {useEffect} from "react";
import Sortable from 'sortablejs';
const LanguageList = () => {
// This will run the effect after every render
useEffect(() => {
// https://github.com/SortableJS/Sortable
const retries = 3;
const millisecords = 1500;
let attempts = 0;
const retrySortable = () => {
const el = document.querySelector('#sortable-list tbody');
if (!el) {
if (++attempts >= retries) {
console.log(`cannot initialise sortable after ${retries} retries.`);
} else {
setTimeout(() => retrySortable(), millisecords);
}
} else {
// #ts-ignore
new Sortable(el, {
handle: ".handle",
draggable: "tr",
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
easing: "cubic-bezier(1, 0, 0, 1)", // Easing for animation. Defaults to null. See https://easings.net/ for examples.
// Element dragging ended
onEnd: (evt) => {
// #ts-ignore
const reorderedList: string[] = [];
const list = document.querySelectorAll('#sortable-list tbody td.column-name span');
[].forEach.call(list, function (span: Element) {
reorderedList.push(span.innerHTML);
});
console.log(JSON.stringify(reorderedList));
console.log(evt);
},
});
}
}
retrySortable();
}, []);
return (
<section id="sortable-list">
<Datagrid>
<MenuIcon sx={{cursor: "pointer"}} className="handle"/>
<TextField source="name"/>
<ShowButton/>
</Datagrid>
</section>
);
};
export default LanguageList;
When someone has a request for a demo, I will draw some time to make this a GitHub repo for better reference.

How to load gltf files in react.js

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.

Resources