Making dynamic text with Three.js and dat.gui - javascript

I want to make a dynamic render of user inputted text using three.js and dat.gui, so far i've made this to render out the text:
var displayGui = function(){
var gui = new dat.GUI();
var parameters = {
message:"sample",
spinVelocity: 0
}
//Adds Text controls
var myText = gui.add(parameters, 'message').name('Text');
myText.onChange(function () {
//adds text
var loader = new THREE.FontLoader();
loader.load('fonts/OpenSansBold.json', function(font) {
console.log(myText);
var textGeo = new THREE.TextGeometry(myText, {
font: font,
size: 200,
height: 50,
curveSegments: 12,
position: 3,
bevelThickness: 2,
bevelSize: 5,
bevelEnabled: true,
});
var textMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000 });
var mesh = new THREE.Mesh(textGeo, textMaterial);
mesh.position.set(100, 100, 100);
scene.add(mesh);
});
});
gui.add(parameters, 'spinVelocity').name('Spin');
gui.open();
};
However, as you can see on here, it just renders out a big red 3D text that says [object Object] , i have suspected that this may be because var myText is an object and not a string, so i tried to String(myText) however, it did not change much and it still did not work.
Is this not working because the text is not a string or is this because three.js is not recognizing the text inputted by the user on the dat.gui interface?

You should not be trying to load the font each time your dat.gui fires. Your code has a horrible performance problem, and it's likely that you would run out of memory after fiddling with the gui for a while.
My understanding is that this code creates a new instance of geometry each time you change the value in the gui, and never disposes of them. You're filling up your gpu with copies of this mesh.
Specific to your question, you're using datgui wrong:
console.log(myText); //logs the intance of a gui object (a JS object with methods and such)
change to:
console.log(parameters.message);
To fix the reloading issue, cache your font
var myFont
loader.load('fonts/OpenSansBold.json', function(font) {
myFont = font
//your gui is not ready until the font comes so, for example you could instantiate it here
gui.add(...).onChange(function(){..})
})

