Getting a font in three.js - three.js

I am trying to load a font in three.js which so far is incredible hard. Hope sb can help on this matter.
I tried using javascript fonts like as suggested in the book Mastering three.js. With the version from the book that worked. I had to upgrade to the most recent version of three.js however for another reason and the fonts from the book don't work with the latest three.js version. So I tried converting the json fonts of three.js to js-fonts via https://gero3.github.io/facetype.js/ That didn't work as the website does not do anything when I press the convert button.
Then I tried to orient myself on some examples from the latest three.js version using the FontLoader. There I struggle with obtaining the font since FontLoader does not return anything. So tried to assign the font to a global variable myfont in the funciton that I pass to the FontLoader. However that function does not get called when I do the loader.load(...) but in my code only when the very last line of the snippet I put here is executed. I.e. I get errors in the code beforehand.
Is there a good reason why the code only gets executed then and not when I load the font?
Cheers
Tom
var loader = new THREE.FontLoader();
loader.load( 'three.js-master/examples/fonts/helvetiker_regular.typeface.json', function ( font ) {
init( font );
} );
var myfont;
function init( font ) {
myfont = font;
console.log("inner value "+myfont);
}
console.log("2nd time" +myfont);
var params = {
material: 0,
extrudeMaterial: 1,
bevelEnabled: false,
bevelThickness: 8,
bevelSize: 4,
font: myfont,
weight: "normal",
style: "normal",
height: 0,
size: 11,
curveSegments: 4
};
var textGeo = new THREE.TextGeometry("3D text", params);
console.log("3rd time "+myfont);
textGeo.computeBoundingBox();
console.log("4th time "+myfont);
textGeo.computeVertexNormals();
console.log("5th time "+myfont);
var material = new THREE.MeshFaceMaterial([
new THREE.MeshPhongMaterial({color: 0xff22cc, shading: THREE.FlatShading}), // front
new THREE.MeshPhongMaterial({color: 0xff22cc, shading: THREE.SmoothShading}) // side
]);
console.log("6th time "+myfont);
var textMesh = new THREE.Mesh(textGeo, material);
console.log("7th time "+myfont);
textMesh.position.x = -textGeo.boundingBox.max.x / 2;
textMesh.position.y = -5;
textMesh.position.z = 30;
textMesh.name = 'text';
scene.add(textMesh);
console.log("8th time "+myfont);
camControl = new THREE.OrbitControls(camera, renderer.domElement);
var geometry = new THREE.BoxGeometry( 70, 70, 70 );
var texture = THREE.ImageUtils.loadTexture('wallpaper.jpg');
var mainMaterial = new THREE.MeshBasicMaterial({
map:texture,
side:THREE.DoubleSide
});
console.log("9th time "+myfont);
var nonMainMaterial = new THREE.MeshBasicMaterial( { color: 0xcccccc } );
var materials = [mainMaterial, nonMainMaterial,nonMainMaterial,nonMainMaterial,nonMainMaterial,nonMainMaterial];
var meshFaceMaterial = new THREE.MeshFaceMaterial( materials );
console.log("10th time "+myfont);
var cube = new THREE.Mesh( geometry, meshFaceMaterial );
console.log("11th time "+myfont);
scene.add( cube );
console.log("12th time "+myfont);
var cubeBSP = new ThreeBSP(cube);
console.log("13th time "+myfont);
var textBSP = new ThreeBSP(textMesh);
console.log("14th time "+myfont);
var resultBSP = cubeBSP.subtract(textMesh);

The FontLoader() uses the XHRLoader under the hood. My understanding is that it's an asynchronous function from looking at the code, where the callback function is executed on the "load" event. Loading takes time, so the rest of your code is executing before the font is finished loading. Move the rest of your code into the init() function and it should work.

Related

Change from Three.js v122 to v123 hides the shadows on a custom layer for Mapbox

