How to center the three js canvas? - javascript

I have this really weird (possibly intended?) bug with my three js canvas.
I want to have it not fill the complete page and center the canvas inside a div.
As you can see in the code below, the canvas is inside the div. I even added some test headings to make sure I'm not going crazy here, but the canvas seems to move itself outside the div and I have no idea how to fix this.
<head>
<meta charset=utf-8>
<title>My first three.js app</title>
<style>
body { margin: 0; }
#test {
width: 100px;
height:100px;
margin: 0px auto;
border: 1px solid red;
}
</style>
<style type="text/css" src="css/main.css"></style>
</head>
<body>
<div id="test">
<h1>test1</h1>
<canvas id="canvasID"></canvas>
<h2>test2</h2>
</div>
<script src="https://github.com/mrdoob/three.js/blob/master/examples/js/Detector.js"></script>
<script src="three.js-master/build/three.js"></script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
var canvas = document.getElementById("canvasID");
var renderer = new THREE.WebGLRenderer({ canvas: canvas });
renderer.setSize( window.innerWidth*0.9, window.innerHeight*0.9 );
document.body.appendChild( renderer.domElement );
var geometry = new THREE.BoxGeometry( 1, 1, 1 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var cube = new THREE.Mesh( geometry, material );
scene.add( cube );
camera.position.z = 5;
function render() {
requestAnimationFrame( render );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
}
render();
if (Detector.webgl) {
init();
animate();
} else {
var warning = Detector.getWebGLErrorMessage();
document.getElementById('container').appendChild(warning);
}
</script>
</body>
As you can see, the canvas has "magically" moved outside of its parent div

In your code you have this line document.body.appendChild( renderer.domElement ); which appends the canvas to the end of the <body> tag. So you are adding the canvas outside of your required div.
You can add your canvas inside that div with a simple javascript selector like:
document.getElementById('test').appendChild( renderer.domElement );
or
document.querySelector('#test').appendChild( renderer.domElement );
That way you are adding the renderer inside the div with the id="test". Let me know if it worked.

Related

Three.js Geometry isn't showing in browser, but the scene is

was following a tutorial and got the whole scene working in the body tag, then I tried moving it inside a div, and now only the scene will render. I'm not getting any errors, and I've console logged everything to make sure it is available to access. But I can't see why I'm nothing displayed.
// console.log ("Java Script is working!")
// get div for 3D area
const container = document.querySelector('#container')
// console.log (container)
var scene = new THREE.Scene()
var camera = new THREE.PerspectiveCamera(
75,
container.innerWidth / container.innerHeight,
0.1,
1000
)
camera.position.z = 3
var renderer = new THREE.WebGLRenderer({
antialias: true
})
renderer.setClearColor('#0D1033')
renderer.setSize(container.innerWidth, container.innerHeight)
container.appendChild(renderer.domElement)
container.addEventListener('resize', () => {
renderer.setSize(container.innerWidth, container.innerHeight)
camera.aspect = container.innerWidth / container.innerHeight
camera.updateProjectionMatrix()
})
var geometry = new THREE.BoxGeometry(10, 1, 1)
var material = new THREE.MeshLambertMaterial({
color: 0x90e9f9,
side: THREE.DoubleSide
})
var mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
var light = new THREE.PointLight(0xffffff, 5, 500)
light.position.set(10, 0, 25)
scene.add(light)
var render = function() {
requestAnimationFrame(render)
renderer.render(scene, camera)
}
console.log(mesh)
console.log(camera)
render()
* {
box-sizing: border-box;
}
body {
padding: 0;
margin: 0;
height: 100vh;
}
canvas {
display: block;
height: 100%;
width: 100%;
}
#container {
background-color: black;
height: 100vh;
}
<!DOCTYPE html>
<body>
<div id="container">
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/102/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.2/TweenMax.min.js"></script>
<script src="main.js"></script>
</body>
</html>
I'm trying to get this working first so I know this can work before I move it into my main project, where I'm trying to have a 3d space inside a div on the website, but not take up the full page
You can only use innerWidth and innerHeight on the window object. Use clientWidth and clientHeight instead:
const container = document.querySelector('#container')
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(
75,
container.clientWidth / container.clientHeight,
0.1,
1000
)
camera.position.z = 3
const renderer = new THREE.WebGLRenderer({
antialias: true
})
renderer.setClearColor('#0D1033')
renderer.setSize(container.clientWidth, container.clientHeight)
container.appendChild(renderer.domElement)
container.addEventListener('resize', () => {
renderer.setSize(container.clientWidth, container.clientHeight)
camera.aspect = container.clientWidth / container.clientHeight
camera.updateProjectionMatrix()
})
const geometry = new THREE.BoxGeometry(10, 1, 1)
const material = new THREE.MeshLambertMaterial({
color: 0x90e9f9,
side: THREE.DoubleSide
})
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
const light = new THREE.PointLight(0xffffff, 5, 500)
light.position.set(10, 0, 25)
scene.add(light)
const render = function() {
requestAnimationFrame(render)
renderer.render(scene, camera)
}
render()
* {
box-sizing: border-box;
}
body {
padding: 0;
margin: 0;
height: 100vh;
}
canvas {
display: block;
height: 100%;
width: 100%;
}
#container {
background-color: black;
height: 100vh;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.133.1/build/three.min.js"></script>
<div id="container">
</div>

