Helle, I'm new to Three.js and I'm trying to show an animated gltf/glb model in my AR project.
There are a lot of examples on how to animate gltf models in a 3d environment but i can't get it to work in my AR project.
I can import the 3d model and show it in AR. But the models refuses to animate.
I would appreciate some help.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<title>WebXr 3d model demo</title>
<!-- three.js -->
<script src="https://unpkg.com/three#0.126.0/build/three.js"></script>
<script src="https://unpkg.com/three#0.126.0/examples/js/loaders/GLTFLoader.js"></script>
</head>
<body>
<!-- Starting an immersive WebXR session -->
<button onclick="activateXR()">Start AR</button>
<script>
async function activateXR() {
// Add a canvas element and initialize a WebGL context that is compatible with WebXR.
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const gl = canvas.getContext("webgl", { xrCompatible: true });
//Step 2
const scene = new THREE.Scene();
let clock = new THREE.Clock();
let mixer;
var hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.set(0, 300, 0);
scene.add(hemiLight);
var dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(75, 300, 0);
scene.add(dirLight);
//step 3
// Set up the WebGLRenderer, which handles rendering to the session's base layer.
const renderer = new THREE.WebGLRenderer({
alpha: true,
preserveDrawingBuffer: true,
canvas: canvas,
context: gl,
});
renderer.autoClear = false;
// The API directly updates the camera matrices.
// Disable matrix auto updates so three.js doesn't attempt
// to handle the matrices independently.
const camera = new THREE.PerspectiveCamera();
camera.matrixAutoUpdate = false;
// step 4
// Initialize a WebXR session using "immersive-ar".
const session = await navigator.xr.requestSession("immersive-ar", {
requiredFeatures: ["hit-test"],
});
session.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl),
});
// A 'local' reference space has a native origin that is located
// near the viewer's position at the time the session was created.
const referenceSpace = await session.requestReferenceSpace("local");
// Create another XRReferenceSpace that has the viewer as the origin.
const viewerSpace = await session.requestReferenceSpace("viewer");
// Perform hit testing using the viewer as origin.
const hitTestSource = await session.requestHitTestSource({
space: viewerSpace,
});
//Use the model loader from the previous step to load a targeting reticle and a sunflower from the web.
const loader = new THREE.GLTFLoader();
let reticle;
loader.load(
"https://immersive-web.github.io/webxr-samples/media/gltf/reticle/reticle.gltf",
function (gltf) {
reticle = gltf.scene;
reticle.visible = false;
scene.add(reticle);
}
);
let key;
loader.load("key/key.glb", function (gltf) {
const model = gltf.scene;
mixer = new THREE.AnimationMixer(model);
mixer.clipAction(gltf.animations[0]).play();
key = gltf.scene;
});
animate();
// XRSession receives select events when the user completes a primary action. In an AR session, this corresponds to a
// tap on the screen.
session.addEventListener("select", (event) => {
if (key) {
const clone = key.clone();
clone.position.copy(reticle.position);
scene.add(clone);
}
});
//step 5
// Create a render loop that allows us to draw on the AR view.
const onXRFrame = (time, frame) => {
// Queue up the next draw request.
session.requestAnimationFrame(onXRFrame);
// Bind the graphics framebuffer to the baseLayer's framebuffer
gl.bindFramebuffer(
gl.FRAMEBUFFER,
session.renderState.baseLayer.framebuffer
);
// Retrieve the pose of the device.
// XRFrame.getViewerPose can return null while the session attempts to establish tracking.
const pose = frame.getViewerPose(referenceSpace);
if (pose) {
// In mobile AR, we only have one view.
const view = pose.views[0];
const viewport = session.renderState.baseLayer.getViewport(view);
renderer.setSize(viewport.width, viewport.height);
// Use the view's transform matrix and projection matrix to configure the THREE.camera.
camera.matrix.fromArray(view.transform.matrix);
camera.projectionMatrix.fromArray(view.projectionMatrix);
camera.updateMatrixWorld(true);
const hitTestResults = frame.getHitTestResults(hitTestSource);
if (hitTestResults.length > 0 && reticle) {
const hitPose = hitTestResults[0].getPose(referenceSpace);
reticle.visible = true;
reticle.position.set(
hitPose.transform.position.x,
hitPose.transform.position.y,
hitPose.transform.position.z
);
reticle.updateMatrixWorld(true);
}
// Render the scene with THREE.WebGLRenderer.
renderer.render(scene, camera);
}
};
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
renderer.render(scene, camera);
}
session.requestAnimationFrame(onXRFrame);
}
</script>
</body>
</html>
The code + glb file
https://github.com/weewautersmaxim/arDemo
In your animate function, it looks like you need something along the lines of:
if (mixer) {
mixer.update(delta);
}
Otherwise the animation mixer doesn't know that time is passing.
Finally was able to solve my issue.
Firstly I load the model in the addEventListener.
I also had to change the animation code inside the load function.
The 'animate' function also needed extra code.
If anyone is interested in the final code. Here is a link to my github repository. Everything is explained there.
https://github.com/Maxim-Weewauters/WebXR_Animations
Related
In a simple IFC.js application, loading IFC models (and fragments for that matter) fails with TypeError: this.geometry is undefined when accelerating the raycast using three-mesh-bvh. Removing that makes the application work again. I have tried with plenty of different IFC models, all of which work when removing the acceleration. Even the test models (Schependomlaan.ifc for example) fail when loading them.
Source of the app: https://github.com/gjkf/simple-ifc
app.js:
import { AmbientLight, AxesHelper, DirectionalLight, GridHelper, PerspectiveCamera, Scene, WebGLRenderer } from 'three';
import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from 'three-mesh-bvh';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { IFCLoader } from 'web-ifc-three/IFCLoader';
//Creates the Three.js scene
const scene = new Scene();
//Object to store the size of the viewport
const size = {
width: window.innerWidth,
height: window.innerHeight,
};
//Creates the camera (point of view of the user)
const camera = new PerspectiveCamera(75, size.width / size.height);
camera.position.z = 15;
camera.position.y = 13;
camera.position.x = 8;
//Creates the lights of the scene
const lightColor = 0xffffff;
const ambientLight = new AmbientLight(lightColor, 0.5);
scene.add(ambientLight);
const directionalLight = new DirectionalLight(lightColor, 1);
directionalLight.position.set(0, 10, 0);
directionalLight.target.position.set(-5, 0, 0);
scene.add(directionalLight);
scene.add(directionalLight.target);
//Sets up the renderer, fetching the canvas of the HTML
const threeCanvas = document.getElementById("three-canvas");
const renderer = new WebGLRenderer({ canvas: threeCanvas, alpha: true });
renderer.setSize(size.width, size.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
//Creates grids and axes in the scene
const grid = new GridHelper(50, 30);
scene.add(grid);
const axes = new AxesHelper();
axes.material.depthTest = false;
axes.renderOrder = 1;
scene.add(axes);
//Creates the orbit controls (to navigate the scene)
const controls = new OrbitControls(camera, threeCanvas);
controls.enableDamping = true;
controls.target.set(-2, 0, 0);
//Animation loop
const animate = () => {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
};
animate();
//Adjust the viewport to the size of the browser
window.addEventListener("resize", () => {
(size.width = window.innerWidth), (size.height = window.innerHeight);
camera.aspect = size.width / size.height;
camera.updateProjectionMatrix();
renderer.setSize(size.width, size.height);
});
//Sets up the IFC loading
const ifcLoader = new IFCLoader();
ifcLoader.ifcManager.useWebWorkers(true, "worker/IFCWorker.js");
ifcLoader.ifcManager.setWasmPath("../wasm/");
ifcLoader.ifcManager.applyWebIfcConfig({
USE_FAST_BOOLS: true,
COORDINATE_TO_ORIGIN: true,
});
ifcLoader.ifcManager.setupThreeMeshBVH(
acceleratedRaycast,
computeBoundsTree,
disposeBoundsTree
);
const input = document.getElementById("file-input");
input.addEventListener(
"change",
async (changed) => {
const ifcURL = URL.createObjectURL(changed.target.files[0]);
// ifcLoader.load(ifcURL, (ifcModel) => scene.add(ifcModel));
const model = await ifcLoader.loadAsync(ifcURL, (e) => console.log(e));
console.log(model);
scene.add(model);
},
false
);
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>IFC.js</title>
</head>
<body>
<input type="file" id="file-input" accept=".ifc, .ifcXML, .ifcZIP">
<canvas id="three-canvas"></canvas>
<script src="bundle.js"></script>
</body>
</html>
I made this simple application to nail down the problem, but in a different (Angular) project I would really need the accelerated raycasting to ensure the experience is smooth.
The problem was with your call to "setupThreeMeshBVH()"
This is a proper call with a proper sequence👇
ifcLoader.ifcManager.setupThreeMeshBVH(
computeBoundsTree,
disposeBoundsTree,
acceleratedRaycast
);
In your code, you had given "computeBoundsTree" as the second parameter whereas it should be first.
As the title states, I am using es6 classes, but because they are all not modules apart from the main.js file, it makes it difficult to use API's because I cannot make use of import modules.
I used the code from this link's answer: How to add in Three.js PointerLockControl? and pasted the code into a js file, calling it up in my HTML, but I get an error stating:
Uncaught ReferenceError: PointerLockControls is not defined
It is not picking up the class when I reference it. I tried to link it to the GitHub raw code, and it didn't pick it up either.
This is my index.html code (only one line referencing the GitHub raw code):
<!-- This html file is what the browser shows when we run index.js on port 5000. Therefore our display of our game-->
<!DOCTYPE html>
<html>
<head>
<title>Aftermath</title>
<!-- SCRIPTS-->
<!--Loads three.js into our game-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.js"> </script>
<!--Loads orbit controls around player-->
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
<!--Loads gltf files (our blender files)-->
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/loaders/GLTFLoader.js"></script>
<!--Pointer lock controls-->
<script src="https://github.com/mrdoob/three.js/blob/dev/examples/js/controls/PointerLockControls.js"></script>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body { margin: 0; height: 100%; width: 100%; overflow: hidden}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<!--MOVEMENT-->
<script src="./important/THREEx.KeyboardState.js" ></script>
<script src="./important/FirstPersonControls.js" ></script>
<script src="./js/CharacterControls.js" ></script>
<!--========================================-->
<!--Lighting models-->
<script src="./js/sceneSubjects/lighting/GeneralLights.js" ></script>
<!--add models to scene-->
<!--Subject (model) scripts-->
<!--This is our main blender house-->
<script src="./js/sceneSubjects/House.js" ></script>
<!--Our character model file (not complete due to loading issues with fbx)-->
<script src="./js/sceneSubjects/characters/MainChar.js" ></script>
<!--Just a demo for you to see how this works-->
<script src="./js/sceneSubjects/objects/SceneSubject.js" ></script>
<!--Block to test raycasting -->
<script src="./js/sceneSubjects/characters/TestBlock.js" ></script>
<!--MANAGERS-->
<!--Load the scene manager-->
<script src="./js/EntityManager.js" ></script>
<script src="./js/SceneManager.js" ></script>
<script src = "./js/Time.js" ></script>
<!--Load our main.js function-->
<script type="module" src="./js/main2.js"></script>
</body>
</html>
This is my Scene Manager, which controls the whole scene. I just tested the initialization, which failed:
//Global Variables
var generalLights = new GeneralLights();
var house = new House();
var sceneSubject = new SceneSubject();
var testBlock = new TestBlock();
var mainChar = new MainChar(testBlock);
class SceneManager {
constructor(canvas) {
//this entire function renders a scene where you can add as many items as you want to it (e.g. we can create the house and add as
//many items as we want to the house). It renders objects from other JavaScript files
//------------------------------------------------------------------------------------------------------------------------------------------
//These are supposed to act like constants. DO NOT CHANGE
this.GAME_PAUSE = "pause";
this.GAME_RUN = "run";
//------------------------------------------------------------------------------------------------------------------------------------------
//we use (this) to make variables accessible in other classes
this.time = new Time();
this.game_state = this.GAME_RUN;
this.screenDimensions = {
width: canvas.width,
height: canvas.height
};
//the essentials for rendering a scene
this.scene = this.buildScene();
this.renderer = this.buildRender(this.screenDimensions);
this.camera = this.buildCamera(this.screenDimensions);
controls = new PointerLockControls( this.camera, document.body );
// this.scene.add(controls.getObject());
this.managers = this.createManagers();
this.loadToScene(this.managers[0].entities);
//Allow camera to orbit target (player) - OrbitPlayer Controls
//this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
//this.controls.target.set(0, 20, 0);
//this.controls.update();
}
loadToScene(entities)
{
for (let i = 0 ; i < entities.length ; i++)
{
console.log("before" +i.toString());
this.scene.add(entities[i].object);
console.log("after");
}
}
//this function creates our scene
buildScene() {
//create a new scene
const scene = new THREE.Scene();
//set the scene's background-> in this case it is our skybox
const loader = new THREE.CubeTextureLoader();
//it uses different textures per face of cube
const texture = loader.load([
'../skybox/House/posx.jpg',
'../skybox/House/negx.jpg',
'../skybox/House/posy.jpg',
'../skybox/House/negy.jpg',
'../skybox/House/posz.jpg',
'../skybox/House/negz.jpg'
]);
scene.background = texture;
//if we wanted it to be a color, it would have been this commented code:
//scene.background = new THREE.Color("#000");
return scene;
}
//this creates a renderer for us
buildRender({ width, height }) {
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true, alpha: true
});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
return renderer;
}
//create a camera for the screen
buildCamera({ width, height }) {
//SETTING FIELD OF VIEW, ASPECT RATIO (which should generally be width/ height), NEAR AND FAR (anything outside near/ far is clipped)
const aspectRatio = width / height;
const fieldOfView = 60;
const nearPlane = 1;
const farPlane = 1000;
//there are 2 types of cameras: orthographic and perspective- we will use perspective (more realistic)
const camera = new THREE.PerspectiveCamera(fieldOfView, aspectRatio, nearPlane, farPlane);
//Set camera initial position to main character
let pos = mainChar.returnWorldPosition();
camera.position.set(pos.x,pos.y,pos.z);
return camera;
}
//add subjects to the scene
createManagers() {
const managers=[new EntityManager()];
//can be altered so we can add multiple entities, and depending on which position
//it is, certain ones won't be paused, and some will be
//Note that these variables are declared globally before the class definition
/*This is so that we can use any of these object's methods or values later somewhere else*/
managers[0].register(generalLights);
managers[0].register(house);
managers[0].register(mainChar);
managers[0].register(sceneSubject);
managers[0].register(testBlock);
return managers;
}
updateCameraPosition() {
//Match camera position and direction to the character's position and direction
let pos = mainChar.returnWorldPosition();
let dir = mainChar.returnObjectDirection();
//Set y to 10 to move camera closer to head-height
this.camera.position.set(pos.x,10,pos.z);
this.camera.rotation.set(dir.x,dir.y,dir.z);
}
//this updates the subject/model every frame
update() {
//won't call this loop if it's paused-> only for objects that need to be paused (managers that need to be paused)
if (this.game_state == this.GAME_RUN)
{
const runTime = this.time.getRunTime();
this.managers[0].update(runTime);
}
//update orbit controls
//this.controls.update();
this.updateCameraPosition();
this.renderer.render(this.scene, this.camera);
}
//this resizes our game when screen size changed
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
pause(){ //when pause mode is entered. The pause menu needs to be rendered.
this.game_state = this.GAME_PAUSE;
this.time.pause();
}
unpause(){
this.game_state = this.GAME_RUN;
this.time.unpause();
}
}
I changed all my es6 classes into es6 modules, and used import. I don't think there was a solution for this with cdn or raw git scripts.
How does one get the current geometry of an animated mesh? Say I have a mesh that's going through some animation and I want to grab a copy of the current pose and create a new mesh using it (not necessarily skinned the same way), how would I do this?
Edit: Here is a simple example. It loads one of the three.js example animations and runs it. The loaded mesh is skinnedMesh. Every time animate() is called, a copy of the mesh is made using skinnedMesh.geometry. The copy is called newMesh, and it's created with a simple red MeshBasicMaterial and offset to one side of the original.
If you run the code, you'll see that although skinnedMesh is animated, newMesh is always a copy of the untransformed geometry. If it was doing what I wanted it to do, newMesh would be in the same pose as skinnedMesh.
Clearly this particular example is very inefficient, as I could just make another copy of skinnedMesh and animate it separately. But that's not what I want to do. I want to be able to grab the state of the animated mesh at any point in time and make a copy of the pose it happens to be in at that moment.
I hope this makes everything clearer.
<!DOCTYPE html>
<html lang="en">
<head>
<title>test</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"> </script>
<script>
var container;
var camera, scene, renderer, loader, clock, light;
var skinnedMesh, animation, groundMaterial, planeGeometry, mixer;
var newMesh = null;
var loaded = false;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(10, 0, 10);
scene = new THREE.Scene();
loader = new THREE.JSONLoader();
clock = new THREE.Clock;
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
groundMaterial = new THREE.MeshPhongMaterial({
emissive: 0x010101
});
planeGeometry = new THREE.PlaneBufferGeometry(16000, 16000);
ground = new THREE.Mesh(planeGeometry, groundMaterial);
ground.position.set(0, -5, 0);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
light = new THREE.HemisphereLight(0x777777, 0x003300, 1);
light.position.set(-80, 500, 50);
scene.add(light);
loader.load('http://threejs.org/examples/models/skinned/simple/simple.js', function(geometry, materials) {
for (var k in materials) {
materials[k].skinning = true;
}
skinnedMesh = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
skinnedMesh.scale.set(1, 1, 1);
skinnedMesh.position.y = 0;
scene.add(skinnedMesh);
mixer = new THREE.AnimationMixer(skinnedMesh);
mixer.addAction(new THREE.AnimationAction(skinnedMesh.geometry.animations[0]));
camera.lookAt(skinnedMesh.position);
loaded = true;
});
}
function animate() {
requestAnimationFrame(animate);
if (!loaded) {
return;
}
if (mixer) mixer.update(clock.getDelta());
if (newMesh) {
scene.remove(newMesh);
}
newMesh = new THREE.Mesh(skinnedMesh.geometry,
new THREE.MeshBasicMaterial({
color: 0xff0000
})
);
newMesh.position.x = skinnedMesh.position.x - 6;
scene.add(newMesh);
render();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
</html>
You can directly reference the geometry property of a Mesh Object which contains an array of faces.
Manipulating the geometry ad-hoc is an adventure onto itself, but the structure should be clear enough.
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({
color: 0xffff00
});
var mesh = new THREE.Mesh(geometry, material);
console.log(mesh.geometry)
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
Bones don't affect the geometry in memory, their matrices are being applied to geometry in a shader during the rendering and that geometry can't be accessed from JS level.
You have to bake the current pose into positions and also to normals, implementation: https://stackoverflow.com/a/66404012/696535
I originally had an animate function in place for my three.js scene that is loaded within an AngularJS Modal, but found that after closing the Modal, the animation keeps going, and that is unneeded since I don't require constant animation like a video game would have.
At this point, I switched it to only render when someone uses the OrbitControls to move the simple box in my example, and have an initial call to render the scene so that users can see the box instead of a big blacked out square.
However, upon initial render, the texture does not appear to be applied until I use the orbit controls and move the box, at which point they appear. This is odd, since both my initial call and the listener tied to the OrbitControls are to the same function. How do I get the initial load to show the texture?
$scope.generate3D = function () {
// 3D OBJECT - Variables
var texture0 = baseBlobURL + 'Texture_0.png';
var boxDAE = baseBlobURL + 'Box.dae';
var scene;
var camera;
var renderer;
var box;
var controls;
var newtexture;
// Update texture
newtexture = THREE.ImageUtils.loadTexture(texture0);
//Instantiate a Collada loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load(boxDAE, function (collada) {
box = collada.scene;
box.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
var animation = new THREE.Animation(child, child.geometry.animation);
animation.play();
}
});
box.scale.x = box.scale.y = box.scale.z = .2;
box.updateMatrix();
init();
// Initial call to render scene, from this point, Orbit Controls render the scene per the event listener
render();
});
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xdddddd);
//renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setSize(500, 500);
// Load the box file
scene.add(box);
// Lighting
var light = new THREE.AmbientLight();
scene.add(light);
// Camera
camera.position.x = 40;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// Rotation Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.addEventListener('change', render);
controls.rotateSpeed = 5.0;
controls.zoomSpeed = 5;
controls.noZoom = false;
controls.noPan = false;
var myEl = angular.element(document.querySelector('#webGL-container'));
myEl.append(renderer.domElement);
}
function render() {
renderer.render(scene, camera);
console.log('loaded');
}
}
You are using ColladaLoader and you want to force a call to render() when the model and all the textures are loaded.
If you add the model to the scene in the loader callback, there is still a chance that even though the model has loaded, the textures may not have.
One thing you can do is add the following before instantiating the loader:
THREE.DefaultLoadingManager.onLoad = function () {
// console.log( 'everything loaded' ); // debug
render();
};
Or alternatively,
THREE.DefaultLoadingManager.onProgress = function ( item, loaded, total ) {
// console.log( item, loaded, total ); // debug
if ( loaded === total ) render();
};
three.js r.72
I've loaded a Blender model using the three.js library and want to allow the users to change the texture of some faces through an input field in a form. I don't have any problem when I use the WebGLRenderer, and it works fine in Chrome, but it doesn't work with the canvas renderer when the texture coming from the input is in data:image... format. Seems if I load a full path image from the server it works fine. Does anybody know if there's a way to load textures this way and render them with the canvasrenderer?
Thank you.
I add here the code after I set the camera, lights and detect it the browswer detects webgl or not to use the WebGLRenderer or the CanvasRenderer.
First I load the model from blender:
var loader = new THREE.JSONLoader();
loader.load('assets/models/mimaquina9.js', function (geometry, mat) {
//I set the overdraw property to 1 for each material like i show here for 16
mat[16].overdraw = 1;
mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(mat) );
mesh.scale.x = 5;
mesh.scale.y = 5;
mesh.scale.z = 5;
scene.add(mesh);
}, 'assets/images');
render();
//To render, I try to make an animation in case WebGL is available and just render one frame in case of using the canvas renderer.
function render() {
if(webgl){
if (mesh) {
mesh.rotation.y += 0.02;
}
// render using requestAnimationFrame
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
else if(canvas){
camera.position.x = 30;
camera.position.y = 20;
camera.position.z = 40;
camera.lookAt(new THREE.Vector3(0, 10, 0));
setTimeout(function (){
//something you want delayed
webGLRenderer.render(scene, camera);
}, 1000);
}
}
$('#datafile').change(function(e)
{
e.preventDefault();
var f = e.target.files[0];
if(f && window.FileReader)
{
var reader = new FileReader();
reader.onload = function(evt) {
console.log(evt);
mesh.material.materials[16].map = THREE.ImageUtils.loadTexture(evt.target.result);
if(canvas && !webgl){
//I read that might be a problem of using Lambert materials, so I tried this commented line without success
//mesh.material.materials[16] = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture(evt.target.result)});
//If I uncomment the next line, it displays the texture fine when rendering after.
//mesh.material.materials[16].map = THREE.ImageUtils.loadTexture("assets/images/foto.jpg");
render();
}
}
reader.readAsDataURL(f);
}
});
Thanks once more.
evt.target.result is a DataURL so you should assign that to a image.src. Something like this should work:
var image = document.createElement( 'img' );
image.src = evt.target.result;
mesh.material.materials[16].map = new THREE.Texture( image );