readRenderTargetPixels results all zeroes - three.js

The problem:
I'm trying to read the pixel values from a render target, but the values are all coming back as zeroes. Am I doing something wrong, or is this functionality broken for the version I'm using?
What I have:
An important note is that I'm stuck using r76. I know, I know, it upsets me, too.
The code below is literally all I have. Just click the "GO" button in the upper-right, and it will produce an alert that says whether or not the entire render target buffer was zeroes or not.
What I've tried:
I did try it with the latest three.js (r86), and the exact same code works correctly.
I also tried accounting for any async issues, by arranging my render function to either render to the canvas (works fine), or to the render target (still all zeroes), based on a flag I set outside the render loop.
function render() {
light.position.copy(camera.position);
if(doRenderTarget){
toRenderTarget();
doRenderTarget = false;
}
else{
renderer.clear();
renderer.render(scene, camera);
}
controls.update();
}
The code:
Using r76:
var hostDiv, scene, renderer, camera, controls, light;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var target = new THREE.WebGLRenderTarget();
function toRenderTarget() {
var size = renderer.getSize();
var w = WIDTH;
var h = HEIGHT;
target.setSize(w, h);
renderer.clearTarget(target, true, true, true);
renderer.render(scene, camera, target);
var renderTargetPixelBuffer = new Uint8Array(w * h * 4);
renderer.readRenderTargetPixels(target, 0, 0, w, h, renderTargetPixelBuffer);
var allZeroes = renderTargetPixelBuffer.reduce(function(prev, item) {
return prev && (item === 0.0);
}, true);
if (allZeroes) {
alert("All zeroes!");
} else {
alert("Never mind, the data is okay.");
}
}
document.getElementById("go").addEventListener("click", toRenderTarget);
function init() {
hostDiv = document.createElement('div');
hostDiv.setAttribute('id', 'host');
document.body.appendChild(hostDiv);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(WIDTH, HEIGHT);
hostDiv.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 500;
controls = new THREE.TrackballControls(camera, renderer.domElement);
//controls.rotateSpeed = 5.0;
controls.dynamicDampingFactor = 0.5;
controls.handleResize();
light = new THREE.PointLight(0xffffff, 1, Infinity);
light.position.copy(camera.position);
scene = new THREE.Scene();
scene.add(camera);
scene.add(light);
var cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(50, 50, 50),
new THREE.MeshLambertMaterial({
color: "red"
})
);
scene.add(cube);
animate();
}
function render() {
light.position.copy(camera.position);
renderer.render(scene, camera);
controls.update();
}
function animate() {
requestAnimationFrame(animate);
render();
}
init();
window.onresize = function() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
body {
position: relative;
}
<!-- r76 -->
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/build/three.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/examples/js/controls/TrackballControls.js"></script>
<input type="button" id="go" value="GO" style="position: absolute; top: 0; right: 0" ; />
Using r86:
var hostDiv, scene, renderer, camera, controls, light;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var target = new THREE.WebGLRenderTarget();
function toRenderTarget() {
var size = renderer.getSize();
var w = WIDTH;
var h = HEIGHT;
target.setSize(w, h);
renderer.clearTarget(target, true, true, true);
renderer.render(scene, camera, target);
var renderTargetPixelBuffer = new Uint8Array(w * h * 4);
renderer.readRenderTargetPixels(target, 0, 0, w, h, renderTargetPixelBuffer);
var allZeroes = renderTargetPixelBuffer.reduce(function(prev, item) {
return prev && (item === 0.0);
}, true);
if (allZeroes) {
alert("All zeroes!");
} else {
alert("Never mind, the data is okay.");
}
}
document.getElementById("go").addEventListener("click", toRenderTarget);
function init() {
hostDiv = document.createElement('div');
hostDiv.setAttribute('id', 'host');
document.body.appendChild(hostDiv);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(WIDTH, HEIGHT);
hostDiv.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 500;
controls = new THREE.TrackballControls(camera, renderer.domElement);
//controls.rotateSpeed = 5.0;
controls.dynamicDampingFactor = 0.5;
controls.handleResize();
light = new THREE.PointLight(0xffffff, 1, Infinity);
light.position.copy(camera.position);
scene = new THREE.Scene();
scene.add(camera);
scene.add(light);
var cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(50, 50, 50),
new THREE.MeshLambertMaterial({
color: "red"
})
);
scene.add(cube);
animate();
}
function render() {
light.position.copy(camera.position);
renderer.render(scene, camera);
controls.update();
}
function animate() {
requestAnimationFrame(animate);
render();
}
init();
window.onresize = function() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
body {
position: relative;
}
<!-- r86 -->
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script>
<input type="button" id="go" value="GO" style="position: absolute; top: 0; right: 0" ; />
Using r76 and deferring render until the next animation loop:
var hostDiv, scene, renderer, camera, controls, light;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
var target = new THREE.WebGLRenderTarget();
var doRenderTarget = false;
function go(){
doRenderTarget = true;
}
function toRenderTarget() {
var size = renderer.getSize();
var w = WIDTH;
var h = HEIGHT;
target.setSize(w, h);
renderer.clearTarget(target, true, true, true);
renderer.render(scene, camera, target);
var renderTargetPixelBuffer = new Uint8Array(w * h * 4);
renderer.readRenderTargetPixels(target, 0, 0, w, h, renderTargetPixelBuffer);
var allZeroes = renderTargetPixelBuffer.reduce(function(prev, item) {
return prev && (item === 0.0);
}, true);
if (allZeroes) {
alert("All zeroes!");
} else {
alert("Never mind, the data is okay.");
}
}
document.getElementById("go").addEventListener("click", toRenderTarget);
function init() {
hostDiv = document.createElement('div');
hostDiv.setAttribute('id', 'host');
document.body.appendChild(hostDiv);
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(WIDTH, HEIGHT);
hostDiv.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 500;
controls = new THREE.TrackballControls(camera, renderer.domElement);
//controls.rotateSpeed = 5.0;
controls.dynamicDampingFactor = 0.5;
controls.handleResize();
light = new THREE.PointLight(0xffffff, 1, Infinity);
light.position.copy(camera.position);
scene = new THREE.Scene();
scene.add(camera);
scene.add(light);
var cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(50, 50, 50),
new THREE.MeshLambertMaterial({
color: "red"
})
);
scene.add(cube);
animate();
}
function render() {
light.position.copy(camera.position);
if(doRenderTarget){
toRenderTarget();
}
else{
renderer.render(scene, camera);
}
controls.update();
}
function animate() {
requestAnimationFrame(animate);
render();
}
init();
window.onresize = function() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
body {
position: relative;
}
<!-- r76 -->
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/build/three.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/6400f2c9b6ee58e01c005a66f00c7cd1113752aa/examples/js/controls/TrackballControls.js"></script>
<input type="button" id="go" value="GO" style="position: absolute; top: 0; right: 0" ; />
Follow-up:
The key here appears to be reading directly from the GL context, but the root cause of my problem is much, much simpler. I wondered what was wrong with how readRenderTargetPixels was doing its read, because it literally calls the same GL method. It was right there in front of my face the whole time. Blocking the _gl.readPixels call:
r76:
if ( ( x > 0 && x <= ( renderTarget.width - width ) ) && ( y > 0 && y <= ( renderTarget.height - height ) ) ) {
r86:
if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
The function in r76 wasn't allowing x or y to be 0, and I was setting both to zero. The r86 function allows both to be 0, which is why it worked.
Since r76 is super-old, I'm not going to bother submitting a bug report or a fix. Thanks, #evil professeur, for helping highlight the issue!

The problem is that by the time you call the function, the buffer will be cleared. What you have to do is to call it just after render. The easiest way is to set a flag on click that informing the rendering function that you want to retrieve the pixels and have it call toRenderTarget immediately after render. the changes would look like that:
var getRenderedPixels = false;
function setAwaitFlag() {
getRenderedPixels = true;
}
document.getElementById("go").addEventListener("click", setAwaitFlag);
and then in the render function
function render() {
light.position.copy(camera.position);
renderer.render(scene, camera);
if (getRenderedPixels) {
toRenderTarget();
getRenderedPixels = false;
}
controls.update();
}
Aside from that I used gl.ReadPixels instead of readRenderTargetPixels and it clicked. here's the change:
var gl = renderer.getContext(),
pixels = new Uint8Array(w * h * 4);
gl.readPixels(0,0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, renderTargetPixelBuffer);
and here's the fiddle https://jsfiddle.net/c06b5p8u/1/
you should narrow the number of pixels read to improve performance, though.
I was stuck on this one a while back too.

Related

How to change the camera position for multiple views in Three JS

I've tried to change the camera view (say: Front View, Left View, Right View, Back View, Bottom View and Top View ) in button click. I have achieved by changing the camera position for each view where I have a concern that what if camera position or the mesh position get changes. I have given the mesh position to the camera position in the initial View and the mesh disappeared from the scene.
So is there any alternate to achieve this without hard coding the camera position.
Kindly, help me out with the issue.
Here's the fiddle https://jsfiddle.net/8L10qkzt/1/ and my piece of code
var camera, scene, renderer;
var views;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 1;
scene = new THREE.Scene();
var geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
var material = new THREE.MeshNormalMaterial();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
setTimeout(() => {
controls.enableDamping = false;
controls.reset();
}, 5000);
document.querySelector('#frontView').addEventListener('click', () => {
console.log("frontview");
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 1;
scene.add(mesh);
controls.update();
render();
});
document.querySelector('#sideView').addEventListener('click', () => {
console.log("Side View");
camera.position.x = 1;
camera.position.y = 1;
camera.position.z = 1;
scene.add(mesh);
controls.update();
render();
});
document.querySelector('#backView').addEventListener('click', () => {
console.log("Back View");
});
}
function render() {
for (var ii = 0; ii < views; ++ii) {
var view = views[ii];
var camera = view.camera;
view.updateCamera(camera, scene, mouseX, mouseY);
var left = Math.floor(windowWidth * view.left);
var top = Math.floor(windowHeight * view.top);
var width = Math.floor(windowWidth * view.width);
var height = Math.floor(windowHeight * view.height);
renderer.setViewport(left, top, width, height);
renderer.setScissor(left, top, width, height);
renderer.setScissorTest(true);
renderer.setClearColor(view.background);
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}
}
function animate() {
render();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}