I'm adding a custom layer to mapbox using three.js, using the official example from mapbox. I found that the shadows that worked perfectly in v122 are not working in v123. After reading carefully the release changelog for v123 and and the migration guide from v122, I cannot find any related commit or change that is making the shadows disappear. I also tested with the latest build available of three.js and it happens the same, but I found the change happens between these two releases. I have tested with different materials apart from ShadowMaterial but same result.
Exactly the same code, just changing the package version from:
https://unpkg.com/three#0.122.0/examples/js/loaders/GLTFLoader.js
https://unpkg.com/three#0.122.0/build/three.min.js
to
https://unpkg.com/three#0.123.0/examples/js/loaders/GLTFLoader.js
https://unpkg.com/three#0.123.0/build/three.min.js
From this:
Here is the Fiddle v122
To this...
Here is the Fiddle v123
Code is exactly the same:
mapboxgl.accessToken = 'pk.eyJ1IjoianNjYXN0cm8iLCJhIjoiY2s2YzB6Z25kMDVhejNrbXNpcmtjNGtpbiJ9.28ynPf1Y5Q8EyB_moOHylw';
var map = (window.map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
zoom: 18,
center: [148.9819, -35.3981],
pitch: 60,
bearing: 45,
antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
}));
// parameters to ensure the model is georeferenced correctly on the map
var modelOrigin = [148.9819, -35.39847];
var modelAltitude = 0;
var modelRotate = [Math.PI / 2, 0, 0];
var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
// transformation parameters to position, rotate and scale the 3D model onto the map
var modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
/* Since our 3D model is in real world meters, a scale transform needs to be
* applied since the CustomLayerInterface expects units in MercatorCoordinates.
*/
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
var THREE = window.THREE;
// configuration of the custom layer for a 3D model per the CustomLayerInterface
var customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function(map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(0, 70, 100);
let d = 100;
let r = 2;
let mapSize = 1024;
dirLight.castShadow = true;
dirLight.shadow.radius = r;
dirLight.shadow.mapSize.width = mapSize;
dirLight.shadow.mapSize.height = mapSize;
dirLight.shadow.camera.top = dirLight.shadow.camera.right = d;
dirLight.shadow.camera.bottom = dirLight.shadow.camera.left = -d;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 400;
dirLight.shadow.camera.visible = true;
this.scene.add(dirLight);
this.scene.add(new THREE.DirectionalLightHelper(dirLight, 10));
this.scene.add(new THREE.CameraHelper(dirLight.shadow.camera))
// use the three.js GLTF loader to add the 3D model to the three.js scene
var loader = new THREE.GLTFLoader();
loader.load(
'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
function(gltf) {
gltf.scene.traverse(function(model) {
if (model.isMesh) {
model.castShadow = true;
}
});
this.scene.add(gltf.scene);
// we add the shadow plane automatically
const s = new THREE.Box3().setFromObject(gltf.scene).getSize(new THREE.Vector3(0, 0, 0));
const sizes = [s.x, s.y, s.z];
const planeSize = Math.max(...sizes) * 10;
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
//const planeMat = new THREE.MeshStandardMaterial({ color: 0xffffff, side: THREE.DoubleSide});
const planeMat = new THREE.ShadowMaterial();
planeMat.opacity = 0.5;
let plane = new THREE.Mesh(planeGeo, planeMat);
plane.rotateX(-Math.PI / 2);
//plane.layers.enable(1); plane.layers.disable(0); // it makes the object invisible for the raycaster
plane.receiveShadow = true;
this.scene.add(plane);
}.bind(this)
);
this.map = map;
// use the Mapbox GL JS map canvas for three.js
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
this.renderer.shadowMap.enabled = true;
},
render: function(gl, matrix) {
var rotationX = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(1, 0, 0),
modelTransform.rotateX
);
var rotationY = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 1, 0),
modelTransform.rotateY
);
var rotationZ = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 0, 1),
modelTransform.rotateZ
);
var m = new THREE.Matrix4().fromArray(matrix);
var l = new THREE.Matrix4()
.makeTranslation(
modelTransform.translateX,
modelTransform.translateY,
modelTransform.translateZ
)
.scale(
new THREE.Vector3(
modelTransform.scale,
-modelTransform.scale,
modelTransform.scale
)
)
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
this.camera.projectionMatrix = m.multiply(l);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
map.on('style.load', function() {
map.addLayer(customLayer, 'waterway-label');
});
It seems like a bug, but honestly I'd love is something I'm missing or I didn't realize. I even checked if with the files from different CDNs happens the same, and yes, it happens the same. Hoping one of the Three.js contributors or other devs can help me with this as I'm completely blocked with this and stopping me to migrate
Thanks in advance for any pointer!
A new implementation of WebGLState.reset() will be available with r126. It does not only reset engine internal state flags but also calls WebGL commands to reset the actual WebGL state. This approach should solve the reported issue.
Link to the PR at GitHub: https://github.com/mrdoob/three.js/pull/21281
I included this suggestion in the comments for the pull request on github https://github.com/mrdoob/three.js/pull/20732, but I'll add it here as well incase someone only finds this Stack Overflow question when searching.
I ran into a similar issue using another library that was sharing the WebGL context. What I found was that the other library (imgui-js) was setting gl.BLEND to true and then, with the change in the mentioned pull request, WebGLState.Reset() is now setting currentBlendingEnabled to null.
This caused a lot of textures in my scene to be displayed incorrectly because when setBlending is subsequently called on the WebGLState, it assumes that if the desired blending method is NoBlending and currentBlendingEnabled is null, that gl.BLEND is already disabled:
function setBlending(blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha) {
if (blending === NoBlending) {
if ( currentBlendingEnabled ) {
disable(3042);
However, with reset nulling the currentBlendingEnabled value each frame but not setting gl.BLEND to false, I believe this assumption is no longer correct. Looking closer, even after removing the external library I was using that sets the gl.BLEND value to true, I found that the change in the pull request was having a negative impacting some of the textures in my scene. In my case I found that updating the setBlending function to honor NoBlending requests, including when currentBlendingEnabled is null, seems to have remedied the situation. Maybe that will work in your case too?
function setBlending(blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha) {
if (blending === NoBlending) {
if (currentBlendingEnabled !== false) {
disable(3042);

Three.js LegacyGLTFLoader.js shadows missing

I have a GLTF version 1.0 model that I am importing into Three.js using LegacyGLTFLoader.js. When I do so, everything looks good, except that the model does not receive shadows. I am guessing that this is because the imported model's material is THREE.RawShaderMaterial, which does not support receiving shadows (I think). How can I fix this so that my imported model can receive shadows?
Here is sample code:
// Construct scene.
var scene = new THREE.Scene();
// Get window dimensions.
var width = window.innerWidth;
var height = window.innerHeight;
// Construct camera.
var camera = new THREE.PerspectiveCamera(75, width/height);
camera.position.set(20, 20, 20);
camera.lookAt(scene.position);
// Construct renderer.
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
renderer.shadowMapEnabled = true;
document.body.appendChild(renderer.domElement);
// Construct cube.
var cubeGeometry = new THREE.BoxGeometry(10, 1, 10);
var cubeMaterial = new THREE.MeshPhongMaterial({color: 0x00ff00});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
cube.translateY(15);
scene.add(cube);
// Construct floor.
var floorGeometry = new THREE.BoxGeometry(20, 1, 20);
var floorMaterial = new THREE.MeshPhongMaterial({color: 0x00ffff});
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.receiveShadow = true;
scene.add(floor);
// Construct light.
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 20, 0);
light.castShadow = true;
scene.add(light);
// Construct light helper.
var lightHelper = new THREE.DirectionalLightHelper(light);
scene.add(lightHelper);
// Construct orbit controls.
new THREE.OrbitControls(camera, renderer.domElement);
// Construct GLTF loader.
var loader = new THREE.LegacyGLTFLoader();
// Load GLTF model.
loader.load(
"https://dl.dropboxusercontent.com/s/5piiujui3sdiaj3/1.glb",
function(event) {
var model = event.scene.children[0];
var mesh = model.children[0];
mesh.receiveShadow = true;
scene.add(model);
},
null,
function(event) {
alert("Loading model failed.");
}
);
// Animates the scene.
var animate = function () {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
// Animate the scene.
animate();
Here are my resources:
https://dl.dropboxusercontent.com/s/y2r8bsrppv0oqp4/three.js
https://dl.dropboxusercontent.com/s/5wh92lnsxz2ge1e/LegacyGLTFLoader.js
https://dl.dropboxusercontent.com/s/1jygy1eavetnp0d/OrbitControls.js
https://dl.dropboxusercontent.com/s/5piiujui3sdiaj3/1.glb
Here is a JSFiddle:
https://jsfiddle.net/rmilbert/8tqc3yx4/26/
One way to fix the problem is to replace the instance of RawShaderMaterial with MeshStandardMaterial. To get the intended effect, you have to apply the existing texture to the new material like so:
var newMaterial = new THREE.MeshStandardMaterial( { roughness: 1, metalness: 0 } );
newMaterial.map = child.material.uniforms.u_tex.value;
You also have to compute normal data for the respective geometry so lighting can be computed correctly. If you need no shadows, the unlint MeshBasicMaterial is actually the better choice.
Updated fiddle: https://jsfiddle.net/e67hbj1q/2/

Three.js - Issue loading Blender model

I want to import a blender model into three.js, but I havent been successful yet.
This is what I have:
<script src="GLTFLoader.js"></script>
// CHILDREN
children = new THREE.Object3D();
// Blender Model
var bld = (blend[4]).toString();
var loader = new THREE.GLTFLoader();
loader.load(bld,handle_load);
var mesh;
console.log(bld); // "/media/accounts/2878836292/2878836292.glb"
function handle_load(gltf) {
mesh = gltf.scene.children[0];
mesh.position.set(-50, 15, -50);
children.add(mesh);
// mesh.position.z = -10;
}
//cylinder
image = (images[0]).toString();
const texture1 = new THREE.TextureLoader().load(image);
const material1 = new THREE.MeshBasicMaterial({ map: texture1 });
geometry = new THREE.CylinderGeometry(0, 20, 40, 20);
var cylinder = new THREE.Mesh(geometry, material1);
cylinder.position.set(-90, 20, 30);
children.add(cylinder);
Blender Model: https://workupload.com/file/AEm5FgER
I don't get any errors in the web console.
Solved it. I had to scale the model up to 15mm to make it visible

Regarding Threejs texture Animation

I'm Working with Threejs in which I'm Facing Problems with Textures, so I would like to ask A question that is, how to load the textures without starting the animation, it shows blank image without starting the animation. Can anyone tell me how to do that..
var geometry = new THREE.PlaneGeometry( 15, 5.3, 2 );
var te = new THREE.ImageUtils.loadTexture("b4.jpg") ;
var material = new THREE.MeshBasicMaterial( {color: "",map:te} );
plane = new THREE.Mesh( geometry, material);
plane.position.set(-12.89,-7.2,19);
plane.visible=false;
scene.add( plane );
Did you try having a callback function to trigger after the texture is successfully loaded? Like how it is done in the documentation: TextureLoader
So something like:
var geometry = new THREE.PlaneGeometry( 15, 5.3, 2 );
var loader = new THREE.TextureLoader();
// load a resource
loader.load(
// resource URL
'b4.jpg',
// Function when resource is loaded
function ( texture ) {
// do something with the texture
var material = new THREE.MeshBasicMaterial( {color: "",map:te} );
plane = new THREE.Mesh( geometry, material);
plane.position.set(-12.89,-7.2,19);
plane.visible=false;
scene.add( plane );
},
// Function called when download errors
function ( xhr ) {
console.log( 'An error happened' );
}
);

I'm new to threejs, how to create a sky dome

I'm pretty new to three.js and I tried for hours to create a skybox/skydome for a better visual feeling to my world (in this case space). Googled, checked tutorials, asked here on StackOverflow. And nothing worked, or I got a silly and dumb answer here on SO. Question is simple: how to make a skybox/dome?
This is how you do a skydome in threejs.
var skyGeo = new THREE.SphereGeometry(100000, 25, 25);
First the geometry. I wanted it big and made it big
var loader = new THREE.TextureLoader(),
texture = loader.load( "images/space.jpg" );
Loads the texture of your background space. One thing here is that you need it to run through a server to be able to load the texture. I use wamp or brackets preview.
Create the material for the skybox here
var material = new THREE.MeshPhongMaterial({
map: texture,
});
Set everything together and add it to the scene here.
var sky = new THREE.Mesh(skyGeo, material);
sky.material.side = THREE.BackSide;
scene.add(sky);
This might not be the best solution for this, but it´s easy specially for a beginner in threejs. Easy to understand and create.
This is how you can load image as texture and apply that on innerside of a sphere geometry to emulate skydome.
Complete solution with error callback for future reference
//SKY
var loader = new THREE.TextureLoader();
loader.load(
"./assets/universe.png",
this.onLoad,
this.onProgress,
this.onError
);
onLoad = texture => {
var objGeometry = new THREE.SphereBufferGeometry(30, 60, 60);
var objMaterial = new THREE.MeshPhongMaterial({
map: texture,
shading: THREE.FlatShading
});
objMaterial.side = THREE.BackSide;
this.earthMesh = new THREE.Mesh(objGeometry, objMaterial);
scene.add(this.earthMesh);
//start animation
this.start();
};
onProgress = xhr => {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
};
// Function called when download errors
onError = error => {
console.log("An error happened" + error);
};

Resources