Three.js + webxr animations not playing from .glb file - animation

I am unable to get the animation clips from the .glb file to play. The .glb model loads and is displayed on the screen but the animation does not play.
The animations play fine when viewing the object in the three.js editor.
The .glb has 1 animation clip associated with it.
Any Help would be much appreciated!!!
import {
Mesh,
AmbientLight,
Clock,
AnimationMixer,
PerspectiveCamera,
Scene,
WebGLRenderer,
MeshBasicMaterial,
RingGeometry,
sRGBEncoding
} from 'three';
import {ARButton} from 'three/examples/jsm/webxr/ARButton';
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js'
class App {
constructor() {
this.clock = new Clock();
this.camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.camera.position.set(0, 1.6, 3);
this.scene = new Scene();
this.renderer = new WebGLRenderer({
antialias: true,
alpha: true
});
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.outputEncoding = sRGBEncoding;
document.body.appendChild(this.renderer.domElement);
this.initXR();
this.initScene();
window.addEventListener('resize', this.onWindowResize.bind(this), false);
this.renderer.setAnimationLoop(this.render.bind(this));
}
initXR() {
this.renderer.xr.enabled = true;
document.body.appendChild(ARButton.createButton(this.renderer, {requiredFeatures: ['hit-test']}));
this.hitTestSourceRequested = false;
this.hitTestSource = null;
this.controller = this.renderer.xr.getController(0);
this.controller.addEventListener('select', this.onSelect.bind(this));
}
initScene() {
let geometry = new RingGeometry(0.08, 0.10, 32).rotateX(-Math.PI / 2);
let material = new MeshBasicMaterial();
this.reticle = new Mesh(geometry, material);
this.reticle.matrixAutoUpdate = false;
this.reticle.visible = false;
this.scene.add(this.reticle);
const loader = new GLTFLoader();
loader.load('./models/stylized_character.glb', gltf => {
this.myObj = gltf;
gltf.scene.scale.set(.25, .25, .25);
gltf.scene.visible = true;
this.scene.add(gltf.scene);
//todo: this doesnt seem to work
this.mixer = new AnimationMixer(gltf);
var action = this.mixer.clipAction(gltf.animations[0]);
action.play();
});
var aLight = new AmbientLight(0xffffff, 1);
this.scene.add(aLight)
}
render(_, frame) {
const delta = this.clock.getDelta();
if (this.mixer) {
this.mixer.update(delta);
}
if (frame) {
if (this.hitTestSourceRequested === false) {
this.requestHitTestSource();
}
if (this.hitTestSource) {
this.getHitTestResults(frame);
}
}
this.renderer.render(this.scene, this.camera);
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.render(this.scene, this.camera);
}
onSelect() {
if (this.reticle.visible) {
this.myObj.scene.position.setFromMatrixPosition(this.reticle.matrix);
this.myObj.scene.visible = true;
}
}
async requestHitTestSource() {
const session = this.renderer.xr.getSession();
session.addEventListener('end', () => {
this.hitTestSourceRequested = false;
this.hitTestSource = null;
});
const referenceSpace = await session.requestReferenceSpace('viewer');
this.hitTestSource = await session.requestHitTestSource({space: referenceSpace, entityTypes: ['plane']});
this.hitTestSourceRequested = true;
}
getHitTestResults(frame) {
const hitTestResults = frame.getHitTestResults(this.hitTestSource);
if (hitTestResults.length) {
const hit = hitTestResults[0];
const pose = hit.getPose(this.renderer.xr.getReferenceSpace());
this.reticle.visible = true;
this.reticle.matrix.fromArray(pose.transform.matrix);
} else {
this.reticle.visible = false;
}
}
}
window.addEventListener('DOMContentLoaded', () => {
new App();
});

Related

How to use Threejs to create a boundary for the Mesh load by fbxloador

