THREE.JS load the 3D model but it is not showed in my vue componet - vue.js

I'm new working with THREE.js and I've been trying to show a .glb model into my component.
<template>
<section id="hero"></section>
</template>
<script>
import * as THREE from "three";
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
//import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { onMounted } from '#vue/runtime-core';
export default {
setup(){
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight,0.1,10);
let renderer = new THREE.WebGLRenderer();
let light = new THREE.AmbientLight( 0x404040 );
let loader = new GLTFLoader();
const init = ()=>{
renderer.setSize(window.innerWidth, window.innerHeight);
loader.load(
process.env.BASE_URL+'GLB/Elephant.glb',
function (glb ) {
console.log(glb);
scene.add(glb.scene);
light.position.set(2,2,5);
scene.add(light);
},
function (xhr) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
function (error) {
console.error( error );
}
);
const container = document.getElementById('hero');
container.appendChild(renderer.domElement);
camera.position.set(0,1,2)
};
const animate = () => {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
onMounted(()=>{
init();
animate();
})
}
}
</script>
<style>
#hero {
width: 100%;
height: 100vh;
background-color: white;
}
</style>
I know that my .glb file is loaded because it's show in the console. but when I add it to my scene there is nothing in the screen, I've also tried with the example from the documentation where I can show the cube from the example but when is a custom model it is not working, I don't have any error in the console.

maybe your camera's position is not good. If you don't set where the camera look at ,the camera will look at (0, 0, 0) by default, if your model is out of sight, you will not see it. You can add a controller to change your perspective.
`setup(){
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight,0.1,10);
let renderer = new THREE.WebGLRenderer();
let light = new THREE.AmbientLight( 0x404040 );
let loader = new GLTFLoader();
const init = ()=>{
renderer.setSize(window.innerWidth, window.innerHeight);
loader.load(
process.env.BASE_URL+'GLB/Elephant.glb',
function (glb ) {
console.log(glb);
scene.add(glb.scene);
light.position.set(2,2,5);
scene.add(light);
},
function (xhr) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
function (error) {
console.error( error );
}
);
const controls = new OrbitControls(camera, renderer.domElement)
const container = document.getElementById('hero');
container.appendChild(renderer.domElement);
camera.position.set(0,1,2)
};
const animate = () => {
requestAnimationFrame( animate );
renderer.render( scene, camera );
controls.update();
};
onMounted(()=>{
init();
animate();
})
}`
if this is not working, you should consider if the size of model is too big/small. you can use
const box = new THREE.Box3().setFromObject(glb.scene);
console.log(box)
to check the size of it, and use glb.scene.scale.set()to set its scale.

Related

GestureDetector gesture handler app crash when calling external function

i tried to use GestureDetector of react-native-gesture-handler
import React from 'react';
import { Directions, Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
/**
* Component used as Home Page
*/
const HomePage: React.FC = () => {
const position = useSharedValue(0);
const trigger = () => {
console.log('fdfs')
}
const flingGesture = Gesture.Fling()
.direction(Directions.RIGHT)
.onStart((e) => {
position.value = withTiming(position.value + 10, { duration: 100 });
console.log(e)
// trigger()
});
const flingGestureLeft = Gesture.Fling()
.direction(Directions.LEFT)
.onStart((e) => {
position.value = withTiming(position.value - 10, { duration: 100 });
// trigger()
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: position.value }],
}));
return (
<GestureDetector gesture={Gesture.Simultaneous(flingGestureLeft, flingGesture)}>
<Animated.View style={[{ width: 100, height: 30, backgroundColor: 'red' }, animatedStyle]} />
</GestureDetector>
);
}
export default HomePage;
this work without problem when i fling my bloc to left or right, but when i tried to call an exeternal function like the trigger(), my app crash. Is a bug of the gesture detector or there is something to add?
The reanimated, gesture handler hooks and callbacks works on the UI thread and the trigger function you defined is by default on the JS thread, so you can not use it directly.
There are two solutions to this:
add 'worklet' in the trigger function as below
const trigger = () => {
'worklet'
console.log('fdfs')
}
Or wrap your function with 'runOnJS' from reanimated as below
import { runOnJS } from 'react-native-reanimated';
const flingGesture = Gesture.Fling()
.direction(Directions.RIGHT)
.onStart((e) => {
position.value = withTiming(position.value + 10, { duration: 100 });
console.log(e)
runOnJS(trigger)()
});
Note:- syntax for runOnJS is like 'runOnJS(functionName)(params).
So if your function takes two params (Ex. 1st number and 2nd string), you would call it like this:- runOnJS(trigger)(1, 'dummyString')
For more details you can read the docs from reanimated and gesture-handler.

