convert dae to gltf, horizontal vector invalidation - javascript

I converted .dae to .gltf through this tool, and display through three.js, and found that the horizontal vector of the model seems to be invalid.
Example file
Convert tool interface:
The situation when I use .dae file to display:
let loaderDae = new ColladaLoader();
loaderDae.load(`assets/untitled.dae`, (dae: any) => {
this.buildingModel = dae.scene;
this.buildingModel.position.multiplyScalar(0);
this.scene.add(this.buildingModel);
});
The situation when I use .gltf file to display:
let loaderGLTF = new GLTFLoader();
loaderGLTF.load(`assets/untitled.glft`, (glft: any) => {
this.buildingModel = glft.scene;
this.buildingModel.position.multiplyScalar(0);
this.scene.add(this.buildingModel);
});
After zooming in(or out):
The gloss of the texture is also not the same. I don't know if it's a file conversion problem or a lighting problem. I am using ambientLight:
let ambColor = new THREE.Color('rgb(118, 240, 147');
this.ambientLight = new THREE.AmbientLight(ambColor);
this.ambientLight.name = 'testname';
this.scene.add(this.ambientLight)

Related

Is it possible to export 3D objects from a Three.js Canvas?

I have a canvas which is created by three.js and it is rendering a complex 3D object. Is it in any way possible to export that 3D object? Not talking about an image, I figured that out. I need the actual 3D object which is rendered inside canvas. I honestly don't even know what file format I am expecting here, anything that can be either processed or imported into another program. Here's how far I've gotten after a few hours of trying, but I just get an (almost) empty file..
function test() {
let canvas = document.querySelector("canvas");
const renderer = new THREE.WebGLRenderer({canvas});
let scene = new THREE.Scene();
renderer.render(scene, new Camera());
console.log(renderer);
console.log(scene);
let exporter = new GLTFExporter();
exporter.parse(scene, (gltf) => {
console.log("GOT GLTF");
saveLikeJSON(gltf);
}, {})
}
Here are my functions which I used to download the file:
function saveLikeJSON(json){
var dataJson = JSON.stringify(json);
downloadTextFile(dataJson,"p")
}
function downloadTextFile(text, name) {
const a = document.createElement('a');
const type = name.split(".").pop();
a.href = URL.createObjectURL( new Blob([text], { type:`text/${type === "txt" ? "plain" : type}` }) );
a.download = name;
a.click();
}
I am importing the canvas from another site using a script tag, and that's the target I want to export which contains a 3D object.

Three.js renders geometry wrongly