The created boundary's scale and rotation are totally different with the import fbxmodel.
Hi, I have loaded a fbx model into the scene by fbxLoador.
const addFbxModel = (modelName: string, position: Vector3) => {
const fbxLoader = new FBXLoader();
fbxLoader.load(
`../../src/assets/models/${modelName}.fbx`,
(fbxObject: Object3D) => {
fbxObject.position.set(position.x, position.y, position.z);
console.log(fbxObject.scale);
fbxObject.scale.set(0.01, 0.01, 0.01);
const material = new MeshBasicMaterial({ color: 0x008080 });
fbxObject.traverse((object) => {
if (object instanceof Mesh) {
object.material = material;
}
});
scene.add(fbxObject);
updateRenderer();
updateCamera();
render();
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
(error) => {
console.log(error);
}
);
};
And now i want to add a click function on this model which will highlight it by showing it's boundary box.
const onclick = (event: MouseEvent) => {
event.preventDefault();
if (renderBox.value) {
const mouse = new Vector2();
mouse.x = (event.offsetX / renderBox.value.clientWidth) * 2 - 1;
mouse.y = -(event.offsetY / renderBox.value.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const intersect = intersects[0];
const object = intersect.object;
createBoundary(object);
}
}
};
const createBoundary = (object: Object3D) => {
if (object instanceof Mesh) {
console.log(object.geometry, object.scale, object.rotation);
const edges = new EdgesGeometry(object.geometry);
const material = new LineBasicMaterial({ color: 0xffffff });
const wireframe = new LineSegments(edges, material);
wireframe.scale.copy(object.scale);
wireframe.rotation.copy(object.rotation);
console.log(wireframe.scale);
scene.add(wireframe);
}
};
But now the boundary's scale and rotation are totally different with the fbxmodel.
And also the boundary is too complex, is it possible to create one only include the outline and the top point.
Thank you. :)

The model scale is too small when using in web application project

I downloaded this model from https://sketchfab.com/3d-models/dcb3a7d5b1ad4f948aa4945d6e378c8a , The model scale is appearing normal when open in Windows 3D Viewer, three.js glTF Viewer and Babylon.js View, but when load the model in three.js module, some model's scale is incorrect, for example.
three.js in Website
three.js glTF Viewer
Babylon.js Viewer
This model scale is correct, when open in another application, it scale correctly.
Model name : dog.glb
Source : https://github.com/craftzdog/craftzdog-homepage
three.js in Website
three.js glTF Viewer
Babylon.js Viewer
This model scale is incorrect and so tiny, but when open in another application, it scale correctly
Model name : แมวประเทศไทย
Source : https://sketchfab.com/3d-models/dcb3a7d5b1ad4f948aa4945d6e378c8a
Here is GLTF Loader Code
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
export function loadGLTFModel(
scene,
glbPath,
options = { receiveShadow: true, castShadow: true }
) {
const { receiveShadow, castShadow } = options
return new Promise((resolve, reject) => {
const loader = new GLTFLoader()
loader.load(
glbPath,
gltf => {
const obj = gltf.scene
obj.name = 'persian'
obj.position.x = 0
obj.position.y = 0
obj.receiveShadow = receiveShadow
obj.castShadow = castShadow
scene.add(obj)
obj.traverse(function (child) {
if (child.isMesh) {
child.castShadow = castShadow
child.receiveShadow = receiveShadow
}
})
resolve(obj)
},
undefined,
function (error) {
reject(error)
}
)
})
}
Here is Model Display Code
import { useState, useEffect, useRef, useCallback } from 'react'
import { Box, Spinner } from '#chakra-ui/react'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { loadGLTFModel } from '../lib/model'
function easeOutCirc(x) {
return Math.sqrt(1 - Math.pow(x - 1, 4))
}
const PersianCat = () => {
const refContainer = useRef()
const [loading, setLoading] = useState(true)
const [renderer, setRenderer] = useState()
const [_camera, setCamera] = useState()
const [target] = useState(new THREE.Vector3(-0.5, 1.2, 0))
const [initialCameraPosition] = useState(
new THREE.Vector3(
20 * Math.sin(0.2 * Math.PI),
10,
20 * Math.cos(0.2 * Math.PI)
)
)
const [scene] = useState(new THREE.Scene())
const [_controls, setControls] = useState()
// On component mount only one time.
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
const { current: container } = refContainer
if (container && !renderer) {
const scW = container.clientWidth
const scH = container.clientHeight
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(scW, scH)
renderer.outputEncoding = THREE.sRGBEncoding
container.appendChild(renderer.domElement)
setRenderer(renderer)
// 640 -> 240
// 8 -> 6
const scale = scH * 0.005 + 4.8
const camera = new THREE.OrthographicCamera(
-scale,
scale,
scale,
-scale,
0.01,
50000
)
camera.position.copy(initialCameraPosition)
camera.lookAt(target)
setCamera(camera)
const ambientLight = new THREE.AmbientLight(0xcccccc, 1)
scene.add(ambientLight)
const controls = new OrbitControls(camera, renderer.domElement)
controls.autoRotate = true
controls.target = target
setControls(controls)
loadGLTFModel(scene, '/persian.glb', {
receiveShadow: false,
castShadow: false
}).then(() => {
animate()
setLoading(false)
})
let req = null
let frame = 0
const animate = () => {
req = requestAnimationFrame(animate)
frame = frame <= 100 ? frame + 1 : frame
if (frame <= 100) {
const p = initialCameraPosition
const rotSpeed = -easeOutCirc(frame / 120) * Math.PI * 20
camera.position.y = 10
camera.position.x =
p.x * Math.cos(rotSpeed) + p.z * Math.sin(rotSpeed)
camera.position.z =
p.z * Math.cos(rotSpeed) - p.x * Math.sin(rotSpeed)
camera.lookAt(target)
} else {
controls.update()
}
renderer.render(scene, camera)
}
return () => {
cancelAnimationFrame(req)
renderer.dispose()
}
}
}, [])
return (
<Box
ref={refContainer}
className="persian-cat"
m="auto"
at={['-20px', '-60px', '-120px']}
mb={['-40px', '-140px', '-200px']}
w={[280, 480, 640]}
h={[280, 480, 640]}
position="relative"
>
{loading && (
<Spinner
size="xl"
position="absolute"
left="50%"
top="50%"
ml="calc(0px - var(--spinner-size) / 2)"
mt="calc(0px - var(--spinner-size))"
/>
)}
</Box>
)
}
export default PersianCat

