When I plot data with ILPlotCube, the (0,0) origin is displayed with some offset (margin/gap) from the x and y axes. How can I remove this offset?
You can use the ILLimits.Set
For example:
var start = -2;
var sz = 6;
ILArray<float> x = Enumerable.Range(start, sz).ToArray();
var pc = new ILPlotCube(twoDMode: true)
{
new ILLinePlot(x)
};
pc.Limits.Set(new Vector3(0, start + sz - 1, 0), new Vector3(sz - 1, start, 0));
Added (01/12/2016)
My understanding was: you want to get rid of the space noted in the red circle?
Get rid of red circle
Also changed the code a bit.
Related
I've been trying to draw an ellipse arc between two arbitrary points but my implementation is not working in some situations.
Because a part of this is involves mathematics, I started by asking this question.
Basically, given two points and the center, you can always get an ellipse if you allow rotation, except for cases where the points are collinear.
The solution proposed to that question is to:
Translate the center to the origin, translating both points by the same vector.
Rotate both points by the angle -alpha which is the simetric of the angle of the largest vector with the positive x-semiaxis.
Solve the ellipse equation to find its radiuses (system of two equations with two unknowns).
Define the ellipse
Rotate back the ellipse with the angle alpha and translate back to its center.
However, I'm having trouble implementing this in Three.js.
The documentation for the EllipseCurve lists the expected parameters. I assume the starting angle to always be zero and then set the end angle to either the angle between the two vectors or its simetric. I also want the arc to always be the smallest (i.e., if the angle is bigger than 180º, I'd use the complementary arc). I assume the center of the ellipse to be the middle point between the centers of the shape's bounding boxes.
This is my example code:
https://jsfiddle.net/at5dc7yk/1/
This example tries to create an arc from a vertex in the original shape and the same vertex in the modified shape.
Code regarding the ellipse arc is under the class EllipseArc and you can mess with the transformation applied to the object in line 190.
It works for some cases:
But not all:
Just an idea from scratch, not the ultimate solution.
When you clone and translate object, to build an arc between two respective points you'll need their coordinates in world coordinate system, and a coordinate of the middle point between centroids of objects.
Find the mid point between points in world space (between start and end vectors).
Find its projection on the vector of translation (this is the center of an arc).
Find the angle between vectors that you get by subtraction the result center vector from each of them.
Divide an angle by amount of divisions - you'll get the step value.
Get the end vector as the base and rotate it around an axis (which is the normal of a triangle, built with start, center, end vectors) in a loop, multiplying that step angle value with the number of the current iteration.
Code example:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 10000);
camera.position.set(0, 0, 150);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var shapeGeom = new THREE.ShapeBufferGeometry(new THREE.Shape(californiaPts));
shapeGeom.center();
shapeGeom.scale(0.1, 0.1, 0.1);
var shapeMat = new THREE.MeshBasicMaterial({
color: "orange"
});
var shape = new THREE.Mesh(shapeGeom, shapeMat);
shape.updateMatrixWorld();
scene.add(shape);
var shapeClone = shape.clone();
shapeClone.position.set(25, 25, 0);
shapeClone.updateMatrixWorld();
scene.add(shapeClone);
var center = new THREE.Vector3().lerpVectors(shapeClone.position, shape.position, 0.5);
var vecStart = new THREE.Vector3();
var vecEnd = new THREE.Vector3();
var pos = shapeGeom.getAttribute("position");
for (let i = 0; i < pos.count; i++) {
vecStart.fromBufferAttribute(pos, i);
shape.localToWorld(vecStart);
vecEnd.fromBufferAttribute(pos, i);
shapeClone.localToWorld(vecEnd);
makeArc(center, vecStart, vecEnd);
}
function makeArc(center, start, end) {
console.log(center, start, end);
let vM = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
let dir = new THREE.Vector3().subVectors(end, start).normalize();
let c = new THREE.Vector3().subVectors(vM, center);
let d = c.dot(dir);
c.copy(dir).multiplyScalar(d).add(center); // get a center of an arc
let vS = new THREE.Vector3().subVectors(start, c);
let vE = new THREE.Vector3().subVectors(end, c);
let a = vS.angleTo(vE); // andgle between start and end, relatively to the new center
let divisions = 100;
let aStep = a / divisions;
let pts = [];
let vecTemp = new THREE.Vector3();
let tri = new THREE.Triangle(start, c, end);
let axis = new THREE.Vector3();
tri.getNormal(axis); // get the axis to rotate around
for (let i = 0; i <= divisions; i++) {
vecTemp.copy(vE);
vecTemp.applyAxisAngle(axis, aStep * i);
pts.push(vecTemp.clone());
}
let g = new THREE.BufferGeometry().setFromPoints(pts);
let m = new THREE.LineDashedMaterial({
color: 0xff0000,
dashSize: 1,
gapSize: 1
});
let l = new THREE.Line(g, m);
l.computeLineDistances();
l.position.copy(c);
scene.add(l);
}
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script>
var californiaPts = [
new THREE.Vector2(610, 320),
new THREE.Vector2(450, 300),
new THREE.Vector2(392, 392),
new THREE.Vector2(266, 438),
new THREE.Vector2(190, 570),
new THREE.Vector2(190, 600),
new THREE.Vector2(160, 620),
new THREE.Vector2(160, 650),
new THREE.Vector2(180, 640),
new THREE.Vector2(165, 680),
new THREE.Vector2(150, 670),
new THREE.Vector2(90, 737),
new THREE.Vector2(80, 795),
new THREE.Vector2(50, 835),
new THREE.Vector2(64, 870),
new THREE.Vector2(60, 945),
new THREE.Vector2(300, 945),
new THREE.Vector2(300, 743),
new THREE.Vector2(600, 473),
new THREE.Vector2(626, 425),
new THREE.Vector2(600, 370),
new THREE.Vector2(610, 320)
];
</script>
If you don't translate, and just rotate an object, in this case you don't need to compute a new center for each arc, just omit that step, as all the centers are equal to the centroid of the object.
I hope I explained it in more or less understandable way ^^
I'm trying to move an arrow, which could be rotated, in a straight line. I'm having some difficulty coming up with the correct formula to use. I know it should probably involve sine and cosine, but I've tried various configurations and haven't been able to get something that works.
Here's a picture of my scene with the arrow and bow
rotateNumber is an integer like -1 (for 1 left rotation), 0 (no rotation), 1 (1 right rotation), etc.
rotateAngle is 10 degrees by default.
Here's the code to move the arrow:
if (arrowMoving) {
var rAngle = rotateAngle * rotateNumber;
var angleInRad = rAngle * (Math.PI/180);
var stepSize = 1/20;
arrowX += stepSize * Math.cos(angleInRad);
arrowY += stepSize * Math.sin(angleInRad);
DrawArrowTranslate(arrowX, arrowY);
requestAnimFrame(render);
} else {
DrawArrow();
arrowX = 0;
arrowY = 0;
}
Here's the code to draw and translate the arrow:
function DrawArrowTranslate(tx, ty) {
modelViewStack.push(modelViewMatrix);
/*
var s = scale4(0.3, -0.7, 1);
var t = translate(0, -4, 0);
*/
var s = scale4(0.3, -0.7, 1);
var t = translate(0, -5, 0);
var t2 = translate(0 + tx, 1 + ty, 0)
// rotate takes angle in degrees
var rAngle = rotateAngle;
var r = rotate(rAngle, 0, 0, 1);
var m = mult(t, r);
var m = mult(m, t2);
modelViewMatrix = mat4();
modelViewMatrix = mult(modelViewMatrix, m);
modelViewMatrix = mult(modelViewMatrix, s);
/*
// update bounding box
arrowBoundingBox.translate(0, -5);
arrowBoundingBox.rotate(rAngle);
arrowBoundingBox.translate(0, 1);
arrowBoundingBox.scale(0.3, -0.7);
*/
gl.uniformMatrix4fv(modelViewMatrixLoc, false, flatten(modelViewMatrix));
gl.drawArrays( gl.LINE_STRIP, 1833, 4);
gl.drawArrays( gl.LINE_STRIP, 1837, 4);
modelViewMatrix = modelViewStack.pop();
}
Your code looks quite correct, but you should eliminate the use of the rotateNumber. You can just use positive and negative angles for rotation instead, eliminating what I imagine is the cause of error here.
Sin and Cos can certainly handle angles of any magnitude positive, negative, or zero.
Good luck!
I figured out the problem. I was translating after rotating when I needed to translate before rotating as the rotation was messing up the translation.
I am using three.js to create a simple 3d vector environment. I am using lines to represent all 3 vector compontens x, y, z and a line for the final vector representation. Problem is that setting the width of a line is not working in Windows. The workaround that I try to implement is placing a cylinder onto the line (see red object in image below).
That is my current result:
As you see I am not able to rotate the cylinder to the correct position.
I faced the problem that the rotation center of the cylinder is in the middle of the object, so I moved the rotation point to the beginning of the cylinder. But still, rotation is not working correctly. I guess, the rotations around the axis influence each other.
Here is the code:
// VEKTOR
var vektor = {};
vektor._x = 2;
vektor._y = 1.5;
vektor._z = 1;
vektor._length = Math.sqrt(vektor._x*vektor._x + vektor._y*vektor._y + vektor._z*vektor._z);
// CYLINDER
var cyl_material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
// cylinder which is our line that represents the vector
var cyl_width = 0.025; // default line width
var cyl_height = vektor._length;
// THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
var cylGeometry = new THREE.CylinderGeometry(cyl_width, cyl_width, cyl_height, 20, 1, false);
// translate the cylinder geometry so that the desired point within the geometry is now at the origin
// https://stackoverflow.com/questions/12746011/three-js-how-do-i-rotate-a-cylinder-around-a-specific-point
cylGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, cyl_height/2, 0 ) );
var cylinder = new THREE.Mesh(cylGeometry, cyl_material);
updateCylinder();
scene.add( cylinder );
And the function updateCylinder trys to do the rotation.
function updateCylinder() {
// ... stuff, then:
cylinder.rotation.x = Math.atan2(vektor._z,vektor._y);
cylinder.rotation.y = 0.5*Math.PI+Math.atan2(vektor._x,vektor._z);
cylinder.rotation.z = Math.atan2(vektor._x,vektor._y);
}
Here is the current demo: http://www.matheretter.de/3d/vektoren/komponenten/
What am i doing wrong with the rotation? How to implement it so that the cylinder is following the vector line?
Thanks for your help.
If you want to transform a cylinder so that one end is at the origin and the other end points toward a specific point, here is the pattern you can follow:
First, transform your geometry so one end of the cylinder is at the origin, and the other end (the top) is on the positive z-axis.
var geometry = new THREE.CylinderGeometry( 0, 1, length, 8, 1, true );
geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, length / 2, 0 ) );
geometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
Then create your mesh, and call the lookAt() method:
var mesh = new THREE.Mesh( geometry, material );
mesh.lookAt( point );
three.js r.67
In this answer to my recent question, there is some code that draws a graph, but I can't manage to edit it into something that accepts any list of points as a parameter.
I'd like the Drawing method to accept these parameters:
List of Vector2, Point or VertexPositionColor, I can work with whichever.
Offset for the whole graph
These optional requirements would be appreciated:
Color that may override VertexPositionColor's color and apply to all points.
Size of the graph, so it can be shrunk or expanded, either as Vector2 as multiplier, or Point as target size. Maybe even combine this with offset in Rectangle.
And if it's possible, I'd like to have it all in a class, so graphs can be used separately from each other, each with its own Effect.world matrix, etc.
Here is that code (by Niko Drašković):
Matrix worldMatrix;
Matrix viewMatrix;
Matrix projectionMatrix;
BasicEffect basicEffect;
VertexPositionColor[] pointList;
short[] lineListIndices;
protected override void Initialize()
{
int n = 300;
//GeneratePoints generates a random graph, implementation irrelevant
pointList = new VertexPositionColor[n];
for (int i = 0; i < n; i++)
pointList[i] = new VertexPositionColor() { Position = new Vector3(i, (float)(Math.Sin((i / 15.0)) * height / 2.0 + height / 2.0 + minY), 0), Color = Color.Blue };
//links the points into a list
lineListIndices = new short[(n * 2) - 2];
for (int i = 0; i < n - 1; i++)
{
lineListIndices[i * 2] = (short)(i);
lineListIndices[(i * 2) + 1] = (short)(i + 1);
}
worldMatrix = Matrix.Identity;
viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
projectionMatrix = Matrix.CreateOrthographicOffCenter(0, (float)GraphicsDevice.Viewport.Width, (float)GraphicsDevice.Viewport.Height, 0, 1.0f, 1000.0f);
basicEffect = new BasicEffect(graphics.GraphicsDevice);
basicEffect.World = worldMatrix;
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
basicEffect.VertexColorEnabled = true; //important for color
base.Initialize();
}
And the drawing method:
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
PrimitiveType.LineList,
pointList,
0,
pointList.Length,
lineListIndices,
0,
pointList.Length - 1
);
}
The Graph class that does the requested can be found here.About 200 lines of code seemed too much to paste here.
The Graph is drawn by passing a list of floats (optionally with colors) to its Draw(..) method.
Graph properties are:
Vector2 Position - the bottom left corner of the graph
Point Size - the width (.X) and height (.Y) of the graph. Horizontally, values will be distributed to exactly fit the width. Vertically, all values will be scaled with Size.Y / MaxValue.
float MaxValue - the value which will be at the top of the graph. All off the chart values (greater than MaxValue) will be set to this value.
GraphType Type - with possible values GraphType.Line and GraphType.Fill, determines if the graph will be drawn line only, or bottom filled.
The graph is drawn with a line list / triangle strip.
I want to create a 3rd person camera similiar to example. The camera should stick behind the object and rotate if the rotation difference between camera and object is too high (maybe above ten percent).
This is my actual camera code:
var targetPosition = this.getTargetPosition();
var targetRotation = this.getTargetRotation();
var tmpQuaternion = new THREE.Quaternion();
tmpQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), 180 * (Math['PI'] / 180));
this.camera.quaternion = targetRotation;
this.camera.position = targetPosition;
this.camera.quaternion.multiplySelf(tmpQuaternion);
this.camera.quaternion.normalize();
this.camera.updateMatrix();
this.camera.translateZ(200);
this.camera.translateY(50);
But there are several problems right now. The camera quaternion should not set directly to the target rotation. But I dont know how to calculate the difference between camera quaternion and target quaternion and use maybe this if the distance is too high:
var qm = new THREE.Quaternion();
THREE.Quaternion.slerp(targetRotation, this.camera.quaternion, qm, time);
this.camera.quaternion = qm;
The second problem is the position itself. Currently I set camera position to the object position and translate it back to view behind, but the translation should be already in target position and the camera position should be translated to the target position.
Update 1: I made an example html: http://ssachtleben.github.com/CameraProblem/
Update 2: I made some progress now. Seems like I get quaternion difference with this function:
getAxisAngle = function(quaternion1, quaternion2) {
var tmpQuaternion = new THREE.Quaternion();
tmpQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), 180 * (Math['PI'] / 180));
var tmpRotation1 = quaternion1.clone();
tmpRotation1.multiplySelf(tmpQuaternion);
tmpRotation1.normalize();
var tmpRotation2 = quaternion2.clone();
if (tmpRotation2.w > 1) {
tmpRotation2.normalize();
}
var angle1 = 2 * Math['acos'](tmpRotation1.w);
var angle2 = 2 * Math['acos'](tmpRotation2.w);
var diff = angle1 > angle2 ? angle1 - angle2 : angle2 - angle1;
return diff;
};
But know I need to freeze the axis if the angle difference is too high. How can I do this?
Any help would be appreciated.
Ok finally the camera is fixed and works as excepted:
var targetPosition = this.getTargetPosition();
var targetRotation = this.getTargetRotation();
var tmpQuaternion = new THREE.Quaternion();
tmpQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), 180 * (Math['PI'] / 180));
targetRotation.multiplySelf(tmpQuaternion);
targetRotation.quaternion.normalize();
var qm = new THREE.Quaternion();
THREE.Quaternion.slerp(this.camera.quaternion, targetRotation, qm, 0.07);
this.camera.quaternion = qm;
this.camera.quaternion.normalize();