I'm using this for a 360 image and I need the camera to stay fixed at (0,0,0)
If I update the camera position the controls stop working.
I've seen this post https://codeworkshop.dev/blog/2020-04-03-adding-orbit-controls-to-react-three-fiber/ which kind of has a fix but seems out of date, the extend functionality doesn't make orbitControls available.
I've tried various combinations of
const onChange = () => {
camera.position.set(0, 0, 0);
ref.current.update();
};
Or
useEffect(() => {
if (ref.current && scene.projection === SceneProjection['3603D']) {
camera.position.set(0, 0, 0);
ref.current.update();
}
}, [ref, scene]);
Is there a simple way to lock the camera position with OrbitControls and just rotate the camera?
I think you might be able to set controls.maxDistance to something really small, like 0.01. That way it only rotates around its origin by an imperceptible amount.
You can read more about the .maxDistance attribute in the docs](https://threejs.org/docs/index.html?#examples/en/controls/OrbitControls.maxDistance)
Related
I have a need to have some text scale in ways only SVG can as far as I could find. The text will change frequently so it also needs to adapt to that.
I'm making the app in react and would like to know how to calculate the bbox of an SVG (initially and every time it changes) before rendering it or at least without flickering / layout shift.
An example could be found here - the current issue is that it flicker. Everything else works fine more or less.
I've seen some other questions that are similar or nearly identical - however they do not have the requirement of changing text so it's possible to compute the bounding box in advance once or at least a one time flicker is not a big issue. Another question / thread also used a class component that supposedly updated the state at component mount but before render which as they claim does not cause a flicker but a lot has changed since then in react and in the example I tried the flicker is there.
The best compromise I've found so far is to just make the SVG's visibility hidden and measure the bbox of the text first and show it when done. Then every time the text changes measure again. Generally speaking this should not be too crazy in terms of jumping around or any other visual quirks but for best results you'd want to set a certain fixed size box for the SVG to fill up and not cause any layout shift.
import { useState, useEffect, useRef } from "react";
export type BBox = { x: number; y: number; width: number; height: number };
const makeViewBox = (bbox: BBox) => {
return `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
};
// Note that this component still sometimes jiggles around a bit
// and it is particularly noticable with nont monospaced fonts.
const ScalableSVGText = ({ text }: { text: string }) => {
// Reference to the SVG element, needed to take bbox measurements
// and adjust the SVG's viewBox
const ref = useRef<null | SVGSVGElement>(null);
// State is somewhat needed to force re-render - other methods
// can be used but whatever...
const [bbox, setbbox] = useState<null | BBox>(null);
// On initial mount & every time the text changes measure the
// bbox and update the state so the component re-renders
useEffect(() => {
if (ref.current) {
setbbox(ref.current.getBBox());
}
}, [text]);
return (
<svg
ref={ref}
width="100%"
height="100%"
viewBox={bbox ? makeViewBox(bbox) : ""}
style={{
// not strictly needed but makes it somwehat easier on the eyes
visibility: bbox ? "visible" : "hidden"
}}
>
<text>{text}</text>
</svg>
);
};
export default ScalableSVGText;
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 animate a stack of cards moving up as the cards on the top get swiped off the deck but I cannot make the animation to run.
I try to run the animation when the number of cards on the deck changes
useEffect(() => {
LayoutAnimation.spring();
},[currentCard]);
Also, I try to run it on the function that is called on swiped completion:
function onSwipeComplete(direction) {
const item = data[currentCard];
direction === 1 ? onSwipeRigt(item) : onSwipeLeft(item);
pan.setValue({ x: 0, y: 0 });
setCurrentCard(index => index + 1);
LayoutAnimation.configureNext({
duration: 5000,
update: {
type: LayoutAnimation.Types.spring,
springDamping: 0.7
}
});
}
On the attached sandbox you will find both solutions implemented but of course I try to use one at the time.
To see the problem jump into the sandbox if you swipe the top card left or right you will notice that the cards bellow snap to the top, instead I want to slowly animate it to the top.
https://codesandbox.io/s/react-native-deckj-z1nwb?file=/src/Deck.js
When zoomed, the map invokes zoomstart, zoom and zoomend events. Is there a way to tell if the map is zooming in our out? Really at zoomend, I need to understand if the map was zoomed in our out.
The following code accomplishes this...
var currentZoomLevel;
map.events.add('zoomstart', layer, function (e) { currentZoomLevel = map.getCamera().zoom; })
map.events.add('zoomend', layer, function (e) {
if (currentZoomLevel > map.getCamera().zoom) {
// zoomed out
}
})
In react native there is a scrollTo function which programatically scrolls to a given x and y coordinate, animated or not.
https://facebook.github.io/react-native/docs/scrollview.html#scrollto
scrollTo({x: 0; y: 0; animated: true})
In some cases this is useful, but there are some cases in which I only want to scroll down for 100 pixels or scroll up for 200 pixels regardless of the position x,y on the screen. Is there a way to programatically do this in react-native currently.
You can keep track of your current X and Y scroll position in state by doing something like what is mentioned in this answer. So for example:
<ScrollView
ref={(scrollView) => { this._scrollView = scrollView; }}
onScroll={this.handleScroll}
/>
handleScroll = (event: Object) => {
this.setState({scrollX: event.nativeEvent.contentOffset.x, scrollY: event.nativeEvent.contentOffset.y})
}
Then you can move your scroll using scrollTo and the current position +/- whatever you wish. Example:
moveScroll = () => {
this._scrollView.scrollTo({y: this.state.scrollY + 100});
}
Do read through the linked StackOverflow answer though to understand the caveats of how onScroll works. I'm also not checking to see if you hit the end/beginning of the scroll window so check your bounds as well.