I'm working on a toy Three.js scene in which I want to follow a sphere with a camera [demo]. Right now, though, I can't figure out how to make the sphere "roll" without also rotating the camera.
Here's the code I use to update the sphere's position each frame:
function moveSphere() {
var delta = clock.getDelta(); // seconds
var moveDistance = 200 * delta; // 200 pixels per second
var rotateAngle = Math.PI / 2 * delta; // pi/2 radians (90 deg) per sec
// move forwards/backwards/left/right
if ( pressed['W'] ) {
sphere.translateZ( -moveDistance );
}
if ( pressed['S'] )
sphere.translateZ( moveDistance );
if ( pressed['Q'] )
sphere.translateX( -moveDistance );
if ( pressed['E'] )
sphere.translateX( moveDistance );
// rotate left/right/up/down
var rotation_matrix = new THREE.Matrix4().identity();
if ( pressed['A'] )
sphere.rotateOnAxis(new THREE.Vector3(0,1,0), rotateAngle);
if ( pressed['D'] )
sphere.rotateOnAxis(new THREE.Vector3(0,1,0), -rotateAngle);
if ( pressed['R'] )
sphere.rotateOnAxis(new THREE.Vector3(1,0,0), rotateAngle);
if ( pressed['F'] )
sphere.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle);
}
And the code to follow the sphere each tick of time:
function moveCamera() {
var relativeCameraOffset = new THREE.Vector3(0,50,200);
var cameraOffset = relativeCameraOffset.applyMatrix4(sphere.matrixWorld);
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
camera.lookAt(sphere.position);
}
Is there an easy way to make the ball roll without making the camera spiral all over the place? Inside of the if (pressed['W']) block, I tried various permutations of sphere.rotateOnAxis(new THREE.Vector3(0,0,1), rotateAngle); but haven't found a natural way to make the ball roll. I would be very grateful for any advice others can offer on this!
Your issue is this line:
var cameraOffset = relativeCameraOffset.applyMatrix4(sphere.matrixWorld);
This takes the offset you specify and applies not only the sphere position, but it's rotation as well. For example, if your sphere is rotated 180 degrees on the Y-axis, the resulting vector is (0, 50, -200) + (sphere position).
You need to extract the translation component from the sphere matrix, and apply it to the offset. The code below uses an intermediate matrix to store the position of the sphere.
/**
* Follow the sphere
**/
var sphereTranslation = new THREE.Matrix4(); // only make it once to reduce overhead
function moveCamera() {
var relativeCameraOffset = new THREE.Vector3(0,50,200);
sphereTranslation.copyPosition(sphere.matrixWorld); // get sphere position only
var cameraOffset = relativeCameraOffset.applyMatrix4(sphereTranslation);
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
camera.lookAt(sphere.position);
}
The key here was to create a sphere, then add that sphere to a group, so that I could translate and rotate the group (which controls the ball's position) while also rotating the sphere inside the group (which allows the ball to "roll"). Separating these entities out allows the camera to follow the sphere group just as before while allowing the ball to rotate independently of the sphere group's movement [updated demo]:
/**
* Generate a scene object with a background color
**/
function getScene() {
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);
return scene;
}
/**
* Generate the camera to be used in the scene. Camera args:
* [0] field of view: identifies the portion of the scene
* visible at any time (in degrees)
* [1] aspect ratio: identifies the aspect ratio of the
* scene in width/height
* [2] near clipping plane: objects closer than the near
* clipping plane are culled from the scene
* [3] far clipping plane: objects farther than the far
* clipping plane are culled from the scene
**/
function getCamera() {
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 10000);
camera.position.set(0,150,400);
camera.lookAt(scene.position);
return camera;
}
/**
* Generate the light to be used in the scene. Light args:
* [0]: Hexadecimal color of the light
* [1]: Numeric value of the light's strength/intensity
* [2]: The distance from the light where the intensity is 0
* #param {obj} scene: the current scene object
**/
function getLight(scene) {
var lights = [];
lights[0] = new THREE.PointLight( 0xffffff, 0.6, 0 );
lights[0].position.set( 100, 200, 100 );
scene.add( lights[0] );
var ambientLight = new THREE.AmbientLight(0x111111);
scene.add(ambientLight);
return light;
}
/**
* Generate the renderer to be used in the scene
**/
function getRenderer() {
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
// Add support for retina displays
renderer.setPixelRatio(window.devicePixelRatio);
// Specify the size of the canvas
renderer.setSize(window.innerWidth, window.innerHeight);
// Add the canvas to the DOM
document.body.appendChild(renderer.domElement);
return renderer;
}
/**
* Generate the controls to be used in the scene
* #param {obj} camera: the three.js camera for the scene
* #param {obj} renderer: the three.js renderer for the scene
**/
function getControls(camera, renderer) {
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
return controls;
}
/**
* Get grass
**/
function getPlane(scene, loader) {
var texture = loader.load('grass.jpg');
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 10, 10 );
var material = new THREE.MeshBasicMaterial({
map: texture, side: THREE.DoubleSide
});
var geometry = new THREE.PlaneGeometry(1000, 1000, 10, 10);
var plane = new THREE.Mesh(geometry, material);
plane.position.y = -0.5;
plane.rotation.x = Math.PI / 2;
scene.add(plane);
return plane;
}
/**
* Add background
**/
function getBackground(scene, loader) {
var imagePrefix = 'sky-parts/';
var directions = ['right', 'left', 'top', 'bottom', 'front', 'back'];
var imageSuffix = '.bmp';
var geometry = new THREE.BoxGeometry( 1000, 1000, 1000 );
var materialArray = [];
for (var i = 0; i < 6; i++)
materialArray.push( new THREE.MeshBasicMaterial({
map: loader.load(imagePrefix + directions[i] + imageSuffix),
side: THREE.BackSide
}));
var sky = new THREE.Mesh( geometry, materialArray );
scene.add(sky);
}
/**
* Add a character
**/
function getSphere(scene) {
var geometry = new THREE.SphereGeometry( 30, 12, 9 );
var material = new THREE.MeshPhongMaterial({
color: 0xd0901d,
emissive: 0xaf752a,
side: THREE.DoubleSide,
flatShading: true
});
var sphere = new THREE.Mesh( geometry, material );
// create a group for translations and rotations
var sphereGroup = new THREE.Group();
sphereGroup.add(sphere)
sphereGroup.position.set(0, 24, 100);
scene.add(sphereGroup);
return [sphere, sphereGroup];
}
/**
* Store all currently pressed keys
**/
function addListeners() {
window.addEventListener('keydown', function(e) {
pressed[e.key.toUpperCase()] = true;
})
window.addEventListener('keyup', function(e) {
pressed[e.key.toUpperCase()] = false;
})
}
/**
* Update the sphere's position
**/
function moveSphere() {
var delta = clock.getDelta(); // seconds
var moveDistance = 200 * delta; // 200 pixels per second
var rotateAngle = Math.PI / 2 * delta; // pi/2 radians (90 deg) per sec
// move forwards/backwards/left/right
if ( pressed['W'] ) {
sphere.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle)
sphereGroup.translateZ( -moveDistance );
}
if ( pressed['S'] )
sphereGroup.translateZ( moveDistance );
if ( pressed['Q'] )
sphereGroup.translateX( -moveDistance );
if ( pressed['E'] )
sphereGroup.translateX( moveDistance );
// rotate left/right/up/down
var rotation_matrix = new THREE.Matrix4().identity();
if ( pressed['A'] )
sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), rotateAngle);
if ( pressed['D'] )
sphereGroup.rotateOnAxis(new THREE.Vector3(0,1,0), -rotateAngle);
if ( pressed['R'] )
sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), rotateAngle);
if ( pressed['F'] )
sphereGroup.rotateOnAxis(new THREE.Vector3(1,0,0), -rotateAngle);
}
/**
* Follow the sphere
**/
function moveCamera() {
var relativeCameraOffset = new THREE.Vector3(0,50,200);
var cameraOffset = relativeCameraOffset.applyMatrix4(sphereGroup.matrixWorld);
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
camera.lookAt(sphereGroup.position);
}
// Render loop
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
moveSphere();
moveCamera();
};
// state
var pressed = {};
var clock = new THREE.Clock();
// globals
var scene = getScene();
var camera = getCamera();
var light = getLight(scene);
var renderer = getRenderer();
// add meshes
var loader = new THREE.TextureLoader();
var floor = getPlane(scene, loader);
var background = getBackground(scene, loader);
var sphereData = getSphere(scene);
var sphere = sphereData[0];
var sphereGroup = sphereData[1];
addListeners();
render();
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>
Related
I am working with ThreeJS to create a solar system. I have a sun in the middle and 8 orbits around it. Now I want to get the nearest ring poisiton when the users clicks anywhere on the map!
Here is an image to describe it visually what I mean
The arrows stands for the "click" of the user, then there should be a function to get the nearest orbit and its coordinates (the white dots) where the line between the click point and middle collides.
I tried many different functions I found here, but non of them gave me the result I want.
Thanks for your help!
The code looks currently like this:
var container, stats, parent, pivots, domEvents, twins, planets, sun, fleets, raycaster, mouse;
var camera, controls, scene, renderer;
var cross;
planets = new Array();
init();
animate();
function init()
{
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
//init
camera = new THREE.PerspectiveCamera(10, 1, 1, 4000);
camera.position.z = 200;
camera.position.x = 200;
camera.position.y = 200;
controls = new THREE.OrbitControls(camera);
controls.addEventListener('change', render);
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0);
// renderer
renderer = new THREE.WebGLRenderer({antialias: false, alpha: true});
renderer.setSize(document.getElementById('canvasreference').offsetWidth, document.getElementById('canvasreference').offsetWidth);
renderer.setClearColor(0x787878, 0.5); // the default
container = document.getElementById('canvasreference');
container.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
domEvents = new THREEx.DomEvents(camera, renderer.domElement);
//axihelper
scene.add(new THREE.AxisHelper(130));
// parent
parent = new THREE.Object3D();
scene.add(parent);
//arrays
orbits = new Array();
addOrbit();
window.addEventListener('click', onMouseMove, false);
}
function onMouseMove(event) {
canvas = renderer.domElement;
raycaster = new THREE.Raycaster();
mousePosition = new THREE.Vector2();
canvasPosition = $("#canvasreference canvas").position();
console.log(canvasPosition);
mousePosition.x = ((event.clientX - canvasPosition.left) / canvas.width) * 2 - 1;
mousePosition.y = -((event.clientY - canvasPosition.top) / canvas.height) * 2 + 1;
raycaster.setFromCamera(mousePosition, camera);
var geometry = new THREE.Geometry();
var origin = new THREE.Vector3(raycaster.ray.origin.x, 0, raycaster.ray.origin.y);
geometry.vertices.push(origin);
var vektor = new THREE.Vector3(raycaster.ray.direction.x, 0, raycaster.ray.direction.y);
for (var i = 0; i < 1000; i++)
{
origin.add(vektor);
geometry.vertices.push(vektor);
}
var material = new THREE.LineBasicMaterial({
color: 0xffffff, linewidth: 20
});
var line = new THREE.Line(geometry, material);
scene.add(line);
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(600, 600);
render();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
function render() {
renderer.render(scene, camera);
}
/**
* add Orbit line
* #param {type} orbit
* #returns {undefined}
*/
function addOrbit(orbit)
{
for (var i = 0; i < 8; i++)
{
//make orbit line
var orbit = new THREE.EllipseCurve(
0, 0, // ax, aY
i * 10 + 30, i * 10 + 30, // xRadius, yRadius
0, 2 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
);
var path = new THREE.Path(orbit.getPoints(100));
var geometry = path.createPointsGeometry(100);
var material = new THREE.LineBasicMaterial({color: 0xffffff});
var ellipse = new THREE.Line(geometry, material);
ellipse.rotation.x = 1.5708;
scene.add(ellipse);
}
}
</script>
Cast a ray from your camera to a point where your mouse is. Then check closest distance from that ray to the middle of you solar system using distanceToPoint function. The length of output vector will be the radius of a sphere to which your ray is tangent. Using this length you can determine how close you are to a sphere that is an orbit and if it should be selected. Here's some pseudo code of that check:
var length = getDistanceToCenter(...);
var closestSphere = _(orbits).min(function(orbit) { return Math.abs(length - orbit.radius); });
if (Math.abs(closestSphere.radius - length) < EPSILON) {
selectOrbit(closestSphere);
}
I can't make it work. on the console, it says that the click event work,
but i cant link the sphere with an url.
Iam using last version of three.js
here is my code : `
// once everything is loaded, we run our Three.js stuff.
$(function () {
var geometry, material, mesha;
var clock = new THREE.Clock();
var raycaster;
var stats = initStats();
// create a scene, that will hold all our elements such as objects, cameras and lights.
var scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render and set the size
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
// position and point the camera to the center of the scene
camera.position.x = 100;
camera.position.y = 10;
camera.position.z = 1000;
camera.lookAt(new THREE.Vector3(0, 0, 0));
var camControls = new THREE.FirstPersonControls(camera);
camControls.lookSpeed = 0.4;
camControls.movementSpeed = 30;
camControls.noFly = true;
camControls.lookVertical = true;
camControls.constrainVertical = false;
camControls.verticalMin = 0.0;
camControls.verticalMax = 1.0;
camControls.lon = -150;
camControls.lat = 120;
var ambientLight = new THREE.AmbientLight(0x383838);
scene.add(ambientLight);
// add spotlight for the shadows
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(100, 140, 130);
spotLight.intensity = 10;
scene.add(spotLight);
// add the output of the renderer to the html element
$("#WebGL-output").append(webGLRenderer.domElement);
// call the render function
var step = 0;
// setup the control gui
var controls = new function () {
// we need the first child, since it's a multimaterial
}
var domEvents = new THREEx.DomEvents(camera, webGLRenderer.domElement)
//////////////////////////////////////////////////////////////////////////////////
// add an object and make it move //
//////////////////////////////////////////////////////////////////////////////////
var geometry = new THREE.SphereGeometry( 100.5 )
var material = new THREE.MeshNormalMaterial()
var mesh = new THREE.Mesh( geometry, material )
scene.add( mesh )
//////////////////////////////////////////////////////////////////////////////////
// linkify our cube //
//////////////////////////////////////////////////////////////////////////////////
var url = 'http://jeromeetienne.github.io/threex/'
THREEx.Linkify(domEvents, mesh, url)
domEvents.addEventListener(mesh, 'click', function(event){
console.log('you clicked on mesh', mesh)
}, false)
var gui = new dat.GUI();
var mesh;
var onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
var percentComplete = xhr.loaded / xhr.total * 100;
console.log( Math.round(percentComplete, 2) + '% downloaded' );
}
};
var onError = function ( xhr ) { };
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setBaseUrl( '../assets/models/door/' );
mtlLoader.setPath( '../assets/models/door/' );
mtlLoader.load( 'door.mtl', function( materials ) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials( materials );
objLoader.setPath( '../assets/models/door/' );
objLoader.load( 'door.obj', function ( object ) {
object.position.y = -1;
object.scale.x = 2;
object.scale.y = 2;
object.scale.z = 2;
scene.add( object );
}, onProgress, onError );
});
// floor
geometry = new THREE.PlaneGeometry( 2000, 2000, 100, 100 );
geometry.rotateX( - Math.PI / 2 );
for ( var i = 0, l = geometry.vertices.length; i < l; i ++ ) {
var vertex = geometry.vertices[ i ];
vertex.x += Math.random() * 20 - 10;
vertex.y += Math.random() * 3;
vertex.z += Math.random() * 20 - 10;
}
for ( var i = 0, l = geometry.faces.length; i < l; i ++ ) {
var face = geometry.faces[ i ];
face.vertexColors[ 0 ] = new THREE.Color().setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
face.vertexColors[ 1 ] = new THREE.Color().setHSL( Math.random() * 0.3 + 10.5, 0.75, Math.random() * 0.25 + 0.75 );
face.vertexColors[ 2 ] = new THREE.Color().setHSL( Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75 );
}
material = new THREE.MeshBasicMaterial( { vertexColors: THREE.VertexColors } );
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
function setCamControls() {
}
render();
function setRandomColors(object, scale) {
var children = object.children;
if (children && children.length > 0) {
children.forEach(function (e) {
setRandomColors(e, scale)
});
} else {
// no children assume contains a mesh
if (object instanceof THREE.Mesh) {
object.material.color = new THREE.Color(scale(Math.random()).hex());
if (object.material.name.indexOf("building") == 0) {
object.material.emissive = new THREE.Color(0x444444);
object.material.transparent = true;
object.material.opacity = 0.8;
}
}
}
}
function render() {
stats.update();
var delta = clock.getDelta();
if (mesh) {
// mesh.rotation.y+=0.006;
}
camControls.update(delta);
webGLRenderer.clear();
// render using requestAnimationFrame
requestAnimationFrame(render);
webGLRenderer.render(scene, camera)
}
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
// Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
$("#Stats-output").append(stats.domElement);
return stats;
}
});
can somebody help ? have you an idea?
Iam quite a newbie...
Solution:
Get the latest version of DomEvents (https://github.com/jeromeetienne/threex.domevents) which has been updated to r74 right now.
Explaination:
This one took me some time to figure out, lets see whats going on:
THREEx.DomEvents is internally using the THREE.Raycaster to detect when the mouse is pointing on meshes. ThreeJS changed lately the behavior of the raycaster to not intersect with invisible meshes anymore (source). Yeah well I dont care, my click event is fireing you say?
Lets have a look at Linkyfy:
THREEx.Linkify = function(domEvents, mesh, url, withBoundingBox){
withBoundingBox = withBoundingBox !== undefined ? withBoundingBox : true
// create the boundingBox if needed
if( withBoundingBox ){
var boundingBox = new THREE.Mesh(new THREE.CubeGeometry(1,1,1), new THREE.MeshBasicMaterial({
wireframe : true
}))
boundingBox.visible = false
boundingBox.scale.copy(size)
mesh.add(boundingBox)
}
// ...
The fourth parameter withBoundingBox is evaluating to true if you dont supply it. Linkify is then creating a "bounding box mesh" which is invisible and wrapping around your mesh you want to linkify. The raycaster does not trigger an intersection anymore and there you have it. To allow the raycaster to detect an intersection although the boundingBox-object is invisible, set only the meshs materials visibility to false and not the mesh:
boundingBox.material.visible = false
I have been struggling with issues concerning raycasting on small circlegeometries on a sphere.
I know raycasting can't be done with sprites and this is why I use circlegeometries, but it doesn't work all the time, and moreover the raycasting doesn't always work on circles but sometimes around them as well.
Does anybody have an idea ? Here is a JSBin to show you basically
Edit :
I updated my previous version of JSBin, you can click any circleGeometries it will work here, run it with output tab only open for better results
This is related to the renderer width and height properties, my sphere isn't in fullscreen and this is why it fails.
Does anybody have an idea on how to set up it right in order to get this to work perfectly ?
The formula used to compute intersections wasn't the good one, here is the one that works :
mouse.x = ( ( event.clientX - renderer.domElement.offsetLeft ) / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( ( event.clientY - renderer.domElement.offsetTop ) / renderer.domElement.height ) * 2 + 1;
mouse x and y have slightly changed from the examples you can get, and are now fine.
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
projector.unprojectVector(vector, camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(
camera.position).normalize());
var intersects = ray.intersectObjects(objects);
if ( intersects.length > 0 ) {
//do something
}
if you looking for some thing like this....Your code might need little changes... check this link http://jsfiddle.net/ebeit303/rjJ6q/
// standard global variables
var container, scene, camera, renderer, controls, stats;
var clock = new THREE.Clock();
// custom global variables
var targetList = [];
var projector, mouse = { x: 0, y: 0 };
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 = 100000;
camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
scene.add(camera);
camera.position.set(600,0,-1200);
camera.lookAt(scene.position);
// RENDERER
renderer = new THREE.CanvasRenderer();
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.getElementById( 'ThreeJS' );
container.appendChild( renderer.domElement );
// EVENTS
// CONTROLS
// this material causes a mesh to use colors assigned to faces
var faceColorMaterial = new THREE.MeshBasicMaterial(
{ color: 0xffffff, vertexColors: THREE.FaceColors } );
var sphereGeometry = new THREE.SphereGeometry( 500, 64, 64 );
for ( var i = 0; i < sphereGeometry.faces.length; i++ )
{
face = sphereGeometry.faces[ i ];
face.color.setRGB( 0, 0, 0.8 * Math.random() + 0.2 );
}
var sphere = new THREE.Mesh( sphereGeometry, faceColorMaterial );
sphere.rotation.set(0, 14.5, 0);
scene.add(sphere);
//targetList.push(sphere);
var j=0;
for (var i =0; i<100;i+=5){
//var circle = new THREE.CubeGeometry(5,5,5);
var circle = new THREE.CircleGeometry(5, 8, 0, Math.PI * 2);
//THREE.GeometryUtils.triangulateQuads(circle);
var circleMaterial = new THREE.MeshBasicMaterial({color: 0xDEF2EF});
circleMaterial.side = THREE.DoubleSide;
var mesh = new THREE.Mesh(circle, circleMaterial);
var Alon = i - 90;
var Alat = j;
var Aphi = Math.PI/2 - Alat * Math.PI / 180;
var Atheta = 2 * Math.PI - Alon * Math.PI / 180;
mesh.position.x = Math.sin(Aphi) * Math.cos(Atheta) * (501);
mesh.position.y = Math.cos(Aphi) * (501);
mesh.position.z = Math.sin(Aphi) * Math.sin(Atheta) * (501);
mesh.verticesNeedUpdate = true;
mesh.lookAt( sphere.position );
sphere.add(mesh);
targetList.push(mesh);
j++;
}
// initialize object to perform world/screen calculations
projector = new THREE.Projector();
// when the mouse moves, call the given function
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( event )
{
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersects = ray.intersectObjects( targetList );
if ( intersects.length > 0 )
{
intersects[ 0 ].object.material.color.setRGB( 0.8 * Math.random() + 0.2,
0.8 * Math.random() + 0.2,
0.8 * Math.random() + 0.2 );
}
}
function animate()
{
requestAnimationFrame( animate );
render();
}
function render()
{
renderer.render( scene, camera );
}
I have used the STL Loader for threejs to import my object from Blender into threejs but for some reason, the textures don't appear. I assume it has to do with the object's MeshBasicMaterial. I tried changing it to Phong but the object turns black instead.
Then I realized, instead of changing the material from basic to phong, why can't I kept the inherent features/textures from Blender when I load the object from STL loader to threejs? Is there a way to keep the texture? Is there a tutorial I can follow?
THanks!
EDIT:
Here's a snippet of my code where I exported my Blender object with textures to a threejs .js file. The object loads in my viewer but it's black with no textures. I'm not sure how to fix this. I followed another example that used an inn for texture (hence the name. Didn't change the name yet).
var Viewport = function ( signals ) {
var container = new UI.Panel( 'absolute' );
container.setBackgroundColor( '#aaa' );
var clock = new THREE.Clock();
// settings
var enableHelpersFog = true;
// helpers
var objects = [];
var INTERSECTED;
var sceneHelpers = new THREE.Scene();
//the grid that appears at the beginning.
var size = 500, step = 25;
var geometry = new THREE.Geometry();
var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );
var color1 = new THREE.Color( 0x444444 ), color2 = new THREE.Color( 0x888888 );
for ( var i = - size; i <= size; i += step ) {
geometry.vertices.push( new THREE.Vector3( -size, 0, i ) );
geometry.vertices.push( new THREE.Vector3( size, 0, i ) );
geometry.vertices.push( new THREE.Vector3( i, 0, -size ) );
geometry.vertices.push( new THREE.Vector3( i, 0, size ) );
var color = i === 0 ? color1 : color2;
geometry.colors.push( color, color, color, color );
}
var grid = new THREE.Line( geometry, material, THREE.LinePieces );
sceneHelpers.add( grid );
///Objects stuff
var objectshapes = [];
//stl files
group = new THREE.Object3D();
var loader_slotted_disk = new THREE.STLLoader();
loader_slotted_disk.addEventListener( 'load', function ( event ) {
var geometry = event.content;
console.log(geometry);
var material = new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 });
var mesh1 = new THREE.Mesh( geometry, material );
mesh1.name='slotted disk';
mesh1.castShadow = true;
mesh1.receiveShadow = true;
sceneHelpers.add( mesh1 );
objectshapes.push( mesh1 );
} );
loader_slotted_disk.load( 'js/ui/assests/slotted_disk.stl' );
var loader_left_bronchus = new THREE.STLLoader();
loader_left_bronchus.addEventListener( 'load', function ( event ) {
var geometry = event.content;
console.log(geometry);
var material = new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 });
var mesh1 = new THREE.Mesh( geometry, material );
mesh1.name='left bronchus';
mesh1.castShadow = true;
mesh1.receiveShadow = true;
mesh1.position.x = Math.random() * 200 - 50;
mesh1.position.y = Math.random() * 200 - 50;
mesh1.position.z = Math.random() * 200 - 50;
sceneHelpers.add( mesh1 );
objectshapes.push( mesh1 );
} );
loader_left_bronchus.load( 'js/ui/assests/left_bronchus.stl' );
var loader_parenchyma = new THREE.STLLoader();
loader_parenchyma.addEventListener( 'load', function ( event ) {
var geometry = event.content;
console.log(geometry);
var material = new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 });
var mesh2 = new THREE.Mesh( geometry, material );
mesh2.name='parenchyma';
mesh2.castShadow = true;
mesh2.receiveShadow = true;
sceneHelpers.add( mesh2 );
objectshapes.push( mesh2 );
} );
//loader_parenchyma.load( 'js/ui/assests/parenchyma.stl' );
//group.rotation.x = Math.PI* 3/2
////////////
// CUSTOM //
////////////
var inn_loader = new THREE.JSONLoader(); inn_loader.load("js/reducedandcoloredmodel[Conflict].js", function(geo, material) { var materials = new THREE.MeshFaceMaterial(material); inn = new THREE.Mesh(geo, materials); scene.add(inn); }); //
///stl files added
var geometry = new THREE.CubeGeometry( 50, 50, 50 );
var geometry2 = new THREE.SphereGeometry( 50, 10, 10);
var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
object.name = "cube";
object.position.x = Math.random() * 200 - 50;
object.position.y = Math.random() * 200 - 50;
object.position.z = Math.random() * 200 - 50;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
sceneHelpers.add( object );
objectshapes.push( object );
var object2 = new THREE.Mesh( geometry2, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
object2.name = "sphere";
object2.position.x = Math.random() * 200 - 50;
object2.position.y = Math.random() * 200 - 50;
object2.position.z = Math.random() * 200 - 50;
object2.rotation.x = Math.random() * 2 * Math.PI;
object2.rotation.y = Math.random() * 2 * Math.PI;
object2.rotation.z = Math.random() * 2 * Math.PI;
sceneHelpers.add( object2 );
objectshapes.push( object2 );
console.log(objectshapes);
///box around object
var selectionBox = new THREE.Mesh( new THREE.CubeGeometry( 1, 1, 1 ), new THREE.MeshBasicMaterial( { color: 0xffff00, wireframe: true, fog: false } ) );
selectionBox.matrixAutoUpdate = false;
selectionBox.visible = false;
sceneHelpers.add( selectionBox );
///axis
var selectionAxis = new THREE.AxisHelper( 100 );
selectionAxis.material.depthTest = false;
selectionAxis.material.transparent = true;
selectionAxis.matrixAutoUpdate = false;
selectionAxis.visible = false;
sceneHelpers.add( selectionAxis );
// default dummy scene and camera
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 50, 1, 1, 5000 );;
// fog
var oldFogType = "None";
var oldFogColor = 0xaaaaaa;
var oldFogNear = 1;
var oldFogFar = 5000;
var oldFogDensity = 0.00025;
// object picking
var intersectionPlane = new THREE.Mesh( new THREE.PlaneGeometry( 10000, 10000, 8, 8 ) );
intersectionPlane.visible = false;
sceneHelpers.add( intersectionPlane );
var ray = new THREE.Raycaster();
var projector = new THREE.Projector();
var offset = new THREE.Vector3();
var cameraChanged = false;
var helpersVisible = true;
//by default the selected scene object is the camera.
var picked = null;
var selected = camera;
mr doob already points out that stl doesn't specify color or texture, it is pure geometry. I suggest exporting something trivial from blender (like a cube with textures) with the three.js exporter, to get an understanding of what's going on. I have example code at threejs-and-blender. View the source of those threejs demos and look at which files are being pulled in, you should find references to images too.
Once you have that working, figuring out the more complex model becomes easier. Plus if you don't want to share you original model you should have no trouble sharing a simple cube for debugging.
While I didn't use the JSON Loader, I tried to use the Collada Loader (as my model retained textures offline when imported in the Webgl loader collada example) and inside this current 3-D viewer, my model was still black. However, after adding a light, the textures were shown.
Essentially, I'm assuming just as well with the JSON Loader, I need to add a light inside my 3-D viewer for the model's exterior to show.
I had the same issue using Blender v 2.68a. The problem was solved with Blender 2.71.
I imported, using Blender 2.71, the .stl file which was previously shown black on WebGl, then exported it without changing any setting parameter from the default .stl exporter menu.
I reloaded the file with WebGl and there it was, full colour!
I'm trying to print text along an arc using Three.js. With help from this post, I was able to get the math for the rotation. But, as you'll see, the text is all jumbled on top of itself. How can I get it to correctly space itself out? Codepen is here.
var container, camera, scene, renderer;
function init() {
container = document.getElementById( 'canvas' );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(50, window.innerWidth/window.innerHeight, 1, 100000);
camera.position.z = 500;
scene.add(camera);
var loader = new THREE.FontLoader();
loader.load( 'https://raw.githubusercontent.com/mrdoob/three.js/master/examples/fonts/helvetiker_bold.typeface.json', function ( font ) {
var theText = "This is some text."
var numRadsPerChar = 2*Math.PI/theText.length;
for (var i = 0; i < theText.length; i++){
var char = theText[i]
var geometry = new THREE.TextBufferGeometry( char, {
font: font,
size: 60,
height: 60,
curveSegments: 20
});
var materials = [
new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, overdraw: 0.5 } ),
new THREE.MeshBasicMaterial( { color: 0x000000, overdraw: 0.5 } )
];
var mesh = new THREE.Mesh( geometry, materials );
mesh.rotation.z = (i * numRadsPerChar);
//mesh.position.x = i * 20;
group = new THREE.Group();
group.add( mesh );
scene.add( group );
};
} );
renderer = new THREE.WebGLRenderer({alpha: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
animate();
} init();
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
Had some help figuring this out, but here's the portion that needed to be edited to work:
mesh.rotation.z = (i * numRadsPerChar);
Needed to be:
mesh.position.x = 100 * Math.sin(i * numRadsPerChar);
mesh.position.y = 100 * Math.cos(i * numRadsPerChar);
mesh.rotation.z = Math.PI/2 -(i * numRadsPerChar);
Basically, I needed to add a constant (Math.PI/2) for the rotation. For the x and y positions, I had to take the sin and cosine, respectively, to get the letters to be placed properly around the arc. Here's a working codepen.
Another way to do this would be to draw your text on a canvas and use the canvas in a texture with texture.wrapS and .wrapT set to THREE.RepeatWrapping.. then put that texture on a material.. then make a path with the extruded path geometry and set it's material. The you can move the text by setting the texture.offset.x or y each frame in your render loop.