Threejs, div innerWidth and innerHeight changes by its own after setting it with plane's width and height?

Hello I have one doubt.
I am trying to set the innerWidth and innerHeight of the div, the container whichs has inside the canvas, with the following code, the important code is the inner loader.load function, which get executed when we have the plane finished loading in the page.
// this class handles the load and the canva for a nrrd
// Using programming based on prototype: https://javascript.info/class
// This class should be improved:
// - Canvas Width and height
InitCanvas = function (IdDiv, Filename) {
this.IdDiv = IdDiv;
this.Filename = Filename
}
InitCanvas.prototype = {
constructor: InitCanvas,
init: function () {
this.container = document.getElementById(this.IdDiv);
// this should be changed.
debugger;
this.container.innerHeight = 600;
this.container.innerWidth = 800;
//These statenments should be changed to improve the image position
this.camera = new THREE.PerspectiveCamera(60, this.container.innerWidth / this.container.innerHeight, 0.01, 1e10);
this.camera.position.z = 300;
let scene = new THREE.Scene();
scene.add(this.camera);
// light
let dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(200, 200, 1000).normalize();
this.camera.add(dirLight);
this.camera.add(dirLight.target);
// read file
let loader = new THREE.NRRDLoader();
loader.load(this.Filename, function (volume) {
//z plane
let sliceZ = volume.extractSlice('z', Math.floor(volume.RASDimensions[2] / 4));
debugger;
this.container.innerWidth = sliceZ.iLength;
this.container.innerHeight = sliceZ.jLength;
sliceZ.mesh.material.color.setRGB(0,1,1);
console.log('Our slice is: ', sliceZ);
scene.add(sliceZ.mesh);
this.scene = scene;
// renderer
this.renderer = new THREE.WebGLRenderer({alpha: true});
this.renderer.setPixelRatio(this.container.devicePixelRatio);
debugger;
this.container.appendChild(this.renderer.domElement);
}.bind(this));
},
animate: function () {
this.renderer.render(this.scene, this.camera);
}
}
As you see I am binding this in the loader.load asynchronous call, because we would like to access this.container into it, which is a property in the class InitCanvas.
Then we are getting the width and height of the plane which has just finished loading, with the sentences:
this.container.innerWidth = sliceZ.iLength;
this.container.innerHeight = sliceZ.jLength;
And I have seen using the web debugger that it does load the plane's width and height into the container, into the div:
We see that the this.container.innerWidth is being changed with 3128 and this.container.innerHeight is being changed with 1760.
However, then we see that after plane's rendering the container div is 300x150:
How is this possible?
In addition, why our web console reports us:
Uncaught TypeError: Cannot read property 'render' of undefined
at InitCanvas.animate (InitCanvas.js:79)
at animate (logic.js:63)
I think, it is because we do have access to this.renderer into loader.load function because of we have a bind(this), however in the animate() function, the InitCanvas' class scope has been lost.
How could we fix it?
Thank you for your help!
Additional code which would be relevant:
logic.js
if (!Detector.webgl) Detector.addGetWebGLMessage();
// global variables for this scripts
let OriginalImg,
SegmentImg;
var mouse = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var mousePressed = false;
var clickCount = 0;
init();
animate();
// initilize the page
function init() {
let filename = "models/nrrd/columna01.nrrd"; // change your nrrd file
let idDiv = 'original';
OriginalImg = new InitCanvas(idDiv, filename);
OriginalImg.init();
console.log(OriginalImg);
filename = "models/nrrd/columnasegmentado01.nrrd"; // change your nrrd file
idDiv = 'segment';
SegmentImg = new InitCanvas(idDiv, filename);
SegmentImg.init();
}
let originalCanvas = document.getElementById('original');
originalCanvas.addEventListener('mousedown', onDocumentMouseDown, false);
originalCanvas.addEventListener('mouseup', onDocumentMouseUp, false);
function onDocumentMouseDown(event) {
mousePressed = true;
clickCount++;
mouse.x = ( ( event.clientX - OriginalImg.renderer.domElement.offsetLeft ) / OriginalImg.renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( ( event.clientY - OriginalImg.renderer.domElement.offsetTop ) / OriginalImg.renderer.domElement.clientHeight ) * 2 + 1
console.log('Mouse x position is: ', mouse.x, 'the click number was: ', clickCount);
console.log('Mouse Y position is: ', mouse.y);
raycaster.setFromCamera(mouse.clone(), OriginalImg.camera);
var objects = raycaster.intersectObjects(OriginalImg.scene.children);
console.log(objects);
}
function onDocumentMouseUp(event) {
mousePressed = false
}
function animate() {
requestAnimationFrame(animate);
OriginalImg.animate();
SegmentImg.animate();
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Prototype: three.js without react.js</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="stylesheet" href="css/styles.css">
<!-- load the libraries and js -->
<script src="js/libs/three.js"></script>
<script src="js/Volume.js"></script>
<script src="js/VolumeSlice.js"></script>
<script src="js/loaders/NRRDLoader.js"></script>
<script src="js/Detector.js"></script>
<script src="js/libs/stats.min.js"></script>
<script src="js/libs/gunzip.min.js"></script>
<script src="js/libs/dat.gui.min.js"></script>
<script src="js/InitCanvas.js"></script>
</head>
<body>
<div id="info">
<h1>Prototype: three.js without react.js</h1>
</div>
<!-- two canvas -->
<div class="row">
<div class="column" id="original">
</div>
<div class="column" id="segment">
</div>
</div>
<script src="js/logic.js"></script>
</body>
</html>
styles.css
body {
font-family: Monospace;
margin: 0px;
overflow: hidden;
}
#info {
color: rgb(137, 145, 192);
position: absolute;
top: 10px;
width: 100%;
text-align: center;
z-index: 5;
display:block;
}
.column {
float: left;
width: 50%;
}
/* Clear floats after the columns */
.row:after {
content: "";
display: table;
clear: both;
}
canvas {
width: 200px;
height: 200px;
margin: 100px;
padding: 0px;
position: static; /* fixed or static */
top: 100px;
left: 100px;
}
EDIT: I have done what #Mugen87 tell us and it makes the canvas have the loaded model's width and height. However I did not expect that the model was offset from top left canvas part. It is being displayer on bottom right corner, and as there is no scrollbar it hiders most of it.
How could we add a scrollbar to the page to be able to see the canvas well?
Also here I post the code which resizes the canvas as #Mugen87 suggested:
let loader = new THREE.NRRDLoader();
loader.load(this.Filename, function (volume) {
//z plane
let sliceZ = volume.extractSlice('z', Math.floor(volume.RASDimensions[2] / 4));
debugger;
this.container.innerWidth = sliceZ.iLength;
this.container.innerHeight = sliceZ.jLength;
this.renderer.setSize(this.container.innerWidth, this.container.innerHeight);
sliceZ.mesh.material.color.setRGB(0,1,1);
console.log('Our slice is: ', sliceZ);
scene.add(sliceZ.mesh);
}.bind(this));
Just resizing the container won't resize the renderer. You have to call renderer.setSize( width, height ); otherwise the size of the respective canvas stays the same.
Besides, devicePixelRatio is a window property. this.container.devicePixelRatio returns undefined.

Three.js animation from blender

CURRENTLY, what's the best way to export skinned mesh animations from blender? I'm pretty sure I have the importing down, both for JSON and dae, but I can't get anything to work perfectly.
I've gotten JSON to the point where it seems like only a small portion of my animation will play, and that's after rolling back blender.
I thought it might have been the parent issue, so I tried no parent, parented, and as a modifier. I've tried all these variations with and without both the object and the armature selected and each seperately, I've tried to make sure there was only a single animation action, which from my understanding is fixed.
With dae, it seems to have some crazy scaling difference? And things seem to work sporadic. A model that will load into the editor (albeit w/no animations) in the examples won't load at all in the collada skinned mesh example/my created scene. And if I try to load a foreign model like the version of the avatar model from the three.js server, it shows up with no skin. And all of this required me to download the dev branch of three.js (at least to get my model to import at all)
My blend file:
http://97.95.26.94/examples/basic_animation.blend
I've spent three days working on this. It seems to be different for every version of blender/three.js
EDIT:
heres some code that I recently tried to get working. not sure where I was at on it, at this point I'm just trying every thing. Please ignore the loader above, thats for the rest of my scene. assume all the directories are right... because they are.
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>Lost Towers - Level designer</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<div id="monitor"
style=
"
position:absolute;
color:white;
"
>ladidda!!</div>
<script src='xxx/jquery.js'></script>
<script src='xxx/three.min.js'></script><script src="xxx/ColladaLoader.js"></script>
<script>
window.onclick= function() {
var temp= renderer.domElement ;
if(temp.requestPointerLock) temp.requestPointerLock();
else if(temp.mozRequestPointerLock) temp.mozRequestPointerLock();
else if(temp.webkitRequestPointerLock) temp.webkitRequestPointerLock();
}
/// initialize three.js
//init_scene();
var container;
container = document.createElement( 'div' );
document.body.appendChild( container );
//main renderer
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
renderer.shadowMapType = THREE.PCFSoftShadowMap;
renderer.shadowCameraNear = 3;
renderer.shadowCameraFar = 100;
renderer.shadowCameraFov = 10;
renderer.shadowMapBias = 0.03;
renderer.shadowMapWidth = 1024;
renderer.shadowMapHeight = 1024;
renderer.autoClear=false;
document.oncontextmenu=function(event){event.preventDefault();};
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
var manager = new THREE.LoadingManager();
var clock = new THREE.Clock();
var delta=0;
var loader = new THREE.BufferGeometryLoader();
var camera;
var scene= new THREE.Scene();
// load a resource
var loader = new THREE.ObjectLoader();
loader.setTexturePath("xxx");
loader.load= function (a, b, c, d) {////////////////Ignore all this, its for loading scene from clara.io
var e = loader,
f = new THREE.XHRLoader(e.manager);
f.setCrossOrigin(loader.crossOrigin);
f.load(a, function (a) {
var x=JSON.parse(a)
if(x.images)
for(var i=0;i<x.images.length;i++)
{
x.images[i].url=loader.texturePath+x.images[i].url;
}
e.parse(x, b)
}, c, d)
}
loader.load('xxx/main.json',function ( obj ) {
scene= obj;
//console.dir(obj);
loader.setTexturePath("xxx");
scene.traverse(function(child){
if(child instanceof THREE.PerspectiveCamera)
{
camera=child;
scene.add(camera);
}
/////////////////////////////////////HERE//////////////////////////////////////////////
});
var daeloader = new THREE.ColladaLoader();
daeloader.load( "xxx/main.dae", function ( collada ) {
console.dir(collada.scene); alert();
collada.scene.traverse( function ( obj ) {
if(obj.type=='Object3D')
obj.traverse( function ( child ) {
if ( child instanceof THREE.SkinnedMesh ) {
console.dir(child);
var animation = new THREE.Animation( child, child.geometry.animation );
animation.play();
camera.lookAt( child.position );
}
} );
} );
scene.add( collada.scene );
} );
},function(){loader.setTexturePath("xxx");});
main();
function main()
{
requestAnimationFrame( main );
THREE.AnimationHandler.update( clock.getDelta() );
if(scene && camera)
renderer.render( scene, camera );
}
</script>
</body>
</html>

Three.js example not displaying on canvas

i found an example of three.js and I am trying to implement it on a <canvas></canvas> element.
I get a reference to the element but I dont get a visual. I have my canvas element with the Id of "mycanvas".
<canvas id="mycanvas" style="border: 5px solid white" width="600" height="500"></canvas>
an I use an onload function in the body called WebGLStart() which calls the script below.
<script>
function WebGLStart()
{
//Get the canvas and the context
var canvas = document.getElementById('mycanvas');
var canvas_context = canvas.getContext("webgl");
console.log("Canvas: "+canvas.width);
console.log("Canvas: "+canvas.height);
var RENDER_DIST = 1000,
FOV = 75;
var WIDTH = canvas.width,
HEIGHT= canvas.height;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, 0.1, RENDER_DIST);
camera.position.z = 100;
scene.add(camera);
renderer = new THREE.WebGLRenderer();
renderer.setSize(WIDTH,HEIGHT);
console.log("R info: "+renderer.info);
canvas.appendChild(renderer.domElement);
init();
loopRun();
function init()
{
var geometry = new THREE.SphereGeometry(50);
var material = new THREE.MeshBasicMaterial({color: 0xff0000});
var sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
}
function loopRun()
{
requestAnimationFrame(loopRun);
renderer.render(scene, camera);
}
}
</script>
Is there any reason why this would not display?
I get outputs on chromes prompt(canvas width and height) but no display. any ideas would be appreciated
Child elements of the canvas element are not visible.
To solve this attach the renderer DOM element to some other DOM element, e.g. document body.
if you change this line:
canvas.appendChild(renderer.domElement);
to this:
document.body.appendChild(renderer.domElement);
You'll get the desired result.
Tade0 was right in the above answer! This is for people who like me had a <canvas></canvas> tags and now have to remove it.
The way I did it was first:
Implement a style tag and call your class something unique:
<style>
canvas_for_three {
width: 600px;
height: 500px;
border: 1px solid white;
position: absolute;
left: 0px;
top: 0px;
z-index: -1;
}
</style>
Next remove your <canvas></canvas> tags and replace them with:
<div id="ctx" class="canvas_for_three">
</div>
From here then on in your onload function use the
var canvas = document.getElementById('ctx');
So when your attaching the render to a DOM element it is now:
canvas.appendChild(renderer.domElement);
Where canvas is your newly created canvas.
This will replace the canvas tags.
Now you should have replaced your canvas tags with divs and the image viewport should be in the same position as where you had your <canvas></canvas> tags

