Skip to content

Instantly share code, notes, and snippets.

@Sphinxxxx
Created January 31, 2020 17:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sphinxxxx/46a47f3dbc5510ff09309668075d9252 to your computer and use it in GitHub Desktop.
Save Sphinxxxx/46a47f3dbc5510ff09309668075d9252 to your computer and use it in GitHub Desktop.
Multi-texture sphere
<script>console.clear();</script>
<h2>Click the sphere to change/reset textures</h2>
//window.onerror = function(msg, url, linenumber) { alert('Error message: '+msg+'\nURL: '+url+'\nLine Number: '+linenumber); }
/* https://jeromeetienne.github.io/threejsboilerplatebuilder/ */
var scene, renderer, canvas;
var camera, cameraControls;
var geom, materials, mesh;
var textureLoader = new THREE.TextureLoader();
var loaderMaterial = createMaterial(
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' style='background:white;'%3E%3Ctext y='55' x='15' font-weight='bold'%3ELoading...%3C/text%3E%3C/svg%3E",
function() {
init();
animate();
}
)
window.onclick = function(e) {
//https://barkofthebyte.azurewebsites.net/post/2014/05/05/three-js-projecting-mouse-clicks-to-a-3d-scene-how-to-do-it-and-how-it-works
//https://github.com/mrdoob/three.js/issues/5587
var raycaster = new THREE.Raycaster(),
mouse = {
x: (e.clientX/canvas.width) * 2 - 1,
y: -(e.clientY/canvas.height) * 2 + 1
};
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects([mesh]);
if(intersects.length) {
swapMaterial(intersects[0].face.materialIndex);
}
}
// init the scene
function init() {
renderer = new THREE.WebGLRenderer({
antialias: true, // to get smoother output
preserveDrawingBuffer: true // to allow screenshot
});
renderer.setClearColor(0xA9E2F3);
renderer.setSize(window.innerWidth, window.innerHeight);
canvas = renderer.domElement;
document.body.appendChild(canvas);
// create a scene
scene = new THREE.Scene();
// put a camera in the scene
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 0, 100);
scene.add(camera);
// create a camera contol
//cameraControls = new THREEx.DragPanControls(camera)
// lights
var light = new THREE.AmbientLight(Math.random() * 0xffffff);
scene.add(light);
// 3d objects
// var imgs = [];
// //for(var i=200; i<220; i++) { imgs.push('https://placekitten.com/'+i+'/'+(i+1)); }
// for(var i=200; i<210; i++) { imgs.push('https://source.unsplash.com/category/people/'+i+'x'+(i+1)); }
// for(var i=0; i<3; i++) { imgs = imgs.concat(imgs); }
//OpenStreetMap tiles
//http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
//Sadly in Mercator projection, while we want Equirectangular here...
/*
imgs = [];
var zoom = 4;
for(var y=0; y<Math.pow(2, zoom); y++) {
for(var x=0; x<Math.pow(2, zoom); x++) {
imgs.push('http://tile.openstreetmap.org/'+zoom+'/'+x+'/'+y+'.png');
}
}
//*/
geom = new THREE.SphereGeometry(20, 9, 8);
var startMat = createMaterial('https://placekitten.com/800/400');
materials = [startMat];
//We must group the faces into as many materialIndex "batches" as we may eventually need,
//even though we start out by only having one texture covering the whole sphere:
//http://stackoverflow.com/questions/12468906/three-js-updating-geometry-face-materialindex
//
//Skip the triangle faces at the top and bottom of the sphere,
//which will need a slightly different approach:
var tris = geom.parameters.widthSegments,
textureCounter = 0;
for (var i = tris; i < geom.faces.length - tris; i += 2) {
textureCounter++;
materials.push(startMat);
geom.faces[i].materialIndex = textureCounter;
geom.faces[i + 1].materialIndex = textureCounter;
if((textureCounter % 7) === 1) {
swapMaterial(textureCounter);
}
}
console.log('Textures/textures used:', materials.length, textureCounter);
//http://stackoverflow.com/questions/35877484/three-js-using-cubetextureloader-to-create-a-different-image-on-each-face-of-a
var m = new THREE.MultiMaterial(materials);
mesh = new THREE.Mesh(geom, m);
scene.add(mesh);
}
// animation loop
function animate() {
// loop on request animation loop
// - it has to be at the begining of the function
// - see details at http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
requestAnimationFrame(animate);
// do the render
render();
}
// render the scene
function render() {
// variable which is increased by Math.PI every seconds - useful for animation
var PIseconds = Date.now() * Math.PI / 10
// update camera controls
//cameraControls.update();
// animation of all objects
scene.traverse(function(object3d, i) {
if (object3d instanceof THREE.Mesh === false) return
object3d.rotation.y = PIseconds * 0.0005;
object3d.rotation.x = Math.sin(PIseconds * 0.002);
});
// actually render the scene
renderer.render(scene, camera);
}
function createMaterial(textureUrl, onLoad) {
var texture = textureLoader.load(textureUrl, onLoad),
material = new THREE.MeshBasicMaterial({
map: texture
});
//Disable the "not power of two" warnings (this also gives us a little sharper details):
//http://stackoverflow.com/questions/36059642/how-to-disable-three-js-to-resize-images-in-power-of-two
texture.minFilter = THREE.LinearFilter;
//Already the default setting:
//texture.wrapS = THREE.ClampToEdgeWrapping;
//texture.wrapT = THREE.ClampToEdgeWrapping;
return material;
}
function swapMaterial(matIndex) {
//console.log('swap', matIndex, materials[matIndex] === materials[0]);
if(matIndex === 0) {
//When clicking the top or bottom of the sphere.
//Don't allow this, because it will change the "base texture" of the sphere at materials[0].
return;
}
if(materials[matIndex] !== materials[0]) {
//Reset
doSwapMaterial(matIndex, null);
return;
}
var textureUrl = 'https://placekitten.com/150/' + (100 + Math.round(Math.random()*100));
// //Make sure the image is in the browser cache before we apply it to the sphere,
// //or else the affected faces will be black while loading:
// var img = new Image();
// img.onload = function() { doSwapMaterial(matIndex, textureUrl); }
// img.src = texture;
//textureUrl = "data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='550,-55,810,650' width='810' height='650'%3E %3Cpath d='M1017,584C1016,430,770,383,737,561L661,561C403,240,791,-75,981,87C1084,190,971,308,888,262C767,192,825,-31,1088,-26C1401,2,1392,439,1168,485C1041,517,994,338,1096,323C1157,312,1185,374,1142,408' fill='none' stroke-width='45' stroke='royalblue' /%3E %3C/svg%3E";
doSwapMaterial(matIndex, loaderMaterial);
//http://stackoverflow.com/questions/35540880/three-textureloader-is-not-loading-images-files
var material = createMaterial(textureUrl, function() {
doSwapMaterial(matIndex, material);
})
}
var originalUVs = [];
function doSwapMaterial(matIndex, material) {
//Find the two faces which have this materialIndex:
var faceIndex,
uvs = geom.faceVertexUvs[0];
for (faceIndex = 0; faceIndex < geom.faces.length; faceIndex++) {
if(geom.faces[faceIndex].materialIndex === matIndex) {
break;
}
}
if(!originalUVs[faceIndex]) {
originalUVs[faceIndex] = uvs[faceIndex];
originalUVs[faceIndex + 1] = uvs[faceIndex + 1];
}
if(material) {
//http://stackoverflow.com/questions/18305318/three-js-whats-the-best-way-to-put-multiple-textures-images-on-a-single-spher
materials[matIndex] = material;
//Change the faceVertexUvs on the two affected faces,
//so the whole texture image is contained inside that "square":
var v00 = new THREE.Vector2(0, 0),
v01 = new THREE.Vector2(0, 1),
v10 = new THREE.Vector2(1, 0),
v11 = new THREE.Vector2(1, 1);
uvs[faceIndex] = [v11, v01, v10];
uvs[faceIndex + 1] = [v01, v00, v10];
}
else {
materials[matIndex] = materials[0];
uvs[faceIndex] = originalUVs[faceIndex];
uvs[faceIndex + 1] = originalUVs[faceIndex + 1];
}
geom.elementsNeedUpdate = true;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r83/three.min.js"></script>
body {
margin: 0;
overflow: hidden;
}
h2 {
position: absolute;
width: 100%;
text-align: center;
z-indez: 999
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment