three.js and openlayers coordinates don't line up - three.js

I'm using three.js on a separate canvas on top of a openlayers map. the way I synchronize the view of the map and the three.js camera (which looks straight down onto the map) looks like this:
// calculate how far away the camera needs to be, to
// see this part of the map
function distanceFromExtentAndFOV(h, vFOV) {
return h / (2 * Math.tan(vFOV / 2));
}
// keep three.js camera in sync with visible part of openlayers map
function updateThreeCam(
group, // three.js group, containing camera
view, // ol view
map // ol map
) {
extent = getViewExtent(view, map);
const h = ol.extent.getHeight(extent);
mapCenter = ol.extent.getCenter(extent);
camDist = distanceFromExtentAndFOV(h, constants.CAM_V_FOV_RAD);
const pos = [...mapCenter, camDist];
group.position.set(...pos);
group.updateMatrixWorld();
}
// whenever view changes, update three.js camera
view.on('change:center', (event) => {
updateThreeCam(group, event.target, map);
});
view.on('change:resolution', (event) => {
updateThreeCam(group, event.target, map);
});
updateThreeCam(group, view, map);
I'm using three.js for custom animations of objects, that in the end land on the ground. however, when I turn the objects into openlayers features, the original three.js objects and the features don't line up perfectly (although their position coordinates are exactly the same). — also, when you pan, you can see that s.th. is slightly off.
// turn three.js planes into openlayers features:
const features = rects.map((rect) => {
const point = new ol.geom.Point([
rect.position.x,
tect.position.y
]);
return new ol.Feature(point);
});
vectorSource.addFeatures(features);
as I am pretty sure that my the code for synchronizing three.js and ol is correct, I am not really sure what what is going wrong. am I missing s.th. here?

It could be that you are using PerspectiveCamera and that will change the size/position based on depth. Try OrtographicCamera instead.

Related

How do I load texture files designed for an FBX model so that the model appears correctly in my ThreeJS scene?

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));
}
);
}

A-frame: How to give a primitive like Ring or Cylinder depth?

Is it possible to give a 2D primitive like Ring thickness/depth? I'm trying to make a 3D door with primitives and I want to make either a ring that has some depth or a cylinder with a thicker mesh. Even if it's at the three.js layer I would like to learn how to do this so I don't have to rely on imported 3D objects for the whole design.
To extrude a shape, you will need to use THREE.js. Below is an example for how to do this.
https://threejs.org/examples/?q=extrude#webgl_geometry_extrude_shapes2
How to use THREE.js geometery inside AFrame?
You make a custom component that creates a new THREE.Geometry, and inside that component, you build your shape and extrude it (see the THREE.js example, click (<>) button in lower right to see the code).
Below is an example that makes a custom quad in a component.
You can see the glitch here.
https://glitch.com/edit/#!/custom-quad
Look in the quad.js for details.
You should be able to copy code from the three.js extrude demo, and place it into your custom component init function to build the extruded shape.
Then you can create parameters, like 'thickness', and put them in the schema, and then they can be accessed from the component name on the entity.
If you don't yet know how to write a custom component, you will need to practice this to understand how it works.
https://aframe.io/docs/0.9.0/introduction/writing-a-component.html
Here is a snippet from my glitch that makes a custom quad. It shows how to make THREE.Geometry in an AFrame custom component.
init: function (data) {
var geometry = new THREE.Geometry();
geometry.vertices = data.vertices.map(function (vertex) {
var points = vertex.split(' ').map(function(x){return parseFloat(x);});
return new THREE.Vector3(points[0], points[1], points[2], points[3]);
});
// Build the UVs on the faces.
geometry.faceVertexUvs[0].push(
[ new THREE.Vector2(1,0), new THREE.Vector2(0,1), new THREE.Vector2(1,1)
],
[ new THREE.Vector2(1,0), new THREE.Vector2(0,0), new THREE.Vector2(0,1)
]);
geometry.computeBoundingBox();
geometry.faces.push(new THREE.Face3(0, 2, 1), new THREE.Face3(0, 3, 2));
geometry.mergeVertices();
geometry.computeFaceNormals();
geometry.computeVertexNormals();
this.geometry = geometry;
//console.log(data);
}

How to declare a "mask" material using A-Frame.js

I'm trying to make a scene where there's a "hole in the wall".
This requires a plane with square removed, and then a material applied to the plane with the following properties:
Invisible to the camera
Hides any other objects from being rendered that are behind it
There's an example of this with three.js here, but how can I do it with the a-frame material syntax?
The "Mask".
Looking at the box-hole example, to create the illusion, Lee creates two boxes.
1) The box which is "in the hole"
2) A slightly bigger invisible box without a top - to cloak the first one. The top is removed to work as a "hole" through which you can see the first box
How it can be done in THREE.js
The cloaking is done by preventing the second box from rendering any color. From Lee's example:
let material = new THREE.MeshBasicMaterial({
colorWrite: false;
})
The docs state, that the flag can be used to create invisible objects hiding others.
How it can be done in a-frame
I'm afraid you can't simply make the "cloak" material in a-frame. The colorWrite property is not exposed in the material component.
What i think the simplest way would be - is creating a cloak component, which will create the second box in THREE.js:
AFRAME.registerComponent('cloak', {
init: function() {
let geometry = new THREE.BoxGeometry(1, 1, 1)
geometry.faces.splice(4, 2) // cut out the top faces
let material = new THREE.MeshBasicMaterial({
colorWrite: false
})
let mesh = new THREE.Mesh(geometry, material)
mesh.scale.set(1.1, 1.1, 1.1)
this.el.object3D.add(mesh)
}
})
and use it like this:
<a-box material="src: myPic.png; side: back;" cloak>
Check it out in this codepen. With a HIRO marker, you should get a hole like this:
Using models, or other objects as "cloaks"
Here we need to apply the colorWrite=false magic to each node/child of the model.
init: function() {
// make sure the model is loaded first
this.el.addEventListener('model-loaded', e=>{
let mesh = this.el.getObject3D('mesh') // grab the mesh
if (mesh === undefined) return; // return if no mesh :(
mesh.traverse(function(node) { // traverse through and apply settings
if (node.isMesh && node.material) { // make sure the element can be a cloak
node.material.colorWrite = false
node.material.needsUpdate = true;
}
});
})
}
Also make sure the cloak is rendered before the elements that needs cloaking:
<a-marker>
<a-entity gltf-model="#wall-with-a-hole" cloak-component></a-entity>
<!-- the other stuff that needs to be cloaked-->
</a-marker