What does "Cannot set property 'getStrokeStyle' of undefined" mean for a Three.js program?

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?

Editing part sizes of a 3d model

I want to develop a web app to entering measurements of a man and displaying a 3d model with these measurements. I have chosen three.js to start it. And I downloaded a 3d model named standard-male-figure from clara.io. Here is my code to display human model.
import React, { Component } from "react";
import PropTypes from "prop-types";
import withStyles from "#material-ui/core/styles/withStyles";
import * as THREE from "three-full";
const styles = (/*theme*/) => ({
});
class ThreeDView extends Component {
constructor(props){
super(props);
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.renderScene - this.renderScene.bind(this);
this.animate = this.animate.bind(this);
}
componentDidMount() {
const width = this.mount.clientWidth;
const height = this.mount.clientHeight;
//ADD SCENE
this.scene = new THREE.Scene();
//ADD CAMERA
this.camera = new THREE.PerspectiveCamera(100,1);
this.camera.position.z = 12;
this.camera.position.y = 0;
this.camera.position.x = 0;
//ADD RENDERER
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setClearColor("#f0f0f0");
this.renderer.setSize(width, height);
this.mount.appendChild(this.renderer.domElement);
// MOUSE ROTATION
this.orbit = new THREE.OrbitControls(this.camera,this.renderer.domElement);
this.orbit.update();
//ADD LIGHTS
this.light = new THREE.PointLight(0xffffff,1.3);
this.light.position.z = 10;
this.light.position.y=20;
this.scene.add(this.light);
// ADD MAN FIGURE
const loader = new THREE.ColladaLoader();
loader.load("/models/standard-male-figure.dae",(manFigure)=>{
this.man = manFigure;
this.man.name = "man-figure";
this.man.scene.position.y = -10;
this.scene.add(this.man.scene);
},undefined,()=>alert("Loading failed"));
this.start();
}
componentWillUnmount() {
this.stop();
this.mount.removeChild(this.renderer.domElement);
}
start() {
if (!this.frameId) {
this.frameId = requestAnimationFrame(this.animate);
}
}
stop () {
cancelAnimationFrame(this.frameId);
}
animate () {
this.renderScene();
this.frameId = window.requestAnimationFrame(this.animate);
}
renderScene () {
this.orbit.update();
this.light.position.z = this.camera.position.z;
this.light.position.y=this.camera.position.y+20;
this.light.position.x=this.camera.position.x;
this.renderer.render(this.scene, this.camera);
}
render() {
return (
<div style={{ height: "640px" }} ref={(mount) => { this.mount = mount; }} >
</div>
);
}
}
ThreeDView.propTypes = {
values: PropTypes.object
};
/*
all values in inches
values = {
heightOfHand:10,
, etc..
}
*/
export default withStyles(styles)(ThreeDView);
values is measurements that user is entering. I have no idea about how to start updating 3d model with these measurements. Please give me a starting point or any advise to complete this. Thank You!.
Firstly you can get the current size and scale of your man
const box = new THREE.Box3().setFromObject(this.man)
const currentObjectSize = box.getSize()
const currentObjectScale = this.man.scale
and when an user update the size value (newSize), you can calculate a new scale for you man
const newScale = new THREE.Vector3(
currentObjectScale.x * newSize.x/currentObjectSize.x,
currentObjectScale.y * newSize.y/currentObjectSize.y,
currentObjectScale.z * newSize.z/currentObjectSize.z
)
and update your man with this scale
this.man.scale.set(newScale.x, newScale.y, newScale.z)

