Related
if some rockstar could look through my code real quick and tell me why I cant render/see my three.js model I'd be forever in your debt!
I'll post the whole script, but I think some preliminary info is important. Basically, I have thousands of points that look like this:
472232.14363148 2943288.56768013 200.129142"
472237.03086105 2943289.62356560 200.119496"
472241.91809061 2943290.67945108 200.109851"
472246.80532018 2943291.73533656 200.100205"
...and so on...
and a bunch of faces that look like this:
["1021 1020 1061", "640 754 641", "1534 1633 1535", "4701 27 26", "654 753 655", ...and so on...
When I extracted all the data and configured it correctly I then push it to the geometry and try to add it to the scene but with no success. Here's the whole script:
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set( 0, 50, 50 );
camera.lookAt( 0, 0, 0 );
var scene = new THREE.Scene();
var material = new THREE.MeshStandardMaterial( { color : 0x00cc00 } );
var geometry = new THREE.Geometry();
var points = bigData['LandXML']['Surfaces']['Surface']['Definition']['Pnts']['P']
var faces = bigData['LandXML']['Surfaces']['Surface']['Definition']['Faces']['F']
for(i=0;i<points.length;i++){
var [point1,point2,point3] = points[i]['__text'].split(' ').map(Number)
//point1 *= .00001
//point2 *= .00001
//point3 *= .00001
geometry.vertices.push(new THREE.Vector3(point1,point2,point3))
}
for(i=0;i<faces.length;i++){
console.log(faces[i]);
var [face1,face2,face3] = faces[i].split(' ').map(Number)
var face = new THREE.Face3(face1 - 1,face2 - 1, face3 - 1)
geometry.faces.push(face)
}
scene.add( new THREE.Mesh( geometry, material ) );
You can see that in the for loop for the points, I have multiplied them by .00001 to scale the model because otherwise the numbers are so huge, if that makes sense. And I subtract 1 from each face because the data was not zero indexed. Anyways, if any coding superhero took the time to read this and has some insight, please help me out! Thanks!
So, I found your model on scene, the code missed those parts:
Your model has big coordinates, but small dimension. After dividing
all coordinates by 100.000 it has boundingSphere center at (4.72;
29,432; 0,00192) and boundingSphere radius 0,00480, So you need
to either translate model so it has center at (0;0;0) or move camera
target to it's center;
geometry.computeFaceNormals() must be called after building faces
in order to make faces resposive to lighting and shading;
Scene needs some light in it. In curent state it will not show
anything
Render loop or at least single call after adding mesh is required.
Here is fixed code, additionally I played with camera distance and added simple rotation animation. Also I used geometry translation approach to have an adequate rotation:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>DXF Test</title>
</head>
<body>
<script src="three.min.js"></script>
<script type="text/javascript" src="JFM.js"></script>
<script>
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.set(0, 650, 650);
camera.lookAt(0, 0, 0);
var scene = new THREE.Scene();
var material = new THREE.MeshStandardMaterial({color: 0x00cc00});
var geometry = new THREE.Geometry();
//geometry.vertices.push(new THREE.Vector3(4.72227256402, 29.4328751179, 0.00200138787));
//geometry.vertices.push(new THREE.Vector3(3, 3, 0));
//geometry.vertices.push(new THREE.Vector3(5, 6, 0));
//var line = new THREE.Line( geometry, material );
//scene.add( line );
//renderer.render( scene, camera );
var points = bigData['LandXML']['Surfaces']['Surface']['Definition']['Pnts']['P'];
var faces = bigData['LandXML']['Surfaces']['Surface']['Definition']['Faces']['F'];
console.log('Complete XML file');
console.log(bigData);
console.log('POINTS');
console.log(points);
console.log('FACES');
console.log(faces);
for (i = 0; i < points.length; i++) {
var [point1, point2, point3] = points[i]['__text'].split(' ').map(Number);
geometry.vertices.push(new THREE.Vector3(point1, point2, point3));
}
for (i = 0; i < faces.length; i++) {
var [face1, face2, face3] = faces[i].split(' ').map(Number);
var face = new THREE.Face3(face1 - 1, face3 - 1, face2 - 1);
geometry.faces.push(face)
}
geometry.computeFaceNormals();
geometry.computeBoundingSphere();
// translate model so it's real center will be the same as mesh pivot
geometry.translate(
-geometry.boundingSphere.center.x,
-geometry.boundingSphere.center.y,
-geometry.boundingSphere.center.z
);
// compute bounding sphere again, because it was broken during translation
geometry.computeBoundingSphere();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var sunLight = new THREE.DirectionalLight(0xffffff);
sunLight.position.set(200, 600, 1000);
scene.add(sunLight);
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
mesh.rotation.y += 0.01;
mesh.rotation.x += 0.01;
}
animate();
</script>
</body>
</html>
If you don't like translation, you can remove it, remove rotation and after computeBoundingSphere do like this:
camera.position.set(
geometry.boundingSphere.center.x + geometry.boundingSphere.radius,
geometry.boundingSphere.center.y + geometry.boundingSphere.radius,
geometry.boundingSphere.center.z + geometry.boundingSphere.radius,
);
camera.lookAt(
geometry.boundingSphere.center.x,
geometry.boundingSphere.center.y,
geometry.boundingSphere.center.z,
);
Hope it will help.
I'm setting up a 3d asset viewer in Three.js. I'm running the code on a Plesk server provided by the university and have it linked via Dreamweaver. I'm a total newbie to JS and it was suggested in many threads and posts that I wrap my code within an 'init();' function. Up doing so, and clearing any errors that the code had, it is now showing a black screen, rather than the 3d model it would show before.
I've spent the whole day error checking removing problems that I was having which included the 'canvas' not being created inside the 'container' div, and the 'onWindowResize' function. All these problems have been resolved, and there are no errors in the code apparently. I've got ambient lights in the code and there was a working skybox, so I'm sure its not a problem with position of camera or lack of lighting.
I know that you need as little code as possible, but I have no idea where the problem is coming from, so a majority of the code on the page is here :
<div id="container" ></div>
<script>
let container;
let camera;
let controls;
let scene;
let renderer;
init();
animate;
function init(){
// Renderer - WebGL is primary Renderer for Three.JS
var renderer = new THREE.WebGLRenderer({antialias : true});
renderer.setClearColor(0xEEEEEE, 0.5);
// Selects and applies parameters to the 'Container' div
var container = document.querySelector("#container");
container.appendChild(renderer.domElement);
renderer.setSize(container.clientWidth, container.clientHeight);
// Perspective Camera (FOV, aspect ratio based on container, near, far)
var camera = new THREE.PerspectiveCamera( 75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.x = 750;
camera.position.y = 500;
camera.position.z = 1250;
// Scene will contain all objects in the world
var scene = new THREE.Scene();
//Lighting (Colour, intensity)
var light1Ambient = new THREE.AmbientLight(0xffffff , 0.3);
scene.add(light1Ambient);
var light1Point = new THREE.PointLight(0xfff2c1, 0.5, 0, 2);
scene.add(light1Point);
var light2Point = new THREE.PointLight(0xd6e3ff, 0.4, 0, 2);
scene.add(light2Point);
// All basic Geomety
var newPlane = new THREE.PlaneGeometry(250,250,100,100);
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial( {color: 0x00ff00} )
);
scene.add(mesh);
// Water
water = new THREE.Water(newPlane,
{
textureWidth: 512,
textureHeight: 512,
waterNormals: new THREE.TextureLoader().load( 'http://up826703.ct.port.ac.uk/CTPRO/textures/waternormals.jpg', function ( texture ) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
} ),
alpha: 1.0,
sunDirection: light1Point.position.clone().normalize(),
sunColor: 0xffffff,
waterColor: 0x001e0f,
distortionScale: 0.5,
fog: scene.fog !== undefined
}
);
water.rotation.x = - Math.PI / 2;
scene.add( water );
// All Materials (Normal for Debugging) (Lambert: color)
var material = new THREE.MeshLambertMaterial({color: 0xF3FFE2});
var materialNew = new THREE.MeshBasicMaterial( {color: 0x00ff00} );
// Skybox
var skybox = new THREE.BoxGeometry(1000,1000, 1000);
var skyboxMaterials =
[
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("http://up826703.ct.port.ac.uk/CTPRO/skybox/blue/bluecloud_ft.jpg"), side: THREE.DoubleSide }),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("http://up826703.ct.port.ac.uk/CTPRO/skybox/blue/bluecloud_bk.jpg"), side: THREE.DoubleSide }),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("http://up826703.ct.port.ac.uk/CTPRO/skybox/blue/bluecloud_up.jpg"), side: THREE.DoubleSide }),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("http://up826703.ct.port.ac.uk/CTPRO/skybox/blue/bluecloud_dn.jpg"), side: THREE.DoubleSide }),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("http://up826703.ct.port.ac.uk/CTPRO/skybox/blue/bluecloud_rt.jpg"), side: THREE.DoubleSide }),
new THREE.MeshBasicMaterial({map: new THREE.TextureLoader().load("http://up826703.ct.port.ac.uk/CTPRO/skybox/blue/bluecloud_lf.jpg"), side: THREE.DoubleSide }),
];
var skyboxMaterial = new THREE.MeshFaceMaterial(skyboxMaterials);
var skyMesh = new THREE.Mesh (skybox, skyboxMaterial);
scene.add(skyMesh);
//Grid Helper Beneath Ship
scene.add(new THREE.GridHelper(250, 250));
//OBJ Model Loading
var objLoader = new THREE.OBJLoader();
objLoader.load('http://up826703.ct.port.ac.uk/CTPRO/models/ship1.obj', function(object){
scene.add(object);
});
// Object positioning
water.position.y = -2.5;
// Misc Positioning
light1Point.position.z =20;
light1Point.position.x = 25;
// z - front-back position
light2Point.position.z = -400;
// x - left-right
light2Point.position.x = -25;
// y - up- down
light2Point.position.y = 250;
window.addEventListener("resize", onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
};
};
// Canvas adapts size based on changing windows size
//Render loop
var animate = function(){
water.material.uniforms[ "time" ].value += 1.0 / 120.0;
function drawFrame(ts){
var center = new THREE.Vector2(0,0);
window.requestAnimationFrame(drawFrame);
var vLength = newPlane.geometry.vertices.length;
for (var i = 0; i < vLength; i++) {
var v = newPlane.geometry.vertices[i];
var dist = new THREE.Vector2(v.x, v.y).sub(center);
var size = 2.0;
var magnitude = 8;
v.z = Math.sin(dist.length()/-size + (ts/900)) * magnitude;
}
newPlane.geometry.verticesNeedUpdate = true;
};
requestAnimationFrame(animate)
renderer.render(scene, camera);
controls.update();
}
</script>
I'm no professional, so I'm sorry if this is super rough for those of you with experience!
I need to point out, before wrapping all of this in the init(); function, it was working perfectly.
When working, I should see a crudely modeled ship sitting in some water, with a cloud skybox. The controls were working and it would auto rotate.
Right now it does none of this. The obj loader is working as seen in the chrome console log OBJLoader: 1661.970703125ms but again, nothing is actually displayed, it's just a black screen.
Thanks to anyone who's able to help me out with this!
this line
animate;
needs to a function call
animate();
Also you probably need to change the code below where you create the animate function from
var animate = function(){
To this
function animate(){
The reason is named functions are defined when the code is loaded but variables var are created when the code is executed. So with code like this
init();
animate();
var animate = function(){ ...
animate doesn't actually exist at the point the code tries to call it whereas with this
init();
animate();
function animate(){ ...
it does exist
You could also re-arrange the code so for example define animate before you use it should work.
var animate = function(){
...
};
init();
animate();
It also appear some are declared inside init which means that are not available to animate. So for example
var renderer = new THREE.WebGLRenderer({antialias : true});
declares a new variable renderer that only init can see. You wanted to set the renderer variable that is outside of init so change the code to
renderer = new THREE.WebGLRenderer({antialias : true});
controls is never defined so you probably need to define it or comment out
controls.update();
to
// controls.update();
note: you might find these tutorials helpful although if you're new to JavaScript you should probably spend time learning JavaScript
Uncaught TypeError: THREE.PointerLockControls is not a constructor
I can't use firstperson controls for whatever reason, I am really lost for reason with this one. It's got me really stumped.
const THREE = require('THREE');
var FirstPersonControls = require('first-person-controls');
const CANNON = require('cannon');
var keyboard = new THREEx.KeyboardState();
var lights = [];
var camSpeed = 1;
var world, mass, body, body2, shape, shape2, timeStep=1/60,
camera, scene, renderer, geometry, material, mesh, textureCube;
initThree();
initCannon();
animate();
function initCannon() {
world = new CANNON.World();
world.gravity.set(0,-9.8,0);
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
shape = new CANNON.Box(new CANNON.Vec3(1,1,1));
shape2 = new CANNON.Box(new CANNON.Vec3(50,1,50));
mass = 1;
body = new CANNON.Body({
mass: 1
});
body2 = new CANNON.Body({
mass: 0
});
body.position.set(1,10,1);
body.addShape(shape);
body2.addShape(shape2);
body.angularDamping = 0.5;
world.addBody(body);
world.addBody(body2);
}
function initThree() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 10000 );
var controls = new THREE.FirstPersonControls(camera);
controls.lookSpeed = 0.1;
controls.movementSpeed = 10;
var clock = new THREE.Clock(true);
var prefix = ".png"
var r = __dirname + "/skyboxes/mp_cliffside/";
var urls = [
r + "px" + prefix, r + "nx" + prefix,
r + "py" + prefix, r + "ny" + prefix,
r + "pz" + prefix, r + "nz" + prefix
];
textureCube = new THREE.CubeTextureLoader().load( urls );
var dottedAlphaMap = new THREE.TextureLoader().load( __dirname + "/textures/brickmap.png" );
var dottedAlphaMap2 = new THREE.TextureLoader().load( __dirname + "/textures/stonemap-wet-texture.jpg" );
scene.background = textureCube;
lights[0] = new THREE.PointLight( '#ffffff', 3, 100 );
lights[0].position.set( 0, 5, 0 );
scene.add( lights[0] );
scene.add( camera );
renderer = new THREE.WebGLRenderer({ alpha:false });
renderer.setSize( window.innerWidth, window.innerHeight );
camera.position.y = 40;
camera.rotation.x = -90 * Math.PI / 180;
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
updatePhysics();
render();
}
var controllee = camera;
function updatePhysics() {
// Step the physics world
world.step(timeStep);
// Copy coordinates from Cannon.js to Three.js
lights[0].position.copy(camera.position)
}
function render() {
requestAnimationFrame(render);
controls.update(clock.getDelta());
if(keyboard.pressed("F")){
camera.fov += 0.1;
camera.updateProjectionMatrix();
}
if(keyboard.pressed("G")){
camera.fov -= 0.1;
camera.updateProjectionMatrix();
}
if(keyboard.pressed("space")){
controllee.translateY(camSpeed/10);
}
if(keyboard.pressed("shift")){
controllee.translateY(-camSpeed/10);
}
if(keyboard.pressed("W")){
controllee.translateZ(-camSpeed/10);
}
if(keyboard.pressed("S")){
controllee.translateZ(camSpeed/10);
}
if(keyboard.pressed("A")){
controllee.translateX(-camSpeed/10);
}
if(keyboard.pressed("D")){
controllee.translateX(camSpeed/10);
}
if(keyboard.pressed("I")){
controllee.rotateX(camSpeed/100);
}
if(keyboard.pressed("K")){
controllee.rotateX(-camSpeed/100);
}
if(keyboard.pressed("J")){
controllee.rotateY(camSpeed/100);
}
if(keyboard.pressed("L")){
controllee.rotateY(-camSpeed/100);
}
if(keyboard.pressed("U")){
controllee.rotateZ(camSpeed/100);
}
if(keyboard.pressed("O")){
controllee.rotateZ(-camSpeed/100);
}
renderer.render( scene, camera );
}
I am using imported three.js and cannon.js, from node package manager.
I am trying to get the controls to be like an fps, but stuff like this keeps getting in my way!
Any help is appreciated, the only thing i can think of is that its not included in the NPM version of three, in which case, I'm SOL
Update: I have changed my code to include three via a tag. Same goes with the PointerLockControls, but now the problem is that I dont know how the heck to lock the pointer.
UNDERSTAND that you have to use "controls.lock()" to effectively lock your mouse to the screen (your pointer will disappear and you will be able to look around like a FPS game).
Unfortunately, you CAN NOT lock the mouse pointer from code. Instead, a user interaction WITH A DOM ELEMENT is required.
The simplest Dom element you can use is the "body", by using document.body.. see:
//add document.body to PointerLockControls constructor
let fpsControls = new PointerLockControls( camera , document.body );
//add event listener to your document.body
document.body.addEventListener( 'click', function () {
//lock mouse on screen
fpsControls.lock();
}, false );
NOTE 1: no need to call fpsControls.update() in animation function;
NOTE 2: make sure your body section is IN FRONT of the canvas and covering the entire screen, setting z-index: -1 on the canvas' CSS if necessary (or setting z-index: 10, in body's CSS ). Example:
body{
z-index: 10
margin: 0;
padding: 0;
height: 100vh;
width: 100vh;
overflow: hidden;
}
This way, you can click anywhere in the screen to experience the expected behavior. ESC will make you unlock the controller
Keep Calm and Happy Coding
but now the problem is that I don't know how the heck to lock the pointer.
You can do it like in the following example:
Create splash screen that says "Click to Play"
Register an click event listener to the respective DOM element
In the listener code call document.body.requestPointerLock() in order to asynchronously ask the browser for the pointer lock
I am able to display a THREE.TubeGeometry figure as follows
Code below, link to jsbin
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<script>
// global variables
var renderer;
var scene;
var camera;
var geometry;
var control;
var count = 0;
var animationTracker;
init();
drawSpline();
function init()
{
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
renderer.setClearColor('lightgray', 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
}
function drawSpline(numPoints)
{
var numPoints = 100;
// var start = new THREE.Vector3(-5, 0, 20);
var start = new THREE.Vector3(-5, 0, 20);
var middle = new THREE.Vector3(0, 35, 0);
var end = new THREE.Vector3(5, 0, -20);
var curveQuad = new THREE.QuadraticBezierCurve3(start, middle, end);
var tube = new THREE.TubeGeometry(curveQuad, numPoints, 0.5, 20, false);
var mesh = new THREE.Mesh(tube, new THREE.MeshNormalMaterial({
opacity: 0.9,
transparent: true
}));
scene.add(mesh);
renderer.render(scene, camera);
}
</script>
</body>
</html>
However, I would like to display incrementally, as in, like an arc that is loading, such that it starts as the start point, draws incrementally and finally looks the below arc upon completion.
I have been putting in some effort, and was able to do this by storing all the points/coordinates covered by the arc, and drawing lines between the consecutive coordinates, such that I get the 'arc loading incrementally' feel. However, is there a better way to achieve this? This is the link to jsbin
Adding the code here as well
<!DOCTYPE html>
<html>
<head>
<title>Incremental Spline Curve</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<script>
// global variables
var renderer;
var scene;
var camera;
var splineGeometry;
var control;
var count = 0;
var animationTracker;
// var sphereCamera;
var sphere;
var light;
function init() {
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
// renderer.setClearColor(0x000000, 1.0);
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// //init for sphere
// sphereCamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
// sphereCamera.position.y = -400;
// sphereCamera.position.z = 400;
// sphereCamera.rotation.x = .70;
sphere = new THREE.Mesh(new THREE.SphereGeometry(0.8,31,31), new THREE.MeshLambertMaterial({
color: 'yellow',
}));
light = new THREE.DirectionalLight('white', 1);
// light.position.set(0,-400,400).normalize();
light.position.set(0,10,10).normalize();
//get points covered by Spline
getSplineData();
}
//save points in geometry.vertices
function getSplineData() {
var curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( -5, 0, 10 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3( 2, 0, -25 )
);
splineGeometry = new THREE.Geometry();
splineGeometry.vertices = curve.getPoints( 50 );
animate();
}
//scheduler loop
function animate() {
if(count == 50)
{
cancelAnimationFrame(animationTracker);
return;
}
//add line to the scene
drawLine();
renderer.render(scene, camera);
// renderer.render(scene, sphereCamera);
count += 1;
// camera.position.z -= 0.25;
// camera.position.y -= 0.25;
animationTracker = requestAnimationFrame(animate);
}
function drawLine() {
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0x0000ff
});
console.log(splineGeometry.vertices[count]);
console.log(splineGeometry.vertices[count+1]);
lineGeometry.vertices.push(
splineGeometry.vertices[count],
splineGeometry.vertices[count+1]
);
var line = new THREE.Line( lineGeometry, lineMaterial );
scene.add( line );
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
<body>
</body>
</html>
Drawback : The drawback of doing it the above way is that, end of the day, I'm drawing a line between consecutive points, and so I lose out on a lot of the effects possible in TubeGeometry such as, thickness, transparency etc.
Please suggest me an alternative way to get a smooth incremental load for the TubeGeometry.
THREE.TubeGeometry returns a THREE.BufferGeometry.
With THREE.BufferGeometry, you have access to a property drawRange that you can set to animate the drawing of the mesh:
let nEnd = 0, nMax, nStep = 90; // 30 faces * 3 vertices/face
...
const geometry = new THREE.TubeGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );
nMax = geometry.attributes.position.count;
...
function animate() {
requestAnimationFrame( animate );
nEnd = ( nEnd + nStep ) % nMax;
mesh.geometry.setDrawRange( 0, nEnd );
renderer.render( scene, camera );
}
EDIT: For another approach, see this SO answer.
three.js r.144
Normally you would be able to use the method .getPointAt() to "get a vector for point at relative position in curve according to arc length" to get a point at a certain percentage of the length of the curve.
So normally if you want to draw 70% of the curve and a full curve is drawn in 100 segments. Then you could do:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = curveQuad.getPointAt( 0 );
for(var i = 1; i < percentage; i++){
end = curveQuad.getPointAt( percentage / 100 );
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
But I think this is not working for your curveQuad since the getPointAt method is not implemented for this type. A work around is to get a 100 points for your curve in an array like this:
points = curve.getPoints(100);
And then you can do almost the same:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = points[ 0 ];
for(var i = 1; i < percentage; i++){
end = points[ percentage ]
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
now your curvePath holds the line segments you want to use for drawing the tube:
// draw the geometry
var radius = 5, radiusSegments = 8, closed = false;
var geometry = new THREE.TubeGeometry(curvePath, percentage, radius, radiusSegments, closed);
Here a fiddle with a demonstration on how to use this dynamically
I'm not really that familiar with three.js. But I think I can be of assistance. I have two solutions for you. Both based on the same principle: build a new TubeGeometry or rebuild the current one, around a new curve.
Solution 1 (Simple):
var CurveSection = THREE.Curve.create(function(base, from, to) {
this.base = base;
this.from = from;
this.to = to;
}, function(t) {
return this.base.getPoint((1 - t) * this.from + t * this.to);
});
You define a new type of curve which just selects a segment out of a given curve. Usage:
var curve = new CurveSection(yourCurve, 0, .76); // Where .76 is your percentage
Now you can build a new tube.
Solution 2 (Mathematics!):
You are using for your arc a quadratic bezier curve, that's awesome! This curve is a parabola. You want just a segment of that parabola and that is again a parabola, just with other bounds.
What we need is a section of the bezier curve. Let's say the curve is defined by A (start), B (direction), C (end). If we want to change the start to a point D and the end to a point F we need the point E that is the direction of the curve in D and F. So the tangents to our parabola in D and F have to intersect in E. So the following code will give us the desired result:
// Calculates the instersection point of Line3 l1 and Line3 l2.
function intersection(l1, l2) {
var A = l1.start;
var P = l2.closestPointToPoint(A);
var Q = l1.closestPointToPoint(P);
var l = P.distanceToSquared(A) / Q.distanceTo(A);
var d = (new THREE.Vector3()).subVectors(Q, A);
return d.multiplyScalar(l / d.length()).add(A);
}
// Calculate the tangentVector of the bezier-curve
function tangentQuadraticBezier(bezier, t) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
return new THREE.Vector3(
THREE.CurveUtils.tangentQuadraticBezier(t, s.x, m.x, e.x),
THREE.CurveUtils.tangentQuadraticBezier(t, s.y, m.y, e.y),
THREE.CurveUtils.tangentQuadraticBezier(t, s.z, m.z, e.z)
);
}
// Returns a new QuadraticBezierCurve3 with the new bounds.
function sectionInQuadraticBezier(bezier, from, to) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
var ns = bezier.getPoint(from),
ne = bezier.getPoint(to);
var nm = intersection(
new THREE.Line3(ns, tangentQuadraticBezier(bezier, from).add(ns)),
new THREE.Line3(ne, tangentQuadraticBezier(bezier, to).add(ne))
);
return new THREE.QuadraticBezierCurve3(ns, nm, ne);
}
This is a very mathematical way, but if you should need the special properties of a Bezier curve, this is the way to go.
Note: The first solution is the simplest. I am not familiar with Three.js so I wouldn't know what the most efficient way to implement the animation is. Three.js doesn't seem to use the special properties of a bezier curve so maybe solution 2 isn't that useful.
I hope you have gotten something useful out of this.
I'm trying to create webgl animation for my website background, inspired by "threejs - Cloud exemple" (http://mrdoob.com/lab/javascript/webgl/clouds/). On my computer it seems rather well... But for some PC it's very slow.
Is there a way to further optimize my code, and detect if the graphics card does not support webgl ?
My animation (in background) : http://wabeo.fr/?theme=auriga-7
My code :
var container = document.getElementById('container');
var wi = window.innerWidth;
var he = window.innerHeight;
var renderer = new THREE.WebGLRenderer({
antialias: true
});
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75,wi/he,1,10000);
var distance = 500;
var geometry2 = new THREE.Geometry();
renderer.setSize(wi ,he);
container.appendChild(renderer.domElement);
scene.add(camera);
var texture = THREE.ImageUtils.loadTexture( '/wp-content/themes/auriga-7/i/cloud.png' );
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
var m = new THREE.MeshBasicMaterial( {color:0x000000} );
var material = new THREE.MeshBasicMaterial( { map: texture,transparent: true} );
var plane = new THREE.PlaneGeometry( 400,400,4,4 );
for ( ix = 0; ix <45; ix++ ) {
item = new THREE.Mesh( plane, m );
item.position.x = ((Math.random()-0.5)*(Math.random() * wi/2) /4)*Math.random()*10;
item.position.y = ((Math.random()-0.5)*(Math.random() * he/2) /4)*Math.random()*10;
item.position.z = ix*10-50;
item.rotation.z = Math.random() *250;
item.scale.x = item.scale.y = Math.random() * Math.random() * 2 + 0.5;
THREE.GeometryUtils.merge(geometry2,item);
}
mesh = new THREE.Mesh( geometry2, material );
scene.add(mesh);
camera.position.z = distance;
camera.lookAt(scene.position);
renderer.sortObjects = false;
// create a point light
var pointLight =
new THREE.PointLight(0xFFFFFF);
// set its position
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
// add to the scene
scene.add(pointLight);
requestAnimationFrame(wanarender);
document.addEventListener('mousemove',onMouseMove, false);
window.addEventListener('resize',onResizeMyFuckinBrowser,false);
function onMouseMove(event){
var mouseX = event.clientX - wi/2;
var mouseY = event.clientY - he/2;
camera.position.x = (mouseX - camera.position.x) * 0.02;
camera.position.y = (-mouseY - camera.position.y) * 0.02;
camera.position.z = distance;
camera.lookAt(scene.position);
}
function onResizeMyFuckinBrowser(){
var wi = window.innerWidth;
var he = window.innerHeight;
renderer.setSize(wi ,he);
}
function wanarender(){
requestAnimationFrame(wanarender);
renderer.render(scene, camera);
}
Thanks for your help :-)
Just looking quickly at the Mr Doob code, I notice a couple of optimisations that might help you. If you inspect Mr Doob's example, you can see that his cloud texture is a 256 x 256 px image, while yours is 800 x 800. There are two things to consider here:
Firstly, try to use powers of 2 for your texture sizes, ie 256, 512, 1024... This is because the graphics card is optimised for textures with these dimensions.
Secondly, 800 x 800 is probably much bigger than you really need, as the Mr Doob demo demonstrates. Most of the time, your texture is being scaled down to half the size or less.
Another thing that stands out in the Mr Doob demo is that he is using mipmaps. Mipmaps are when the graphics card pre-caches multiple versions of the texture at different scales, and uses the closest one to the current level at any given time. This makes the texture scaling more efficient, so turning them on might speed things up for you a little.
Your Code:
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
Mr Doob's Code:
texture.magFilter = THREE.LinearMipMapLinearFilter;
texture.minFilter = THREE.LinearMipMapLinearFilter;
Regarding detection of WebGL, See this Stack Overflow answer for information:
Three.js detect webgl support and fallback to regular canvas
I'm new to Three.jS myself but it is quite problematic to optimise your code. Few things I learned. Render before you append element if you don't like the flash of black.
keep you geometry and textures simple. The more complicated the shape, and the more images used as textures, the slower it gets.
I'm sure there's a way to optimise the graphics, but I don't know it yet. Start by trying to solve that problem.