Emit light from an object

I'm making a Three.js scene in which I have stars object and I would like to be able to make them "glow".
By glow I mean make them really emit light not just put a "halo" effect around them.
I tried to put a PointLight object at the same position as the star, this make light emit from the object but as you can see, it doesn't make the object "glow" which make a weird effect.
My current code looks like this:
class Marker extends THREE.Object3D {
constructor() {
super();
// load obj model
const loader = new OBJLoader();
loader.load(
"https://supersecretdomain.com/star.obj",
object => {
object.traverse(child => {
if (child instanceof THREE.Mesh) {
// child.material.map = texture;
child.material = new THREE.MeshLambertMaterial({
color: 0xffff00
});
child.scale.set(0.01, 0.01, 0.01);
}
});
this.add(object);
}
);
const light = new THREE.PointLight(0xffff00, 0.5, 5);
this.add(light);
}
}
Any idea of how to do this ? ;)
Adding a point light to the star is the correct way to make other objects be affected by its light. To make the star itself shine, you can set the emissive color of the material to something other than black (for best results, you probably want it to be the same color as the light).
In addition to setting up your light, and setting the material emissive property as mentioned by Jave above..
You might want to look into the THREE PostProcessing examples.. Specifically Unreal Bloom pass... If you can get that working, it really sells the effect.
https://threejs.org/examples/webgl_postprocessing_unreal_bloom.html
Notice how the highlight glow actually bleeds into the scene around the object...

THREE.js repeating UV texture using JSONLoader

I need help for getting UV Textures to be rendered correctly in three.js. I've created a model with repeating tile texture in Blender. The tile texture is applied using UV mapping, and it looks like this if it is being rendered correctly:
Render image using Blender
.However, when it is loaded using JSONLoader by three.js, the tiles are just stretched to fill each polygon, giving weird result like this:Screenshot of render using three.js
. I've tried setting THREE.RepeatWrapping in my code but nothing changed:
bodyLoader = new THREE.JSONLoader();
bodyLoader.load('./starofthesea_threejs.json', function(geometry, materials) {
mainBodyMaterials = new THREE.MeshFaceMaterial(materials);
console.log(materials);
mainBodyMaterials.wrapS = THREE.RepeatWrapping;
mainBodyMaterials.wrapT = THREE.RepeatWrapping;
mainBodyMaterials.needsUpdate = true;
mainBody = new THREE.Mesh(geometry, mainBodyMaterials);
mainBody.traverse ( function (child) {
if (child instanceof THREE.Mesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
mainBody.scale.x = mainBody.scale.y = mainBody.scale.z = 1;
geometry.computeBoundingBox();
geometry.computeFaceNormals();
geometry.computeFlatVertexNormals();
scene.add(mainBody);
});
Is there anything wrong in my code, or workaround to get it rendered correctly? All help is deeply appreciated.
Finally I've figured out the problem by myself, where both the Blender model and JS are misconfigured. RepeatWrapping should be applied to texture but not material. I need to study the structure of THREE.MeshFaceMaterial to find the handle for the underlying textures. I need to traverse through the materials to find out all materials with image textures:
mainBodyMaterials = new THREE.MeshFaceMaterial(materials);
for(prop in mainBodyMaterials.materials) {
if(mainBodyMaterials.materials[prop].map != null) {
mainBodyMaterials.materials[prop].map.wrapS = THREE.RepeatWrapping;
mainBodyMaterials.materials[prop].map.wrapT = THREE.RepeatWrapping;
tex.push(mainBodyMaterials.materials[prop].map.clone());
tex[tex_sequence].needsUpdate = true;
tex_sequence++;
}
}
After applying wrapS and wrapT to textures correctly, one of the tile materials get rendered correctly, but 'Texture marked for update but image is undefined' error keep throwing out. I need to clone the texture to get rid of the error, according to another question: Three.js r72 - Texture marked for update but image is undefined?
As there are several materials with repeating tiles, I need to create a global array at the beginning of the JS routine, and push the modified textures one by one when looping through the materials:
var tex = new Array();
var tex_sequence = 0;
After fixing the JS calls, one of the textures are still not working. I forgot that only ONE UV slot is allowed for three.js. I need to unwrap the UVs again under the same UV slot in Blender. Everything works like a charm, and I hope my painful experience can help those who are getting mad by similar problems.

Resources