How to overlay HTML text/buttons on three.js? - javascript

Ok, very new to three.js here but I am trying to achieve what Google has with https://beinternetawesome.withgoogle.com/interland
See - they have their FULL SCREEN Three.js scene and their text, buttons (which are clearly HTML divs, not generated via JS) overlaid perfectly on top.
I have looked at
https://discourse.threejs.org/t/trying-to-overlay-a-button-and-text-on-top-of-a-three-js-scene/390/2
and similar Questions but can't understand the proper way to do this. I don't want to generate my UI elements in Three.js - I just want to use HTML.
What I have so far is I generate my Three.js canvas and add it to my document so that it gets and STAYS the full width/height of my window:
var controls, camera, renderer, scene, container, w, h;
renderer = new THREE.WebGLRenderer()
// container = document.getElementById('canvas');
container = window;
w = container.innerWidth;
h = container.innerHeight;
renderer.setSize(w, h)
renderer.setPixelRatio( window.devicePixelRatio );
document.body.appendChild(renderer.domElement);
And then with my existing HTML:
<body>
<div id="ui">
<p id="message">Test to change</p>
</div>
I get the UI div stuck on top, no matter how I play with the CSS or the z index of the UI div:
How can I achieve the Google effect with HTML overlaid over my three.js scene? What am I doing wrong?
I need my canvas to fill the whole window just as Google's does and can't achieve that it seems making the canvas HTML element beforehand and trying to attach everything in JS.