How to update theme when device orientation changes

I am trying to implement orientation changing with hooks. I called the orientation hook from app.tsx and I want to update everything(theme,style in component) that uses widthPercentageToDP() function. How can I achieve this. I can't figured out.
useOrientation.tsx
export let { width, height } = Dimensions.get("window");
const heightPercentageToDP = (heightPercent: string | number): number => {
// Parse string percentage input and convert it to number.
const elemHeight =
typeof heightPercent === "number"
? heightPercent
: parseFloat(heightPercent);
// Use PixelRatio.roundToNearestPixel method in order to round the layout
// size (dp) to the nearest one that correspons to an integer number of pixels.
return PixelRatio.roundToNearestPixel((height * elemHeight) / 100);
};
export const useScreenDimensions = () => {
const [screenData, setScreenData] = useState({});
useEffect(() => {
setScreenData({orientation:currentOrientation()});
Dimensions.addEventListener("change", (newDimensions) => {
width = newDimensions.screen.width;
height = newDimensions.screen.height;
setScreenData({orientation:currentOrientation()}); // can be used with this height and width
//console.log(newDimensions.window);
});
return () => Dimensions.removeEventListener("change", () => {});
});
return {
width,height,
screenData
};
};
Theme file
const theme = {
spacing: {
m:widthPercentageToDP("2%") // it must be updated when orientation changes.
},
borderRadii: {
s:widthPercentageToDP("5%") // it must be updated when orientation changes.
},
textVariants: {
body:{
fontSize:widthPercentageToDP("%3"),
}
},
};
App.tsx
const {screenData} = useScreenDimensions();
console.log(screenData)
return (
<ThemeProvider>
<LoadAssets {...{ fonts, assets }}>
<Example/>
</LoadAssets>
</ThemeProvider>
);
}
Example.tsx
export const Example = ({}) => {
return (
<Box>
<Text variant="body">hey</Text>
{/* // it must be updated when orientation changes. */}
<View style={{width:widthPercentageToDP("40%")}}/>
</Box>
);
}
Box and theme come from theme.tsx file. Text component accepts variant prop that defined in theme.tsx
Using react-native-orientation you can do what you want, then the device orientation changes.
Example:
import Orientation from 'react-native-orientation';
export default class AppScreen extends Component {
componentWillMount() {
const initial = Orientation.getInitialOrientation();
if (initial === 'PORTRAIT') {
// do something
} else {
// do something else
}
},
componentDidMount() {
// this will listen for changes
Orientation.addOrientationListener(this._orientationDidChange);
},
_orientationDidChange = (orientation) => {
if (orientation === 'LANDSCAPE') {
// do something with landscape layout
} else {
// do something with portrait layout
}
},
componentWillUnmount() {
// Remember to remove listener to prevent memory leaks
Orientation.removeOrientationListener(this._orientationDidChange);
}

Unable to render a 3D model in Expo

SDK Version: 37.0.0
Platforms(Android/iOS/web/all): Android/iOS.
I’m trying to load a 3D object in my application and followed many tutorials.
the model is loaded successfully and attached to scene variable but it doesn’t appear in the view.
this is a part of my graduation project and i have searched a lot the 2 months to answers
looking for a real working solution as most of the solution i found was too old and doesn’t work.
my component is as described below:
import * as React from 'react';
import { Ionicons } from '#expo/vector-icons';
import { View, StyleSheet,PixelRatio ,TouchableOpacity} from 'react-native';
import { ExpoWebGLRenderingContext, GLView } from 'expo-gl';
import { Renderer, TextureLoader } from 'expo-three';
import ExpoTHREE from 'expo-three';
import * as THREE from 'three'
import {
AmbientLight,
BoxBufferGeometry,
Fog,
Mesh,
MeshStandardMaterial,
PerspectiveCamera,
PointLight,
Scene,
SpotLight,
} from 'three';
export default class ModelScreen extends React.Component {
constructor(props) {
super(props);
let timeout;
this.state= {
loadingCompleted:true,
}
}
componentDidMount(){
THREE.suppressExpoWarnings(true)
clearTimeout(this.timeout)
}
componentWillUnmount(){
clearTimeout(this.timeout)
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={[styles.backButton]} activeOpacity={0.8}
onPress= {()=>{
this.props.setViewModel(false);
}}
>
<Ionicons style={styles.backButtonIcon} name="md-arrow-back"></Ionicons>
</TouchableOpacity>
<GLView
style={styles.viewer}
onContextCreate={async (gl: ExpoWebGLRenderingContext) => {
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const sceneColor = '#111111';
const scale = PixelRatio.get();
// Create a WebGLRenderer without a DOM element
const renderer = new Renderer({gl,alpha:true});
renderer.capabilities.maxVertexUniforms = 52502;
renderer.setSize(width/scale, height/scale);
renderer.setPixelRatio(scale);
renderer.setClearColor(0x000000,0);
const camera = new PerspectiveCamera(45, width / height, 1, 1000);
camera.position.set(0, 2, 5);
camera.lookAt(0,0,0);
const scene = new Scene();
scene.fog = new Fog(sceneColor, 1, 1000);
const ambientLight = new AmbientLight(0x101010);
scene.add(ambientLight);
const pointLight = new PointLight(0xffffff, 2, 1000, 1);
pointLight.position.set(0, 200, 200);
scene.add(pointLight);
const spotLight = new SpotLight(0xffffff, 0.5);
spotLight.position.set(0, 500, 100);
spotLight.lookAt(scene.position);
scene.add(spotLight);
var object = null;
const model = {
'thomas.obj': require('./../assets/models/thomas/thomas.obj'),
'thomas.mtl': require('./../assets/models/thomas/thomas.mtl'),
'thomas.png': require('./../assets/models/thomas/thomas.png'),
};
// Load model!
await ExpoTHREE.loadAsync(
[model['thomas.obj'], model['thomas.mtl']],
null,
name => model[name],
).then((obj)=>{
// // Update size and position
ExpoTHREE.utils.scaleLongestSideToSize(obj, 5);
ExpoTHREE.utils.alignMesh(obj, { y: 1 });
// Smooth mesh
ExpoTHREE.utils.computeMeshNormals(obj.children[0]);
// Add the mesh to the scene
scene.add(obj.children[0]);
}).catch((error)=>{
console.log(error);
});
console.log(scene.children.length)
function update() {
if (scene.children.length == 4)
scene.children[3].rotateY(0.03);
}
// Setup an animation loop
const render = () => {
this.timeout = requestAnimationFrame(render);
update();
renderer.render(scene, camera);
gl.endFrameEXP();
};
render();
}}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex:1,
justifyContent:"center",
alignItems:"center",
backgroundColor:"#111111",
},
backButton:{
position:'absolute',
top:30,
width:50,
height:50,
alignItems:"center",
justifyContent:"center",
borderRadius:100,
backgroundColor:"rgb(1,175,250)",
left:20,
alignSelf:"flex-start",
zIndex:10,
},
backButtonIcon:{
fontSize:25,
fontWeight:'900',
color:"#111111",
},
viewer:{
width:"80%",
height:"80%",
}
});
can anyone help me please?
Edit
it's working now without material i have changed
await ExpoTHREE.loadAsync([model['thomas.obj'], model['thomas.mtl']],null,name => model[name])
to
await ExpoTHREE.loadAsync(model['thomas.obj'],null,name => model[name])
but it doesn't render if i load the material and don't know what's wrong with it. i tried different objects and cannot render the material. the object disappears

