Adding image on THREE.js plane - vue.js

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.

Related

Reanimated 2 reusable animation in custom hook

How can I create a reusable React hook with animation style with Reanimated 2? I have an animation that is working on one element, but if I try to use the same animation on multiple elements on same screen only the first one registered is animating. It is too much animation code to duplicate it everywhere I need this animation, so how can I share this between multiple components on the same screen? And tips for making the animation simpler is also much appreciated.
import {useEffect} from 'react';
import {
cancelAnimation,
Easing,
useAnimatedStyle,
useSharedValue,
withRepeat,
withSequence,
withTiming,
} from 'react-native-reanimated';
const usePulseAnimation = ({shouldAnimate}: {shouldAnimate: boolean}) => {
const titleOpacity = useSharedValue(1);
const isAnimating = useSharedValue(false);
useEffect(() => {
if (shouldAnimate && !isAnimating.value) {
isAnimating.value = true;
titleOpacity.value = withRepeat(
withSequence(
withTiming(0.2, {duration: 700, easing: Easing.inOut(Easing.ease)}),
withTiming(
1,
{duration: 700, easing: Easing.inOut(Easing.ease)},
() => {
if (!shouldAnimate) {
cancelAnimation(titleOpacity);
}
},
),
),
-1,
false,
() => {
if (titleOpacity.value < 1) {
titleOpacity.value = withSequence(
withTiming(0.2, {
duration: 700,
easing: Easing.inOut(Easing.ease),
}),
withTiming(
1,
{duration: 700, easing: Easing.inOut(Easing.ease)},
() => {
isAnimating.value = false;
},
),
);
} else {
titleOpacity.value = withTiming(
1,
{
duration: 700,
easing: Easing.inOut(Easing.ease),
},
() => {
isAnimating.value = false;
},
);
}
},
);
} else {
isAnimating.value = false;
cancelAnimation(titleOpacity);
}
}, [shouldAnimate, isAnimating, titleOpacity]);
const pulseAnimationStyle = useAnimatedStyle(() => {
return {
opacity: titleOpacity.value,
};
});
return {pulseAnimationStyle, isAnimating: isAnimating.value};
};
export default usePulseAnimation;
And I am using it like this inside a component:
const {pulseAnimationStyle} = usePulseAnimation({
shouldAnimate: true,
});
return (
<Animated.View
style={[
{backgroundColor: 'white', height: 100, width: 100},
pulseAnimationStyle,
]}
/>
);
The approach that I've taken is to write my Animations as wrapper components.
This way you can build up a library of these animation components and then simply wrap whatever needs to be animated.
e.g.
//Wrapper component type:
export type ShakeProps = {
// Animation:
children: React.ReactNode;
repeat?: boolean;
repeatEvery?: number;
}
// Wrapper component:
const Shake: FC<ShakeProps> = ({
children,
repeat = false,
repeatEvery = 5000,
}) => {
const shiftY = useSharedValue(0);
const animatedStyles = useAnimatedStyle(() => ({
//Animation properties...
}));
const shake = () => {
//Update shared values...
}
// Loop every X seconds:
const repeatAnimation = () => {
shake();
setTimeout(() => {
repeatAnimation();
}, repeatEvery);
}
// Start Animations on component Init:
useEffect(() => {
// Run animation continously:
if(repeat){
repeatAnimation();
}
// OR ~ call once:
else{
shake();
}
}, []);
return (
<Animated.View style={[animatedStyles]}>
{children}
</Animated.View>
)
}
export default Shake;
Wrapper Component Usage:
import Shake from "../../util/animated-components/shake";
const Screen: FC = () => {
return (
<Shake repeat={true} repeatEvery={5000}>
{/* Whatever needs to be animated!...e.g. */}
<Text>Hello World!</Text>
</Shake>
)
}
From their docs:
CAUTION
Animated styles cannot be shared between views.
To work around this you can generate multiple useAnimatedStyle in top-level loop (number of iterations must be static, see React's Rules of Hooks for more information).
https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

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

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.

Render children of ViroARImageMarker onAnchorFound

I'm currently working with the ViroReact Community Package in React Native to display a video in AR when a specific image is found. However the onTargetFound function of the ViroARImageMarker is not triggered, and the children of the ViroARImageMarker are not displayed.
When I added the onAnchorFound function to the ARScene (parent) the onAnchorFound method was triggered, however the children of the ViroARImageMarker still weren't rendered. Why is the function not triggered and therefore the children not displayed? How do I fix this?
The image is a 12x12cm black card with a bright orange logo (about 3cm) in the center. Neither of the targets are found in the ViroARImageMarker.
Here's my code:
Image Recognition Class
import React, { useEffect, useState } from 'react';
const {
ViroARScene,
ViroARImageMarker,
ViroARTrackingTargets,
ViroAnimations,
ViroVideo,
ViroMaterials,
ViroBox
} = require('#viro-community/react-viro');
const NewViroTracker = () => {
const videoPath = require('#assets/videos/wham.mp4');
const [videoAnimationName] = useState('showVideo');
const [playAnim, setPlayAnim] = useState(false);
function _onAnchorFound(evt: any) {
console.log('Anchor found in Marker :', evt);
setPlayAnim(true);
}
return (
<ViroARScene>
<ViroARImageMarker
target={'inviteCard'}
onAnchorFound={_onAnchorFound}>
<ViroVideo source={videoPath} />
</ViroARImageMarker>
<ViroARImageMarker
target={'logo'}>
<ViroBox position={[0, 0.25, 0]} scale={[0.5, 0.5, 0.5]} />
</ViroARImageMarker>
</ViroARScene>
);
};
ViroARTrackingTargets.createTargets({
inviteCard: {
source: require('#assets/images/invite-card.png'),
orientation: 'Up',
physicalWidth: 0.12 // real world width in meters
},
logo: {
source: require('#assets/images/logo-empty.png'),
orientation: 'Up',
physicalWidth: 0.0287 // real world width in meters
}
});
ViroMaterials.createMaterials({
chromaKeyFilteredVideo: {
chromaKeyFilteringColor: '#00FF00'
}
});
ViroAnimations.registerAnimations({
showVideo: {
properties: { scaleX: 1, scaleY: 1, scaleZ: 1 },
duration: 1000
},
closeVideo: {
properties: { scaleX: 0, scaleY: 0, scaleZ: 0 },
duration: 1
}
});
export default NewViroTracker;
App
import React from 'react';
const { ViroARSceneNavigator } = require('#viro-community/react-viro');
import styled from 'styled-components/native';
import NewViroTracker from 'components/NewViroTracker';
const App = () => {
return (
<ViroWrapper
autofocus={true}
initialScene={{
scene: NewViroTracker
}}
/>
);
};
export default App;
const ViroWrapper = styled(ViroARSceneNavigator)`
flex: 1;
`;
Dependencies:
"#viro-community/react-viro": "^2.21.1",
"react": "17.0.2",
"react-native": "0.66.3",

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

Angular2 Component listen when parent's resize change

I have a requirement in which I want to change properties of a child component depending on the size of its parent component though a method call. The issue I am running into is that the only resize event I can listen to is that of the window, which doesn't help as the window size is not changing, only the parent component is due to a side panel div opening and closing.
The only possibility I can see at the moment is to have some sort of polling in which we within the child component itself that checks if its width has changed every x amount of time.
Thanks for your help!
You are correct that you can't get the resize event on a div (without installing some js extension). But something like this works.
The Parent Component:
import {Component, AfterContentInit, ElementRef} from '#angular/core';
import { ChildComponent } from "./ChildComponent";
export interface IParentProps {
width: number;
height: number;
}
#Component({
selector: 'theParent',
template: `text text text text text text
text text text text text text
<the-child [parentProps]="parentProps"></the-child>`,
directives: [ChildComponent]
})
export class ParentComponent implements AfterContentInit {
sizeCheckInterval = null;
parentProps: IParentProps = {
width: 0,
height: 0
}
constructor(private el: ElementRef) {
}
ngAfterContentInit() {
this.sizeCheckInterval = setInterval(() => {
let h = this.el.nativeElement.offsetHeight;
let w = this.el.nativeElement.offsetWidth;
if ((h !== this.parentProps.height) || (w !== this.parentProps.width)) {
this.parentProps = {
width: w,
height: h
}
}
}, 100);
}
ngOnDestroy() {
if (this.sizeCheckInterval !== null) {
clearInterval(this.sizeCheckInterval);
}
}
}
The Child Component:
import {Component, Input, SimpleChange} from "#angular/core";
import { IParentProps } from "./ParentComponent";
#Component({
directives: [],
selector: "the-child",
template: `child`
})
export class ChildComponent {
#Input() parentProps: IParentProps;
constructor() {
this.parentProps = {
width: 0,
height: 0
}
}
ngOnChanges(changes: { [propName: string]: SimpleChange }) {
this.parentProps = changes["parentProps"].currentValue;
console.log("parent dims changed");
}
}