Texturing a sphere with Three.js does'nt work on smartphones

I have some troubles using Three.js. I want to apply a texture on a sphere (an image). My code works without any problem... until I try it on a smartphone. I try to look for the bug with Firefox and its remote debugger, but I did not find the issue. Here is my code :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test</title>
<meta name="viewport" content="initial-scale=1.0" />
<script src="./three.min.js"></script>
<script src="./sphere.js"></script>
<style>
html, body, #container {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container" style="width: 300px; height: 200px;"></div>
<script>
init();
</script>
</body>
</html>
and :
var renderer, scene, camera, mesh;
function init() {
var container = document.getElementById('container');
var width = container.offsetWidth;
var height = container.offsetHeight;
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
container.appendChild(renderer.domElement);
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, width/height, 1, 1000);
camera.position.set(0, 0, 300);
scene.add(camera);
var geometry = new THREE.SphereGeometry(200, 16, 16);
var texture = new THREE.Texture();
var loader = new THREE.ImageLoader();
var f = function(img) {
texture.needsUpdate = true;
texture.image = img;
var material = new THREE.MeshBasicMaterial({map: texture, overdraw: true});
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
render();
animate();
}
loader.load('sphere.jpg', f);
}
function animate() {
requestAnimationFrame(animate);
mesh.rotation.y += 0.003;
render();
}
function render() {
renderer.render(scene, camera);
}
What do I do wrong?
If you want to see the code in action, you can go here. Note that the problem is here with both WebGLRenderer and CanvasRenderer.
As a repost: The problem is solved by using power of two texture sizes.

Categories

Resources