I'm really struggling here and I can't get it right, not even knowing why.
I'm using p5.js in WEBGL mode, I want to compute the position of on point rotated on the 3 axes around the origin in order to follow the translation and the rotation given to object through p5.js, translation and rotatation on X axis, Y axis and Z axis.
The fact is that drawing a sphere in 3d space, withing p5.js, is obtained by translating and rotating, since the sphere is created at the center in the origin, and there is no internal model giving the 3d-coordinates.
After hours of wandering through some math too high for my knowledge, I understood that the rotation over 3-axis is not as simple as I thought, and I ended up using Quaternion.js. But I'm still not able to match the visual position of the sphere in the 3d world with the coordinates I have computed out of the original point on the 2d-plane (150, 0, [0]).
For example, here the sphere is rotated on 3 axis. At the beginning the coordinates are good (if I ignore the fact that Z is negated) but at certain point it gets completely out of sync. The computed position of the sphere seems to be completely unrelated:
It's really hours that I'm trying to solve this issue, with no result, what did I miss?
Here it follows my code:
//font for WEBGL
var robotoFont;
var dotId = 0;
var rotating = true;
var orbits = [];
var dotsData = [];
function preload() {
robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
textFont(robotoFont);
background(0);
let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
orbit1.obj.push(new Dot(0, 0));
orbits.push(orbit1);
// let orbit2 = new Orbit(90, 45, 0);
// orbit2.obj.push(new Dot(0, 0));
// orbits.push(orbit2);
}
function draw() {
angleMode(DEGREES);
background(0);
orbitControl();
let len = 200;
fill('white');
stroke('white');
sphere(2);
stroke('red');
line(0, 0, 0, len, 0, 0);
text('x', len, 0)
stroke('green');
line(0, 0, 0, 0, len, 0);
text('y', 0, len)
push();
rotateX(90);
stroke('yellow');
line(0, 0, 0, 0, len, 0);
text('z', 0, len)
pop();
dotsData = [];
orbits.forEach(o => o.draw());
textSize(14);
push();
for (let i = 0; i < 2; i++) {
let yPos = -(windowHeight / 2) + 15;
for (let i = 0; i < dotsData.length; i++) {
let [id, pos, pos3d] = dotsData[i];
let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
yPos += 18;
}
rotateX(-90);
}
pop();
}
function mouseClicked() {
// controls.mousePressed();
}
function keyPressed() {
// controls.keyPressed(keyCode);
if (keyCode === 32) {
rotating = !rotating;
}
}
class Orbit {
constructor(x, y, z, xr, yr, zr) {
this.obj = [];
this.currentRot = [
x ? x : 0,
y ? y : 0,
z ? z : 0
]
this.rot = [
xr ? xr : 0,
yr ? yr : 0,
zr ? zr : 0
]
}
draw() {
push();
if (rotating) {
this.currentRot[0] += this.rot[0];
this.currentRot[1] += this.rot[1];
this.currentRot[2] += this.rot[2];
}
rotateY(this.currentRot[1]);
rotateX(this.currentRot[0]);
rotateZ(this.currentRot[2]);
noFill();
stroke('white');
ellipse(0, 0, 300, 300);
for (let i = 0; i < this.obj.length; i++) {
let o = this.obj[i];
o.draw();
dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
}
pop();
}
#get3DPos(o) {
let [x, y, z] = o.getPosition();
let w = 0;
let rotX = this.currentRot[0] * PI / 180;
let rotY = this.currentRot[1] * PI / 180;
let rotZ = this.currentRot[2] * PI / 180;
let rotation = Quaternion.fromEuler(rotZ, rotX, rotY, 'ZXY').conjugate();
[x, y, z] = rotation.rotateVector([x, y, z]);
return createVector(x, y, z);
}
}
class Dot {
constructor(angle) {
this.id = ++dotId;
this.x = cos(angle) * 150;
this.y = sin(angle) * 150;
}
draw() {
push();
fill('gray');
translate(this.x, this.y);
noStroke();
sphere(15);
pop();
}
getPosition() {
return [this.x, this.y, 0];
}
}
It doesn't work in stackoverflow because I need local asset like the font.
Here the working code: https://editor.p5js.org/cigno5/sketches/_ZVq0kjJL
I've finally sorted out. I can't really understand why works this way but I didn't need quaternion at all, and my first intuition of using matrix multiplications to apply rotation on 3-axis was correct.
What I did miss in first instance (and made my life miserable) is that matrix multiplication is not commutative. This means that applying rotation on x, y and z-axis is not equivalent to apply same rotation angle on z, y and x.
The working solution has been achieved with 3 simple steps:
Replace quaternion with matrix multiplications using vectors (method #resize2)
Rotating the drawing plane with Z-Y-X order
Doing the math of rotation in X-Y-Z order
//font for WEBGL
var robotoFont;
var dotId = 0;
var rotating = true;
var orbits = [];
var dotsData = [];
function preload() {
robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
textFont(robotoFont);
background(0);
let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
orbit1.obj.push(new Dot(0, 0.5));
orbits.push(orbit1);
// let orbit2 = new Orbit(90, 45, 0);
// orbit2.obj.push(new Dot(0, 0));
// orbits.push(orbit2);
}
function draw() {
angleMode(DEGREES);
background(0);
orbitControl();
let len = 200;
fill('white');
stroke('white');
sphere(2);
stroke('red');
line(0, 0, 0, len, 0, 0);
text('x', len, 0)
stroke('green');
line(0, 0, 0, 0, len, 0);
text('y', 0, len)
push();
rotateX(90);
stroke('yellow');
line(0, 0, 0, 0, len, 0);
text('z', 0, len)
pop();
dotsData = [];
orbits.forEach(o => o.draw());
textSize(14);
push();
for (let i = 0; i < 2; i++) {
let yPos = -(windowHeight / 2) + 15;
for (let i = 0; i < dotsData.length; i++) {
let [id, pos, pos3d] = dotsData[i];
let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
yPos += 18;
}
rotateX(-90);
}
pop();
}
function mouseClicked() {
// controls.mousePressed();
}
function keyPressed() {
// controls.keyPressed(keyCode);
if (keyCode === 32) {
rotating = !rotating;
}
}
class Orbit {
constructor(x, y, z, xr, yr, zr) {
this.obj = [];
this.currentRot = [
x ? x : 0,
y ? y : 0,
z ? z : 0
]
this.rot = [
xr ? xr : 0,
yr ? yr : 0,
zr ? zr : 0
]
}
draw() {
push();
if (rotating) {
this.currentRot[0] += this.rot[0];
this.currentRot[1] += this.rot[1];
this.currentRot[2] += this.rot[2];
}
rotateZ(this.currentRot[2]);
rotateY(this.currentRot[1]);
rotateX(this.currentRot[0]);
noFill();
stroke('white');
ellipse(0, 0, 300, 300);
for (let i = 0; i < this.obj.length; i++) {
let o = this.obj[i];
o.draw();
dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
}
pop();
}
#get3DPos(o) {
let [x, y, z] = o.getPosition();
let pos = createVector(x, y, z);
pos = this.#rotate2(pos, createVector(1, 0, 0), this.currentRot[0]);
pos = this.#rotate2(pos, createVector(0, 1, 0), this.currentRot[1]);
pos = this.#rotate2(pos, createVector(0, 0, 1), this.currentRot[2]);
return pos;
}
//https://stackoverflow.com/questions/67458592/how-would-i-rotate-a-vector-in-3d-space-p5-js
#rotate2(vect, axis, angle) {
// Make sure our axis is a unit vector
axis = p5.Vector.normalize(axis);
return p5.Vector.add(
p5.Vector.mult(vect, cos(angle)),
p5.Vector.add(
p5.Vector.mult(
p5.Vector.cross(axis, vect),
sin(angle)
),
p5.Vector.mult(
p5.Vector.mult(
axis,
p5.Vector.dot(axis, vect)
),
(1 - cos(angle))
)
)
);
}
}
class Dot {
constructor(angle, speed) {
this.id = ++dotId;
this.angle = angle;
this.speed = speed
}
draw() {
this.angle += this.speed;
this.x = cos(this.angle) * 150;
this.y = sin(this.angle) * 150;
push();
fill('gray');
translate(this.x, this.y);
noStroke();
sphere(15);
pop();
}
getPosition() {
return [this.x, this.y, 0];
}
}
And now it works like a charm:
https://editor.p5js.org/cigno5/sketches/PqB9CEnBp
I just know how to use for to draw a tree (the tree data is the picture one, the result is picture two), but now I want to use recursion to draw the tree.
Please tell me how change writing style from for to recursive
first input point
//input point
const line_point =[0, 0, 0,
2, 151, 2,
2, 151, 2,
-62, 283, 63,
2, 151, 2,
62, 297, -58,
-62, 283, 63,
-104, 334, 74,
-62, 283, 63,
-58, 338, 45,
62, 297, -58,
67, 403, -55,
62, 297, -58,
105, 365, -86];
take out star point and end point
const star_line_x= new Array();
const star_line_y= new Array();
const star_line_z= new Array();
const end_line_x= new Array();
const end_line_y= new Array();
const end_line_z= new Array();
for (var q=0; q < line_point.length; q+=6){
star_line_x.push(line_point[q]);
}
for (var r=1; r < line_point.length; r+=6){
star_line_y.push(line_point[r]);
}
for (var s=2; s < line_point.length; s+=6){
star_line_z.push(line_point[s]);
}
for (var t=3; t < line_point.length; t+=6){
end_line_x.push(line_point[t]);
}
for (var u=4; u < line_point.length; u+=6){
end_line_y.push(line_point[u]);
}
for (var v=5; v < line_point.length; v+=6){
end_line_z.push(line_point[v]);
}
var cylinder_star_point = new Array();
var cylinder_end_point = new Array();
//star_point end_point
for (var w=0; w < line_point.length/6; w++){
var star_point = new THREE.Vector3 (star_line_x[w],star_line_y[w],star_line_z[w]);
var end_point = new THREE.Vector3 (end_line_x[w],end_line_y[w],end_line_z[w]);
cylinder_star_point.push( star_point);
cylinder_end_point.push( end_point);
}
calculation cylinder high
//calculation cylinder high
var line_len = new Array();
for (var dd=0; dd < line_point.length/6; dd++){
var len_x = Math.pow(end_line_x[dd]-star_line_x[dd],2);
var len_y = Math.pow(end_line_y[dd]-star_line_y[dd],2);
var len_z = Math.pow(end_line_z[dd]-star_line_z[dd],2);
var len_direction = Math.sqrt(len_x+len_y+len_z);
line_len.push(len_direction);//Cylinder high
}
calculation center point
//center_point
const cylinder_center_point= new Array();
for (var bb=0; bb< cylinder_end_point.length; bb++){
var star_set_point = cylinder_star_point[bb];
var end_set_point = cylinder_end_point[bb];
var center_point = end_set_point.clone().add(star_set_point).divideScalar(2);
cylinder_center_point.push(center_point);
}
calculation cylinder direction vector
//cylinder direction
const cylinder_direction= new Array();
for (var cc=0; cc < cylinder_end_point.length; cc++){
var star_direction = cylinder_star_point[cc];
var end_direction = cylinder_end_point[cc];
var center_direction = end_direction.clone().sub(star_direction);
cylinder_direction.push(center_direction);
}
draw cylinder
for (var dd=0; dd <cylinder_direction.length;dd++){
var material = new THREE.MeshPhongMaterial({color:'#ff0000'});
let upVector = new THREE.Vector3(0, 1, 0);
var geometry = new THREE.CylinderGeometry(5, 5, line_len[dd], 20, 1, false);
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, line_len[dd]/2, 0);
var group = new THREE.Group();
group.position.set(star_line_x[dd],star_line_y[dd],star_line_z[dd]);
group.add(mesh);
let targetVector =cylinder_direction[dd];
let quaternion = new THREE.Quaternion().setFromUnitVectors(upVector, targetVector.normalize());
group.setRotationFromQuaternion(quaternion)
scene.add(group)
}
picture two: use for to draw the tree
For a tree the simplest method is to start with just a tree depth and assume 2 children. The function makes one branch and if depth > 0 then it recursively calls itself to make 2 more branches.
const numBranches = 2;
const spread = 1.5;
const branchShrinkFactor = 0.8;
const branchSpreadFactor = 0.8;
function addBranch(parent, depth, offset, angle, branchLength, spread) {
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderBufferGeometry(5, 5, branchLength, 20, 1, false);
geometry.translate(0, branchLength / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
if (depth > 1) {
for (let i = 0; i < numBranches; ++i) {
const a = i / (numBranches - 1) - 0.5;
addBranch(mesh, depth - 1, branchLength, a * spread, branchLength * branchShrinkFactor, spread * branchSpreadFactor)
}
}
}
addBranch(scene, 5, 0, 0, 100, 1.5);
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 150, 300);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const numBranches = 2;
const spread = 1.5;
const branchShrinkFactor = 0.8;
const branchSpreadFactor = 0.8;
function addBranch(parent, depth, offset, angle, branchLength, spread) {
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderBufferGeometry(5, 5, branchLength, 20, 1, false);
geometry.translate(0, branchLength / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
if (depth > 1) {
for (let i = 0; i < numBranches; ++i) {
const a = i / (numBranches - 1) - 0.5;
addBranch(mesh, depth - 1, branchLength, a * spread, branchLength * branchShrinkFactor, spread * branchSpreadFactor)
}
}
}
addBranch(scene, 5, 0, 0, 100, 1.5);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
If you want specific data for each branch then you need to pass that in. For example
const tree = [
{ length: 100, angle: 0, branches: 2 }, // root
{ length: 40, angle: -1, branches: 3 }, // first branch
{ length: 50, angle: 0.8, branches: 0 }, // 1st child branch
{ length: 40, angle: 0.3, branches: 0 }, // 2nd child branch
{ length: 30, angle: -0.3, branches: 0 }, // 3rd child branch
{ length: 50, angle: 0.8, branches: 2 }, // second branch
{ length: 50, angle: 0.5, branches: 0 }, // 1st child branch
{ length: 40, angle: -0.6, branches: 2 }, // 2nd child branch
{ length: 40, angle: -0.3, branches: 0 }, // 1st grandchild branch
{ length: 95, angle: 0.3, branches: 0 }, // 2st grandchild branch
];
and then walk the tree description, if a branches for a particular branch is > 0 then it recursively calls itself to add those branches. Each branches consumes a row in the array of branches so we pass back ndx so we can tell how many rows were consumed.
function addBranch(parent, offset, tree, ndx = 0) {
const {length, angle, branches} = tree[ndx];
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
for (let i = 0; i < branches; ++i) {
ndx = addBranch(mesh, length, tree, ++ndx);
}
return ndx;
}
addBranch(scene, 0, tree);
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 150, 300);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const tree = [
{ length: 100, angle: 0, branches: 2 }, // root
{ length: 40, angle: -1, branches: 3 }, // first branch
{ length: 50, angle: 0.8, branches: 0 }, // 1st child branch
{ length: 40, angle: 0.3, branches: 0 }, // 2nd child branch
{ length: 30, angle: -0.3, branches: 0 }, // 3rd child branch
{ length: 50, angle: 0.8, branches: 2 }, // second branch
{ length: 50, angle: 0.5, branches: 0 }, // 1st child branch
{ length: 40, angle: -0.6, branches: 2 }, // 2nd child branch
{ length: 40, angle: -0.3, branches: 0 }, // 1st grandchild branch
{ length: 95, angle: 0.3, branches: 0 }, // 2st grandchild branch
];
function addBranch(parent, offset, tree, ndx = 0) {
const {length, angle, branches} = tree[ndx];
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.y = offset;
mesh.rotation.z = angle;
parent.add(mesh);
for (let i = 0; i < branches; ++i) {
ndx = addBranch(mesh, length, tree, ++ndx);
}
return ndx;
}
addBranch(scene, 0, tree);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
It's not clear to me what your input data is. Your tree has a depth of 3 and 2 branches per level so this data would work
const endPoints = [
[ 0, 0, 0], // A
[ 2, 151, 2], // B
[ -62, 283, 63], // C
[-104, 334, 74], // E
[ -58, 338, 45], // F
[ 62, 296, -58], // D
[ 67, 403, -55], // G
[ 105, 365, -86], // H
];
using this code
// assumes there are 2 branches per
function addBranch(parent, depth, offset, tree, parentNdx = 0, childNdx = 1) {
const start = tree[parentNdx];
const end = tree[childNdx];
const length = start.distanceTo(end);
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
let ndx = childNdx + 1;
if (depth > 1) {
const numBranches = 2;
for (let i = 0; i < numBranches; ++i) {
ndx = addBranch(mesh, depth - 1, length, tree, childNdx, ndx);
}
}
return ndx;
}
addBranch(scene, 3, 0, tree);
I pointed the cylinders in the positive Z direction which means I can use lookAt to point the cylinder from its start to its end point.
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(250, 170, 250);
camera.lookAt(0, 170, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const tree = [
[ 0, 0, 0], // A
[ 2, 151, 2], // B
[ -62, 283, 63], // C
[-104, 334, 74], // E
[ -58, 338, 45], // F
[ 62, 296, -58], // D
[ 67, 403, -55], // G
[ 105, 365, -86], // H
].map(v => new THREE.Vector3().fromArray(v));
// assumes there are 2 branches per
function addBranch(parent, depth, offset, tree, parentNdx = 0, childNdx = 1) {
const start = tree[parentNdx];
const end = tree[childNdx];
const length = start.distanceTo(end);
const material = new THREE.MeshPhongMaterial({color:'#ff0000'});
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
let ndx = childNdx + 1;
if (depth > 1) {
const numBranches = 2;
for (let i = 0; i < numBranches; ++i) {
ndx = addBranch(mesh, depth - 1, length, tree, childNdx, ndx);
}
}
return ndx;
}
addBranch(scene, 3, 0, tree);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
note: this only one of infinite ways to create the tree recursively. Rather than an array in depth first order you could also create a tree structure to pass into the algorithm
const E = {
pos: [-104, 334, 74],
};
const F = {
pos: [ -58, 338, 45],
};
const C = {
pos: [ -62, 283, 63],
children: [E, F],
};
const G = {
pos: [ 67, 403, -55],
};
const H = {
pos: [ 105, 365, -86],
};
const D = {
pos: [ 62, 296, -58],
children: [G, H],
};
const B = {
pos: [ 2, 151, 2],
children: [C, D],
};
const A = {
pos: [0, 0, 0],
children: [B],
};
function addBranch(parent, branch, offset = 0) {
const {pos, children} = branch;
const start = new THREE.Vector3().fromArray(pos);
for (const child of children) {
const end = new THREE.Vector3().fromArray(child.pos);
const length = start.distanceTo(end);
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const material = new THREE.MeshPhongMaterial({color: 'red'});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
if (child.children) {
addBranch(mesh, child, length);
}
}
}
addBranch(scene, A);
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 1;
const far = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(250, 170, 250);
camera.lookAt(0, 170, 0);
const scene = new THREE.Scene();
scene.background = new THREE.Color('lightskyblue');
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const E = {
pos: [-104, 334, 74],
};
const F = {
pos: [ -58, 338, 45],
};
const C = {
pos: [ -62, 283, 63],
children: [E, F],
};
const G = {
pos: [ 67, 403, -55],
};
const H = {
pos: [ 105, 365, -86],
};
const D = {
pos: [ 62, 296, -58],
children: [G, H],
};
const B = {
pos: [ 2, 151, 2],
children: [C, D],
};
const A = {
pos: [0, 0, 0],
children: [B],
};
function addBranch(parent, branch, offset = 0) {
const {pos, children} = branch;
const start = new THREE.Vector3().fromArray(pos);
for (const child of children) {
const end = new THREE.Vector3().fromArray(child.pos);
const length = start.distanceTo(end);
const geometry = new THREE.CylinderGeometry(5, 5, length, 20, 1, false);
geometry.translate(0, length / 2, 0);
geometry.rotateX(Math.PI / 2);
const material = new THREE.MeshPhongMaterial({color: 'red'});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = offset;
parent.add(mesh);
mesh.lookAt(end);
if (child.children) {
addBranch(mesh, child, length);
}
}
}
addBranch(scene, A);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render(time) {
time *= 0.001;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
// requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
I have this part of code
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'getNewUsers.php',false);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send();
var json;
if (xhr.readyState == 4 && xhr.status == 200) { // If file is loaded correctly.
json = JSON.parse(xhr.responseText);
alert(json.en);
}
else if(xhr.readyState == 4 && xhr.status != 200) { // En cas d'erreur !
alert( ' Une erreur est survenue !\n\nCode :' + xhr.status + '\nTexte : ' + xhr.statusText);
}
</script>
<script id="bs">
function Sector(x, y, radius, startAngle, endAngle) {
SpecialAttrPath.call(this, {
radius: 0,
startAngle: startAngle,
endAngle: endAngle
});
this.attr({
x: x,
y: y,
radius: radius,
startAngle: startAngle,
endAngle: endAngle
});
}
Sector.prototype = Object.create(SpecialAttrPath.prototype);
Sector.prototype._make = function() {
var attr = this._attributes,
radius = attr.radius,
startAngle = attr.startAngle,
endAngle = attr.endAngle;
var startX, startY, endX, endY;
var diffAngle = Math.abs(endAngle - startAngle);
this.startX = startX = radius * Math.cos(startAngle);
this.startY = startY = radius * Math.sin(startAngle);
if (diffAngle < Math.PI*2) {
endX = radius * Math.cos(endAngle);
endY = radius * Math.sin(endAngle);
} else { // angles differ by more than 2*PI: draw a full circle
endX = startX;
endY = startY - .0001;
}
this.endX = endX;
this.endY = endY;
this.radiusExtentX = radius * Math.cos(startAngle + (endAngle - startAngle)/2);
this.radiusExtentY = radius * Math.sin(startAngle + (endAngle - startAngle)/2);
return this.moveTo(0, 0)
.lineTo(startX, startY)
.arcTo(radius, radius, 0, (diffAngle < Math.PI) ? 0 : 1, 1, endX, endY)
.lineTo(0, 0);
};
Sector.prototype.getDimensions = function() {
var x = this.attr('x'),
y = this.attr('y'),
left = Math.min(x, x + this.startX, x + this.endX, x + this.radiusExtentX),
top = Math.min(y, y + this.startY, y + this.endY, y + this.radiusExtentY),
right = Math.max(x, x + this.startX, x + this.endX, x + this.radiusExtentX),
bottom = Math.max(y, y + this.startY, y + this.endY, y + this.radiusExtentY);
console.log(y, y + this.startY, y + this.endY, y + this.radiusExtentY)
return {
left: left,
top: top,
width: right - left,
height: bottom - top
};
};
PieChart.BASE_COLOR = color('red');
function PieChart(data) {
this.angle = 0;
this.labelY = 30;
this.kolor = PieChart.BASE_COLOR.clone();
var n = 0;
for (var i in data) {
this.slice(i, data[i], n++);
}
}
PieChart.prototype = {
slice: function(name, value, i) {
var start = this.angle,
end = start + (Math.PI*2) * value/100,
// Increase hue by .1 with each slice (max of 10 will work)
kolor = this.kolor = this.kolor.clone().hue(this.kolor.hue()+.1);
var s = new Sector(
400, 200, 150,
start,
end
);
var animDelay = (i * 200) + 'ms';
var label = this.label(name, value, kolor);
label.attr({ opacity: 0 });
s.stroke('#FFF', 3);
s.fill(kolor);
s.attr({
endAngle: start,
radius: 0
}).addTo(stage).on('mouseover', over).on('mouseout', out);
label.on('mouseover', over).on('mouseout', out);
function over() {
label.text.attr('fontWeight', 'bold');
label.animate('.2s', {
x: 40
});
s.animate('.2s', {
radius: 170,
fillColor: kolor.lighter(.1)
}, {
easing: 'sineOut'
});
}
function out() {
label.text.attr('fontWeight', '');
label.animate('.2s', {
x: 30
});
s.animate('.2s', {
radius: 150,
fillColor: kolor
});
}
s.animate('.4s', {
radius: 150,
startAngle: start,
endAngle: end
}, {
easing: 'sineOut',
delay: animDelay
});
label.animate('.4s', {
opacity: 1
}, { delay: animDelay });
this.angle = end;
},
label: function(name, v, fill) {
var g = new Group().attr({
x: 30,
y: this.labelY,
cursor: 'pointer'
});
var t = new Text(name + ' (' + v + '%)').addTo(g);
var r = new Rect(0, 0, 20, 20, 5).fill(fill).addTo(g);
t.attr({
x: 30,
y: 17,
textFillColor: 'black',
fontFamily: 'Arial',
fontSize: '14'
});
g.addTo(stage);
this.labelY += 30;
g.text = t;
return g;
}
};
new PieChart({
English: json.en,
French: 20,
German: 30,
Dutch: 5,
Spanish: 19,
Others: 18
})
</script>
The problem is I would like to change the pie dynamically using Json, in the demi it is shown with integers but here it is also an integer. This is the line that cause the problem for rendering the pie.
new PieChart({
English: json.en,
BonsaiJS is executed in a separate execution environment (mostly worker) and because of that it can't reach objects that were defined outside (in your case the json variable) of the BonsaiJS movie code (in your example <script id="bs">).
You can read about the special execution of BonsaiJS here: http://docs.bonsaijs.org/overview/Execution.html. If you want to pass data from your page to the Bonsai movie execution you can do the following:
Dynamically communicate through sendMessage with the execution context (described here: http://docs.bonsaijs.org/overview/Communication.html)
If you just have to pass data into your context once you can do that through bonsai.run(myNode, {myJson: json}); and access it from within your movie code through stage.options.myJson (documented on the bottom of http://docs.bonsaijs.org/overview/Execution.html).
You also have the third option to move the XMLHttpRequest code into the movie-code and do the request from there. Every client-side bonsai execution context (worker, iframe) does support that.