Threejs :: determine if an object is screened by others - javascript

I have been developing an 3d model viewer with three.js but I have encountered this problem and I cannot seem to find any solution to it.
I want to have 2d overlay tracking a sphere while it's rotating.
I managed to code the overlay tracking the sphere referring to http://zachberry.com/blog/tracking-3d-objects-in-2d-with-three-js.
But I want to hide the tracking overlay while the sphere is screened by the cude.
So I tried to caculate intesects between camera and the sphere to determine is visible from camera.
But rayCaster.intersectObjects always return 0.
Here is my code.
How can i determain the sphere is visible or not?
$(function() {
// Some defaults/hardcoded values:
var DEFAULT_VIEW_ANGLE = 45;
var DEFAULT_CAMERA_X = 0;
var DEFAULT_CAMERA_Y = 0;
var DEFAULT_CAMERA_Z = 200;
var PERSPECTIVE_NEAR = 0.1;
var PERSPECTIVE_FAR = 10000;
var WIDTH = $('#viewport').width();
var HEIGHT = $('#viewport').height();
// state
var rotate = true;
// three.js objects:
var renderer;
var scene;
var camera;
var projector;
var container;
var cube;
var sphere;
var pointLight;
var rayCaster;
// elements
var $trackingOverlay = $('#tracking-overlay');
// create three.js elements
projector = new THREE.Projector();
renderer = new THREE.WebGLRenderer();
rayCaster = new THREE.Raycaster();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(DEFAULT_VIEW_ANGLE, WIDTH / HEIGHT, PERSPECTIVE_NEAR, PERSPECTIVE_FAR);
camera.position.set(DEFAULT_CAMERA_X, DEFAULT_CAMERA_Y, DEFAULT_CAMERA_Z);
pointLight = new THREE.PointLight(0xFFFFFF);
pointLight.position.set(10, 50, 150);
container = new THREE.Object3D();
cube = new THREE.Mesh(
new THREE.BoxGeometry(50, 50, 50),
new THREE.MeshLambertMaterial({wireframe: false, color: 0x00CC00})
);
cube.position.set(0, 0, 0);
sphere = new THREE.Mesh(
new THREE.SphereGeometry(4, 32, 32),
new THREE.MeshLambertMaterial({wireframe:false, color: 0xCCCC00})
);
sphere.position.set(0, 64, 0);
container.add(cube);
container.add(sphere);
scene.add(container);
scene.add(pointLight);
scene.add(camera);
// setup viewport
viewport = document.getElementById('viewport');
renderer.setSize(WIDTH, HEIGHT);
viewport.appendChild(renderer.domElement);
// start the animation loop
requestAnimationFrame(update);
function update()
{
if(rotate)
{
// hardcoded rotation for this demo:
container.rotation.z += 0.01;
container.rotation.x += 0.001;
container.rotation.y += 0.025;
}
renderer.render(scene, camera);
positionTrackingOverlay();
requestAnimationFrame(update);
}
function positionTrackingOverlay()
{
var visibleWidth, visibleHeight, p, v, percX, percY, left, top;
// this will give us position relative to the world
//p = sphere.matrixWorld.getPosition().clone();
p =new THREE.Vector3().setFromMatrixPosition(sphere.matrixWorld);
// projectVector will translate position to 2d
v = p.project(camera);
// translate our vector so that percX=0 represents
// the left edge, percX=1 is the right edge,
// percY=0 is the top edge, and percY=1 is the bottom edge.
percX = (v.x + 1) / 2;
percY = (-v.y + 1) / 2;
// scale these values to our viewport size
left = percX * WIDTH;
top = percY * HEIGHT;
// position the overlay so that it's center is on top of
// the sphere we're tracking
$trackingOverlay
.css('left', (left - $trackingOverlay.width() / 2) + 'px')
.css('top', (top - $trackingOverlay.height() / 2) + 'px');
if(isSphereVisible()){
$trackingOverlay.show();
}else{
$trackingOverlay.hide();
}
}
function isSphereVisible(){
rayCaster.setFromCamera(sphere,camera);
var intersects = rayCaster.intersectObjects(cube.children);
console.log(intersects.length);
return intersects.length>0?false:true;
}
});