Adding image on THREE.js plane

I am trying to put an .png img on my plane, but when I try to load it, it disappears.
It may be that THREE.js is not made for Vue.js, is there any other 3D-Library that supports Vue.js?
I also want to add an SVG, THREE has an SVGLoader which I have not figured out how it works yet.
<template>
<div id="container"></div>
</template>
<script>
import * as THREE from 'three'
//import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js';
export default {
name: 'Three',
data() {
return {
camera: null,
scene: null,
renderer: null,
mesh: null,
shape: null,
geomarty: null,
stats: null,
gui: null,
guiData: null,
texture: null
}
},
methods: {
init(){
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( this.renderer.domElement );
this.geometry = new THREE.PlaneGeometry( 5, 5, 5 );
this.texture = new THREE.TextureLoader().load('../img/shark.png')
this.material = new THREE.MeshLambertMaterial({map: this.texture})
this.mesh = new THREE.Mesh( this.geometry, this.material );
this.scene.add( this.mesh );
this.camera.position.z = 5;
},
animate() {
requestAnimationFrame( this.animate );
this.renderer.render( this.scene, this.camera );
}
},
mounted() {
this.init();
this.animate();
//this.svgLoad();
}
}
</script>
Your animation function might be losing context. When you call it via requestAnimationFrame, the this turns into window. When I want to avoid this, I usually bind the animation function to the desired context.
I'm not super-familiar with Vue, but try this...
<script>
import * as THREE from 'three'
let boundAnimation = null
export default {
//...
methods:
// ...
animate() {
requestAnimationFrame( boundAnimation );
this.renderer.render( this.scene, this.camera );
}
},
mounted() {
this.init();
boundAnimation = this.animate.bind(this);
boundAnimation();
}
}
</script>
Inside mounted, the this should represent the current implementation of the exported object. By assigning boundAnimation in this manner, all calls to boundAnmation will use for context the same this as when the function was bound. Using the bound function in requestAnimationFrame ensures that when that function fires, it will still be using the desired this context.

