The current build of Three.js (R84) uses image tags to load textures, which do not support onProgress events. I can reason how many files have loaded, but cannot expose granular information about bytes loaded.
What is the best way to load a texture with onProgress event support? I would like to support a wide range of mobile clients, but legacy desktop support is not a concern.
One solution is to load the file via FileLoader first, then load from the cache with a TextureLoader. In-memory caching means the file is only loaded once, even if it doesn't get cache headers from the server.
The result is that we get progress events via FileLoader's initial AJAX fetch, but can still exploit all the useful behaviour of the proper TextureLoader (such as disabling alpha channels for JPEG textures, and other optimisations).
/**
* Loads THREE Textures with progress events
* #augments THREE.TextureLoader
*/
function AjaxTextureLoader() {
/**
* Three's texture loader doesn't support onProgress events, because it uses image tags under the hood.
*
* A relatively simple workaround is to AJAX the file into the cache with a FileLoader, create an image from the Blob,
* then extract that into a texture with a separate TextureLoader call.
*
* The cache is in memory, so this will work even if the server doesn't return a cache-control header.
*/
const cache = THREE.Cache;
// Turn on shared caching for FileLoader, ImageLoader and TextureLoader
cache.enabled = true;
const textureLoader = new THREE.TextureLoader();
const fileLoader = new THREE.FileLoader();
fileLoader.setResponseType('blob');
function load(url, onLoad, onProgress, onError) {
fileLoader.load(url, cacheImage, onProgress, onError);
/**
* The cache is currently storing a Blob, but we need to cast it to an Image
* or else it won't work as a texture. TextureLoader won't do this automatically.
*/
function cacheImage(blob) {
// ObjectURLs should be released as soon as is safe, to free memory
const objUrl = URL.createObjectURL(blob);
const image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img');
image.onload = ()=> {
cache.add(url, image);
URL.revokeObjectURL(objUrl);
document.body.removeChild(image);
loadImageAsTexture();
};
image.src = objUrl;
image.style.visibility = 'hidden';
document.body.appendChild(image);
}
function loadImageAsTexture() {
textureLoader.load(url, onLoad, ()=> {}, onError);
}
}
return Object.assign({}, textureLoader, {load});
}
module.exports = AjaxTextureLoader;
Related
I'm completely new to this, and I don't know how to scale my texture to make it look smaller. it is a seamless texture. The three methods that come from materials do work well but the ones that have to do with texture don't do anything, surely it will be silly, but I'm starting to program and I don't know how to do it.
<mesh geometry={nodes.BON_GRAVA.geometry}
material={materials.BON_GRAVA}
material-map={useTexture("Gravel_001_BaseColor.jpg")}(works)
material-metalness={0.0} (works)
material-roughness={1.0} (works)
material-roughnessMap={useTexture("Gravel_001_Roughness.jpg")} (works)
material-normalMap={useTexture("Gravel_001_Normal.jpg")} (works)
material-aoMap={useTexture("Gravel_001_AmbientOcclusion.jpg")} (works)
This is me trying to do something, sorry
I've been trying .repeat, .offset, .wrapS but I don't know how the syntax for THREE methods works since it's a file gltfjsx + react
Call the useTexture hook at the top, then you can modify the Texture's (when the component mounts) with a useEffect hook: (be sure to set the needsUpdate flag to true on the Texture after any changes)
export function Component() {
const color = useTexture("Gravel_001_BaseColor.jpg");
const roughness = useTexture("Gravel_001_Roughness.jpg");
const normal = useTexture("Gravel_001_Normal.jpg");
const ao = useTexture("Gravel_001_AmbientOcclusion.jpg");
useEffect(() => {
color.repeat = new Vector2(1, 1);
color.wrapS = RepeatWrapping;
color.wrapT = RepeatWrapping;
color.needsUpdate = true; // don't forget this!
}, [color])
return (
<mesh
geometry={nodes.BON_GRAVA.geometry}
material={materials.BON_GRAVA}
material-map={color}
material-metalness={0.0}
material-roughness={1.0}
material-roughnessMap={roughness}
material-normalMap={normal}
material-aoMap={ao}
/>
)
}
I have been buying animated 3D models from TurboSquid and they fall into two starkly different categories:
Ready to use - I just load the FBX file and it looks fine in my ThreeJS scene
Missing textures - The loaded FBX model looks geometrically fine and the animations work, but all the surfaces are white
I want to know how to load the provided textures using the FBXLoader module. I have noticed that all of the FBX models, which I believe are PBR material based, have this set of files:
*002_BaseColor.png
*002_Emissive.png
*002_Height.png
*002_Metallic.png
*002_Roughness.png
*002_Normal.png
I have these questions:
How would I use the FBXLoader module to load the textures "in the right places"? In other words, how do I make the calls so that the BaseColor texture goes to the right place, the Emissive texture so it goes where it should, etc?
Do I need to pass it a custom material loader to the FBXLoader and if so, which one and how?
UPDATE: I trace through the ThreeJS FBX loader code I discovered that the FBX model that is not showing any textures does not have a Texture node at all. The FBX model that does show the textures does, as you can see in this screenshot of the debugger:
I'll keep tracing. If worse comes to worse I'll write custom code to add a Textures node with the right fields since I don't know how to use Blender to add the textures there. My concern is of course that even if I create a Textures node at run-time, they still won't end up in the proper place on the model.
This is the code I am currently using to load FBX models:
this.initializeModel = function (
parentAnimManagerObj,
initModelArgs= null,
funcCallWhenInitialized = null,
) {
const methodName = self.constructor.name + '::' + `initializeModel`;
const errPrefix = '(' + methodName + ') ';
self.CAC.parentAnimManagerObj = parentAnimManagerObj;
self.CAC.modelLoader = new FBXLoader();
self.CAC.modelLoader.funcTranslateUrl =
((url) => {
// Fix up the texture file names.
const useRobotColor = misc_shared_lib.uppercaseFirstLetter(robotColor);
let useUrl =
url.replace('low_Purple_TarologistRobot', `Tarologist${useRobotColor}`);
return useUrl;
});
// PARAMETERS: url, onLoad, onProgress, onError
self.CAC.modelLoader.load(
MODEL_FILE_NAME_TAROLOGIST_ROBOT_1,
(fbxObj) => {
CommonAnimationCode.allChildrenCastAndReceiveShadows(fbxObj);
fbxObj.scale.x = initModelArgs.theScale;
fbxObj.scale.y = initModelArgs.theScale;
fbxObj.scale.z = initModelArgs.theScale;
self.CAC.modelInstance = fbxObj;
// Set the initial position.
self.CAC.modelInstance.position.x = initModelArgs.initialPos_X;
self.CAC.modelInstance.position.y = initModelArgs.initialPos_Y;
self.CAC.modelInstance.position.z = initModelArgs.initialPos_Z;
// Create an animation mixer for this model.
self.CAC.modelAnimationMixer = new THREE.AnimationMixer(self.CAC.modelInstance);
// Speed
self.CAC.modelAnimationMixer.timeScale = initModelArgs.theTimeScale;
// Add the model to the scene.
g_ThreeJsScene.add(self.CAC.modelInstance);
// Don't let this part of the code crash the entire
// load call. We may have just added a new model and
// don't know certain values yet.
try {
// Play the active action.
self.CAC.activeAction = self.CAC.modelActions[DEFAULT_IDLE_ANIMATION_NAME_FOR_TAROLOGIST_ROBOT_1];
self.CAC.activeAction.play();
// Execute the desired callback function, if any.
if (funcCallWhenInitialized)
funcCallWhenInitialized(self);
} catch (err) {
const errMsg =
errPrefix + misc_shared_lib.conformErrorObjectMsg(err);
console.error(`${errMsg} - try`);
}
},
// onProgress
undefined,
// onError
(err) => {
// Log the error message from the loader.
console.error(errPrefix + misc_shared_lib.conformErrorObjectMsg(err));
}
);
}
I have copied code for loading an animation (dancing twerk from mixamo) onto an existing fbx model (Douglas from mixamo) from the tutorial here https://www.youtube.com/watch?v=8n_v1aJmLmc but it does not work, as SimonDev uses version 118 of three.js whilst I am using version 128.
const loader = new FBXLoader();
//load model
loader.setPath('../models/characters/');
loader.load('Douglas.fbx', (fbx) => {
//scale the model down
fbx.scale.setScalar(0.0115);
/*
fbx.traverse(c => {
c.castShadow = true;
});
*/
//animate character
const anim = new FBXLoader();
anim.setPath('../models/characters/animations/');
anim.load('Twerk.fbx', (anim) => {
this.walkMixer = new THREE.AnimationMixer(anim);
//creates animation action
const walk = this.walkMixer.clipAction(anim.animations[0]);
walk.play();
});
this.object.add(fbx);
});
This is in my update function
//animation
if (this.walkMixer) {
this.walkMixer.update(this.clock.getDelta());
}
The animation worked previously when it was the same model with the animation, and with gltf, but I am trying to separate the model and animation, so that I can apply different animations from mixamo.
My code does not load the animation at all and he simply stands in a T-pose. I do not know what the current code for loading a separate animation from mixamo onto a current fbx model is. I would like to know how, as I would like to load and play multiple animations onto the same model.
I managed to sort it out-> it was due to me having multiple imports of three.js
I'm using a package called laravel-admin.
I created a custom signature field with popular SignaturePad package, the data got converted into image successfully. However when I tried to submit the form I've got an error saying CSRF token miss match.
Here's my code for rendering SignaturePad class:
public function render()
{
$this->script = <<<EOT
var canvas = document.getElementById('signature-pad');
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
function resizeCanvas() {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext("2d").scale(ratio, ratio);
}
window.onresize = resizeCanvas;
resizeCanvas();
var signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgb(255, 255, 255)' // necessary for saving image as JPEG; can be removed is only saving as PNG or SVG
});
$('button[type="submit"]').click(function() {
console.log(signaturePad.toDataURL());
$('input[type=hidden]').val(signaturePad.toDataURL());
});
EOT;
return parent::render();
}
If I remove the line below then I won't get it, but obviously my data is not sent. The way it works is I converts the value of the image and put it to the hidden input field to be sent. Can anyone help?
$('button[type="submit"]').click(function() {
console.log(signaturePad.toDataURL());
$('input[type=hidden]').val(signaturePad.toDataURL());
});
Apparently I forgot that laravel gives hidden input containing CSRF token that I am changing all together with jQuery code
$('button[type="submit"]').click(function() {
console.log(signaturePad.toDataURL());
$('input[type=hidden]').val(signaturePad.toDataURL());
});
Thanks everyone
i want to load image from remote url and synchronicity and change it width and height.
i am using the folowwing code, but it want let me change the width and highet, i think i needto convert the loader to a Bitmap object.
how can i do that, thank you very much.
var imageURLRequest:URLRequest = new URLRequest(pic);
var myImageLoader:Loader = new Loader();
myImageLoader.load(imageURLRequest);
var urlRequest:URLRequest = new URLRequest(pic);
var loader:Loader = new Loader();
loader.load(urlRequest);
trace(loader.width); // return 0
loader.width =100; //dosent work
allMC[i].img.addChild(loader);
To access what the loader loaded, use loader.content reference. If you are loading an image, you can retrieve its raw data via (loader.content as Bitmap).bitmapData, of course first check if it's so via if (loader.content is Bitmap). Also, you need to do all of this after your loader will finish loading, it'll send an event indicating this.
...
loader.load(urlRequest);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderComplete);
...
private function loaderComplete(e:Event):void {
// now your image is fully loaded
trace(loader.content.width);
// etc etc, whatever you need to do with your image prior to
// addressing it from elsewhere.
}