Related

How to select single material using Raycaster and GLTF loader

I am loading a model through GLTF loader. I want to select a mesh on mouse hover. Everything is going cool, but the main problem is when hovering its changing the color all material whose name is same (as per my researches). When i am debugging its INTERSECTED returning single material. I don't know why its happening. After many researches i am asking this question here.
Please see my code below.
<div id="ThreeJS" style="position: absolute; left:0px; top:0px"></div>
var container, scene, camera, renderer, controls, stats;
var clock = new THREE.Clock();
var xyzz;
// custom global variables
var cube;
var projector,
mouse = {
x: 0,
y: 0
},
INTERSECTED;
init();
animate();
// FUNCTIONS
function init() {
// SCENE
scene = new THREE.Scene();
// CAMERA
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45,
ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT,
NEAR = 0.1,
FAR = 20000;
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
scene.add(camera);
camera.position.set(0, 0, 0);
camera.lookAt(scene.position);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.getElementById("ThreeJS");
container.appendChild(renderer.domElement);
// EVENTS
// CONTROLS
controls = new THREE.OrbitControls(camera, renderer.domElement);
// STATS
stats = new Stats();
stats.domElement.style.position = "absolute";
stats.domElement.style.bottom = "0px";
stats.domElement.style.zIndex = 100;
container.appendChild(stats.domElement);
// LIGHT
const skyColor = 0xb1e1ff; // light blue
const groundColor = 0xb97a20; // brownish orange
const intensity = 5;
const light = new THREE.HemisphereLight(
skyColor,
groundColor,
intensity
);
scene.add(light);
scene.background = new THREE.Color("#fff");
// GLTF Loader
function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
const halfFovY = THREE.Math.degToRad(camera.fov * 0.5);
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
// compute a unit vector that points in the direction the camera is now
// in the xz plane from the center of the box
const direction = new THREE.Vector3()
.subVectors(camera.position, boxCenter)
.multiply(new THREE.Vector3(1, 0, 1))
.normalize();
// move the camera to a position distance units way from the center
// in whatever direction the camera was from the center already
camera.position.copy(
direction.multiplyScalar(distance).add(boxCenter)
);
// pick some near and far values for the frustum that
// will contain the box.
camera.near = boxSize / 100;
camera.far = boxSize * 100;
camera.updateProjectionMatrix();
// point the camera to look at the center of the box
// camera.position.set(0, 150, 400);
camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
}
var loader = new THREE.GLTFLoader();
loader.load(
// resource URL
"models/gltf/DamagedHelmet/glTF/50423_ Revit Model.gltf",
// called when the resource is loaded
function(gltf) {
const root = gltf.scene;
scene.add(root);
// console.log(dumpObject(root).join("\n"));
const box = new THREE.Box3().setFromObject(root);
const boxSize = box.getSize(new THREE.Vector3()).length();
const boxCenter = box.getCenter(new THREE.Vector3());
// set the camera to frame the box
frameArea(boxSize * 1, boxSize, boxCenter, camera);
// update the Trackball controls to handle the new size
controls.maxDistance = boxSize * 10;
controls.target.copy(boxCenter);
controls.update();
},
// called while loading is progressing
function(xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
// called when loading has errors
function(error) {
debugger;
console.log("An error happened");
}
);
projector = new THREE.Projector();
// when the mouse moves, call the given function
document.addEventListener("mousemove", onDocumentMouseMove, false);
}
function onDocumentMouseMove(event) {
// update the mouse variable
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
function animate() {
requestAnimationFrame(animate);
render();
update();
}
function update() {
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3(mouse.x, mouse.y, 1);
vector.unproject(camera);
var ray = new THREE.Raycaster(
camera.position,
vector.sub(camera.position).normalize()
);
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects(scene.children, true);
// INTERSECTED = the object in the scene currently closest to the camera
// and intersected by the Ray projected from the mouse position
// if there is one (or more) intersections
if (intersects.length > 0) {
// if the closest object intersected is not the currently stored intersection object
if (intersects[0].object != INTERSECTED) {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED) {
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
}
// store reference to closest object as current intersection object
INTERSECTED = intersects[0].object;
console.log(INTERSECTED);
// store color of closest object (for later restoration)
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// set a new color for closest object
INTERSECTED.material.color.setHex(0xffff00);
}
}
// there are no intersections
else {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
controls.update();
stats.update();
}
function render() {
renderer.render(scene, camera);
}
function dumpObject(obj, lines = [], isLast = true, prefix = "") {
const localPrefix = isLast ? "└─" : "├─";
lines.push(
`${prefix}${prefix ? localPrefix : ""}${obj.name || "*no-name*"} [${
obj.type
}]`
);
const newPrefix = prefix + (isLast ? " " : "│ ");
const lastNdx = obj.children.length - 1;
obj.children.forEach((child, ndx) => {
const isLast = ndx === lastNdx;
dumpObject(child, lines, isLast, newPrefix);
});
return lines;
}
Please help me out.
I didn't read through all of the code, but I think this might already help:
In your intersection-handler, you are updating the color of the material assigned to the object (INTERSECTED.material.color.setHex(...)). This will cause the problems you describe as identical materials are very likely reused for multiple objects. To prevent that, you could use a different material:
const hightlightMaterial = new MeshStandardMaterial(...);
and instead of just updating the color, replace the material:
INTERSECTED.originalMaterial = INTERSECTED.material;
INTERSECTED.material = highlightMaterial;
Restore the original when "unhighlighting" the object:
INTERSECTED.material = INTERSECTED.originalMaterial;
delete INTERSECTED.originalMaterial;
If you need the highlightMaterial to retain other material-properties from the original, you can do this to copy over all material properties beforehand:
highlightMaterial.copy(INTERSECTED.material);
highlightMaterial.color.copy(highlightColor);

smooth terrain from height map three js

I am currently trying to create some smooth terrain using the PlaneBufferGeometry of three.js from a height map I got from Google Images:
https://forums.unrealengine.com/filedata/fetch?id=1192062&d=1471726925
but the result is kinda choppy..
(Sorry, this is my first question and evidently I need 10 reputation to post images, otherwise I would.. but here's an even better thing: a live demo! left click + drag to rotate, scroll to zoom)
I want, like i said, a smooth terrain, so am I doing something wrong or is this just the result and i need to smoothen it afterwards somehow?
Also here is my code:
const IMAGE_SRC = 'terrain2.png';
const SIZE_AMPLIFIER = 5;
const HEIGHT_AMPLIFIER = 10;
var WIDTH;
var HEIGHT;
var container = jQuery('#wrapper');
var scene, camera, renderer, controls;
var data, plane;
image();
// init();
function image() {
var image = new Image();
image.src = IMAGE_SRC;
image.onload = function() {
WIDTH = image.width;
HEIGHT = image.height;
var canvas = document.createElement('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;
var context = canvas.getContext('2d');
console.log('image loaded');
context.drawImage(image, 0, 0);
data = context.getImageData(0, 0, WIDTH, HEIGHT).data;
console.log(data);
init();
}
}
function init() {
// initialize camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, .1, 100000);
camera.position.set(0, 1000, 0);
// initialize scene
scene = new THREE.Scene();
// initialize directional light (sun)
var sun = new THREE.DirectionalLight(0xFFFFFF, 1.0);
sun.position.set(300, 400, 300);
sun.distance = 1000;
scene.add(sun);
var frame = new THREE.SpotLightHelper(sun);
scene.add(frame);
// initialize renderer
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.append(renderer.domElement);
// initialize controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = .05;
controls.rotateSpeed = .1;
// initialize plane
plane = new THREE.PlaneBufferGeometry(WIDTH * SIZE_AMPLIFIER, HEIGHT * SIZE_AMPLIFIER, WIDTH - 1, HEIGHT - 1);
plane.castShadow = true;
plane.receiveShadow = true;
var vertices = plane.attributes.position.array;
// apply height map to vertices of plane
for(i=0, j=2; i < data.length; i += 4, j += 3) {
vertices[j] = data[i] * HEIGHT_AMPLIFIER;
}
var material = new THREE.MeshPhongMaterial({color: 0xFFFFFF, side: THREE.DoubleSide, shading: THREE.FlatShading});
var mesh = new THREE.Mesh(plane, material);
mesh.rotation.x = - Math.PI / 2;
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
plane.computeFaceNormals();
plane.computeVertexNormals();
scene.add(mesh);
animate();
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
controls.update();
}
The result is jagged because the height map has low color depth. I took the liberty of coloring a portion of the height map (Paint bucket in Photoshop, 0 tolerance, non-continuous) so you can see for yourself how large are the areas which have the same color value, i.e. the same height.
The areas of the same color will create a plateau in your terrain. That's why you have plateaus and sharp steps in your terrain.
What you can do is either smooth out the Z values of the geometry or use a height map which utilizes 16bits or event 32bits for height information. The current height map only uses 8bits, i.e. 256 values.
One thing you could do to smooth things out a bit is to sample more than just a single pixel from the heightmap. Right now, the vertex indices directly correspond to the pixel position in the data-array. And you just update the z-value from the image.
for(i=0, j=2; i < data.length; i += 4, j += 3) {
vertices[j] = data[i] * HEIGHT_AMPLIFIER;
}
Instead you could do things like this:
get multiple samples with certain offsets along the x/y axes
compute an (weighted) average value from the samples
That way you would get some smoothing at the borders of the same-height areas.
The second option is to use something like a blur-kernel (gaussian blur is horribly expensive, but maybe something like a fast box-blur would work for you).
As you are very limited in resolution due to just using a single byte, you should convert that image to float32 first:
const highResData = new Float32Array(data.length / 4);
for (let i = 0; i < highResData.length; i++) {
highResData[i] = data[4 * i] / 255;
}
Now the data is in a format that allows for far higher numeric resolution, so we can smooth that now. You could either adjust something like the StackBlur for the float32 use-case, use ndarrays and ndarray-gaussian-filter or implement something simple yourself. The basic idea is to find an average value for all the values in those uniformly colored plateaus.
Hope that helps, good luck :)

How to use a number of pictures to form a graphic in three.js

I want to use three.js to generate a shape(maybe some word) composed of some pictures like that:
I think what i have to do is get some points which form a shape, then put the picture to these points. I have searched for something information , but i still have no idea how can i get these points because the shape maybe irregular. Is there any solutions?
The way I see it, you have two ways to proceed here:
You can use a modeling software like Blender to first generate the shape along with the pictures, and then export the JSON (refer this for how to setup the threejs json exporter in blender ) and then use the JSON loader to load that JSON.
The other way is that you create simple geometries of your requried shape using the ones threejs provides like box, circle, etc ( refer docs ) and then add textures to it as shown here .
Hope one of these solutions is what you're looking for.
I would use the canvas to plot the 3d positions of each photo.
I created a fiddle here with text:
https://jsfiddle.net/2qu6m4h3/
And one with random shapes:
https://jsfiddle.net/d2th9ekb/
It creates a canvas element to draw text to. It interrogates the canvas for pixel positions. Those positions are then sent to a function which places cubes in 3d. Rather than place cubes you could place sprite objects which display each one of your photos. Use the scale property to give yourself more room between positions.
Here's the code:
/*
Start Setup text canvas and tools
*/
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.canvas.width = 400;
ctx.canvas.height = 200;
function createTextSourceCanvas(text,src) {
src.font = '50pt Times';
src.fillStyle = '#00ff00';
src.fillRect(0, 0, canvas.width, canvas.height);
src.fillStyle = '#FFFFFF';
src.textAlign = "center";
src.textBaseline = "middle";
src.fillText(text, canvas.width /2, canvas.height /2);
}
function examineText(src, fi){
var positiveResults = [];
var width = src.canvas.width;
var height = src.canvas.height;
var imgData = src.getImageData(0, 0,width, height);
for(var x = 0; x < width; x+=fi){
for(var y = 0; y < height; y+=fi ){
var pixel = getPixelXY(imgData, x, y)
if(pixel[0] == 0 && pixel[1] == 255 && pixel[2] == 0){
continue;
}
positiveResults.push([x,y]);
}
}
return positiveResults;
}
function getPixel(imgData, index) {
var i = index*4, d = imgData.data;
return [d[i],d[i+1],d[i+2],d[i+3]]
}
function getPixelXY(imgData, x, y) {
return getPixel(imgData, y*imgData.width+x);
}
/*
End Setup text canvas and tools
*/
/*
Start Setup Threejs canvas and tools
*/
var scene;
var renderer;
var camera;
var cube;
var controls;
function init3d(){
renderer = new THREE.WebGLRenderer( {antialias:true} );
var width = window.innerWidth;
var height = window.innerHeight;
renderer.setSize (width, height);
document.body.appendChild (renderer.domElement);
scene = new THREE.Scene()
camera = new THREE.PerspectiveCamera (45, width/height, 1, 10000);
camera.position.y = 160;
camera.position.z = 400;
camera.lookAt (new THREE.Vector3(0,0,0));
controls = new THREE.OrbitControls (camera, renderer.domElement);
var gridXZ = new THREE.GridHelper(100, 10);
scene.add(gridXZ);
var pointLight = new THREE.PointLight (0xffffff);
pointLight.position.set (0,300,200);
scene.add (pointLight);
window.addEventListener ('resize', onWindowResize, false);
}
function onWindowResize (){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize (window.innerWidth, window.innerHeight);
}
function animate(){
controls.update();
requestAnimationFrame ( animate );
renderer.render (scene, camera);
}
/*
End Setup Threejs canvas and tools
*/
/*
Start create 3d from text examination
*/
function create3dProjectionText(scene, positions, wExtent, hExtent, scale){
if(scale == undefined){
scale = 1;
}
var group = new THREE.Object3D();
var cubeGeometry = new THREE.BoxGeometry (2,2,2);
var cubeMaterial = new THREE.MeshLambertMaterial ({color: 0xFFFFFF});
for(var i in positions){
cube = new THREE.Mesh (cubeGeometry, cubeMaterial);
cube.position.set (positions[i][0]*scale - (wExtent*scale/2), positions[i][1]*scale -(hExtent*scale/2), 0);
group.add (cube);
}
group.rotateX( -Math.PI );
scene.add(group);
}
/*
End create 3d from text examination
*/
//initialize the 3d space
init3d();
//initialize the text canvas
createTextSourceCanvas("Hello World", ctx);
//
create3dProjectionText(scene, examineText(ctx ,4), ctx.canvas.width, ctx.canvas.height, 1.5);
animate();

Incrementally display three.js TubeGeometry

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.

How to add markers as sprite bitmap Panorama viewer in three.js

We have developed a panorama viewer in three.js. A texture bitmap is added to Sphere and camera position is moved inside. We want to add markers into this as sprites.
When we add the sprite to the scene it is not getting rendered. Why doesn't the sprite get rendered?
Here is the solution:
function onDocumentMouseDown(event) {
var interSects = castScreenToSphere(event);
if (interSects.length > 0)
MarkAnotation(interSects[0].point);
}
function castScreenToSphere(event) {
var mouse3D = new THREE.Vector3();
mouse3D.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse3D.y = -(event.clientY / window.innerHeight) * 2 + 1;
mouse3D.z = 0.5;
projector.unprojectVector(mouse3D, camera);
var ray = new THREE.Raycaster(camera.position, mouse3D.sub(camera.position).normalize());
var res = ray.intersectObject(mesh);
return res;
}
function MarkAnotation(anot) {
var ballTexture = THREE.ImageUtils.loadTexture('/map_sprite.png');
var ballMaterial = new THREE.SpriteMaterial({ map: ballTexture });
var ballSprite = new THREE.Sprite(ballMaterial);
ballSprite.scale.set(16, 16, 0.5);
ballSprite.position.set(anot.x, anot.y, anot.z);
//ballSprite.position.multiplyScalar(0.9);
scene.add(ballSprite);
}

Categories

Resources