undefined is not an object (evaluating '_expo.Permission.askAsync')

i don't know what's the problem exactly but when i click on the button to choose image that erreur fire in the console
here's my code
_checkPermissions = async () => {
try {
const { status } = await Permission.askAsync(Permission.CAMERA);
this.setState({ camera: status });
const { statusRoll } = await Permission.askAsync(Permission.CAMERA_ROLL);
this.setState({ cameraRoll: statusRoll });
} catch (err) {
console.log(err);
}
};
findNewImage = async () => {
try {
this._checkPermissions();
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: "Images",
allowsEditing: true,
quality: 1
});
if (!result.cancelled) {
this.setState({
image: result.uri
});
} else {
console.log("cancel");
}
} catch (err) {
// console.log(err);
}
};
to me what solved it was importing the permissions and imagePicker like this:
import * as Permissions from 'expo-permissions';
import * as ImagePicker from 'expo-image-picker';
instead of this:
import Permissions from 'expo-permissions';
import ImagePicker from 'expo-image-picker';
And that's basically because there is no default export
It is getAsync(), not askAsync()
https://docs.expo.io/versions/latest/sdk/permissions/
I know I'm a little late to the party, but I feel it's important to show a way that is currently working (as of 2022) and also askAsync is deprecated ...
Getting image from (native) CAMERA
TL;DR: Even though we want "camera", we will actually use expo-image-picker FOR THE CAMERA (yes, you read right!)
I REPEAT: DO NOT USE expo-camera FOR CAMERA!
REMEMBER: USE ImagePickerExpo.requestCameraPermissionsAsync()AND ImagePickerExpo.launchCameraAsync() NOT Camera....!
So install it first: expo install expo-image-picker
Then import everything, from it under 1 alias, I like to use ImagePickerExpo because ImagePicker itself is confusing since it can mean more libraries, + import all needed for this code - you can replace Button with any other button/pressable that supports onPress (to use react-native-elements, you need to install it with yarn add react-native-elements)
Create displaying component
Create a state & setter to save current image source
Create a function that requests the permissions and opens the camera
Return the coponent with button binding onPress on function from 5. and Image that is displayed from the state from 4. but only when available.
working & tested(so far on android in expo go) code:
import React, { useState } from 'react';
import { View, Image, Alert, StyleSheet } from 'react-native';
import { Button } from 'react-native-elements';
import * as ImagePickerExpo from 'expo-image-picker';
const MyCameraComponent = () => {
const [selectedImage, setSelectedImage] = useState(null);
const openCameraWithPermission = async () => {
let permissionResult = await ImagePickerExpo.requestCameraPermissionsAsync();
if (permissionResult.granted === false) {
Alert.alert("For this to work app needs camera roll permissions...");
return;
}
let cameraResult = await ImagePickerExpo.launchCameraAsync({
// ...
});
console.log(cameraResult);
if (cameraResult.cancelled === true) {
return;
}
setSelectedImage({ localUri: cameraResult.uri });
};
return (
<View>
<Button title='Take a photo' onPress={openCameraWithPermission}></Button>
{(selectedImage !== null) && <Image
source={{ uri: selectedImage.localUri }}
style={styles.thumbnail}
/>}
</View>
);
}
const styles = StyleSheet.create({
thumbnail: {
width: 300,
height: 300,
resizeMode: "contain"
}
});
export default MyCameraComponent;
Note that I had to style the Image for it to display, it didn't display to me without proper styling which I find misleading, but I guess that's the react native way...
BTW: This also works in Android emulator (besides expo go in real Android device)
It also works on snack on desktop but only when you choose android (or Web) - https://snack.expo.dev/#jave.web/expo-camera-from-expo-image-picker
Getting image from (native) gallery (not camera)
In case you're wondering how to do the same for gallery, the code is basically the same, you just need a different callback function for the button that uses requestMediaLibraryPermissionsAsync / launchImageLibraryAsync instead of the camera ones.
let openImagePickerAsync = async () => {
let permissionResult = await ImagePickerExpo.requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
Alert.alert("For this to work app needs media library/gallery permissions...");
return;
}
let pickerResult = await ImagePickerExpo.launchImageLibraryAsync({
presentationStyle: 0, // without this iOS was crashing
});
console.log(pickerResult);
if (pickerResult.cancelled === true) {
return;
}
setSelectedImage({ localUri: pickerResult.uri });
}