Here is a cube of planes that I can click
The main issue with this so far is that clicking on a plane clicks through when I would only want to click the plane that my mouse is over. What am I missing on my Three.js Plane?
I have tried searching for something relating to collision in three.js but to no avail so far.
After further research, I think it has something to do with RayCasting?
import React, { useState, useRef } from "react";
import { OrbitControls, Plane } from "#react-three/drei";
import { Canvas, useFrame, useThree, extend } from "#react-three/fiber";
import styles from "../styles/game.module.css";
import { DoubleSide } from "three";
const Cell = (props) => {
const [hovered, hover] = useState(false);
const [checked, setChecked] = useState(false);
const colorStyle = () => {
if (hovered) return "hotpink";
if (checked) return "lightblue";
return "orange";
};
return (
<Plane
scale={1}
onClick={() => setChecked(!checked)}
onPointerEnter={() => hover(true)}
onPointerLeave={() => hover(false)}
position={props.position}
rotation={props.rotation}
>
<meshPhongMaterial side={DoubleSide} color={colorStyle()} />
</Plane>
);
};
const Cube = () => {
useFrame((state, delta) => {
});
return (
<>
{/* Back Face */}
<Cell position={[-1, 1, -1.5]} rotation={[0, 0, 0]} />
// other cells here
{/* Front Face */}
<Cell position={[-1, 1, 1.5]} rotation={[0, 0, 0]} />
// other cells here
{/* Left Face */}
<Cell position={[-1.5, 1, 1]} rotation={[0, Math.PI / 2, 0]} />
// other cells here
{/* Right Face */}
<Cell position={[1.5, 1, 1]} rotation={[0, Math.PI / 2, 0]} />
// other cells here
{/* Bottom Face */}
<Cell position={[1, -1.5, 1]} rotation={[Math.PI / 2, 0, 0]} />
// other cells here
{/* Top */}
<Cell position={[1, 1.5, 1]} rotation={[Math.PI / 2, 0, 0]} />
// other cells here
</>
);
};
const SceneItems = () => {
return (
<>
<OrbitControls minDistance={7.5} maxDistance={15} />
<ambientLight intensity={0.5} />
<spotLight position={[10, 15, 10]} angle={0.3} />
<Cube position={[1, 1, 1]} />
</>
);
};
const CompleteScene = () => {
return (
<div id={styles.scene}>
<Canvas>
<SceneItems />
</Canvas>
</div>
);
};
It appears that all I needed to prevent this click-through from happening was to add event.stopPropagation() to my event listeners on my Plane. Now I no longer click through the Plane
const Cell = (props) => {
const [hovered, hover] = useState(false);
const [checked, setChecked] = useState(false);
useCursor(hovered);
const colorStyle = () => {
if (hovered) return "hotpink";
if (checked) return "lightblue";
return "orange";
};
return (
<Plane
scale={1}
onClick={(e) => {
e.stopPropagation();
setChecked(!checked);
}}
onPointerEnter={(e) => {
e.stopPropagation();
hover(true);
}}
onPointerLeave={(e) => {
e.stopPropagation();
hover(false);
}}
position={props.position}
rotation={props.rotation}
>
<meshPhongMaterial side={DoubleSide} color={colorStyle()} />
</Plane>
);
};
More details here: https://docs.pmnd.rs/react-three-fiber/api/events#pointer-capture
Related
I started react three fiber not long ago, and I try to move my camera to my mouse position when I click on my plane.
Here is my code:
import { OrbitControls, PerspectiveCamera, useGLTF } from '#react-three/drei'
import { Perf } from 'r3f-perf'
import { useRef, useEffect } from 'react'
import { useFrame, useThree } from '#react-three/fiber'
import * as THREE from 'three'
export default function Experience() {
const cameraRef = useRef()
const cubeRef = useRef()
const camera = useThree( state => state.camera )
// Move camera to the mouse
function cameraMovement( event ) {
const x = event.point.x
const y = event.point.y
const z = event.point.z
console.log( camera.position )
cameraRef.current.position.lerp( new THREE.Vector3( x, 2, z ), 1 )
console.log( camera.position )
}
return <>
<Perf position="top-left" />
<OrbitControls makeDefault />
<PerspectiveCamera ref={cameraRef} makeDefault position={[40, 40, 40]} />
<ambientLight intensity={0.5} />
<mesh receiveShadow onClick={cameraMovement}>
<boxGeometry args={[20, 0.5, 20]} />
<meshStandardMaterial color="greenyellow" />
</mesh>
<mesh receiveShadow position={[0, 3, 0]} ref={cubeRef}>
<boxGeometry args={[3, 3, 3]} />
<meshPhongMaterial color='red' />
</mesh>
</>
}
The camera moves, but the movement is quite strange, and also the position seems to be wrong.
But when I try to move an object for example it follows my click.
I can't understand why it doesn't work, if you have any ideas I'm interested !
Thanks!
I have 3 .GLB files which I want to change based on the state (Sort of a gallery and one main image), but it loads weirdly when changing and it doesn't render some 3d models. Using three.js, ChakraUI, and nextJS. As I've only begun to test out 3d model I don't know if this is the correct way to re-render or conditionally show the model.
Series.js
const itemData = [
{
image: "/images/ssr-img1.jpg",
model: "zombie.glb",
},
{
image: "/images/ssr-img2.jpg",
model: "star-icon.glb",
},
{
image: "/images/ssr-img3.jpg",
model: "dog.glb",
},
{
image: "/images/ssr-img4.jpg",
model: "zombie.glb",
},
{
image: "/images/ssr-img5.jpg",
model: "star-icon.glb",
},
{
image: "/images/ssr-img6.jpg",
model: "dog.glb",
},
];
export default function Series() {
const [items, setItems] = useState(itemData);
const [model, setModel] = useState("star-icon.glb");
const [currentImage, setCurrentImage] = useState(itemData[0].image);
return (
<Box id="nfc" bg="#edf3f8" py="2rem">
<Suspense fallback={null}>
<Test model={model} />
</Suspense>
<Title color="black">Rarity List</Title>
<Stack>
<HStack align="center" justify="center" my="1rem" maxW="full">
{items.map((item, index) => (
<Image
maxW="5%"
key={index}
onClick={() => setModel(item.model)}
cursor="pointer"
src={item.image}
alt="test"
/>
))}
</HStack>
</Stack>
</Box>
);
}
Test.js (The 3d model)
import { Canvas, useFrame } from "#react-three/fiber";
import { useGLTF, Environment, OrbitControls } from "#react-three/drei";
import { Box } from "#chakra-ui/react";
function Dog(props) {
const { scene } = useGLTF(props.model);
return <primitive object={scene} {...props} />;
}
function Zoom() {
useFrame((state, delta) => {
state.camera.zoom += delta / 100;
state.camera.updateProjectionMatrix();
});
}
export default function Test({ model }) {
return (
<Box h="xl">
<Canvas camera={{ position: [2.5, 2.5, -2.5], fov: 35 }}>
<ambientLight intensity={0.5} />
<Dog
model={model}
position={[-0.1, -0.2, 0]}
rotation={[0, Math.PI / 2, 0]}
scale={0.2}
/>
<Environment preset="city" />
<OrbitControls
minPolarAngle={Math.PI / 2.5}
maxPolarAngle={Math.PI / 2.5}
/>
<Zoom />
</Canvas>
</Box>
);
}
I have a camera view which shows the camera view as the background. On that page I have a joystick and some sliders to control exposure and move around the view. How can I make a toggle to disable the joystick and sliders so that the view is unobstructed?
Below is my code. What I need is a toggle which can hide the sliders and joystick which are in this view. So when the toggle is selected it just hides everything and only shows the mjpeg stream and when unselected it shows the sliders and joystick again.
import { Layout } from "components/common";
import { Box, Text, Icon, IconButton, VStack, Button } from "native-base";
import React, {
cloneElement,
useCallback,
useLayoutEffect,
useState,
} from "react";
import * as ScreenOrientation from "expo-screen-orientation";
import { useFocusEffect, useNavigation } from "#react-navigation/core";
import { Feather } from "#expo/vector-icons";
import { KorolJoystick, KorolSlider, Stepper } from "components/ui";
import { SettingsModal } from "components/CameraView";
import { WebView } from "react-native-webview";
import { useAppSelector } from "redux-store/store";
// import socket from "tools/poseCam";
var ip = "10.42.0.1";
let ws = new WebSocket(`ws://${ip}:2100/ws`);
ws.onopen = () => {
console.log("connection established");
};
const CameraView = () => {
const nav = useNavigation();
const [showSettings, setShowSettings] = useState(false);
const { controlType, step } = useAppSelector((state) => state.cameraSettings);
useLayoutEffect(() => {
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE);
}, []);
const [x, setX] = useState(0);
useFocusEffect(
useCallback(() => {
return () => {
ScreenOrientation.unlockAsync();
};
}, [])
);
return (
<Layout>
<Box flex={1} position="relative">
<WebView
originWhitelist={["*"]}
scrollEnabled={false}
scalesPageToFit
style={{
backgroundColor: "transparent",
}}
containerStyle={{
flex: 1,
}}
source={{
uri: "http://10.42.0.1:2101/mjpeg",
// uri: "http://mjpeg.sanford.io/count.mjpeg",
}}
/>
<VStack
position="absolute"
display="flex"
width="full"
flexDirection="row"
padding="5"
alignItems="center"
>
<IconButton
marginRight="auto"
icon={<Icon as={Feather} color="primary.500" name="arrow-left" />}
colorScheme="primary"
onPress={() => nav.goBack()}
/>
<Button
size="md"
startIcon={<Icon size="xs" as={Feather} name="plus" />}
backgroundColor="primary.500:alpha.40"
>
Add Waypoint
</Button>
<IconButton
icon={<Icon as={Feather} color="primary.500" name="settings" />}
colorScheme="primary"
onPress={() => setShowSettings(true)}
/>
</VStack>
<VStack position="absolute" bottom="30" left="30" width="56" space={2}>
<VStack>
{/* <Text>Focus</Text>
<KorolSlider /> */}
</VStack>
<VStack>
{/* <Text>Zoom</Text>
<KorolSlider /> */}
</VStack>
<VStack>
<Text>Slide</Text>
<KorolSlider
value={x}
onChange={(slider) => {
setX(slider);
console.log(slider);
}}
onDragEnd={() => {
let message = {
from: "FromClient",
data: {
case: "joystick",
joystick: {
slider: x,
},
},
};
ws.send(JSON.stringify(message));
}}
/>
</VStack>
</VStack>
<Box position="absolute" right="30" bottom="30">
{controlType === "joystick" ? (
<KorolJoystick
onMove={(values) => {
console.log(values);
let message = {
from: "FromClient",
data: {
case: "joystick",
joystick: {
angle: values.angle.radian,
force: values.force,
},
},
};
ws.send(JSON.stringify(message));
}}
onStop={(values) => {
let message = {
from: "FromClient",
data: {
case: "joystick",
joystick: {
angle: 0,
force: 0,
},
},
};
ws.send(JSON.stringify(message));
}}
/>
) : (
<Stepper
onPress={(dir) => {
const vel = 5;
const tiltVel = 5;
// if (Math.abs(step)<=1){
// vel=1
// }
if (dir === "up") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { tilt: [step, vel] },
},
};
console.log(message);
ws.send(JSON.stringify(message));
}
if (dir === "down") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { tilt: [-step, vel] },
},
};
ws.send(JSON.stringify(message));
}
if (dir === "left") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { pan: [-step, vel] },
},
};
ws.send(JSON.stringify(message));
}
if (dir === "right") {
const message = {
from: "FromClient",
data: {
case: "controlCMDRel",
moveData: { pan: [step, vel] },
},
};
ws.send(JSON.stringify(message));
}
}}
/>
)}
</Box>
</Box>
<SettingsModal
show={showSettings}
onClose={() => setShowSettings(false)}
/>
</Layout>
);
};
export default CameraView;
I cant see where (on which element) your background image is set,but you can render any component conditionally like this:
return(
<div>
I am wrapper
{isVisible && <div>I am visible if isVisible is true</div>}
</div>
)
and you can set background image on the parent div, so when the child div is not visible you can see the background image.
I have a horizontal <Animated.ScrollView>, a "carousel" in my React-native app that displays one item at the center of the screen and the edges of previous and next item. I want to show data (lessons) below the ScrollView. Can someone tell me or point to a resource about how can I know what item the screen is now displaying and then showing data based on that? Do I need to calculate the current item in the scrollview or pass it as an argument to some function?
My goal:
Parent component:
return (
<View style={styles.screen}>
<View style={styles.thumbnailScrollContainer}>
<HorizontalContentScroll
data={LESSONS_DATA}
/>
</View>
<View style={styles.dataScrollContainer}>
<FlatList numColumns={2} data={lessonsByCategory} renderItem={renderLessonItem} />
</View>
</View> );
And here my horizontal Scrollview
const HorizontalContentScroll = ({ data}: HorizontalContentProps) => {
const { width, height } = Dimensions.get('window');
const scrollX = useRef(new Animated.Value(0)).current;
const ITEM_SIZE = width * 0.8;
const getInterval = (offset: any) => {
// console.log('offset', offset);
};
const scrollableData = (data as Array<ContentCategory>).map(
(item: ContentCategory, index: number) => {
const inputRange = [
(index - 1) * ITEM_SIZE,
index * ITEM_SIZE,
(index + 1) * ITEM_SIZE,
];
const translateY = scrollX.interpolate({
inputRange,
outputRange: [40, 10, 40],
// extrapolate: 'clamp',
});
return (
<Card
size="large"
style={{
...styles.titleCard,
transform: [{ translateY }],
width: ITEM_SIZE,
}}
key={`${item.category}-${index}`}
>
<Text>{item.category}</Text>
</Card>
);
}
);
return (
<Animated.ScrollView
contentContainerStyle={styles.contentContainer}
horizontal
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{
useNativeDriver: true,
listener: (event) => {
getInterval(event);
},
}
)}
scrollEventThrottle={16}
showsHorizontalScrollIndicator={false}
bounces={false}
pagingEnabled
snapToAlignment="center"
snapToInterval={330}
decelerationRate={'fast'}
>
{scrollableData}
</Animated.ScrollView>
);
};
export default HorizontalContentScroll;
I think I have to do something in this map function like pass the current item up to my parent component but how? If I try to call a function that sets the state in the parent I get an error of "Warning: Cannot update a component from inside the function body of a different component."
const scrollableData = (data as Array<ContentCategory>).map(
(item: ContentCategory, index: number) => {
const inputRange = [
(index - 1) * ITEM_SIZE,
index * ITEM_SIZE,
(index + 1) * ITEM_SIZE,
];
const translateY = scrollX.interpolate({
inputRange,
outputRange: [40, 10, 40],
});
// filterLessonsInTheParent(item)
return (
<Card
size="large"
style={{
...styles.titleCard,
transform: [{ translateY }],
width: ITEM_SIZE,
}}
key={`${item.category}-${index}`}
>
<Text>{item.category}</Text>
</Card>
);
}
Okay I solved it.
I used Animated.Flatlist instead of Animated.Scrollview so that I could get my hands on the onViewableItemsChanged prop and then I had to refactor my component to a class component so that viewabilityConfig prop would work properly.
I pass the current viewable item to the parent in a useCallback function that updates the local state. I then use that and React Pure Component to avoid re-rendering my HorizontalContentScroll which would mess up the animation positions. (I don't know if this is the most optimal way but it works so far).
// Parent
const handleViewableChange = useCallback((item: ContentCategory) => {
setContentsToShow((prevItem) => item.contents);
}, []);
return (
<View style={styles.screen}>
<View style={styles.thumbnailScrollContainer}>
<HorizontalContentScroll
data={LESSONS_DATA}
onViewableChange={handleViewableChange }
/>
</View>
<View>
<FlatList
numColumns={2}
data={contentsToShow}
renderItem={renderLessonItem}
/>
// HorizontalContentScroll
class HorizontalContentScroll extends PureComponent<HoriProps, any> {
viewabilityConfig: { viewAreaCoveragePercentThreshold: number };
scrollX: any;
constructor(props: any) {
super(props);
this.handleViewableItemsChanged = this.handleViewableItemsChanged.bind(
this
);
this.viewabilityConfig = { viewAreaCoveragePercentThreshold: 50 };
}
handleViewableItemsChanged = (info: any) => {
const currItemInView = info.viewableItems[0].item;
this.props.onViewableChange(currItemInView);
};
render() {
const { data } = this.props;
const { width, height } = Dimensions.get('window');
const scrollX = new Animated.Value(0);
const ITEM_SIZE = width * 0.8;
return (
<Animated.FlatList
data={data}
contentContainerStyle={styles.contentContainer}
horizontal
pagingEnabled
onViewableItemsChanged={this.handleViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
scrollEventThrottle={16}
showsHorizontalScrollIndicator={false}
bounces={false}
snapToAlignment="center"
snapToInterval={330}
decelerationRate={'fast'}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { x: scrollX } } }],
{
useNativeDriver: true,
}
)}
renderItem={({
item,
index,
}: {
item: ContentCategory;
index: number;
}) => {
const inputRange = [
(index - 1) * ITEM_SIZE,
index * ITEM_SIZE,
(index + 1) * ITEM_SIZE,
];
const translateY = scrollX.interpolate({
inputRange,
// [x, y, x]
outputRange: [40, 10, 40],
// extrapolate: 'clamp',
});
return (
<Card
size="large"
style={{
...styles.titleCard,
transform: [{ translateY }],
width: ITEM_SIZE,
}}
key={`${item.category}-${index}`}
>
<Text style={styles.categoryText}>{item.category}</Text>
</Card>
);
}}
/>
);
}
}
export default HorizontalContentScroll;
I'm currently new and learning about three.js. And i'm using react-three-fiber to make it happen with React, but i stumbled upon a problem. The model however, cannot receive any shadow from another model. I've tried to use obj.castShadow = true and obj.receiveShadow = true to one of the receiving shadow model object on the parent and the children as well but it shows no difference. Is there any way to cast a shadow to another model?
And the shadow.. it seems very rough. Is there any way to smoothen it?
Here's my sandbox:
https://codesandbox.io/s/modest-newton-np1sw
Code:
import React, { Suspense, useMemo, useState } from "react";
import { Canvas } from "react-three-fiber";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { OrbitControls} from "drei";
import { Mesh } from "three";
import billboard from "../assets/models/billboard.obj";
import bridge from "../assets/models/bridge.obj";
const Model = ({ modelPath }) => {
const [obj, setObj] = useState();
useMemo(() => new OBJLoader().load(modelPath, setObj), [modelPath]);
if (obj) {
obj.castShadow = true;
obj.traverse((children) => {
if (children instanceof Mesh) {
children.castShadow = true;
}
});
}
return obj ? <primitive object={obj} /> : null;
};
const ShadowedModel = ({ modelPath }) => {
const [obj, setObj] = useState();
useMemo(() => new OBJLoader().load(modelPath, setObj), [modelPath]);
if (obj) {
obj.castShadow = true;
obj.receiveShadow = true;
obj.traverse((children) => {
if (children instanceof Mesh) {
children.castShadow = true;
children.receiveShadow = true;
}
});
}
return obj ? <primitive object={obj} /> : null;
};
const Lights = () => {
return (
<>
<ambientLight intensity={0.1} />
<spotLight
castShadow
position={[-50, 50, 20]}
intensity={0.5}
shadow-mapSize-shadowMapWidth={2048}
shadow-mapSize-shadowMapHeight={2048}
shadow-camera-left={-50}
shadow-camera-right={50}
shadow-camera-top={-50}
shadow-camera-bottom={50}
/>
<pointLight position={[10, -10, -20]} intensity={0.3} />
<pointLight position={[0, 10, 5]} intensity={0.3} />
<spotLight intensity={1} position={[0, 1000, 0]} />
</>
);
};
const Billboard = () => {
return (
<mesh
castShadow
position={[-15, 5, -35]}
scale={[0.05, 0.05, 0.05]}
rotation={[0, 20, 0]}
>
<Model modelPath={billboard} />
</mesh>
);
};
const Bridge = () => {
return (
<mesh
castShadow
receiveShadow
position={[10, -40, -80]}
// position={[-80, -40, -150]}
scale={[0.15, 0.15, 0.15]}
rotation={[0, 10.2, 0]}
>
<ShadowedModel modelPath={bridge} />
</mesh>
);
};
const Shadow = () => {
return (
<group>
<mesh
receiveShadow
rotation={[-Math.PI / 2, 0, 0]}
position={[-20, -32, -40]}
>
<planeBufferGeometry attach="geometry" args={[500, 500]} />
<meshLambertMaterial attach="material" color={"lightblue"} />
</mesh>
</group>
);
};
const MegatronModel = () => {
return (
<>
<Canvas
shadowMap
colorManagement
camera={{ position: [0, 0, 5], fov: 60 }}
>
<OrbitControls
enablePan={Boolean("Pan", true)}
enableZoom={Boolean("Zoom", true)}
enableRotate={Boolean("Rotate", true)}
/>
<Shadow />
<Suspense fallback={null}>
<Bridge />
</Suspense>
<Billboard />
<Lights />
</Canvas>
</>
);
};
export default MegatronModel;
Any help would be appreciated.
Thank you very much.
Your model is indeed receiving shadows. The problem is that you have several PointLights at full intensity that are washing everything out into white. Even shaded areas are blown out to white. See here, I've disabled all your PointLights and only kept the spotlight and ambient light so you can see the shadows again:
I recommend you use as few lights as possible to make rendering less resource-intensive. Adding so many lights could lower your framerate, overheat laptops, and drain more battery on cell-phones. For a pretty realistic lighting setup, I like using a single hemisphere light with a directional light like in this demo. Hemisphere makes it feel like there's atmospheric light coming from the top, with reflected ground light from the bottom.
As far as pixelated shadows go, you're using a large 2048x2048 map, but it's spread out across too large an area. I see you're trying to set left, right, top, bottom distances but those only apply to DirectionalLight, which uses an orthographic camera. Left, right, top, & bottom properties don't apply to SpotLight, which uses a perspective camera. If you want to make the Spotlight shadowmap narrower, use the .angle property.