How to change multiple views in button click in Three JS

I'm trying to change multiple views (say : Front View, Side View and Back View) in button click by changing the camera position. I have tried that way but can't able to achieve the back view of the object.Kindly help me out with the issue. I have mentioned the fiddle link https://jsfiddle.net/8L10qkzt/1/
var camera, scene, renderer;
var views;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.z = 1;
scene = new THREE.Scene();
var geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
var material = new THREE.MeshNormalMaterial();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
setTimeout(() => {
controls.enableDamping = false;
controls.reset();
}, 5000);
document.querySelector('#frontView').addEventListener('click', () => {
console.log("frontview");
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 1;
scene.add(mesh);
controls.update();
render();
});
document.querySelector('#sideView').addEventListener('click', () => {
console.log("Side View");
camera.position.x = 1;
camera.position.y = 1;
camera.position.z = 1;
scene.add(mesh);
controls.update();
render();
});
document.querySelector('#backView').addEventListener('click', () => {
console.log("Back View");
});
}
function render() {
for (var ii = 0; ii < views; ++ii) {
var view = views[ii];
var camera = view.camera;
view.updateCamera(camera, scene, mouseX, mouseY);
var left = Math.floor(windowWidth * view.left);
var top = Math.floor(windowHeight * view.top);
var width = Math.floor(windowWidth * view.width);
var height = Math.floor(windowHeight * view.height);
renderer.setViewport(left, top, width, height);
renderer.setScissor(left, top, width, height);
renderer.setScissorTest(true);
renderer.setClearColor(view.background);
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}
}
function animate() {
render();
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
I've updated your code with some changes. First, it's good for debugging to add the following helper in order to easily verify the position of the camera.
scene.add( new THREE.AxesHelper() );
Next, the event handler for the "back view" button looks like so:
console.log("Back View");
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = - 1;
controls.update();
As you can see, it's not necessary to call render() again since you already have an ongoing animation loop.
Hint: By applying an array of materials to your box mesh, it's easier to distinct the different sides of your cube.
https://jsfiddle.net/zejLa143/1/
three.js R108

Forcing OrbitControls to navigate around a moving object (almost working)

I am learning Three.js and I am playing with the model of solar system to learn how it works. So I have a scene in which the Earth rotates around the Sun, and the Moon around the Earth.
Now I would like to focus on the Moon and use controls to rotate around it (while having it all the time in the center of the screen). OrbitControls seem to be ideal for that, but I cannot get them to work with the moving Moon.
Here are my 3 attempts (please ignore that the Earth and the Moon are cubes).
Attempt 1 - Placing camera (jsfiddle)
First, I created a scene where camera is a child of the Moon (without OrbitControls).
moon.add(camera);
camera.lookAt(0, 0, 0);
var camera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
Result: it nicely puts the Moon in the center, but obviously you cannot navigate the scene, because I haven't employed OrbitControls yet. But this attempt acts as a reference.
Attempt 2 - Adding OrbitControls (jsfiddle)
Then I added OrbitControls.
var camera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enablePan = false;
controls.enableDamping = false;
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
Result: the Moon has been moved from the center to the side (no idea why?). And when you start navigating with the mouse, everything goes crazy. The effect is as if OrbitControls navigates around the center of the scene and the camera around its parent (the Moon). Effectively they don't change state in a consistent manner, and everything goes wild.
Attempt 3 - Controlling Orbits' target (jsfiddle)
Last option I tried was to forcefully set controls.target so that it always points at the Moon. Because the Moon constantly moves around, I had to do it before each rendering.
const p = new THREE.Vector3();
const q = new THREE.Quaternion();
const s = new THREE.Vector3();
moon.matrixWorld.decompose(p, q, s);
// now setting controls target to Moon's position (in scene's coordinates)
controls.target.copy(p);
render();
var camera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
const p = new THREE.Vector3();
const q = new THREE.Quaternion();
const s = new THREE.Vector3();
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enablePan = false;
controls.enableDamping = false;
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
moon.matrixWorld.decompose(p, q, s);
controls.target.copy(p);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
Result: Initially the Moon is located on the side of the screen (same position as in the second attempt), but then when you start navigate, the Moon "jumps" to the center of the screen, and you can navigate around it. Almost perfect. As long you don't zoom. When you zoom in/zoom out, you start seeing that the Moon rotates during the zooming action.
Questions
why does OrbitControls not respect the fact that camera's parent is the Moon, and keeps navigating around the center of the scene?
why did the Moon "jump" to the side of the screen after adding OrbitControls?
what would be the elegant way of making it work? (forcing target to follow the Moon in a loop is neither elegant nor working due to the zooming issue)?
r. 98
Edit: editorial changes to make a sentence more clear.
Edit: upgrade to three.js r. 109.
I made it work by introducing a fake camera, which has everything the same as the original camera, except for camera.parent
fakeCamera = camera.clone(); // parent becomes null
controls = new THREE.OrbitControls(fakeCamera, renderer.domElement);
This way OrbitControls has a camera with its own coordinate system.
Then, before rendering, I copy fakeCamera's values back to the real camera, which is used for rendering.
camera.position.copy(fakeCamera.position);
camera.quaternion.copy(fakeCamera.quaternion);
camera.scale.copy(fakeCamera.scale);
render();
and it works well.
EDIT
I noticed that
camera.position.copy(fakeCamera.position);
camera.quaternion.copy(fakeCamera.quaternion);
camera.scale.copy(fakeCamera.scale);
can be replaced with
camera.copy(fakeCamera);
(the code below has been updated accordingly)
var camera, fakeCamera, controls, scene, renderer, labelRenderer;
var solarPlane, earth, moon;
var angle = 0;
function buildScene() {
scene = new THREE.Scene();
solarPlane = createSolarPlane();
earth = createBody("Earth");
moon = createBody("Moon");
scene.add(solarPlane);
solarPlane.add(earth);
earth.add(moon);
moon.add(camera);
}
init();
animate();
function init() {
renderer = new THREE.WebGLRenderer({
antialias: false
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(labelRenderer.domElement);
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(13.670839104116506, 10.62941701834559, 0.3516419193657562);
camera.lookAt(0, 0, 0);
buildScene();
fakeCamera = camera.clone();
controls = new THREE.OrbitControls(fakeCamera, renderer.domElement);
controls.enablePan = false;
controls.enableDamping = false;
}
function animate(time) {
angle = (angle + .005) % (2 * Math.PI);
rotateBody(earth, angle, 1);
rotateBody(moon, angle, 2);
camera.copy(fakeCamera);
render();
requestAnimationFrame(animate);
function rotateBody(body, angle, radius) {
body.rotation.x = angle;
body.position.x = radius * Math.cos(angle);
body.position.y = radius * Math.sin(angle);
body.position.z = radius * Math.sin(angle);
}
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
function createBody(name, parent) {
var geometry = new THREE.CubeGeometry(1, 1, 1);
const body = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
body.position.set(1, 1, 1);
body.scale.set(.3, .3, .3);
body.name = name;
body.add(makeTextLabel(name));
return body;
}
function createSolarPlane() {
var solarPlane = new THREE.GridHelper(5, 10);
solarPlane.add(makeTextLabel("solar plane"));
return solarPlane;
}
function makeTextLabel(label) {
var text = document.createElement('div');
text.style.color = 'rgb(255, 255, 255)';
text.textContent = label;
return new THREE.CSS2DObject(text);
}
body {
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/CSS2DRenderer.js"></script>
I think your workaround is a nice solution because it does not require modifying imported code. Also, an extra camera is not expensive to maintain as long as it is not used for rendering. Here is an OrbitControls subclass that can be applied, based on the same principle. Note that the localTarget property is just an alias for the target property. There is no globalTarget property.
THREE.OrbitControlsLocal = function ( realObject, domElement ) {
this.realObject = realObject;
//Camera and Object3D have different forward direction:
let placeholderObject = realObject.isCamera ? new THREE.PerspectiveCamera() : new THREE.Object3D;
this.placeholderObject = placeholderObject;
THREE.OrbitControls.call( this, placeholderObject, domElement );
let globalUpdate = this.update;
this.globalUpdate = globalUpdate;
this.update = function() {
//This responds to changes made to realObject from outside the controls:
placeholderObject.position.copy( realObject.position );
placeholderObject.quaternion.copy( realObject.quaternion);
placeholderObject.scale.copy( realObject.scale );
placeholderObject.up.copy( realObject.up );
var retval = globalUpdate();
realObject.position.copy( placeholderObject.position );
realObject.quaternion.copy( placeholderObject.quaternion);
realObject.scale.copy( placeholderObject.scale );
return retval ;
};
this.update();
};
THREE.OrbitControlsLocal.prototype = Object.create(THREE.OrbitControls.prototype);
THREE.OrbitControlsLocal.prototype.constructor = THREE.OrbitControlsLocal;
Object.defineProperties(THREE.OrbitControlsLocal.prototype, {
localTarget: {
get: ()=>this.target,
set: v=>this.target=v
}
});
My previous solution of merely converting the local target to world space before applying lookAt was not correct. The problem seems to be that lookAt orients the camera according to its world-space up direction (camera.up or object.up) on every update. This problem does not exist with the placeholder/fakeCamera solution. (See PR https://github.com/mrdoob/three.js/pull/16374)

how can i modify mesh size in my scene?

First to say, I do not speak English well.
Anyway, Let's get something straight.
I want to modify mesh(cube) size when i scroll the mouse wheel to zoomin or zoomout.
I hope to increase mesh(cube) size when zoom in and Opposite case, too.
my code is below.
<script src="../lib/Three.js/build/Three.js"></script>
<script src="http://code.jquery.com/jquery.min.js"></script>
<script>
var CANVAS_WIDTH = 400,
CANVAS_HEIGHT = 300;
var renderer = null, //웹지엘 또는 2D
scene = null, //씬 객체
camera = null; //카메라 객체
var capture = false,
start = [],
angleX = 0,
angleY = 0,
zoom = 1.0;
function initWebGL()
{
setupRenderer();
setupScene();
setupCamera();
var myColor = new THREE.Color( 0xff0000 );
myColor.setRGB(0.0, 1.0, 0.0);
var alpha = 1.0;
renderer.setClearColor(myColor, alpha);
(function animLoop(){
//camera zoom in and zomm out
renderer.render(scene, camera);
requestAnimationFrame( animLoop );
})();
/**
mouse event code for screen control about zoom, rotate
**/
$(document).ready(function() {
console.log($("#my-canvas").length);
$("#my-canvas").on("mousedown", function(e) {
capture = true;
start = [e.pageX, e.pageY];
console.log("start:" + start);
});
$("#my-canvas").on("mouseup", function(e) {
console.log(e.type);
capture = false;
console.log("end capture");
});
$("#my-canvas").mousemove(function(e) {
console.log(e.type);
if (capture)
{
var x = (e.pageX - start[0]);
var y = (e.pageY - start[1]);
//시작위치 업데이트
start[0] = e.pageX;
start[1] = e.pageY;
angleX += x;
angleY += y;
//console.log()
}
});
});
$(document).ready(function(evt) {
$("#my-canvas").on("mousewheel", function (e) {
adjustZoom(window.event.wheelData);
}).on("DOMMouseScroll", function (e) {
//파이어폭스
adjustZoom(e.originalEvent.detail * -1.0);
});
});
function adjustZoom(delta) {
if(delta > 0)
{
zoom += 0.1;
} else {
zoom -= 0.1;
if(zoom < 0.01) { zoom = 0.1;}
}
}
}
function setupRenderer()
{
renderer = new THREE.WebGLRenderer({canvas: document.createElement( 'canvas')});
renderer.setSize( CANVAS_WIDTH, CANVAS_HEIGHT );
$(renderer.domElement).attr('id','my-canvas');
//캔버스 엘리먼트를 추가하는 곳
document.body.appendChild( renderer.domElement );
}
function setupScene()
{
scene = new THREE.Scene();
addMesh();
addLight();
}
function setupCamera()
{
camera = new THREE.PerspectiveCamera(
35, //시야
CANVAS_WIDTH / CANVAS_HEIGHT, //종횡비
.1, //전방 절단면
10000 //후방 절단면
);
camera.position.set(-15, 10, 10);
camera.lookAt( scene.position );
scene.add( camera );
}
function addMesh()
{
var cube = new THREE.Mesh(
new THREE.CubeGeometry( 5, 7, 5 ),
new THREE.MeshLambertMaterial( { color: 0x0000FF} )
);
scene.add(cube);
}
function addLight()
{
var light = new THREE.PointLight( 0xFFFFFF );
light.position.set( 20, 20, 20 );
scene.add(light);
}
</script>
You wish to modify the scale value of the object. This can be done for each axis.
Each mesh object has a scale value as a vector.
So this would
mesh.scale.set( 2, 1, 1 )
Or in your case
cube.scale.set();
You can also access it this way,
cube.scale.x = 2.0;
Though the cube object is stored locally, you might want to set the globally and alter this value with the mouse action.
Hope that well.
As a note, the question provides a bit too much of the script, shorter and faster to the point is better.

Three.js mouseover mesh with ray intersection, no reaction

I'm trying to set up a website that runs in a single WebGL element. I'm trying to get it to tell when the mouse is over an object. I'm using a couple of planes with a with transparent textures on them as links, but it won't pick up on any intersections at all. It does absolutely nothing. I've even added a cube and used the same code as the interactive cubes example (http://mrdoob.github.com/three.js/examples/webgl_interactive_cubes.html) and it does absolutely nothing. No changing the color, absolutely nothing.
I've gone through and tried about 8 different examples online having to do with this and not a single one has worked for me.
Here is my code:
<meta charset="utf-8">
<style type="text/css">
body {
margin: 0px;
overflow: hidden;
background: #000000;
}
div {
margin: 0px;
}
</style>
</head>
<div>
<script src="http://www.html5canvastutorials.com/libraries/Three.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
//Variable Declarations
var camera, scene, renderer, aspect, projector;
var INTERSECTED;
var mouseX = 0, mouseY = 0;
init();
//Method Declarations
function init() {
aspect = window.innerWidth / window.innerHeight;
//aspect = 1.25;
//Sets the camera variable to a new Perspective Camera with a field of view of 45 degrees, an aspect ratio
//of the window width divided by the window height, the near culling distance of 1 and the far culling distance of 2000
camera = new THREE.PerspectiveCamera(45, aspect, 1, 2000);
//Sets the height of the camera to 400
camera.position.z = 700;
//Sets a variable "lookat" to a new 3d vector with a position of 0, 0, 0,
//or the center of the scene/center of the menu plane
var lookat = new THREE.Vector3(0, 0, 0);
//Uses a function built in to every camera object to make it look at a given coordinate
camera.lookAt(lookat);
//Makes a new scene object to add everything to
scene = new THREE.Scene();
//Adds the camera object to the scene
scene.add(camera);
//Sets the renderer varialbe to a new WebGL renderer object
//Change "WebGLRenderer" to "CanvasRenderer" to use canvas renderer instead
renderer = new THREE.WebGLRenderer({antialias:true});
//renderer.sortObjects = false;
//Sets the size of the renderer object to the size of the browser's window
//renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setSize(window.innerWidth, window.innerHeight);
//Adds an event listener to the webpage that "listens" for mouse movements and when the mouse does move, it runs the
//"onMouseMove" function
document.addEventListener('mousemove', onMouseMove, false);
//Force canvas to stay proportional to window size
window.addEventListener('resize', onWindowResize, false);
document.addEventListener('mousedown', onDocumentMouseDown, false);
//Sets the update frequency of the webpage in milliseconds
setInterval(update, 1000/60);
//Makes a variable "texture" and sets it to the returned value of the method "loadTexture"
//supplied by the THREE.js library
var texture = THREE.ImageUtils.loadTexture('imgs/backgrounds.png');
//Makes a variable "geometry" and sets it to a "PlaneGeometry" object with a width of 645 and a height of 300
var geometry = new THREE.PlaneGeometry(645, 300);
//Makes a variable "material" and sets it to a "MeshBasicMaterial" object with it's map set to
//the "texture" object, it also makes it transparent
var material = new THREE.MeshBasicMaterial({map: texture, transparent: true});
//Makes a variable "plane" and sets it to a "Mesh" object with its geometry set to the "geometry" object
//and it's material set to the "material" object
var plane = new THREE.Mesh(geometry, material);
//Background Texture
var backgroundTexture = THREE.ImageUtils.loadTexture('imgs/gears.png');
var backgroundGeo = new THREE.PlaneGeometry(3500, 2500);
var backgroundMat = new THREE.MeshBasicMaterial({map: backgroundTexture});
var backgroundPlane = new THREE.Mesh(backgroundGeo, backgroundMat);
backgroundPlane.position.z = -1000;
backgroundPlane.overdraw = true;
//Home Button
//Makes a "homeTexture" variable and sets it to the returned value of the makeTextTexture function when
//the text passed in is set to "Home", the width is set to 300, height of 150, font set to 80pt Arial,
//fillStyle set to white, textAlign set to center, textBaseLine set to middle, and the color of the
//background set to red = 0, green = 0, blue = 0 and alpha = 0
var homeTexture = makeTextTexture("Home", 300, 150, '80pt Arial', 'white', "center", "middle", "rgba(0,0,0,0)");
var homeGeom = new THREE.PlaneGeometry(50, 25);
var homeMaterial = new THREE.MeshBasicMaterial({map: homeTexture, transparent: true});
var homeTest = new THREE.Mesh(homeGeom, homeMaterial);
homeTest.position.x -= 270;
homeTest.position.y += 120;
homeTest.position.z = 40;
homeTest.castShadow = true;
//Gallery Button
var galleryTexture = makeTextTexture("Gallery", 340, 150, '80pt Arial', 'white', "center", "middle", "rgba(0,0,0,0)");
var galleryGeom = new THREE.PlaneGeometry(50, 25);
var galleryMaterial = new THREE.MeshBasicMaterial({map: galleryTexture, transparent: true});
var galleryTest = new THREE.Mesh(galleryGeom, galleryMaterial);
galleryTest.position.x -= 270;
galleryTest.position.y += 90;
galleryTest.position.z = 40;
galleryTest.castShadow = true;
//The Team Button
var theTeamTexture = makeTextTexture("Company", 510, 150, '80pt Arial', 'white', "center", "middle", "rgba(0,0,0,0)");
var theTeamGeom = new THREE.PlaneGeometry(80, 25);
var theTeamMaterial = new THREE.MeshBasicMaterial({map: theTeamTexture, transparent: true});
var theTeamTest = new THREE.Mesh(theTeamGeom, theTeamMaterial);
theTeamTest.position.x -= 260;
theTeamTest.position.y += 60;
theTeamTest.position.z = 40;
theTeamTest.castShadow = true;
projector = new THREE.Projector();
var cubeGeom = new THREE.CubeGeometry(20, 20, 20);
var cubeMat = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
var cubeMesh = new THREE.Mesh(cubeGeom, cubeMat);
cubeMesh.position.z = 15;
//scene.add(cubeMesh);
//Adds all of the previously created objects to the scene
scene.add(plane);
scene.add(backgroundPlane);
scene.add(homeTest);
scene.add(theTeamTest);
scene.add(galleryTest);
//Adds the renderer to the webpage
document.body.appendChild(renderer.domElement);
}
function update() {
camera.position.x = (mouseX - (window.innerWidth / 2)) * 0.1;
camera.position.y = -((mouseY - (window.innerHeight / 2)) * 0.15);
camera.lookAt(new THREE.Vector3(0, 0, 0));
render();
}
function render() {
renderer.render(scene, camera);
}
function onMouseMove(event) {
mouseX = event.clientX;
mouseY = event.clientY;
}
function onDocumentMouseDown(event) {
event.preventDefault();
var vector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1,
0.5
);
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position,
vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( objects );
if ( intersects.length > 0 ) {
intersects[ 0 ].object.materials[ 0 ].color.setHex( Math.random() * 0xffffff );
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function makeTextTexture(text, width, height, font, fillStyle, textAlign, textBaseline, backgroundColor)
{
//Makes a new canvas element
var bitmap = document.createElement('canvas');
//Gets its 2d css element
var g = bitmap.getContext('2d');
//Sets it's width and height
bitmap.width = width;
bitmap.height = height;
//Takes "g", it's 2d css context and set's all of the following
g.font = font;
g.fillStyle = backgroundColor;
g.fillRect(0, 0, width, height);
g.textAlign = "center";
g.textBaseline = "middle";
g.fillStyle = fillStyle;
g.fillText(text, width / 2, height / 2);
//Rendered the contents of the canvas to a texture and then returns it
var texture = new THREE.Texture(bitmap);
texture.needsUpdate = true;
return texture;
}
</script>
</div>
</html>
Thanks to anyone that can help out. I wish I could figure out what's going on myself, it seems like I'm using StackOverflow to answer my questions way too much.
You have not declared the variable objects. Do this:
var objects = [];
Then populate it:
objects.push( plane );
objects.push( backgroundPlane );
Now, this line will work:
var intersects = ray.intersectObjects( objects );

Resources