I have a function that creates a simple THREE.PlaneGeometry and using an image texture material maps a piece of the image onto the plane. What I am trying to figure out is how to convert this logic from using a THREE.PlaneGeometry to a THREE.PlaneBufferGeometry, but I cannot figure out how to access the vertices & alter in the same way.
I apologize if this is a repeat question. I did search around & if one existed, I could not find it.
Here is the function & below it is a sample call for it:
/**
* options:
* imageSize: { w: #, h: # } - size of the source image
* planeSize: { w: #, h:# } - size of the actual plane to create
* position: { x: #, y:#, z: # } - position of the next plane
* material: material - the material to apply
* clipRect: { x: #, y:#, w: #, h:# } - the part of the image to clip
* x and y from the bottom left of the image.
* w and h width and height of the image region,
* name: name of the mesh
*/
function createPlane( opts ) {
var i, faces, v, vertexes, point, p,
plane = new THREE.PlaneGeometry( opts.planeSize.w, opts.planeSize.h, 1, 1 ),
mesh = new THREE.Mesh(plane, opts.material),
imgRect = {
x: opts.clipRect.x / opts.imageSize.w,
y: opts.clipRect.y / opts.imageSize.h,
w: opts.clipRect.w / opts.imageSize.w,
h: opts.clipRect.h / opts.imageSize.h
};
if (opts.name !== '') {
mesh.name = opts.name;
}
if (opts.position !== null) {
mesh.position.set(
opts.position.x,
opts.position.y,
opts.position.z
);
}
for( i = 0; i < plane.faceVertexUvs.length; i++) {
faces = plane.faceVertexUvs[i];
for( v = 0; v < faces.length; v++) {
vertexes = faces[v];
for( p = 0; p < vertexes.length; p ++ ) {
point = vertexes[p];
point.x = imgRect.x + ( point.x * imgRect.w );
point.y = imgRect.y + ( point.y * imgRect.h );
}
}
}
return mesh;
}
Here is a sample call:
var plane = createPlane({
imageSize: { w: 1024, h: 1024},
planeSize: { w: 23, h: 31 },
position: null,
material: new THREE.MeshBasicMaterial({
map: myTexture, // defined elsewhere
transparent: true
}),
name: 'myPlane',
clipRect: { x: 958, y: 226, w: 23, h: 31 }
});
You can set them directly like this:
object.geometry.attributes.uv.array[0] = 0.1;
Another way (less likely to break in the future I guess):
var quad_uvs =
[
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0
];
var uvs = new Float32Array( quad_uvs);
object.geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
Related
I'm looking for a very basic implementation of the Z-buffer, ideally in JS. I am trying to take a look at a very simple code, for example two polygons overlapping, one hiding the other.
I can't find such basic example, while I can find a couple of "well-above my current level and understanding" samples.
Is there a resource you could recommend to get started?
Thank you for your help and recommendations!
A Z-Buffer(also known as depth buffer) is nothing more than a 2D pixel array(think image). Instead of RGB it only stores a single value in each pixel, the distance from the current viewpoint:
// one value for each pixel in our screen
const depthBuffer = new Array(screenWidth * screenHeight);
It augments the color buffer that contains the actual image you present to the user:
// create buffer for color output
const numChannels = 3; // R G B
const colorBuffer = new Array(screenWidth * screenHeight * numChannels);
For every pixel of a shape you draw you check the Z-Buffer to see if there's anything closer to the camera that occludes the current pixel, if so you don't draw it. This way you can draw things in any order and they're still properly occluded on a per pixel level.
Z-Buffering may not only be used in 3D but also in 2D to achieve draw order-independence. Lets say we want to draw a few boxes, this will be our box class:
class Box {
/** #member {Object} position of the box storing x,y,z coordinates */
position;
/** #member {Object} size of the box storing width and height */
size;
/** #member {Object} color of the box given in RGB */
color;
constructor (props) {
this.position = props.position;
this.size = props.size;
this.color = props.color;
}
/**
* Check if given point is in box
* #param {Number} px coordinate of the point
* #param {Number} py coordinate of the point
* #return {Boolean} point in box
*/
pointInBox (px,py) {
return this.position.x < px && this.position.x + this.size.width > px
&& this.position.y < py && this.position.y + this.size.height > py;
}
}
With this class we can now create a few boxes and draw them:
const boxes = [
new Box({
position: { x: 50, y: 50, z: 10 },
size: { width: 50, height: 20 },
color: { r: 255, g: 0, b:0 }
}),
// green box
new Box({
position: { x: 80, y: 30, z: 5 },
size: { width: 10, height: 50 },
color: { r: 0, g: 255, b:0 }
}),
// blue
new Box({
position: { x: 60, y: 55, z: 8 },
size: { width: 50, height: 10 },
color: { r: 0, g: 0, b: 255 }
})
];
With our shapes specified we can now draw them:
for(const box of boxes) {
for(let x = 0; x < screenWidth; x++) {
for(let y = 0; y < screenHeight; y++) {
// check if our pixel is within the box
if (box.pointInBox(x,y)) {
// check if this pixel of our box is covered by something else
// compare depth value in depthbuffer against box position
// this is commonly referred to as "depth-test"
if (depthBuffer[x + y * screenWidth] < box.position.z) {
// something is already closer to the viewpoint than our current primitive, don't draw this pixel:
continue;
}
// we passed the depth test, put our current depth value in the z-buffer
depthBuffer[x + y * screenWidth] = box.position.z;
// put the color in the color buffer, channel by channel
colorBuffer[(x + y * screenWidth)*numChannels + 0] = box.color.r;
colorBuffer[(x + y * screenWidth)*numChannels + 1] = box.color.g;
colorBuffer[(x + y * screenWidth)*numChannels + 2] = box.color.b;
}
}
}
}
Note that this code is exemplary so it's overly verbose and inefficient for the sake of laying out the concept.
const ctx = document.getElementById("output").getContext('2d');
const screenWidth = 200;
const screenHeight = 200;
// one value for each pixel in our screen
const depthBuffer = new Array(screenWidth * screenHeight);
// create buffer for color output
const numChannels = 3; // R G B
const colorBuffer = new Array(screenWidth * screenHeight * numChannels);
/**
* Represents a 2D box
* #class
*/
class Box {
/** #member {Object} position of the box storing x,y,z coordinates */
position;
/** #member {Object} size of the box storing width and height */
size;
/** #member {Object} color of the box given in RGB */
color;
constructor (props) {
this.position = props.position;
this.size = props.size;
this.color = props.color;
}
/**
* Check if given point is in box
* #param {Number} px coordinate of the point
* #param {Number} py coordinate of the point
* #return {Boolean} point in box
*/
pointInBox (px,py) {
return this.position.x < px && this.position.x + this.size.width > px
&& this.position.y < py && this.position.y + this.size.height > py;
}
}
const boxes = [
// red box
new Box({
position: { x: 50, y: 50, z: 10 },
size: { width: 150, height: 50 },
color: { r: 255, g: 0, b:0 }
}),
// green box
new Box({
position: { x: 80, y: 30, z: 5 },
size: { width: 10, height: 150 },
color: { r: 0, g: 255, b:0 }
}),
// blue
new Box({
position: { x: 70, y: 70, z: 8 },
size: { width: 50, height: 40 },
color: { r: 0, g: 0, b: 255 }
})
];
const varyZ = document.getElementById('varyz');
varyZ.onchange = draw;
function draw () {
// clear depth buffer of previous frame
depthBuffer.fill(10);
for(const box of boxes) {
for(let x = 0; x < screenWidth; x++) {
for(let y = 0; y < screenHeight; y++) {
// check if our pixel is within the box
if (box.pointInBox(x,y)) {
// check if this pixel of our box is covered by something else
// compare depth value in depthbuffer against box position
if (depthBuffer[x + y * screenWidth] < box.position.z) {
// something is already closer to the viewpoint that our current primitive, don't draw this pixel:
if (!varyZ.checked) continue;
if (depthBuffer[x + y * screenWidth] < box.position.z + Math.sin((x+y))*Math.cos(x)*5) continue;
}
// we passed the depth test, put our current depth value in the z-buffer
depthBuffer[x + y * screenWidth] = box.position.z;
// put the color in the color buffer, channel by channel
colorBuffer[(x + y * screenWidth)*numChannels + 0] = box.color.r;
colorBuffer[(x + y * screenWidth)*numChannels + 1] = box.color.g;
colorBuffer[(x + y * screenWidth)*numChannels + 2] = box.color.b;
}
}
}
}
// convert to rgba for presentation
const oBuffer = new Uint8ClampedArray(screenWidth*screenHeight*4);
for (let i=0,o=0; i < colorBuffer.length; i+=3,o+=4) {
oBuffer[o]=colorBuffer[i];
oBuffer[o+1]=colorBuffer[i+1];
oBuffer[o+2]=colorBuffer[i+2];
oBuffer[o+3]=255;
}
ctx.putImageData(new ImageData(oBuffer, screenWidth, screenHeight),0,0);
}
document.getElementById('redz').oninput = e=>{boxes[0].position.z=parseInt(e.target.value,10);draw()};
document.getElementById('greenz').oninput = e=>{boxes[1].position.z=parseInt(e.target.value,10);draw()};
document.getElementById('bluez').oninput = e=>{boxes[2].position.z=parseInt(e.target.value,10);draw()};
draw();
canvas {
border:1px solid black;
float:left;
margin-right: 2rem;
}
label {display:block;}
label span {
display:inline-block;
width: 100px;
}
<canvas width="200" height="200" id="output"></canvas>
<label><span>Red Z</span>
<input type="range" min="0" max="10" value="10" id="redz"/>
</label>
<label><span>Green Z</span>
<input type="range" min="0" max="10" value="5" id="greenz"/>
</label>
<label><span>Blue Z</span>
<input type="range" min="0" max="10" value="8" id="bluez"/>
</label>
<label><span>Vary Z Per Pixel</span>
<input type="checkbox" id="varyz"/>
</label>
First Note: They wont let me embed images until i have more reputation points (sorry), but all the links are images posted on imgur! :) thanks
I have replicated a method to animate any single path (1 closed path) using fourier transforms. This creates an animation of epicylces (rotating circles) which rotate around each other, and follow the imputed points, tracing the path as a continuous loop/function.
I would like to adopt this system to 3D. the two methods i can think of to achieve this is to use a Spherical Coordinate system (two complex planes) or 3 Epicycles --> one for each axis (x,y,z) with their individual parametric equations. This is probably the best way to start!!
2 Cycles, One for X and one for Y:
Picture: One Cycle --> Complex Numbers --> For X and Y
Fourier Transformation Background!!!:
• Eulers formula allows us to decompose each point in the complex plane into an angle (the argument to the exponential function) and an amplitude (Cn coefficients)
• In this sense, there is a connection to imaging each term in the infinite series above as representing a point on a circle with radius cn, offset by 2πnt/T radians
• The image below shows how a sum of complex numbers in terms of phases/amplitudes can be visualized as a set of concatenated cirlces in the complex plane. Each red line is a vector representing a term in the sequence of sums: cne2πi(nT)t
• Adding the summands corresponds to simply concatenating each of these red vectors in complex space:
Animated Rotating Circles:
Circles to Animated Drawings:
• If you have a line drawing in 2D (x-y) space, you can describe this path mathematically as a parametric function. (two separate single variable functions, both in terms of an auxiliary variable (T in this case):
• For example, below is a simple line drawing of a horse, and a parametric path through the black pixels in image, and that path then seperated into its X and Y components:
• At this point, we need to calculate the Fourier approximations of these two paths, and use coefficients from this approximation to determine the phase and amplitudes of the circles needed for the final visualization.
Python Code:
The python code used for this example can be found here on guithub
I have successful animated this process in 2D, but i would like to adopt this to 3D.
The Following Code Represents Animations in 2D --> something I already have working:
[Using JavaScript & P5.js library]
The Fourier Algorithm (fourier.js):
// a + bi
class Complex {
constructor(a, b) {
this.re = a;
this.im = b;
}
add(c) {
this.re += c.re;
this.im += c.im;
}
mult(c) {
const re = this.re * c.re - this.im * c.im;
const im = this.re * c.im + this.im * c.re;
return new Complex(re, im);
}
}
function dft(x) {
const X = [];
const Values = [];
const N = x.length;
for (let k = 0; k < N; k++) {
let sum = new Complex(0, 0);
for (let n = 0; n < N; n++) {
const phi = (TWO_PI * k * n) / N;
const c = new Complex(cos(phi), -sin(phi));
sum.add(x[n].mult(c));
}
sum.re = sum.re / N;
sum.im = sum.im / N;
let freq = k;
let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
let phase = atan2(sum.im, sum.re);
X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
Values[k] = {phase};
console.log(Values[k]);
}
return X;
}
The Sketch Function/ Animations (Sketch.js):
let x = [];
let fourierX;
let time = 0;
let path = [];
function setup() {
createCanvas(800, 600);
const skip = 1;
for (let i = 0; i < drawing.length; i += skip) {
const c = new Complex(drawing[i].x, drawing[i].y);
x.push(c);
}
fourierX = dft(x);
fourierX.sort((a, b) => b.amp - a.amp);
}
function epicycles(x, y, rotation, fourier) {
for (let i = 0; i < fourier.length; i++) {
let prevx = x;
let prevy = y;
let freq = fourier[i].freq;
let radius = fourier[i].amp;
let phase = fourier[i].phase;
x += radius * cos(freq * time + phase + rotation);
y += radius * sin(freq * time + phase + rotation);
stroke(255, 100);
noFill();
ellipse(prevx, prevy, radius * 2);
stroke(255);
line(prevx, prevy, x, y);
}
return createVector(x, y);
}
function draw() {
background(0);
let v = epicycles(width / 2, height / 2, 0, fourierX);
path.unshift(v);
beginShape();
noFill();
for (let i = 0; i < path.length; i++) {
vertex(path[i].x, path[i].y);
}
endShape();
const dt = TWO_PI / fourierX.length;
time += dt;
And Most Importantly! THE PATH / COORDINATES:
(this one is a triangle)
let drawing = [
{ y: -8.001009734 , x: -50 },
{ y: -7.680969345 , x: -49 },
{ y: -7.360928956 , x: -48 },
{ y: -7.040888566 , x: -47 },
{ y: -6.720848177 , x: -46 },
{ y: -6.400807788 , x: -45 },
{ y: -6.080767398 , x: -44 },
{ y: -5.760727009 , x: -43 },
{ y: -5.440686619 , x: -42 },
{ y: -5.12064623 , x: -41 },
{ y: -4.800605841 , x: -40 },
...
...
{ y: -8.001009734 , x: -47 },
{ y: -8.001009734 , x: -48 },
{ y: -8.001009734 , x: -49 },
];
This answer is in response to: "Do you think [three.js] can replicate what i have in 2D but in 3D? with the rotating circles and stuff?"
Am not sure whether you're looking to learn 3D modeling from scratch (ie, creating your own library of vector routines, homogeneous coordinate transformations, rendering perspective, etc) or whether you're simply looking to produce a final product. In the case of the latter, three.js is a powerful graphics library built on webGL that in my estimation is simple enough for a beginner to dabble with, but has a lot of depth to produce very sophisticated 3D effects. (Peruse the examples at https://threejs.org/examples/ and you'll see for yourself.)
I happen to be working a three.js project of my own, and whipped up a quick example of epicyclic circles as a warm up exercise. This involved pulling pieces and parts from the following references...
https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene
https://threejs.org/examples/#misc_controls_orbit
https://threejs.org/examples/#webgl_geometry_shapes (This three.js example is a great resource showing a variety of ways that a shape can be rendered.)
The result is a simple scene with one circle running around the other, permitting mouse controls to orbit around the scene, viewing it from different angles and distances.
<html>
<head>
<title>Epicyclic Circles</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script>
<script>
// Set up the basic scene, camera, and lights.
var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
scene.add(camera)
var light = new THREE.PointLight( 0xffffff, 0.8 );
camera.add( light );
camera.position.z = 50;
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// Add the orbit controls to permit viewing the scene from different angles via the mouse.
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.minDistance = 0;
controls.maxDistance = 500;
// Create center and epicyclic circles, extruding them to give them some depth.
var extrudeSettings = { depth: 2, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: .25, bevelThickness: .25 };
var arcShape1 = new THREE.Shape();
arcShape1.moveTo( 0, 0 );
arcShape1.absarc( 0, 0, 15, 0, Math.PI * 2, false );
var holePath1 = new THREE.Path();
holePath1.moveTo( 0, 10 );
holePath1.absarc( 0, 10, 2, 0, Math.PI * 2, true );
arcShape1.holes.push( holePath1 );
var geometry1 = new THREE.ExtrudeBufferGeometry( arcShape1, extrudeSettings );
var mesh1 = new THREE.Mesh( geometry1, new THREE.MeshPhongMaterial( { color: 0x804000 } ) );
scene.add( mesh1 );
var arcShape2 = new THREE.Shape();
arcShape2.moveTo( 0, 0 );
arcShape2.absarc( 0, 0, 15, 0, Math.PI * 2, false );
var holePath2 = new THREE.Path();
holePath2.moveTo( 0, 10 );
holePath2.absarc( 0, 10, 2, 0, Math.PI * 2, true );
arcShape2.holes.push( holePath2 );
var geometry2 = new THREE.ExtrudeGeometry( arcShape2, extrudeSettings );
var mesh2 = new THREE.Mesh( geometry2, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
scene.add( mesh2 );
// Define variables to hold the current epicyclic radius and current angle.
var mesh2AxisRadius = 30;
var mesh2AxisAngle = 0;
var animate = function () {
requestAnimationFrame( animate );
// During each animation frame, let's rotate the objects on their center axis,
// and also set the position of the epicyclic circle.
mesh1.rotation.z -= 0.02;
mesh2.rotation.z += 0.02;
mesh2AxisAngle += 0.01;
mesh2.position.set ( mesh2AxisRadius * Math.cos(mesh2AxisAngle), mesh2AxisRadius * Math.sin(mesh2AxisAngle), 0 );
renderer.render( scene, camera );
};
animate();
</script>
</body>
</html>
Note that I've used basic trigonometry within the animate function to position the epicyclic circle around the center circle, and fudged the rate of rotation for the circles (rather than doing the precise math), but there's probably a better "three.js"-way of doing this via matrices or built in functions. Given that you obviously have a strong math background, I don't think you'll have any issues with translating your 2D model of multi-epicyclic circles using basic trigonometry when porting to 3D.
Hope this helps in your decision making process on how to proceed with a 3D version of your program.
The method that I would suggest is as follows. Start with a parametrized path v(t) = (v_x(t), v_y(t), v_z(t)). Consider the following projection onto the X-Y plane: v1(t) = (v_x(t)/2, v_y(t), 0). And the corresponding projection onto the X-Z plane: v2(t) = (v_x(t)/2, 0, v_z(t)).
When we add these projections together we get the original curve. But each projection is now a closed 2-D curve, and you have solutions for arbitrary closed 2-D curves. So solve each problem. And then interleave them to get a projection where your first circle goes in the X-Y plane, your second one in the X-Z plane, your third one in the X-Y plane, your fourth one in the X-Z plane ... and they sum up to your answer!
you probably noticed me asking quite a few questions related to my project so thank you in advance for all the support.
My project consists of a planet earth and moon that rotate around the sun. (They aren't specifically rotating around the sun, it's more just around the 0,0 axis where the sun happens to be).
I initially created my geometries and then added them into the scene and then into the orbit group as follows
var orbitGroup = new THREE.Object3D();
scene.add(orbitGroup);
scene.add(planetEarth);
orbitGroup.add(planetEarth);
scene.add(planetMoon);
orbitGroup.add(planetMoon);
I then declared the rotation within the render function as follows
planetEarth.add( planetMoon );
planetEarth.add(rocketGroup);
// call the render function
var angle = 0;
render();
function render() {
stats.update();
// rotate the orbit group
angle += 0.002;
angle += 0.002 * controls.EarthRotationSpeed;
planetEarth.rotation.y += controls.EarthRotationSpeed;
planetMoon.rotation.y += controls.MoonRotationSpeed;
angle+= 0.01 * controls.SunRotationSpeed;
planetSun.rotation.y += controls.SunRotationSpeed;
// rotate the orbit group
angle += 0.02;
orbitGroup.rotation.y = -angle / 10;
littleOrbitGroup.rotation.x = -angle;
// render using requestAnimationFrame
requestAnimationFrame(render);
renderer.render(scene, camera);
}
As you can tell, the moon and the earth are both orbiting around the sun instead of the moon rotating the earth while it rotates the sun. Is there a way I can declare the specific point or object that it should orbit and also make it orbit it at whatever axis I want rather than specifically on the y axis?
----------------EDIT-----------------
function createMesh(geom) {
var loader = new THREE.TextureLoader();
var planetTexture = loader.load("../assets/textures/planets/Earth.png");
var normalTexture = loader.load("../assets/textures/planets/EarthNormal.png");
var planetMaterial = new THREE.MeshPhongMaterial({map: planetTexture, bumpMap: normalTexture});
// create a multimaterial
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]);
return mesh;
}
You can easily do that using nesting of THREE.Object3D one inside another, shifting them by rotarions radiuses and rotating it's around centers.
Look at this simple example, here used orbit controls - use mousewheel to zoom out and drag to rotate:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script>
THREE.TOUCH = {}
let base64prefix = 'data:image/png;base64,'
let blue = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkuPn/PwAFkgLZdUNuXwAAAABJRU5ErkJggg=='
let blue2= 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkqPz/HwAEcgJ5UUczUQAAAABJRU5ErkJggg=='
let orange = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8H8PwHwAGGQJcoBRfLQAAAABJRU5ErkJggg=='
let solarsystem = [{
texture: blue,
name: 'earth', radius: 2, orbit: 30, speed: 2,
satellites: [{
texture: blue2, rotation: [1, 1, 1],
name: 'rock', radius: 0.5, orbit: 4, speed: 5,
},{
texture: blue2,
name: 'moon', radius: 1, orbit: 6, speed: 1,
}]
}, {
texture: orange,
name: 'mars', radius: 2, orbit: 50, speed: 1,
satellites: [{
texture: blue2,
name: 'phobos', radius: 0.5, orbit: 3, speed: 1,
}, {
texture: blue2,
name: 'deimos', radius: 0.5, orbit: 4, speed: 3,
}]
}];
var scene = new THREE.Scene();
var aspect = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// planets
solarsystem.forEach(d => create(d, scene));
// sun
let sun = sphere({radius:3, orbit:0, texture: orange});
scene.add(sun)
function create(d, target) {
var o = new THREE.Object3D(d.name);
d.rotation && o.rotateX(d.rotation[0]);
d.rotation && o.rotateY(d.rotation[1]);
d.rotation && o.rotateZ(d.rotation[2]);
o.add(orbit(d));
let p = sphere(d)
o.add(p);
d.satellites && d.satellites.forEach(d1 => create(d1, p))
target.add(o);
d.o=o;
}
function orbit(d) {
var o = new THREE.Object3D('orbit '+d.name);
o.rotateX(Math.PI/2);
o.add( new THREE.Line(
new THREE.CircleGeometry( d.orbit, 64 ),
new THREE.LineBasicMaterial( { color: 0xffffff } ) ));
return o;
}
function sphere(d){
var o = new THREE.Object3D('sphere '+d.name);
o.translateX(d.orbit);
var loader = new THREE.TextureLoader();
loader.load( base64prefix + d.texture, function ( texture ) {
var geometry = new THREE.SphereGeometry( d.radius, 20, 20 );
var material = new THREE.MeshBasicMaterial( {
map: texture, overdraw: 0.5
} );
o.add( new THREE.Mesh( geometry, material ) );
} );
return o;
}
var grid = new THREE.GridHelper(500, 100, 0x666666, 0x444444)
grid.rotateY(Math.PI/2);
scene.add(grid);
camera.position.set(25,25,25);
new THREE.OrbitControls( camera, renderer.domElement );
let t = 0
function render(dt) {
let t2 = dt - t;
requestAnimationFrame( render );
renderer.render( scene, camera );
solarsystem.forEach(upd);
sun.rotateY(t2/1000);
t = dt;
function upd(d) {
d.o.rotateY(t2/10000*d.speed);
d.satellites && d.satellites.forEach(upd)
}
}
requestAnimationFrame( render );
</script>
<style>
body, canvas {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: black;
}
</style>
PS: Sorry for my english
In three.js I have created an ellipseCurve for which I want to extrude and make 3d.
CODE USE TO MAKE THIS:
var curve = new THREE.EllipseCurve(
0, 0, // ax, aY
10, 13.3, // xRadius, yRadius
0, 2 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
);
var path = new THREE.Path( curve.getPoints( 100 ) );
var geometrycirc = path.createPointsGeometry( 50 );
var materialcirc = new THREE.LineBasicMaterial( {
color : 0xff0000
} );
// Create the final object to add to the scene
var ellipse = new THREE.Line( geometrycirc, materialcirc );
this.scene.add( ellipse );
I want to use this ellipseCurve as a basis to create an extruded shape similar to these examples.
https://threejs.org/examples/#webgl_geometry_extrude_splines
These examples seem to use vectors to do this, so I assume I need to convert the curve into one.
I am not sure how to do this since I have been unable to find references on this matter.
Any help to do this?
UPDATE: 22/03/2017
Right so I tried to implement the same method of extrusion as found on:
https://threejs.org/examples/#webgl_geometry_extrude_splines
I was able to but this spline into my scene:
HERE IS THE CODE TO DO THIS:
/////////////////////////////////////////////////////////////////////////
// My line curve //
/////////////////////////////////////////////////////////////////////////
var curve = new THREE.EllipseCurve(
0, 0, // ax, aY
10, 13.3, // xRadius, yRadius
0, 2 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
);
var path = new THREE.Path( curve.getPoints( 100 ) );
var geometrycirc = path.createPointsGeometry( 50 );
var materialcirc = new THREE.LineBasicMaterial( {
color : 0xff0000
} );
// Create the final object based on points and material
var ellipse = new THREE.Line( geometrycirc, materialcirc );
this.scene.add( ellipse );
////////////////////////////////////////////////////////////////////////
// Example of sample closed spine //
////////////////////////////////////////////////////////////////////////
var sampleClosedSpline = new THREE.CatmullRomCurve3( [
new THREE.Vector3( 0, -40, -40 ),
new THREE.Vector3( 0, 40, -40 ),
new THREE.Vector3( 0, 140, -40 ),
new THREE.Vector3( 0, 40, 40 ),
new THREE.Vector3( 0, -40, 40 )
] );
sampleClosedSpline.type = 'catmullrom';
sampleClosedSpline.closed = true;
//////////////////////////////////////////////////////////////////////////////
// Extrusion method to covert the spline/vector data into 3d object //
//////////////////////////////////////////////////////////////////////////////
// I used this method and have tried the following properties but these do not work
//
// var tube = new THREE.TubeBufferGeometry( curve, 12, 2, 20, true);
//
// 1. ellipse.clone()
// 2. geometrycirc.clone()
// 3. materialcirc.clone()
// 4. path.clone()
// 5. curve
//
// Therefore I am either doing something wrong or there must be a further process that needs
// to be implemented.
// this works as standard
var tube = new THREE.TubeBufferGeometry( sampleClosedSpline, 12, 2, 20, true);
var tubeMesh = THREE.SceneUtils.createMultiMaterialObject( tube, [
new THREE.MeshLambertMaterial( {
color: 0xffffff
} ),
new THREE.MeshBasicMaterial( {
color: 0xff00ff,
opacity: 0.3,
wireframe: true,
transparent: true
} ) ] );
tubeMesh.scale.set( .2, .2, .2 );
this.scene.add( tubeMesh );
///////////////////////////////////////////////////////////////////////////////
So when I place the spline property for the one that I have created i get a black screen and the following error msgs:
var curve;
and the other variables used (refer to code to see what I have tried)
EDIT: 23/03/2017
WestLangley's method was the ideal solution
You want to create a TubeGeometry or TubeBufferGeometry in the shape of an ellipse.
Here is one way to do it that is general enough for others to use, too.
First, create a new class that defines your path:
// Ellipse class, which extends the virtual base class Curve
class Ellipse extends THREE.Curve {
constructor( xRadius, yRadius ) {
super();
// add radius as a property
this.xRadius = xRadius;
this.yRadius = yRadius;
}
getPoint( t, optionalTarget = new THREE.Vector3() ) {
const point = optionalTarget;
var radians = 2 * Math.PI * t;
return new THREE.Vector3( this.xRadius * Math.cos( radians ),
this.yRadius * Math.sin( radians ),
0 );
}
}
Then create the geometry from the path.
// path
var path = new Ellipse( 5, 10 );
// params
var pathSegments = 64;
var tubeRadius = 0.5;
var radiusSegments = 16;
var closed = true;
var geometry = new THREE.TubeBufferGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );
Super easy. :)
Fiddle: http://jsfiddle.net/62qhxags/
three.js r.129
I am trying to create spikes on earth(sphere geometry). Though everything works fines, but spikes dont align with globe. I want spike to align something like below image. But my spikes dont lookAt(new THREE.Vector3(0,0,0)) despite mentioned. Please help me out.
I purposefully mentioned code required for debugging. Let me know if you need more code for this. Below image is how i want my spikes to align with sphere.
But this is how it looks
My Main JS initialization file.
$(document).ready(function () {
// Initializing Camera
Influx.Camera = new Influx.Camera({
fov: 60,
aspectRatio: window.innerWidth / window.innerHeight,
near: 1,
far: 1000,
position: {
x: 0,
y: 0,
z: 750
}
});
//Initializing Scene
Influx.Scene = new Influx.Scene();
// Initializing renderer
Influx.Renderer = new Influx.Renderer({
clearColor: 0x000000,
size: {
width: window.innerWidth,
height: window.innerHeight
}
});
Influx.Globe = new Influx.Globe({
radius: 300,
width: 50,
height: 50
});
//
Influx.Stars = new Influx.Stars({
particleCount: 15000,
particle: {
color: 0xFFFFFF,
size: 1
}
});
Influx.moveTracker = new Influx.moveTracker();
Influx.EventListener = new Influx.EventListener();
(function animate() {
requestAnimationFrame( animate );
render();
controls.update();
})();
function render() {
camera.lookAt(scene.position);
group.rotation.y -= 0.001;
renderer.render( scene, camera );
};
});
Below is code responsible for generating spikes on Globe.
Influx.Spikes = function (lat, long) {
// convert the positions from a lat, lon to a position on a sphere.
var latLongToVector3 = function(lat, lon, RADIUS, heigth) {
var phi = (lat) * Math.PI/180,
theta = (lon-180) * Math.PI/180;
var x = -(RADIUS+heigth) * Math.cos(phi) * Math.cos(theta),
y = (RADIUS+heigth) * Math.sin(phi),
z = (RADIUS+heigth) * Math.cos(phi) * Math.sin(theta);
return new THREE.Vector3(x, y, z);
};
var geom = new THREE.Geometry();
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
//iterates through the data points and makes boxes with the coordinates
var position = latLongToVector3(lat, long, 300, 2);
var box = new THREE.Mesh( BoxGeometry );
//each position axis needs to be set separately, otherwise the box
//will instantiate at (0,0,0)
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
box.lookAt(new THREE.Vector3(0, 0, 0));
box.updateMatrix();
//merges the geometry to speed up rendering time, don't use THREE.GeometryUtils.merge because it's deprecated
geom.merge(box.geometry, box.matrix);
var total = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
color: getRandomColor(),
morphTargets: true
}));
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
//add boxes to the group
group.add(total);
scene.add(group);
};
Influx.Camera = function(params = {}) {
if ( !$.isEmptyObject(params) ) {
window.camera = new THREE.PerspectiveCamera(params.fov, params.aspectRatio, params.near, params.far);
camera.position.set(params.position.x, params.position.y, params.position.z);
camera.lookAt(new THREE.Vector3(0,0,0));
} else {
console.log("Trouble with Initializing Camera");
return;
}
};
Remember that lookAt takes a direction vector, you give to this method the vector (0, 0, 0), this is actually not a normalized direction vector. So you must calculate the direction:
from your box position to the center of the sphere AND normalize it.
var dir = box.position.sub(world.position).normalize();
box.lookAt(dir);
And now just a set of code good conventions that may help you:
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
Here I would rather use another var name for the box geometry, not to mix up with the "class" definition from THREE and to follow naming conventions:
var boxGeometry = new THREE.BoxGeometry(1, 100, 1);
And here:
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
You can just set:
box.position.copy(position);
I also meet this problem, and I fixed it, the solution is: box.lookAt(new THREE.Vector3(0, 0, 0)) must after box.scale.z = xxxx