Creativity is up to you, as there are many ways to achieve the desired result.
You can start from looking at the source code of examples from the official web site of Three.js. There you can see how the info section of an example sets on a web page.
var buttons = document.getElementsByTagName("button");
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", onButtonClick, false);
};
function onButtonClick(event) {
alert(event.target.id);
}
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: true
});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
var animate = function() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
body {
overflow: hidden;
margin: 0;
}
.ui {
position: absolute;
}
button{
margin: 20px;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<div style="width:100%;height:50px;text-align: center; color:white;" class="ui">Info header</div>
<button style="top:0" id="fire" class="ui">Fire</button>
<button style="bottom:0" id="spell" class="ui">Spell</button>
<button style="right:0" id="health" class="ui">Health</button>
<button style="right:0; bottom:0;" id="shield" class="ui">Shield</button>

z-index only works with position property except for the case position: static and position: initial.
This button class worked fine with my threejs scene. You can edit left, top accordingly.
button {
position: absolute;
display: block;
z-index: 99;
left: 50%;
top: 50%;
}

Related

How to place two 3d models in different div? Three.js

The page has a divs with classes "skin", "skin2", "skin3"...
And for each class you need to place your 3d model.
I'm trying to do this, but all the 3d models, are tied to the first model.
I want to do this using the example of this site minecraft-skins
Image code results
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(10, window.innerWidth / window.innerHeight, 0.1, 50);
camera.position.z = 20;
camera.position.y = 1.5;
camera.position.x = 0;
renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setClearColor(0x000000, 0);
renderer.setSize(200, 300);
renderer.gammaOutput = true;
var light = new THREE.AmbientLight();
scene.add(light);
let loader = new THREE.GLTFLoader();
loader.load('/Стив.gltf', function (gltf) {
let obj = gltf;
document.getElementsByClassName('skin')[0].insertBefore(renderer.domElement, document.getElementsByClassName('skin')[0].firstChild);
scene.add(obj.scene);
obj.scene.scale.set(4.8, 1.5, 4.9);
obj.scene.rotation.y += 3.6;
obj.scene.rotation.x += 0.06;
});
loader.load('/Стив1.gltf', function (gltf2) {
let obj = gltf2;
document.getElementsByClassName('skin1')[0].insertBefore(renderer.domElement, document.getElementsByClassName('skin1')[0].firstChild);
scene.add(obj.scene);
obj.scene.scale.set(4.8, 1.5, 4.9);
obj.scene.rotation.y += 3.6;
obj.scene.rotation.x += 0.06;
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
<!DOCTYPE html>
<htm>
<head>
<title>Skins</title>
<script src="https://cdn.jsdelivr.net/npm/three#0.128.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.128.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.128.0/examples/js/loaders/GLTFLoader.js"></script>
</head>
<body>
<div class="block">
<div class="skin">
</div>
</div>
<div class="block">
<div class="skin1">
</div>
</div>
<script src="./script_01.js"></script>
</body>
</html>
Your code creates one renderer. On the first call to .insertBefore(), that renderer puts its context in the skin div. On the second call to .insertBefore(), that context is moved out of the skin div and into the skin1 div; at that point, there's no rendering being done inside of skin.
To achieve what you want, it's best to create a renderer that covers the whole window. Then you can use the renderer's .setScissor() method to render inside the limits of each div. Three.js provides an example of this.

Can Three.js/WebGL render complicated SVGs made with Adobe Illustrator?

I am trying to design a web page with a bunch of different SVG illustrations using Three.js and WebGL cameras to navigate throughout them. However, the more I research rendering SVGs, the more I realize that the way Adobe exports these SVGs may not be readable by Three.js. I initially drew these images on Adobe Illustrator Draw on the iPad, so theres tons of non-standard, organic strokes. For instance, attached is one of the illustrations I hope to use.
This results in some pretty complicated code, which is over 3,000 lines. You can find the code here for the above SVG image for reference:
https://jsfiddle.net/hjnrmfe3/1/
What I've tried to do so far is using a URL linking to a file with the 3,000 lines of code to load the SVG with Three.js, because pasting that within the JS file would be a nightmare. However, I haven't managed to make it work. The only instance I've been able to make it load is to use a TextureLoader, which results in a completely grainy image and defeats the purpose of having the illustrations in SVG format, as shown below:
function init() {
container = document.querySelector( '#viewport' );
scene = new THREE.Scene();
var fieldOfView = 75;
var aspectRatio = window.innerWidth / window.innerHeight;
var nearPlane = 0.1;
var farPlane = 10000;
var camera = new THREE.PerspectiveCamera(
fieldOfView, aspectRatio, nearPlane, farPlane
);
camera.position.z = 1;
var renderer = new THREE.WebGLRenderer({antialias: true});
var canvasWidth = window.innerWidth
renderer.setSize( canvasWidth, 2000);
document.body.appendChild( renderer.domElement );
//image
var loader = new THREE.TextureLoader();
var material = new THREE.MeshLambertMaterial({
map: loader.load(""),
});
var geometry = new THREE.PlaneGeometry(5, 2.5);
var desk = new THREE.Mesh(geometry, material);
desk.position.set(0,0,0)
scene.add(desk);
var light = new THREE.PointLight( 0xffffff, 1, 0 );
light.position.set(1, 1, 100 );
scene.add(light)
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
}
init();
#viewport {
position: fixed;
margin: 0;
padding: 0;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
<canvas id="viewport"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="http://threejs.org/examples/js/renderers/SVGRenderer.js"></script>
So, I'm curious - is it possible to render these complex SVGs from Adobe Illustrator with Three.js? I plan on using about 10-15 of these style illustrations whose code looks the same way. What is the best way to go about doing this, if its possible?
Any guidance is greatly appreciated. Thanks!

Render Screen is Dificult

So I'm trying to learn how to use THREE.js to make browser games, however when I try to use the code to import a model the screen renders black. I'm using Brackets to code and run it and I haven't had an issue until now.
Heres the code
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
scene.background = new THREE.Color(0xdddddd);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var loader = new THREE.GLTFLoader();
loader.load('CabbageBase.glb', function(gltf) {
scene.add(gltf.scene);
}, undefined, function(error) {
console.error(error);
});
camera.position.z = 5;
var animate = function() {
requestAnimationFrame(animate);
/*cube.rotation.x += 0.01;
cube.rotation.y += 0.01;*/
renderer.render(scene, camera);
};
animate();
body {
background: linear-gradient(#e4e0ba, #f7d9aa);
margin: 0px;
overflow: hidden;
}
canvas {
display: block;
}
<script src="//cdn.rawgit.com/mrdoob/three.js/master/build/three.min.js"></script>
<script src="GLTFLoader.js"></script>
I don't see anything wrong with the code except for the URLs.
You need to use a specific version of three.js by either downloading it or using a versioned CDN. All of the loaders, controls, and the examples require a specific version. Never link to threejs.org, always either download your own version or use a CDN. For example jsdeliver.
https://www.jsdelivr.com/package/npm/three
So for the three.js library the URL is
https://cdn.jsdelivr.net/npm/three#0.113.2/build/three.min.js
and for the version compatible GLTFLoader the URL is
https://cdn.jsdelivr.net/npm/three#0.113.2/examples/js/loaders/GLTFLoader.js
After that you need a model. I pointed to one on the three.js site (which is also bad since there is no guarantee it will stay their or stay compatible but for models we have less choices, normally you'd use your own model)
And with that the code works as is.
Note: For three.js you also generally need to run a local server which is covered in the docs
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
scene.background = new THREE.Color(0xdddddd);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var loader = new THREE.GLTFLoader();
loader.load('https://threejs.org/examples/models/gltf/Horse.glb', function(gltf) {
scene.add(gltf.scene);
}, undefined, function(error) {
console.error(error);
});
camera.position.z = 5;
var animate = function() {
requestAnimationFrame(animate);
/*cube.rotation.x += 0.01;
cube.rotation.y += 0.01;*/
renderer.render(scene, camera);
};
animate();
body {
background: linear-gradient(#e4e0ba, #f7d9aa);
margin: 0px;
overflow: hidden;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.113.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.113.2/examples/js/loaders/GLTFLoader.js"></script>

CSS3DRenderer disable scaling

see my previous question here for reference to what I am trying to achieve
TL;DR:
I am trying to get HTML elements to rotate in conjunction with OrbitControls to make it seem as if these HTML elements are stuck to the globe and move with it. (think map markers on a 3D earth above certain countries)
I achieved this almost successfully using the THREE.js CSS3DRenderer, and was able to get the HTML elements to stick to a location on my 3D globe with a lat/long calculation and rotate with the globe when OrbitControls are used.
The problem
The only issue I am having is that the HTML elements are scaling proportionate to how close/far they are from the camera. Usually I assume this would be the desired effect to help the sense of getting closer/further, but the scaling is causing me not to be able to size my HTML elements correctly and consistently, and also causing text and SVGs inside the elements to blur/become pixelated
What I want
I am looking for a way to turn off this scaling so that the HTML elements still transform in every other way, but stay the same size (i.e. scale(1, 1, 1) or their original scale) no matter where they are in the 3D renderer created by CSS3DRenderer.
I assume I will have to edit the CSS3DRenderer.js code for this, but I have absolutely no idea where to start and I cannot find any information anywhere else.
Thanks.
Some of my code:
Creating the CSS3DRenderer
//CSS3D Renderer
rendererHTML = new THREE.CSS3DRenderer();
rendererHTML.setSize(WIDTH, HEIGHT);
rendererHTML.domElement.classList.add('CSS3D-container');
containerHTML = document.querySelector('.globe__container');
containerHTML.appendChild(rendererHTML.domElement);
Resizing function (called on window resize event)
HEIGHT = sizeControlElem.getBoundingClientRect().width;
WIDTH = sizeControlElem.getBoundingClientRect().width;
renderer.setSize(WIDTH, HEIGHT);
rendererHTML.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
Creating the CSS3DSprite objects from <li> elements in the HTML and setting their initial positions on the globe
for (let key in this.locationsObject) {
_this.locationsObject[key].coordinates = calcPosFromLatLonRad(this.locationsObject[key].lat, this.locationsObject[key].long, 300);
let CSS3D_Object = new THREE.CSS3DSprite(_this.locationsObject[key].element);
CSS3D_Object.position.set(_this.locationsObject[key].coordinates[0], _this.locationsObject[key].coordinates[1], _this.locationsObject[key].coordinates[2]);
CSS3D_Object.receiveShadow = false;
CSS3D_Object.castShadow = false;
sceneHTML.add(CSS3D_Object);
_this.locationsObject[key].CSS_Object = CSS3D_Object;
console.info(CSS3D_Object);
}
You can see some more of my code in the question here
The only way to stop the scaling is by projecting the 3D positions to 2D with the Vector3.project() method. Take a look at the code sample below, I commented the key points in the JavaScript code, but a quick explanation is this:
Copy the 3D position where you want the hotspot into a new vector.
Use vector.project(camera) to translate that 3D point to 2D coordinates.
Transform the range of 2D coords from [-1, 1] to [0, window.width]
Apply these coordinates via CSS to your hotspot.
Bonus: You can still use the .z attribute of the 2D vector to determine if it's within the camera's frustum or not.
var camera, controls, scene, renderer;
// This array will hold all positions in 3D space
var posArray3D = [];
// This array will hold all hotspot DIVs
var divArray = [];
// Create temp vector to reuse on loops
var tempVec = new THREE.Vector3();
init();
animate();
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xcccccc );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 3000 );
camera.position.set( 400, 200, 0 );
// controls
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.minDistance = 100;
controls.maxDistance = 500;
controls.maxPolarAngle = Math.PI / 2;
// world
var geometry = new THREE.CylinderBufferGeometry( 0, 10, 30, 4, 1 );
var material = new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true } );
// This is where all hotspot DIVs will go
var hotspotBox = document.getElementById("hotspotBox");
for ( var i = 0; i < 100; i ++ ) {
var mesh = new THREE.Mesh( geometry, material );
mesh.position.x = Math.random() * 1600 - 800;
mesh.position.y = 0;
mesh.position.z = Math.random() * 1600 - 800;
mesh.updateMatrix();
mesh.matrixAutoUpdate = false;
scene.add( mesh );
// Populate array of 3D positions
posArray3D.push(mesh.position);
// Create 'hotspot' DIV, and place within 'hotspotBox' holder
divArray.push(document.createElement("div"));
divArray[i].classList.add("hotspot");
hotspotBox.appendChild(divArray[i]);
}
// lights
var light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
var light = new THREE.DirectionalLight( 0x002288 );
light.position.set( - 1, - 1, - 1 );
scene.add( light );
var light = new THREE.AmbientLight( 0x222222 );
scene.add( light );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
// Loops through all divs and updates their positions based on the camera
function updateDivs() {
var vectorScreen = new THREE.Vector3();
// Loop through all positions
for (var i = 0; i < posArray3D.length; i ++) {
vectorScreen.copy(worldToScreen(posArray3D[i], camera));
// Update CSS attributes of each DIV
divArray[i].style.transform = "translate(" + vectorScreen.x + "px, " + vectorScreen.y + "px)";
// Checks for depth, hides if it's behind the camera
if(vectorScreen.z <= 1) {
divArray[i].style.display = "block";
} else {
divArray[i].style.display = "none";
}
}
}
// Projects 3D coordinates into 2D space
function worldToScreen(_position, _cam) {
tempVec.copy(_position);
tempVec.project(_cam);
// Converts range from [-1, 1] to [0, windowWidth]
tempVec.x = ( tempVec.x + 1 ) * window.innerWidth / 2;
tempVec.y = ( - tempVec.y + 1 ) * window.innerHeight / 2;
return tempVec;
}
function animate() {
requestAnimationFrame( animate );
controls.update();
updateDivs();
render();
}
function render() {
renderer.render( scene, camera );
}
body {
color: #000;
font-family:Monospace;
font-size:13px;
text-align:center;
font-weight: bold;
background-color: #fff;
margin: 0px;
overflow: hidden;
}
/*hotspotBox holds all .hotspots It's placed on top of WebGL canvas*/
#hotspotBox{
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
border: 1px dashed #f90;
pointer-events: none;
}
/*100 hotspots to be placed within #hotspotBox */
.hotspot {
background: #f90;
width: 10px;
height: 10px;
border-radius: 5px;
position: absolute;
cursor: pointer;
pointer-events: auto;
}
<div id="hotspotBox"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
It turns out that the easiest solution to my question is simply to use the CSS2DRenderer instead of the CSS3DRenderer.
It essentially does the same thing but only transforms the HTML element with translate, and does not rotate or scale it, meaning you can modify the size of the HTML elements freely using CSS width and size etc... which is what I wanted to do.
It is implemented in the exact same way, the only thing I had to change in my code was replacing CSS3DSprite with CSS2DObject.
Read more about CSS2DRenderer here.

Three.js fast text engraving

I'm trying to engrave some text on a surface using Three.js. I've achieved it using csg.js and ThreeCSG and it works perfect, the result is really good but the problem is it takes a lot of time. On my PC it takes about 30 seconds to engrave the word Hello.
Searching for other solution I found this site. They make custom jewelry and you can engrave text on it and the time it takes to engrave the text is really short! So I assume they are not using csg.js. What other technique can be used to achieve this result?
I though about using bump maps, I should generate a bump map for each letter but I don't know if that's the correct approach.
I can see by looking at the shaders that the site your linked to uses bump maps.
I don't think you would create a bump map for each letter, you would just do all the drawing (text) on a single canvas and apply that as a bump map.
Click on "Run Code Snippet" below for a demo of canvas bump maps (click and drag in the white box).
I hope this helps.
var camera, scene, renderer, mesh, material, stats;
var drawStartPos = {x:0, y:0};
init();
setupCanvasDrawing();
animate();
function init() {
// Renderer.
renderer = new THREE.WebGLRenderer();
//renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// Add renderer to page
document.getElementById('threejs-container').appendChild(renderer.domElement);
// Create camera.
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 400;
// Create scene.
scene = new THREE.Scene();
// Create material
material = new THREE.MeshPhongMaterial();
// Create cube and add to scene.
var geometry = new THREE.BoxGeometry(200, 200, 200);
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Create ambient light and add to scene.
var light = new THREE.AmbientLight(0x404040); // soft white light
scene.add(light);
// Create directional light and add to scene.
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(1, 1, 1).normalize();
scene.add(directionalLight);
// Add listener for window resize.
window.addEventListener('resize', onWindowResize, false);
// Add stats to page.
stats = new Stats();
document.body.appendChild( stats.dom );
}
function setupCanvasDrawing() {
// get canvas and context
var drawingCanvas = document.getElementById('drawing-canvas');
var drawingContext = drawingCanvas.getContext('2d');
// draw white background
drawingContext.fillStyle = "#FFFFFF";
drawingContext.fillRect(0,0,128,128);
// set canvas as bumpmap
material.bumpMap = new THREE.Texture(drawingCanvas);
// set the variable to keep track of when to draw
var paint = false;
// add canvas event listeners
drawingCanvas.addEventListener('mousedown', function(e){
paint = true
drawStartPos = {x:e.offsetX, y:e.offsetY};
});
drawingCanvas.addEventListener('mousemove', function(e){
if(paint){
draw(drawingContext, e.offsetX, e.offsetY);
}
});
drawingCanvas.addEventListener('mouseup', function(e){
paint = false;
});
drawingCanvas.addEventListener('mouseleave', function(e){
paint = false;
});
}
// Draw function
function draw(drawContext, x, y) {
drawContext.moveTo(drawStartPos.x, drawStartPos.y);
drawContext.lineTo(x,y);
drawContext.stroke();
drawStartPos = {x:x, y:y};
material.bumpMap.needsUpdate = true;
}
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
stats.update();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
body {
padding: 0;
margin: 0;
}
#drawing-canvas {
position: absolute;
background-color: #000;
top: 0px;
right: 0px;
z-index: 3;
}
#threejs-container {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
z-index: 1;
}
<script src="https://rawgit.com/mrdoob/three.js/r83/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/stats.js/r17/build/stats.min.js"></script>
<canvas id="drawing-canvas" height="128" width="128"></canvas>
<div id="threejs-container"></div>

Categories