I have a rather broad question, but no idea how to tackle that. So forgive me.
I am trying to have several (like 200 and more) objects and, let's just say, a container to the side of the field, where I draw the objects. Now what I want is, that each object has some non visual attributes and when I click on that object, the attributes should appear in that container.
Now I could go about it?
I mean, I know I can ask for the name of the selected object and then do a key value query from some dictionary. Question is, whether there is an easier way to go about it.
For the click event I used a library called threex.domevents, check the GitHub page for more information, the code for the event it's self explanatory.
First domevents needs to be initialized in your scene like this:
var domEvents = new THREEx.DomEvents(camera, renderer.domElement);
Then I created a custom Mesh object:
// random id
function genRandomId()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
// random int for position
var min = -50;
var max = 50;
function genRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
// custom mesh --------------------------------------------
function MyMesh(geometry, material, destinationContainer) {
THREE.Mesh.call(this, geometry, material);
this.userData = {
foo1: genRandomId(),
foo2: genRandomId(),
foo3: genRandomId(),
};
this.position.x = genRandomInt(min, max);
this.position.y = genRandomInt(min, max);
this.position.z = genRandomInt(min, max);
var that = this;
// click event listener
domEvents.addEventListener(this, 'click', function(event) {
console.log('clicked object on position:');
console.log(that.position);
destinationContainer.userData = that.userData;
console.log('Now the conainer has:');
console.log(destinationContainer.userData);
destinationContainer.userData = that.userData;
}, false);
}
MyMesh.prototype = Object.create(THREE.Mesh.prototype);
MyMesh.prototype.constructor = MyMesh;
genRandomId and genRandomInt are random generators for the pourpose of illustrating this example, I took the code for the random ids from Generate random string/characters in JavaScript.
In your scene you can generate 200 (or more) MyMesh meshes and add them to the scene:
const color = 0x156289;
const emissive = 0x072534;
var planeGeometry = new THREE.PlaneGeometry(5, 5);
var planeMaterial = new THREE.MeshPhongMaterial({
color: color,
emissive: emissive,
side: THREE.DoubleSide,
shading: THREE.FlatShading
});
var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(planeMesh);
var objGeometry = new THREE.BoxGeometry(1, 1, 1);
var objMaterial = new THREE.MeshPhongMaterial({
color: color,
emissive: emissive,
shading: THREE.FlatShading
});
var i = 0;
while (i < 200) {
scene.add(new MyMesh(objGeometry, objMaterial, planeMesh));
i++;
}
And finally render the scene:
var render = function() {
requestAnimationFrame(render);
planeMesh.rotation.x += 0.010;
planeMesh.rotation.y += 0.010;
renderer.render(scene, camera);
};
render();
This is a demo with the full source code: http://run.plnkr.co/plunks/W4x8XsXVroOaLUCSeXgO/
Open the browser console and click on a cube and you'll see the that planeMesh is switching its userData attributes with the ones of the clicked cube mesh.
Yes, that's fine. You can put your own custom keys directly on a Three.js object and it shouldn't bother it as long as you don't accidentally overwrite an important built-in Three.js key. For that reason I'd recommend that you put all of your custom keys in a "namespace" on the object so they're nice and neat and contained.
For example, if you had a Three.js object foo, you could put all your keys under foo.myCustomNamespace, so that your custom data like foo.myCustomNamespace.name, foo.myCustomNamespace.description, etc. are all together and won't interfere with THREE.js properties.
Edit: Three.js provides a built-in namespace for user data called, conveniently, userData. Access it on THREE.Object3D.userData.
https://github.com/mrdoob/three.js/blob/master/src/core/Object3D.js#L92
Related
I'm trying to wrap my head around prototypal inheritance.
I've read the MDN documentation on it, but it didn't leave me much smarter. I have the following code so far...
var Vertex = function(id, options){
this.options = options || {};
this.id = id;
this.position = new THREE.Vector3(0, 0, 0);
this.velocity = new THREE.Vector3(0, 0, 0);
this.acceleration = new THREE.Vector3(0, 0, 0);
this.toString = function(){
return this.id.toString();
};
this.edge_count = 0;
this.edges = {};
if(!this.options.hasOwnProperty('label')){
this.options.label = {
text: '',
direction: 'x',
distance: '10'
};
};
};
... from which I want to "inherit" all the properties added by the constructor. Vertex has a paint method that adds a Mesh as the Vertex's object. So I wrote...
var CameraVertex = function(id, camera){
Vertex.call(this);
this.object = camera;
this.id = id;
};
CameraVertex.prototype = Object.create(Vertex.prototype);
CameraVertex.prototype.constructor = CameraVertex;
... so I'd be able to use CameraVertex as a drop in replacement for Vertex (except the constructor, which simply assigns the camera to the Vertex's object property, which would usually hold a Mesh or a Group.
But for some reason, there seems to be no source.object when I create an edge between the CameraVertex and a regular vertex.
The complete example can be found at Social Cartography after clicking signin with google and selecting the vertex with your mouse.
When you call the constructor of the inherited Object you need to pass also all necessary parameters.
var CameraVertex = function(id, camera, options){
Vertex.call(this, id, options);
this.object = camera;
this.id = id;
};
Even though I'm not familiar with THREE so I don't understand your problem with source.object, so far I can see this problem.
I have try a lot of ways to complete this effect,i want to draw a line of mouse down event,and i have seen many other questions but do not have any idea about it,so far i use the Ray caster method of intersectObjects() and get the position of the click,but i do not how to then,Hope someone give me advice,thanks very much.
Here are part of mine code:
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = -( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
var raycaster=new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(objects,true);
console.log(intersects[0].point);//type of intersects[0] is object,one of attribute is point?
yesterday i write some codes and have complete part of my effect. Here are some code:
var clickCount = 0;//initial value
clickCount = clickCount+1;
if(intersects.length>0)
{
switch(clickCount)
{
case 1:
var startPoint=intersects[0].point;//store the first intersect point when the first click
break;
case 2:
var endPoint =intersects[0].point;//store the second intersect point when the second click
break;
}
}
clickCount=1;
var geometry = new THREE.Geometry();
material = new THREE.LineBasicMaterial({color:0xffffff,linewidth:2});
geometry.vertices.push(startPoint);
geometry.vertices.push(endPoint);
line = new THREE.Line(geometry, material);
scene.add(line);
but this is not the final effect what i wanted.this segment lines are all rays set up from the vector(0,0,0). here is the screenshot:
the red line is the effect what i want to implementate. Could anyone figure out the reason?thanks very much.
It's better to remember the first clicked object, then on the next click you will check, if it's the same object or another one, and if it's another one, then draw a line between them.
var intersected, lineObjects = [],
objects = [];
. . .
and in the event handler:
if (intersects.length > 0) {
var found = intersects[0].object;
if (found != intersected) {
if (lineObjects.length > 0) {
var line = lineObj(lineObjects[0].position, found.position);
scene.add(line);
lineObjects[0].material.color.setHex(lineObjects[0].userData.oldColor);
intersected = null;
lineObjects = [];
} else {
found.userData.oldColor = found.material.color.getHex();
found.material.color.set(0x00FF00);
intersected = found;
lineObjects.push(found);
}
} else {
found.material.color.setHex(found.userData.oldColor);
intersected = null;
lineObjects = [];
}
}
I've made a jsfiddle example. Look at onMouseDown() function. All the things I described above are there.
Given 200+ vertices generated by the AutoCad API in the form of an array of vectors {X:,Y:,Z:}, I am trying to render them in THREE.js with no luck.
For now I am making all the possible permutations for the number 200 and connecting all the vertices together - which I don't think is the way this is done as it gives over 200k faces.
EDIT: My AutoCAD code is getting all the vertices and then it tries to get it's connected vertices' ids (vertex1 and vertex2). GetHashCode() doesn't work though. The problem isn't that it returns huge id numbers like 148335760 and 682610240. The problem is that these ids are not unique and they happen to be defined and they are not connected to any other vertices.
AutoCAD code:
//data structures for serialisation
public class EdgeMe
{
public int vertex1;
public int vertex2;
}
public class VertexMe
{
public int id;
public Point3d Point;
public List<EdgeMe> Edges = new List<EdgeMe>();
}
public class DataMe{
public Extents3d extents;
public string layer;
public List<VertexMe> points = new List<VertexMe>();
}
//...
// Get each Solid3d in modelspace and add its extents
// to the list
foreach (var id in ms)
{
var obj = tr.GetObject(id, OpenMode.ForRead);
var sol = obj as Solid3d;
DataMe dataMe = new DataMe();
if (sol != null)
{
dataMe.extents = sol.GeometricExtents;
dataMe.layer = sol.Layer;
using (var brep = new Autodesk.AutoCAD.BoundaryRepresentation.Brep(sol))
{
foreach (var vertex in brep.Vertices)
{
VertexMe vertexMe = new VertexMe();
vertexMe.Point = vertex.Point;
vertexMe.id = vertex.Brep.GetHashCode();
foreach(var edge in vertex.Edges)
{
EdgeMe edgeMe = new EdgeMe();
edgeMe.vertex1 = edge.Vertex1.Brep.GetHashCode();
edgeMe.vertex2 = edge.Vertex2.Brep.GetHashCode();
vertexMe.Edges.Add(edgeMe);
}
dataMe.points.Add(vertexMe);
}
}
}
sols.Add(dataMe);
}
Javascript code:
var faces = function(vertices) {
var results = [];
var vertex = [0, 1, 2];
results.push(vertex.slice());
while(true) {
vertex[2]++;
if(vertex[2] > vertices) {
vertex[1]++;
if(vertex[1] >= vertices) {
vertex[0]++;
vertex[1] = vertex[0] + 1;
if(vertex[0] > vertices - 2)
return results;
}
vertex[2] = vertex[1] + 1;
}
results.push(vertex.slice());
}
};
var generate = function( ... ) {
// Process each box, adding it to the scene
for (var sol in sols) {
var s = sols[sol];
var vertices = [];
for(var vertix in s.points) {// deserialize
vertix = s.points[vertix];
vertices.push(new THREE.Vector3(vertix.X, vertix.Y, vertix.Z));
}
var holes = [];
var triangles, mesh;
var geometry = new THREE.Geometry();
geometry.vertices = vertices;
var xfaces = faces(vertices.length);
for(var i = 0; i < xfaces.length; i++) {
geometry.faces.push( new THREE.Face3( xfaces[i][0], xfaces[i][1], xfaces[i][2] ));
}
geometry.computeFaceNormals();
geometry.computeVertexNormals();
mesh = new THREE.Mesh( geometry, customimg );
mesh.rotation.set( Math.PI/2, 0, 0);
root.add(mesh);
}
}
Regards,
Ioan
You cannot do what you are doing. You are trying to guess the connectivity info. That should not be guess work. The application needs to return the connectivity info to you.
Instead I would suggest you use Forge Model Derivative API to extract the viewable from your file. Once you have the file translated, you can view it using the Viewer (which is based on Three.js, but optimized for engineering files). You'll find several examples at this Github.
The problem:
Bounding box volume is smaller than volume calculated from mesh.
What I've tried:
First I calculate the volume of a bounding box with the following code:
//loaded .obj mesh object:
var sizer = new THREE.Box3().setFromObject(obj);
console.log(sizer.size().x*sizer.size().z*sizer.size().y);
log output: 197112.65382983384
Then I calculate the volume of the mesh using this solution with this solution with the legacy geometry object in the method "calculateVolume" included below:
console.log(scope.calculateVolume(child));
calculateVolume is part of a class. My class methods are as follows:
threeDeeAsset.prototype.signedVolumeOfTriangle = function(p1, p2, p3) {
var v321 = p3.x*p2.y*p1.z;
var v231 = p2.x*p3.y*p1.z;
var v312 = p3.x*p1.y*p2.z;
var v132 = p1.x*p3.y*p2.z;
var v213 = p2.x*p1.y*p3.z;
var v123 = p1.x*p2.y*p3.z;
return (-v321 + v231 + v312 - v132 - v213 + v123)/6;
}
threeDeeAsset.prototype.calculateVolume = function(object){
var volumes = 0;
object.legacy_geometry = new THREE.Geometry().fromBufferGeometry(object.geometry);
for(var i = 0; i < object.legacy_geometry.faces.length; i++){
var Pi = object.legacy_geometry.faces[i].a;
var Qi = object.legacy_geometry.faces[i].b;
var Ri = object.legacy_geometry.faces[i].c;
var P = new THREE.Vector3(object.legacy_geometry.vertices[Pi].x, object.legacy_geometry.vertices[Pi].y, object.legacy_geometry.vertices[Pi].z);
var Q = new THREE.Vector3(object.legacy_geometry.vertices[Qi].x, object.legacy_geometry.vertices[Qi].y, object.legacy_geometry.vertices[Qi].z);
var R = new THREE.Vector3(object.legacy_geometry.vertices[Ri].x, object.legacy_geometry.vertices[Ri].y, object.legacy_geometry.vertices[Ri].z);
volumes += this.signedVolumeOfTriangle(P, Q, R);
}
return Math.abs(volumes);
}
log output: 336896.1562770668
Checking the source of the vertexes:
I also tried buffergeometry and of course, it's really the same array, but a typed Float32Array and predictably gave the same result:
var vol = 0;
scope.mesh.traverse(function (child) {
if (child instanceof THREE.Mesh) {
var positions = child.geometry.getAttribute("position").array;
for(var i=0;i<positions.length; i+=9){
var t1 = {};
t1.x = positions[i+0];
t1.y = positions[i+1];
t1.z = positions[i+2];
var t2 = {};
t2.x = positions[i+3];
t2.y = positions[i+4];
t2.z = positions[i+5];
var t3 = {};
t3.x = positions[i+6];
t3.y = positions[i+7];
t3.z = positions[i+8];
//console.log(t3);
vol += scope.signedVolumeOfTriangle(t1,t2,t3);
}
}
});
console.log(vol);
log output: 336896.1562770668
The question: Why is my bounding box volume smaller than the calculated volume of a closed mesh?
Perhaps I missing something such as an offset position or maybe I am calculating the bounding box volume wrong. I did several searches on google and stack, which is how I came to find the signedVolumeOfTriangle function above. it seems to be the most common accepted approach.
Could be a problem with winding order, you could try negating the result from signed volume, or reordering arguments.
The winding order will be clockwise or counterclockwise and determines the facing (normal) of the polygon,
volumes += this.signedVolumeOfTriangle(P, Q, R);
Swapping P and R inverts the normal,
volumes += this.signedVolumeOfTriangle(R, Q, P);
This can be complicated by storage techniques like triangle strips, where vertices are shared by adjacent triangles, causing winding order to alternate.
Another problem could be - especially if it works correctly for simple meshes - is degenerate vertices. If you're getting your meshes from an editor, and the mesh has been modified and tweaked a million times (usually the case), it's almost guaranteed to have degenerates.
There may be an option to weld close vertices in the editor that can help, or test with a known good mesh e.g. the Stanford bunny.
Is it possible to load a JSON model once and add it to the scene multiple times with different scales, positions, etc?
If I add the Object3D() to an array, give a position and scale to the object in the array, add it to the scene, and then repeat this process, the position and scale are overwritten for every object in the array.
I can't think of anything that works, so I'm hoping someone could give me a working example of what I'm trying to accomplish.
Here's (one of many) of my failed attempts. Should give you a basic idea of what I'm trying to do, if my explanation wasn't sufficient.
function addModels(){
var models = [];
var model = new THREE.Object3D();
var modelTex = THREE.ImageUtils.loadTexture( "textures/model.jpg" );
var loader = new THREE.JSONLoader();
loader.load( "models/model.js", function( geometry ) {
mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { map: modelTex }) );
model.add(mesh);
} );
for(var i = 0; i < 5; i++){
model.scale.set(i,i,i);
model.position.set(i,i,i);
models[i] = model;
scene.add(models[i]);
}
}
You need to clone a model first and then set position and scale.
for(var i = 0; i < 5; i++){
var newModel = model.clone();
newModel.position.set(i,i,i);
newModel.scale.set(i,i,i);
scene.add(newModel);
}
Updated: Example how you can create json model without load : Fiddle example or just simple add loop inside load function.
You can create new meshes from the same geometries and materials:
loader.load( "models/model.js", function( geometry ) {
var mat = new THREE.MeshLambertMaterial( { map: modelTex });
for (var i = 0; i < 5; i++) {
var mesh = new THREE.Mesh( geometry, mat );
mesh.position.set(i, i, i);
mesh.scale.set(i, i, i);
scene.add(mesh);
}
});