Change line
myText.onChange(function () {
to
myText.onChange(function (value) {
Then value will contain the new value of the input.

Related

Adding custom object to Group in paperJS

In PaperJS, only regular types seem to be able to be added to a Group. Whenever I try to append the group with a custom object I get the error item._remove is not a function
Whenever I create my own object, for instance:
function textBox(point, width) {
this.box = new Path.Rectangle({
point: point,
size: new Size(width,40),
strokeColor: new Color(0,0,0,0.1),
fillColor: 'white',
strokeWidth: 1
});
this.box.onMouseUp = function(event) {
cursor = this.point + new Point(5,2);
}
}
and try to append this to a group:
var testGroup = new Group();
testGroup.appendTop(new textBox(new Point(0,0), 400));
The error shows up. My question is thus: how do I add custom objects to a group? Surely I can't be expected to either manually create each individual object or otherwise manipulate them all on an individual level without using Group dynamics. It seems I have to, just like every other type in PaperJS, make my object extend Item, but I so far have failed to get it to accept my constructor for it. I'm wondering what is required for it to be accepted.
Indeed, there currently is no built-in mechanism to extend Paper.js classes apart from compiling them along with the library.
So for simple cases like the one that you seem to encounter, I would use a factory function that instantiates my custom object and then interact with it as with any other Paper.js object.
For example, if your custom object is a box with a text in it, you can instantiate a group with a rectangle and a text in it and then just interact with this group.
Here is a sketch demonstrating the solution.
function createMyTextBox(point, content) {
// Create a text item
var text = new PointText({
point: point,
content: content,
fontSize: 36,
fillColor: 'red',
justification: 'center'
});
// Create a rectangle around the text
var rectangle = new Path.Rectangle({
rectangle: text.bounds.scale(1.2),
strokeColor: 'black'
});
// Create a Group that will wrap our items.
var group = new Group([text, rectangle]);
// Return the group
return group;
}
// Create 2 text boxes
var textBox1 = createMyTextBox(view.center, 'text 1');
var textBox2 = createMyTextBox(view.center + [0, 100], 'text 2');
// You can use text boxes like regular items.
textBox2.translate(100, 0);
textBox2.scale(0.8);

how to add 2d text over my 3d object created through blender

i have a 3d model created with blender and i want to place 2d text over the various meshes of the imported model. i am successfull in changing colors of the meshes on the basis of their indices by diffuse.color but unable to drawtext over my mesh.
code used to change color:
scene.meshes[indexValue].material.diffuseColor = color;
allMeshChange =indexValue == scene.meshes.length - 1;
and for text i am doing this:
var impact = var impact =scene.meshes[1];
impact.material = new BABYLON.StandardMaterial("impactMat", scene);
impact.material.emissiveColor = new BABYLON.Color3(1, 1, 0.5);
impact.position = new BABYLON.Vector3(scene.meshes[1].position);
var backgroundTexture = new BABYLON.DynamicTexture("dynamic texture", 512, scene, true);
impact.material.diffuseTexture = backgroundTexture;
impact.material.specularColor = new BABYLON.Color3(1, 1, 1);
impact.material.backFaceCulling = false;
backgroundTexture.drawText("test", null, 80, "italic 80px Segoe UI", "white", "#555555");
i am new to babylon js . Kindly help in writing text over mesh.
What is the options argument meaning with the current value 512 on the BABYLON.DynamicTexture constructor ? Cause to determine the 2d context for printing the text it's looking for a getContext(:string) method. Instead, you should pass it your canvas known as a HTMLCanvasElement.

Why javascript Cannot read property width of textureImage

I am trying to render image texture dynamically but in console it displays Uncaught TypeError: Cannot read property 'width' of undefined. few suggest to make needsUpdate = true to false. When I am doing so some unpleasing black patch is appearing .But I need it true. how can i do so?
Actual code is very big i am pointing where problem occurs..
var g = new THREE.PlaneGeometry(100,100);
var tx =THREE.ImageUtils.loadTexture(imgUrl,undefined,callback,callbackError);
var m = new THREE.MeshBasicMaterial({
map:tx
});
m.map.needsUpdate = true; //1
tx.needsUpdate = true; //2
////if 1 and 2 is commented then its ok but rendering is not good
var b = THREE.SceneUtils.createMultiMaterialObject(g,[
m
,new THREE.MeshBasicMaterial({wireframeLinewidth:3,color:0x222222,wireframe:true})
]);
There may be a situation when renderer tries to use you texture before it is loaded. When you do
var tx =THREE.ImageUtils.loadTexture(imgUrl,undefined,callback,callbackError);
the texture is returned but image itself is not yet loaded. So renderer tries to take width and height to find out is your texture power of 2 and throws an error in this moment.
You may try to wrap creation of your object and placing it to the scene into the callback function and pass it as the third parameter to the loadTexture method. Something like that:
var geometry = new THREE.PlaneGeometry(100, 100);
var tx =THREE.ImageUtils.loadTexture(imgUrl, undefined, function(texture){
var material = new THREE.MeshBasicMaterial({
map: texture
});
var b = THREE.SceneUtils.createMultiMaterialObject(
geometry,
[
material,
new THREE.MeshBasicMaterial({wireframeLinewidth:3,color:0x222222,wireframe:true})
]
);
}, callbackError);

Native cursor in Adobe AIR JavaScript with MouseCursorData

I've been reading this article: http://blogs.adobe.com/cantrell/archives/2011/03/native-cursors-in-air-2-6.html on how to create a native cursor in AIR without having to hack it by moving a sprite in place of a hidden cursor to fake it.
However I'm using HTML/JavaScript instead of ActionScript.
So far I have:
function nativeCursor(){
// load in a bitmap
var loader = new air.Loader();
loader.load(new air.URLRequest('./assets/cursor.png'));
var bitmaps = new air.Vector["<String>"]();
var bmd = new air.BitmapData(32, 32, true, 0x00000000);
var p = new window.runtime.flash.geom.Point(0, 0);
var r = new window.runtime.flash.geom.Rectangle(32 , 0, 32, 32);
var image = new window.runtime.flash.display.Bitmap(loader.content);
bmd.copyPixels([image.bitmapData], r, p);
bitmaps.push(bmd);
var mcd = new window.runtime.flash.ui.MouseCursorData();
mcd.data = bitmaps;
mcd.hotSpot = new Point(0, 0);
mcd.frameRate = 24;
window.runtime.flash.ui.Mouse.registerCursor("defaultCursor", mcd);
window.runtime.flash.ui.Mouse.cursor = "defaultCursor";
}
But I get an error TypeError: Error #1034: Type Coercion failed: cannot convert []#2b9d1f1 to flash.display.BitmapData. for this line: bmd.copyPixels([image.bitmapData], r, p);
If I remove the brackets for that line so it's just: bmd.copyPixels(image.bitmapData, r, p); the error becomes TypeError: Error #2007: Parameter sourceBitmapData must be non-null.
So I'm assuming that the error is because the bitmap data is null... but why? The image is being loaded in fine so is the way I'm trying to get the bitmap data incorrect?
BitmapData, not String
The vector is supposed to be of type BitmapData, not String, that is:
air.Vector["<flash.display.BitmapData>"]
See HTML Developer’s Guide for Adobe AIR - Working with Vectors for more information.
Asynchronous loaders
Also the Loader class probably works asynchronously in JavaScript too, it's not documented properly in the HTML API reference and I've never used JS for AIR development, so I can only assume that, and that one can refer to the AS3 reference for the missing docs, however it makes sense judging from the available examples.
http://help.adobe.com/.../html/flash/display/BitmapData.html#includeExamplesSummary
Loader.bitmapData doesn't exist
There is no bitmapData property on the Loader class, only a content property that holds a DisplayObject, which might actually be a Bitmap object which in turn has a bitmapData property.
An example
Here's some untested example code that should get you started:
var mcd = new window.runtime.flash.ui.MouseCursorData();
mcd.hotSpot = new air.Point(0, 0);
mcd.frameRate = 24;
var loader = new air.Loader();
loader.contentLoaderInfo.addEventListener(air.Event.COMPLETE, function(event)
{
var image = air.Bitmap(loader.content);
var bitmaps = new air.Vector["<flash.display.BitmapData>"]();
bitmaps.push(image.bitmapData);
mcd.data = bitmaps;
air.Mouse.registerCursor('defaultCursor', mcd);
air.Mouse.cursor = 'defaultCursor';
});
var request = new air.URLRequest('./assets/cursor.png');
loader.load(request);

Change length of cylinder or extrudedHeight of circle

I'm trying to change the length of a cylinder or the extrudedHeight of a circle when it has been added to the primitives and is shown in the cesium widget/viewer. For example this cylinder:
var length = 100;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(
Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
var primitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
widget.scene.primitives.add(primitive);
Because it's added to the primitives array it will be shown in the widget, but after 2 seconds for example I get a notification that the length should be halved (that means set to 50). Is there any way to do this? Simply changing it in cylinderGeometry doesn't seem to do the job.
I kind of have it working by creating a new cylinder with the new height, adding it and removing the old one. This however tends to flicker the cylinder (it's gone for a fraction of a second) before the new one is shown. I fixed this problem by removing the old instance after a set time after the new one is added. This whole solution isn't very elegant and doesn't work very well on devices with a small amount of computing power, hence my search for a better solution.
I don't care if this is achieved using cylinders or extruded circles. If you need any more information don't hesitate to ask in the comments below the question.
EDIT
I implemented the second solution Matthew suggested but after a while of it running perfectly the cylinders stop changing height (which didn't occur when I used my solution. The callback in the interval does get called. Here is some code showing what my new solution is (not working):
primitives.add(prim);
window.nodeValuesInterval = setInterval(function () {
if (prim._state == Cesium.PrimitiveState.COMPLETE) {
clearInterval(window.nodeValuesInterval);
clearTimeout(window.nodeValuesTimeout);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}
}, cylindervalueinterval);
window.nodeValuesTimeout = setTimeout(function () {
clearInterval(window.nodeValuesInterval);
primitives.remove(primitiveObjects.value);
primitiveObjects.value = prim;
}, cylindervaluedelay);
Cesium's geometry is currently optimized for static data. Some attributes, such as visibility, color, and material can be changed on the fly, but items that actually modify the geometry (like cylinder height) require you to remove the primitive and recompute the geometry. The flickering your seeing is the result of asynchronous primitive creation being on by default. There are two ways to do want you want.
Disable asynchronous primitive create by passing [options.asynchronous: false to the Primitive constructor. This means that when you add a new primitive, Cesium will not render until it is ready. For one or two objects, you won't notice anything. For lots of objects it will lock up the browser until everything is ready. This does guarantee that you can remove old/add new primitives without any flicker.
The second option is to add your new primitive (without removing the old one) and then every frame, check the _state property of your new Primitive (I thought this was part of the public API but apparently it's not). When the _state is equal to Cesium.PrimitiveState.COMPLETE you can safely remove the old primitive and your guaranteed the new one will render (hence no flicker).
I think we have a bug/feature request to expose the state variable publicly or otherwise notify when the Primitive is ready; but using _state should be fine for the forseeable future. I'll update this issue if we add an official way sometime soon.
Hope that helps.
EDIT: Since more help was requested; here's a complete example. You can copy and paste the below code into Sandcastle using this link.
Basically it uses the scene.preRender event instead of a timeout (preRender is almost always the better answer here). Also, if you receive a new update before the old one is finished processing, it's important to remove that one before computing the new one. Let me know if you are still having problems.
require(['Cesium'], function(Cesium) {
"use strict";
var widget = new Cesium.CesiumWidget('cesiumContainer');
var ellipsoid = widget.scene.globe.ellipsoid;
var lon = 0;
var lat = 0;
var cylinderradius = 30000;
var length = 10000000;
var cylinderslices = 32;
var newPrimitive;
var currentPrimitive;
//This function creates a new cylinder that is half the length of the old one.
function decreaseLength() {
//If there's a pending primitive already, remove it.
if(Cesium.defined(newPrimitive)){
widget.scene.primitives.remove(newPrimitive);
}
length /= 2;
var cylinderGeometry = new Cesium.CylinderGeometry({
length : length,
topRadius : cylinderradius,
bottomRadius : cylinderradius,
slices: cylinderslices,
vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
});
var cylinder = new Cesium.GeometryInstance({
geometry: cylinderGeometry,
modelMatrix: Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lon, lat))),
new Cesium.Cartesian3(0.0, 0.0, length * 0.5)),
attributes: {
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED)
},
id: "Cylinder1"
});
newPrimitive = new Cesium.Primitive({
geometryInstances : cylinder ,
appearance : new Cesium.PerInstanceColorAppearance({
closed : false,
translucent: true,
flat: false,
faceForward: true
}),
allow3DOnly: true,
vertexCacheOptimize: true,
allowPicking: true,
releaseGeometryInstances: false
});
//We add the new cylinder immediately, but don't remove the old one yet.
widget.scene.primitives.add(newPrimitive);
}
//Create the initial cylinder.
decreaseLength();
//Subscribe to the preRender event so we can check the primitive every frame.
widget.scene.preRender.addEventListener(function(scene, time) {
//Remove the old cylinder once the new one is ready.
if(Cesium.defined(newPrimitive) && newPrimitive._state === Cesium.PrimitiveState.COMPLETE){
if(Cesium.defined(currentPrimitive)){
widget.scene.primitives.remove(currentPrimitive);
}
currentPrimitive = newPrimitive;
newPrimitive = undefined;
}
});
Sandcastle.addToolbarButton('Decrease Length', decreaseLength);
Sandcastle.finishedLoading();
});

Categories

Resources