Three.js often renders unnecessary surfaces when loading .obj files (I haven't tried other types).
In the screenshot I attached below, there are some triangle surfaces that look like webbed fingers. How do I get rid of it?
I think the .obj file is okay because it's rendered correctly in Blender. It's exported from sketchup and it wasn't webbed before exporting.
View in Blender
View in Three.js
Below is the code I'm using to load .obj
const loadManager = new THREE.LoadingManager();
loadManager.addHandler(/\.dds$/i, new DDSLoader());
const loadObj = (path, mtl) => {
return new Promise((res) => {
new OBJLoader(loadManager).setMaterials(mtl).load(
path,
async (object) => {
scene.add(object);
render()
res(object)
},
() => {
console.log("loading .obj, started at", new Date());
},
() => {
console.log("loading .obj got error");
}
);
})
}
I have no clue to fix this problem. I'll appreciate any kind of opinion.
Thanks.

Load Collada/image data stored in a multipart file into three.js

I have Collada and related image data stored in a multipart file that is outputted from an application. I need to be able to load the Collada object and images for display in the Web via three.js. Can three.js interpret the multipart file or the data within the file, or does this need to be parsed out into actual files, since the Collada loader example shows it linking to a .dae file (ex. loader.load('models/monster.dae') and not the actual data.
In any case, how would I achieve this? The end goal is to load and view the "box" represented by the multipart data in the file.
Here is the sample structure of the multipart file I receive from the application:
MIME-Version:1.0
Content-Type:multipart/mixed;
boundary="----=_Part_4_153315749.1440434094461"
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=Texture_1.png
Content-ID: response-1
Content-Disposition: attachment; filename=Texture_1.png
‰PNG
"blob data here"
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=manifest.xml
Content-ID: response-2
Content-Disposition: attachment; filename=manifest.xml
<?xml version="1.0"?>
<dae_root>blank_3D.dae</dae_root>
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=texture_3D.dae
Content-ID: response-3
Content-Disposition: attachment; filename=texture_3D.dae
<xml data here... lots of xml data>
------=_Part_4_153315749.1440434094461
Content-Type: application/octet-stream; name=Texture_0.png
Content-ID: response-4
Content-Disposition: attachment; filename=Texture_0.png
‰PNG
"blob image data"
UPDATE:
By having the vendor split up the files, I am able to load the collada file directly into the viewer... but I don't know how to get the images along with it loaded into memory or elsewise. Any ideas of how to include the images/textures that belong with the collada file? Here is my current loading code:
// instantiate a loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('http://applicationvendorapi.com/dae', 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();
animate();
});
The location of the textures tend to be in the same directory as the Collada file. Here is the XML of the Collada. The question is can I reference these by pulling in the other images over the Web service (ex. /endpoint/texture_0 and /endpoint/texture_1
<library_images>
<image id="Texture_0_png">
<init_from>./Texture_0.png</init_from>
</image>
<image id="Texture_1_png">
<init_from>./Texture_1.png</init_from>
</image>
UPDATE 2:
Using the advice of #gaitat, I am attempting to load the texture first via the ImageUtils. However, it never makes the call to the URL for the texture/load the texture. It does however, still make the object call. Here is the current object wrapped in a texture call:
// Load texture before loading and initializing 3D object
var texture0 = THREE.ImageUtils.loadTexture('http://vendorwebservice/texture_0', {}, function loaded() {
// Instantiate a Collada loader
var loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
loader.load('http://vendorwebservice/dae', 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();
animate();
});
});
Here is where it gets interesting. Since the DAE file I pull in has the following xml references to its textures, it actually tries to "get" these as well, but an endpoint does not exist with the .png extension so it fails (ex. http://vendorwebservice/texture_0.png):
<library_images>
<image id="Texture_0_png">
<init_from>./Texture_0.png</init_from>
</image>
<image id="Texture_1_png">
<init_from>./Texture_1.png</init_from>
</image>
As your application is a web-service where data does not have static URI's you need to download the data the service returns locally. As long as the texture files that the Collada file references are in a relative path (to the .dae), the THREE.ColladaLoader() will be able to find them, load them and display them.
I know it's too late but I hope my answer to help future readers.
I was in a similar situation where the files are not referenced by static URI. I didn't want to download the files because I concerned about some inefficiency of data traveling through network and taking up the server's resource. So I decided to handle it manually on browser by this process:
Read the name of the image file that the collada's texture is referencing
Find the image file with the name you found in step 1
Generate Threejs texture using the file you found in step 2
Connect the texture you made in step 3 to the texture slot in collada material
And here's an example code. In my case, the URLs in the code is data URL
const loadCollada = async (dae, img) => {
const collada = new ColladaLoader().load(dae.url);
// Get all materials in the loaded collada
const materials = new Set();
collada.traverse((object) => {
const mat = object.material
if (mat) {
if (mat.constructor.name === "Array") {
mat.forEach((e) => {
materials.add(e);
})
} else {
materials.add(mat);
}
}
});
// Parse collada file
const daeText = await fetch(dae.url).then((r) => r.text())
const daeXml = new DOMParser().parseFromString(daeText,"text/xml")
const materialLibrary = daeXml.getElementsByTagName("material")
// Get material name and texture name(image file name)
Array.from(materialLibrary).forEach((e) => {
const materialName = e.getAttribute("name")
const effect = e.getElementsByTagName("instance_effect")[0]
const textureUrl = effect.attributes.url.value.slice(1)
const textureTag = daeXml.getElementById(textureUrl)
const textureName = textureTag.getElementsByTagName("init_from")[0]?.innerHTML
// Find the material that matches name
if (textureName) {
Array.from(materials).forEach((mat) => {
if (mat.name === materialName) {
// Generate a texture and override it to the material we found
const texture = new THREE.TextureLoader().load(img[textureName].url)
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
mat.map = texture
}
});
}
})
}

Stream canvas to imgITools to make ico

So on WinXP I have been having a hard time converting a PNG file to an ICO with canvas. I found this method encodeImage. I don't know if it works but it looks promising but I can't figure out how to feed the image I drew on a canvas into imgITools.decodeData.
What should I use for aImageStream and/or aMimeType?
imgTools.decodeImageData(aImageStream, aMimeType, imgContainer);
This is more of my code:
img['asdf'].file = new FileUtils.File(myPathHere);
let iconStream;
try {
let imgTools = Cc["#mozilla.org/image/tools;1"]
.createInstance(Ci.imgITools);
let imgContainer = { value: null };
imgTools.decodeImageData(aImageStream, aMimeType, imgContainer);
iconStream = imgTools.encodeImage(imgContainer.value,
"image/vnd.microsoft.icon",
"format=bmp;bpp=32");
} catch (e) {
alert('failure converting icon ' + e)
throw("processIcon - Failure converting icon (" + e + ")");
}
let outputStream = FileUtils.openSafeFileOutputStream(img['asdf'].file);
NetUtil.asyncCopy(iconStream, outStream, netutilCallback);
Since you're having a canvas already(?), it would be probably easier to use either on of the following canvas methods:
toDataURI/toDataURIHD
toBlob/toBlobHD
mozFetchAsStream
There is also the undocumented -moz-parse-options:, e.g -moz-parse-options:format=bmp;bpp=32. (ref-tests seem to depend on it, so it isn't going away anytime soon I'd think).
So, here is an example for loading stuff into an ArrayBuffer.
(canvas.toBlobHD || canvas.toBlob).call(canvas, function (b) {
var r = new FileReader();
r.onloadend = function () {
// r.result contains the ArrayBuffer.
};
r.readAsArrayBuffer(b);
}, "image/vnd.microsoft.icon", "-moz-parse-options:format=bmp;bpp=32");
Here is a more complete example fiddle creating a 256x256 BMP icon.
Since you likely want to feed that data into js-ctypes, having an ArrayBuffer is great because you can create pointers from it directly, or write it to a file using OS.File.

Three.js Update Texture image

I'm using three.js to create a minecraft texture editor, similar to this. I'm just trying to get the basic click-and-paint functionality down, but I can't seem to figure it out. I currently have textures for each face of each cube and apply them by making shader materials with the following functions.
this.createBodyShaderTexture = function(part, update)
{
sides = ['left', 'right', 'top', 'bottom', 'front', 'back'];
images = [];
for (i = 0; i < sides.length; i++)
{
images[i] = 'img/'+part+'/'+sides[i]+'.png';
}
texCube = new THREE.ImageUtils.loadTextureCube(images);
texCube.magFilter = THREE.NearestFilter;
texCube.minFilter = THREE.LinearMipMapLinearFilter;
if (update)
{
texCube.needsUpdate = true;
console.log(texCube);
}
return texCube;
}
this.createBodyShaderMaterial = function(part, update)
{
shader = THREE.ShaderLib['cube'];
shader.uniforms['tCube'].value = this.createBodyShaderTexture(part, update);
shader.fragmentShader = document.getElementById("fshader").innerHTML;
shader.vertexShader = document.getElementById("vshader").innerHTML;
material = new THREE.ShaderMaterial({fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: shader.uniforms});
return material;
}
SkinApp.prototype.onClick =
function(event)
{
event.preventDefault();
this.change(); //makes texture file a simple red square for testing
this.avatar.remove(this.HEAD);
this.HEAD = new THREE.Mesh(new THREE.CubeGeometry(8, 8, 8), this.createBodyShaderMaterial('head', false));
this.HEAD.position.y = 10;
this.avatar.add(this.HEAD);
this.HEAD.material.needsUpdate = true;
this.HEAD.dynamic = true;
}
Then, when the user clicks any where on the mesh, the texture file itself is update using canvas. The update occurs, but the change isn't showing up in the browser unless the page is refreshed. I've found plenty of examples of how to change the texture image to a new file, but not on how to show changes in the same texture file during runtime, or even if it's possible. Is this possible, and if not what alternatives are there?
When you update a texture, whether its based on canvas, video or loaded externally, you need to set the following property on the texture to true:
If an object is created like this:
var canvas = document.createElement("canvas");
var canvasMap = new THREE.Texture(canvas)
var mat = new THREE.MeshPhongMaterial();
mat.map = canvasMap;
var mesh = new THREE.Mesh(geom,mat);
After the texture has been changed, set the following to true:
cube.material.map.needsUpdate = true;
And next time you render the scene it'll show the new texture.
Here is all the basics of what you have to know about "updating" stuff in three.js: https://threejs.org/docs/#manual/introduction/How-to-update-things

Categories