I'm using an image as a texture several times, once via the material component, another time in a custom component. In the latter I'm using THREE.TextureLoader() which cause the application to load the image twice. I bet there's another way.
Current situation
HTML
<!-- Assets -->
<a-assets>
<img id="my-map" src="path/to/map.jpg">
<a-asset-item id="my-model" src=path/to/model.gltf""></a-asset-item>
</a-assets>
<!-- Entities -->
<a-box material="src: #my-map">
<a-entity gltf-model="src: #my-model" custom-component="mymap: #my-map">
JS
// custom-component extract
schema: { mymap: { type: 'map' } }
init: function()
{
let mesh = this.el.getObject3D('mesh')
mesh.traverse( function( child )
{
if ( child.isMesh )
{
let TextureLoader = new THREE.TextureLoader()
let mymap = TextureLoader.load( data.mymap )
child.material = new THREE.MeshPhongMaterial({ map: mymap })
child.material.needsUpdate = true;
}
})
}
Question
How can I use the same image asset in the custom component without loading it twice?
You're using mesh.traverse() which calls your function once per child of mesh. If you have two children in your mesh, it'll call TextureLoader.load() twice. Simply take the load call out of traverse(), and you should see the image loaded just once.
let mesh = this.el.getObject3D('mesh')
let TextureLoader = new THREE.TextureLoader()
let mymap = TextureLoader.load( data.mymap )
mesh.traverse( function( child )
{
if ( child.isMesh )
{
child.material = new THREE.MeshPhongMaterial({ map: mymap })
child.material.needsUpdate = true;
}
})
After some research digging in ThreeJs code I've found that TextureLoader relies on ImageLoader (see here) which, before loading a new image, it looks into THREE.Cache to see if the image has already been loaded (see here).
Loading images as img in AFrame doesn't store them in THREE.Cache therefore the image was loaded twice. But if you load the image as a-asset-item it does.
So there is no need to change anything in javascript. Just use a-asset-item instead of img.
For more info see AFrame documentation
Related
I have a galaxy made of 3 planets and a spaceship and I want all of them to be clickable to load another page. I managed to do so with the 3 planets adding planet.userData = {URL: "my.html"}; but not with the spaceship, which is a GLTF loaded model.
This is how I loaded the spaceship:
const loader = new GLTFLoader();
loader.load('models/spaceship.glb', function(gltf) {
const spaceship = gltf.scene;
spaceship.scale.set(0.08, 0.08, 0.08);
spaceship.userData = {URL: "my.html"}; //Adding the URL here
objects.push(spaceship); //This is for raycasting
scene.add(spaceship);
}, undefined, function(error){
console.error(error);
});
And this is the function for the Mouse Down event:
function onDocumentMouseDown(){
mouse.x = (event.clientX/window.innerWidth)*2-1;
mouse.y = -(event.clientY/window.innerHeight)*2+1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(objects, true);
if ( intersects.length > 0 ) {
window.open(intersects[0].object.userData.URL); // <-- Doesn't work
}
}
When clicking on the spaceship the URL value is "undefined". I'm sure there is another way to do so but I can't find any solution. Thank you so much in advance to anyone helping!
I am new to three.js, pls guide me how to change the image dynamically
Tried with the below option of replacing the image from the original image
I tried with the below code
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load( 't2.mtl', function( materials ) {
materials.preload();
var texture = THREE.TextureLoader('bbc.png');
var material = new THREE.MeshLambertMaterial( { map: texture } );
materials.setMaterials( material );
objLoader.load( 't2.obj', function ( object ) {
scene.add( object );
}, onProgress, onError );
});
I am not getting any error but updated image is not displayed ? but the actual object is displayed in blank color. Pls suggest
Thanks
Shaik
I'm trying to load obj files using three.js and webGL. Currently I can load one object by directly changing the code, but I want users to be able to submit their own .obj files.
So far, this is the code that I have. I tried using various examples but I can't really grasp how to use it correctly. I think I might have to write a function to update, but would that require redoing everything in init? (By "redo" I pretty much mean copy/paste.) Is there an easier way of doing it?
relevant parts of html file:
<form id="upload" method="get">
.obj Upload: <input type="text" size="20" name="objfile" id="objfile">
<!-- <input type="button" id="objsubmit" value="Send"> -->
</form>
<script type="text/javascript" src="cubemain.js"></script>
cubemain.js:
var scene, camera, renderer;
var container;
var objFile;
window.onload=function() {
init();
render();
}
function init() {
// upload
objFile = document.getElementById("objfile");
// set up scene, camera, renderer
scene = new THREE.Scene();
var FOV = 70;
var ASPECT = window.innerWidth/window.innerHeight;
var NEAR = 1;
var FAR = 1000;
camera = new THREE.PerspectiveCamera(FOV, ASPECT, NEAR, FAR);
// move camera back otherwise in same position as cube
camera.position.z = 5;
var light = new THREE.AmbientLight(0x101030);
scene.add(light);
var directionalLight = new THREE.DirectionalLight( 0xffeedd );
directionalLight.position.set( 0, 0, 1 );
scene.add( directionalLight );
// use OBJLoader
var loader = new THREE.OBJLoader();
loader.load(objFile.value + ".obj", function(object) {
scene.add(object);
});
renderer = new THREE.WebGLRenderer();
renderer.setSize(.70*window.innerWidth, .75*window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.setClearColor(0xCC99FF, 1);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
It really depends on various aspects. What I did was simple.
//In the html.html
The object: <input type="text" id="inputFile">
<input type="button" id="submitBtn" onclick="loadTheFile()">
Now when the button is clicked, it will trigger a function in your javascript file. All you have to do now is to create the loadTheFile function and another function that loads the obj file. Here a simple snippet of it.
//The rest of your javascript file
function loadTheFile(){
//First we need to get the value of the text. We shall store the name of
//the file in a fileName variable
var fileName = document.getElementById("inputFile").value;
//Now what you should do is to run another function, let us call it loadingFile
//which has the argument fileName, the name of the file that we have received
loadingFile(fileName);
}
function loadingFile(fileName){
//Use your loader and instead of the static path, use fileName as your new path
//Please note that the path that you pass into the loader is a relative path
//to your main applciation. So for example you store your file in the model folder
//and the fileName variable is just a file name and not a relative directory, then
//you should add the path of the folder like as follows:
var completePath = "models/" + filename;
//the rest of your loader
// use OBJLoader
var loader = new THREE.OBJLoader();
loader.load(completePath + ".obj", function(object) {
scene.add(object);
});
}
Hope that helps!
To help you further, since we have declared and define a helper function, loadingFile, you can change the loader part in your init() function as follows:
before:
var loader = new THREE.OBJLoader();
loader.load(objFile.value + ".obj", function(object) {
scene.add(object);
});
after:
loadingFile(objFile.value);
Follow up question: Does your code run properly? As in you can see the object? This is because I see that your code is much cleaner and more concise than what I usually use; so if it does I can use your code to improve the readability of mine. Thank you very much and I really hope my answer helps you! ^u^
I am trying to change the material of a Collada model exported from Sketchup:
I have used:
dae.traverse(function(child) {
if (child.material!==undefined) {
if (child.material.materials===undefined) {
child.material.map = THREE.ImageUtils.loadTexture( "textures/monster.jpg", null, function( child ) {
child.geometry.buffersNeedUpdate;
child.geometry.uvsNeedUpdate;
});
} else {
for (var j=0;j<child.material.materials.length;j++)
child.material.materials[j].map = THREE.ImageUtils.loadTexture( "textures/monster.jpg", null, function( child ) {
child.material.materials[j].needsUpdate;
child.geometry.buffersNeedUpdate;
child.geometry.uvsNeedUpdate;
});
};
};
};
});
However when I try to change the texture map, or even the entire texture, my geometry disappears completely.
I have seen this, but it was not helpful. Three.js Map material showing warnings despite updating buffers
Does anyone know what I am doing wrong? Thanks!
UPDATE:
Changing my material from MeshLambertMaterial to ShaderMaterial makes the geometry appear, but it is red. (It should be a porcelain white). Should I not be using MeshLambertMaterial for a complex Collada model?
I have the ascii ply file loading fine now, but it has a texture and I can not seem to get it to load not matter how I configure things.
cameraMain = new THREE.Camera(8, MainWidth / MainHeight, 1, 10000);
sceneMain = new THREE.Scene();
ambientLightMain = new THREE.AmbientLight(0x202020);
directionalLightMainRight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLightMainLeft = new THREE.DirectionalLight(0xffffff, 0.5);
pointLightMain = new THREE.PointLight(0xffffff, 0.3);
directionalLightMainRight.position.x = dlpx;
directionalLightMainRight.position.y = dlpy;
directionalLightMainRight.position.z = dlpz;
directionalLightMainRight.position.normalize();
directionalLightMainLeft.position.x = -dlpx;
directionalLightMainLeft.position.y = dlpy;
directionalLightMainLeft.position.z = dlpz;
directionalLightMainLeft.position.normalize();
pointLightMain.position.x = plpx;
pointLightMain.position.y = plpy;
pointLightMain.position.z = plpz;
sceneMain.addLight(ambientLightMain);
sceneMain.addLight(directionalLightMainRight);
sceneMain.addLight(directionalLightMainLeft);
sceneMain.addLight(pointLightMain);
rendererMain = new THREE.WebGLRenderer();
texture = THREE.ImageUtils.loadTexture('3D/'+ mainURL +'.jpg', {}, function() {
rendererMain.render(sceneMain);
});
rendererMain.domElement.style.backgroundColor = backgroundColor;
rendererMain.setSize(MainWidth, MainHeight);
rendererMain.domElement.addEventListener('DOMMouseScroll', onRendererMainScroll, false);
rendererMain.domElement.addEventListener('mousewheel', onRendererMainScroll, false);
rendererMain.domElement.addEventListener('dblclick', onRendererMainDblClick, false);
rendererMain.domElement.addEventListener('mousedown', onRendererMainMouseDown, false);
$("#viewerMain").append(rendererMain.domElement);
window.addEventListener('mousemove', onMouseMove, false);
window.addEventListener('mouseup', onMouseUp, false);
That chunk of code initializes the model. Some of it is not needed for question purposes, it has code for zooming, rotating, etc. I know there is another line, I have it commented out but for some reason it wont show here so I will do it separately.
material = new THREE.MeshBasicMaterial({map: texture});
That line is after the loadTexture code.
The rest of it is loaded with a loadPly function
var geometryMain = new THREE.Geometry();
for (i in event.data.content[0])
geometryMain.vertices.push(new THREE.Vertex(new THREE.Vector3(event.data.content[0][i][0], event.data.content[0][i][1], event.data.content[0][i][2])));
for (i in event.data.content[1])
geometryMain.faces.push(new THREE.Face3(event.data.content[1][i][0], event.data.content[1][i][1], event.data.content[1][i][2]));
geometryMain.computeCentroids();
geometryMain.computeFaceNormals();
mainModel = new THREE.Mesh(geometryMain, new THREE.MeshLambertMaterial({color:0xffffff, shading:THREE.FlatShading}));
sceneMain.addObject(mainModel);
mainModel.overdraw = true;
mainModel.doubleSided = true;
I have tried tweaking the THREE.mesh() section to include the material but it breaks everything. I have tried just adding the map:material to the MeshLambertMaterial, also a no go. Anyone have any insight? Sorry if this is over complicated but i am far from knowing this enough to be efficient yet.
If I add
map:THREE.ImageUtils.loadTexture('3D/'+ mainURL +'.jpg')
to the THREE.Mesh() line, I get
.WebGLRenderingContext: GL ERROR :GL_INVALID_OPERATION : glDrawElements: attempt to access out of range vertices in attribute 2
In order to display a texture on an object, it needs texture coordinates. You only seem to add vertices and faces to the geometry. Looking at THREE.PLYLoader sources, it doesn't seem to support loading the texture coords. (Although I'm not sure if you use that loader as it should already return a ready Geometry instance instead of requiring you to construct arrays manually)
Use THREE.MeshNormalMaterial. It shows texture when you use MeshNormalMaterial.