Error loading three.js Collada file: Works fine on first object, succeeding objects fail

I have a problem loading Collada files on three.js. The first object I load works properly:
But when I remove it from the scene and load another object (after removing from the scene and disposing assets), the geometry appears garbled:
What could be the problem?
Here's the code used:
(Special thanks to #gaitat for the dispose functions! originally answered here.
var renderer = null;
var scene = null;
var camera = null;
var controls = null;
var dae = null;
//var loader = null;
function init() {
renderer = new THREE.WebGLRenderer( { alpha: 1, antialias: true, clearColor: 0xffffff } );
renderer.setSize( 800, 600 );
var elem = $('.main3d')[0];
elem.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 20, 800/600, 1, 1000 );
camera.position.set( 0, -100, 50 );
//camera.lookAt( scene.position );
controls = new THREE.TrackballControls( camera, renderer.domElement );
var light = new THREE.AmbientLight( 0xffffff ); // soft white light
scene.add( light );
threeAnimate();
}
function load(url) {
loader = new THREE.ColladaLoader();
loader.load(url, function (collada) {
dae = collada.scene;
scene.add(dae);
});
}
var animFrame = null;
function animate() {
animFrame = requestAnimationFrame( threeAnimate );
renderer.render( scene, camera );
controls.update();
}
// special thanks to #gaitat for this function!
// originally answered here https://stackoverflow.com/questions/33152132/three-js-collada-whats-the-proper-way-to-dispose-and-release-memory-garbag/33199591
function disposeNode (node)
{
if (node instanceof THREE.Camera)
{
node = undefined;
}
else if (node instanceof THREE.Light)
{
node.dispose ();
node = undefined;
}
else if (node instanceof THREE.Mesh)
{
if (node.geometry)
{
node.geometry.dispose ();
node.geometry = undefined;
}
if (node.material)
{
if (node.material instanceof THREE.MeshFaceMaterial)
{
$.each (node.material.materials, function (idx, mtrl)
{
if (mtrl.map) mtrl.map.dispose ();
if (mtrl.lightMap) mtrl.lightMap.dispose ();
if (mtrl.bumpMap) mtrl.bumpMap.dispose ();
if (mtrl.normalMap) mtrl.normalMap.dispose ();
if (mtrl.specularMap) mtrl.specularMap.dispose ();
if (mtrl.envMap) mtrl.envMap.dispose ();
mtrl.dispose (); // disposes any programs associated with the material
mtrl = undefined;
});
}
else
{
if (node.material.map) node.material.map.dispose ();
if (node.material.lightMap) node.material.lightMap.dispose ();
if (node.material.bumpMap) node.material.bumpMap.dispose ();
if (node.material.normalMap) node.material.normalMap.dispose ();
if (node.material.specularMap) node.material.specularMap.dispose ();
if (node.material.envMap) node.material.envMap.dispose ();
node.material.dispose (); // disposes any programs associated with the material
node.material = undefined;
}
}
node = undefined;
}
else if (node instanceof THREE.Object3D)
{
node = undefined;
}
} // disposeNode
function disposeHierarchy (node, callback)
{
for (var i = node.children.length - 1; i >= 0; i--)
{
var child = node.children[i];
disposeHierarchy (child, callback);
callback (child);
}
}

Resources