I'm facing an error when implementing three JS Gltf Loader with nuxt 3.
Error message :
" Uncaught (in promise) TypeError: Class constructor Loader cannot be invoked without 'new' .. "
versions:
"three": "^0.148.0",
"three-gltf-loader": "^1.111.0"
<template>
<div ref="container"></div>
</template>
<script>
import { ref, onMounted } from "vue";
import * as THREE from "three";
import GLTFLoader from "three-gltf-loader";
export default {
setup() {
const container = ref(null);
const scene = ref(new THREE.Scene());
const renderer = ref(new THREE.WebGLRenderer({ antialias: true }));
const width = 700;
const height = 700;
const camera = ref(
new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
);
const loader = ref(new GLTFLoader());
onMounted(async () => {
renderer.value.setSize(
container.value.clientWidth,
container.value.clientHeight
);
container.value.appendChild(renderer.value.domElement);
camera.value.position.z = 5;
const response = await fetch("logo.gltf");
const gltf = await response.json();
loader.value.parse(
gltf,
"",
(gltf) => {
scene.value.add(gltf.scene);
renderer.value.render(scene.value, camera.value);
},
undefined,
(error) => {
console.error(error);
}
);
});
return { container };
},
};
</script>
"three": "^0.148.0", "three-gltf-loader": "^1.111.0"
This kind of setup isn't recommended since you can import the latest GLTFLoader module from the three repository. Try it again with these imports:
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
I found a solution on Alvaro Dev Labs' YT channel, he used TroisJS instead of ThreeJS ( in fact, it's Vite + Three).
And for nuxt 3 you just need to add ".client.vue " to the file name of your component to run it on the client side (Model.client.vue).
<script lang="ts">
import { defineComponent, ref, onMounted } from "vue";
import {
Renderer,
Scene,
Camera,
PointLight,
AmbientLight,
GltfModel,
} from "troisjs";
export default defineComponent({
components: {
Renderer,
Scene,
Camera,
PointLight,
AmbientLight,
GltfModel,
},
setup() {
const renderer = ref(null);
const model = ref(null);
function onReady(model) {
console.log("Ready", model);
}
onMounted(() => {
renderer?.value?.onBeforeRender(() => {
model.value.rotation.x += 0.01;
});
});
return {
renderer,
model,
onReady,
};
},
});
</script>
<template>
<div>
<Renderer ref="renderer" antialias orbit-ctrl resize="window">
<Camera :position="{ x: -10, z: 20 }" />
<Scene background="#fff">
<AmbientLight />
<PointLight
color="white"
:position="{ x: 100, y: 1000, z: 40 }"
:intensity="1"
/>
<GltfModel ref="model" src="/Models/logo.gltf" #load="onReady" />
</Scene>
</Renderer>
</div>
</template>
import like this
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
Then use like this
const loader = new GLTFLoader()
Related
Hello
I made a component for orbitControls which doesn't work :
extend({ OrbitControls })
const OrbitA = () => {
const { camera, gl } = useThree()
return (
<OrbitControls
args={[camera, gl.domElement]}
/>
)
}
I have found a workaroud with useEffect :
extend({ OrbitControls })
const Orbit = () => {
const { camera, gl } = useThree()
useEffect(() => {
const controls = new OrbitControls(camera, gl.domElement)
controls.minDistance = 3
controls.maxDistance = 20
return () => {
controls.dispose()
}
}, [camera, gl])
return null
}
But in the last case, i don't know how to pass the attach parameter like I would have done it in the first case :
return (
<OrbitControls
attach='orbitControls'
args={[camera, gl.domElement]}
/>
)
}
Any help ?
i use dependencies
"#react-three/drei": "7.5.1",
"#react-three/fiber": "6.2.3",
"#types/three": "0.128.0",
/* eslint-disable new-cap */
/* eslint-disable no-nested-ternary */
import React, { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import * as THREE from 'three';
import { useThree } from '#react-three/fiber';
import BMJUA from './BMJUA.json';
import GeometryUtils from './GeometryUtils';
const Particle = ({
children,
vAlign = 'center',
hAlign = 'center',
size = 40,
color = '#0000ef',
ref,
}: any) => {
const font = new THREE.FontLoader().parse(BMJUA);
const config = useMemo(
() => ({ font, size: 40, height: 3, bevelSegments: 5, step: 10 }),
[font]
);
const textGeoRef = useRef<THREE.TextGeometry>();
return (
<group ref={ref}>
<points>
<textGeometry
ref={textGeoRef}
attach="geometry"
args={[children, config]}
/>
<pointsMaterial size={1} sizeAttenuation vertexColors color="lime" />
</points>
</group>
);
};
export default Particle;
i tried text with points mesh
But I got the missing points.
my program screen
Why are there no intermediate midpoints?
I want to add a function to adjust the position of the text point.
Please help me
Can you use new Image() inside the Next.js? I am getting error, saying it is not defined.
I know I can simply use inside JSX, but in this case, I am not sure if it will work, as I must reference the image outisde JSX, as shown below.
If there is no way to make new Image() work with Next.js, please show a different way to make this work. This new Image() is for making a GSAP scroll animation work.
Here is my code:
import Link from 'next/link'
import Head from 'next/head'
import Image from 'next/image'
import {useEffect, useRef, useState} from 'react'
import styles from '../styles/Home.module.css'
import {gsap} from 'gsap'
import { ScrollTrigger } from "gsap/dist/ScrollTrigger";
const Home = () => {
const viewer = useRef(null)
const image = new Image();
if (typeof window !== "undefined") {
gsap.registerPlugin(ScrollTrigger);
}
useEffect(()=>{
const rows = 5
const columns = 10
const frame_count = rows * columns - 1
// const imageWidth = 6336
// const imageHeight = 1782
const imageWidth = 4049
const imageHeight = 3000
const horizDiff = imageWidth / columns
const vertDiff = imageHeight / rows
const ctx = viewer.current.getContext("2d");
viewer.current.width = horizDiff;
viewer.current.height = vertDiff;
const image = new Image()
image.src = "./spriteDesk.jpg";
// image.src = "./spriteMobile.jpg";
image.onload = () => onUpdate();
const setPos = gsap.quickSetter(viewer.current, "background-position");
const obj = {num: 0};
gsap.to(obj, {
num: frame_count,
ease: "steps(" + frame_count + ")",
scrollTrigger: {
trigger: ".section-one",
start: "top top",
end: "+=" + imageHeight,
pin: true,
anticipatePin: 1,
scrub: 1
},
onUpdate
});
function onUpdate() {
ctx.clearRect(0, 0, horizDiff, vertDiff);
const x = Math.round((obj.num % columns) * horizDiff);
const y = Math.round(Math.floor(obj.num / columns) * vertDiff);
ctx.drawImage(image, x, y, horizDiff, vertDiff, 0, 0, horizDiff, vertDiff);
}
},[])
return (
<>
<Head>
<title>TalkingSkunk | Home</title>
<meta name='keywords' content='talkingskunk' />
<link rel="icon" href="/favicon.ico" />
{/* <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.6.1/ScrollTrigger.min.js"></script> */}
</Head>
{/* <div className={styles.bgWrap}>
<Image
src="/spriteDesk.png"
className='cityscape'
data-speed="0.2"
layout="fill"
objectFit="cover"
quality={100}
/> */}
{/* <Image src ='/spriteDesk.jpg' alt="spriteDesk" width ={4049} height = {3000} /> */}
{/* <p className={styles.bgText}>
Discover
</p> */}
<section className="styles.scene styles.section section-one">
<canvas ref={viewer} className="styles.viewer"></canvas>
</section>
</>
)
}
export default Home;
If anyone still needs this it can be accomplished by following the next steps:
Replace import Image from 'next/image' with import NextImage from 'next/image' (or anything similar),
Replace all <Image ... tags with <NextImage ... (or, if you don't have any, than just remove the import)
Use The Image() constructor to create a new HTMLImageElement instance described on mdn page.
In case you use Next.js version 11+ (or earlier) use one of the solutions described on this question to disable no-img-element lint in order to prevent the error.
I load GLTF model downloaded from Sketchfab in React app. Model loads perfectly well, but animation doesn`t play at all. I tried different approaches. Any ideas?
function Model({ url }) {
const model = useRef()
const { scene, animations } = useLoader(GLTFLoader, url)
const [mixer] = useState(() => new THREE.AnimationMixer())
useEffect(() => void mixer.clipAction(animations[0], group.current).play(), [])
return(
<primitive
ref={model}
object={scene}
/>
)
}
Solution
I had to use node instead of scene and select "RootNode" (console.log node and choose what seemed to me the main node, the model contained a dozen of nodes)
Update mixer by frames with useFrame
Apply animation to the model (a bit updated)
Working code:
function Model({ url }) {
const group = useRef()
const { nodes, scene, materials, animations } = useLoader(GLTFLoader, url)
const actions = useRef()
const [mixer] = useState(() => new THREE.AnimationMixer())
useFrame((state, delta) => mixer.update(delta))
useEffect(() => {
actions.current = { idle: mixer.clipAction(animations[0], group.current) }
actions.current.idle.play()
return () => animations.forEach((clip) => mixer.uncacheClip(clip))
}, [])
return(
<group ref={group} dispose={null}>
<primitive
ref={group}
name="Object_0"
object={nodes["RootNode"]}
/>
</group>
)
}
Now it works
You need to call mixer.update(delta) in useFrame inside your component:
import { useFrame } from 'react-three-fiber'
function Model({url}) {
.
.
.
useFrame((scene, delta) => {
mixer?.update(delta)
})
.
.
.
Put your glb file inside public filer.
import { Suspense, useEffect, useRef } from "react";
import { useAnimations, useGLTF } from "#react-three/drei";
export const Character = (props: any) => {
const ref = useRef() as any;
const glf = useGLTF("assets/Soldier.glb");
const { actions } = useAnimations(glf.animations, ref);
useEffect(() => {
actions.Run?.play();
});
return (
<Suspense fallback={null}>
<primitive
ref={ref}
object={glf.scene}
/>
</Suspense>
);
};
I'm using SVGloader to load an SVG so I can map it on my OBJ file. But when gave it url to the svg file it generates an error
TypeError:Cannot set property 'getStrokeStyle' of undefined
I'm using Angular 8 and rendering a Obj file using THREE.js. I want to load an svg and map it on the obj file to add texture to that file, but as I told above it is generating an error and I don't know how to solve it.
Here is code file.
import { Component, AfterViewInit, ViewChild, Input, ElementRef } from '#angular/core';
import * as THREE from 'three';
import { OrbitControls } from '#avatsaev/three-orbitcontrols-ts';
import {OBJLoader} from 'three-obj-mtl-loader';
import {SVGLoader} from 'three-svg-loader';
#Component({
selector: 'app-scene',
templateUrl: './scene.component.html',
styleUrls: ['./scene.component.css']
})
export class SceneComponent implements AfterViewInit {
#Input() name: string;
#ViewChild('canvas', {static:true}) canvasRef: ElementRef;
renderer = new THREE.WebGLRenderer;
scene = null;
camera = null;
controls = null;
mesh = null;
light = null;
loader;
svgLoader;
private calculateAspectRatio(): number {
const height = this.canvas.clientHeight;
if (height === 0) {
return 0;
}
return this.canvas.clientWidth / this.canvas.clientHeight;
}
private get canvas(): HTMLCanvasElement {
return this.canvasRef.nativeElement;
}
constructor() {
// this.loader = new OBJLoader();
this.scene = new THREE.Scene();
this.loader = new OBJLoader();
this.svgLoader = new SVGLoader();
this.camera = new THREE.PerspectiveCamera(15, window.innerWidth / window.innerHeight, 0.1, 1000)
}
ngAfterViewInit() {
this.configScene();
this.configCamera();
this.configRenderer();
this.configControls();
this.createLight();
this.createMesh();
this.animate();
}
configScene() {
// this.scene.background = new THREE.Color( 0xdddddd );
}
configCamera() {
this.camera.aspect = this.calculateAspectRatio();
this.camera.updateProjectionMatrix();
this.camera.position.set( 0, 0, 3 );
this.camera.lookAt( this.scene.position );
}
configRenderer() {
this.renderer = new THREE.WebGLRenderer({
canvas: this.canvas,
antialias: true,
alpha: true
});
this.renderer.setPixelRatio(devicePixelRatio);
// setClearColor for transparent background
// i.e. scene or canvas background shows through
this.renderer.setClearColor( 0x000000, 0 );
this.renderer.setSize((window.innerWidth/2), (window.innerHeight/2));
window.addEventListener('resize', ()=>{
this.renderer.setSize((window.innerWidth/2), (window.innerHeight)/2);
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
})
console.log('clientWidth', this.canvas.clientWidth);
console.log('clientHeight', this.canvas.clientHeight);
}
configControls() {
this.controls = new OrbitControls(this.camera);
this.controls.autoRotate = false;
this.controls.enableZoom = false;
// this.controls.maxDistance = 5;
// this.controls.minDistance = 10;
this.controls.enablePan = false;
this.controls.update();
}
createLight() {
this.light = new THREE.PointLight( 0xffffff );
this.light.position.set( -10, 10, 10 );
this.scene.add( this.light );
}
createMesh() {
this.svgLoader.load('../../../../assets/abc.svg')
console.log("SVG Loader", this.svgLoader)
this.loader.load('../../../../assets/nonunified.obj', (object)=>{
object.traverse( function ( child ) {
if ( child instanceof THREE.Mesh ) {
child.geometry.center();
}
} );
this.scene.add(object)
},
// called when loading is in progresses
function (xhr) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
// called when loading has errors
function ( error ) {
console.log( 'An error happened' );
}
)}
animate() {
window.requestAnimationFrame(() => this.animate());
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
}
The SVGLoader is not for loading svg-files to be used as textures but for loading them as Geometry: https://threejs.org/docs/#examples/en/loaders/SVGLoader
If you want to use an svg-file as a texture, you should be able to use the TextureLoader like this:
obj.material.map = new TextureLoader().load('../../../../assets/abc.svg');
I'm not sure if you actually need to rasterize it to a canvas first, if the above doesn't work, try what is described here: How do you load and display SVG graphics in three.js?