I am trying to create a curve wall using some vertices array in three JS.
What I will get in array is some base vertices in 2D which will be the bottom vertices of wall. These includes the center, lower and upper vertices which means it is two faced wall. based on these vertices, I added some wall height and convert that 2D to 3D.
Below is the code and working fiddle
/**
* Created by Singh on 7/30/2018.
*/
var renderer, scene, camera;
init();
animate();
function init() {
wallsGeometry = function(totalPoints){
var material = new THREE.MeshBasicMaterial({/*color: 0xff0000,*/ side: THREE.DoubleSide, wireframe : false});
var material2 = new THREE.MeshBasicMaterial({/*color: 0x0044400, */side: THREE.DoubleSide, wireframe : true});
var geometry = new THREE.BufferGeometry();
var geometry2 = new THREE.BufferGeometry();
var wallHeight = 200;
var pointindices = [
0,1,2,0,2,3, //left
5,4,7,5,7,6, //right
4,0,3,4,3,7, //back
1,5,6,1,6,2, //front
2,6,7,2,7,3, //top
5,1,0,5,0,4, //bottom
];
var pointindices2 = [
1,0,2,1,3,2 , //left
4,5,7,4,6,7, //right
0,4,3,0,7,3, //back
5,1,2,5,2,6, //front
6,2,7,6,3,7, //top
1,5,0,1,4,0, //bottom
];
var tempIndices = [];
var tempIndices2 = [];
for(var i=0; i<4; i++) {
for (var j = 0; j < pointindices.length; j++) {
tempIndices[pointindices.length * i + j] = 4 * i + pointindices[j];
}
}
for(var i=0; i<4; i++) {
for (var j = 0; j < pointindices2.length; j++) {
tempIndices2[pointindices2.length * i + j] = 4 * i + pointindices2[j];
}
}
var tempVerMesh = [];
var tempVerMesh2 = [];
var indices = new Uint32Array( tempIndices );
var pointsArray = { // for testing
pointUpper1: new THREE.Vector3(),
pointUpper2: new THREE.Vector3(),
pointCenter1: new THREE.Vector3(),
pointCenter2: new THREE.Vector3(),
pointLower1: new THREE.Vector3(),
pointLower2: new THREE.Vector3()
};
console.log(totalPoints);
/*function generateUVs(geometry) {
geometry.computeBoundingBox();
var max = geometry.boundingBox.max, min = geometry.boundingBox.min;
var offset = new THREE.Vector3(0 - min.x, 0 - min.y);
var range = new THREE.Vector3(max.x - min.x, max.y - min.y);
var faces = geometry.faces;
geometry.faceVertexUvs[0] = [];
for (var i = 0; i < faces.length ; i++) {
var v1 = geometry.vertices[faces[i].a],
v2 = geometry.vertices[faces[i].b],
v3 = geometry.vertices[faces[i].c];
geometry.faceVertexUvs[0].push([
new THREE.Vector3((v1.x + offset.x)/range.x ,(v1.y + offset.y)/range.y),
new THREE.Vector3((v2.x + offset.x)/range.x ,(v2.y + offset.y)/range.y),
new THREE.Vector3((v3.x + offset.x)/range.x ,(v3.y + offset.y)/range.y),
]);
}
geometry.uvsNeedUpdate = true;
return geometry;
}*/
for (var i = 0; i < totalPoints.lower.length ; i++) {
pointsArray.pointCenter1 = totalPoints.center[i];
//pointsArray.pointCenter2 = totalPoints.center[i + 1];
pointsArray.pointLower1 = totalPoints.lower[i];
//pointsArray.pointLower2 = totalPoints.lower[i + 1];
pointsArray.pointUpper1 = totalPoints.upper[i];
//pointsArray.pointUpper2 = totalPoints.upper[i + 1];
tempVerMesh.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y, pointsArray.pointCenter1.z);
tempVerMesh.push(pointsArray.pointLower1.x, pointsArray.pointLower1.y, pointsArray.pointLower1.z);
tempVerMesh.push(pointsArray.pointLower1.x, pointsArray.pointLower1.y + wallHeight, pointsArray.pointLower1.z);
tempVerMesh.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y + wallHeight, pointsArray.pointCenter1.z);
tempVerMesh2.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y, pointsArray.pointCenter1.z);
tempVerMesh2.push(pointsArray.pointUpper1.x, pointsArray.pointUpper1.y, pointsArray.pointUpper1.z);
tempVerMesh2.push(pointsArray.pointUpper1.x, pointsArray.pointUpper1.y + wallHeight, pointsArray.pointUpper1.z );
tempVerMesh2.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y + wallHeight, pointsArray.pointCenter1.z);
}
var vertices = new Float32Array(tempVerMesh);
var vertices2 = new Float32Array(tempVerMesh2);
//var uvs = new Float32Array(pointUVs);
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
//geometry.addAttribute('uv', new THREE.BufferAttribute(uvs, 2));
geometry.computeFaceNormals();
geometry.computeVertexNormals();
console.log(geometry);
var mesh = new THREE.Mesh(geometry, material);
var indices2 = new Uint32Array(tempIndices2);
geometry2.addAttribute('position', new THREE.BufferAttribute(vertices2, 3));
geometry2.setIndex(new THREE.BufferAttribute(indices2, 1));
geometry2.computeFaceNormals();
geometry2.computeVertexNormals();
/*var geom = new THREE.Geometry().fromBufferGeometry(geometry2);
var temp = generateUVs(geom);
geometry2 = new THREE.BufferGeometry().fromGeometry(temp);*/
var mesh2 = new THREE.Mesh(geometry2, material2);
//geometry2.addAttribute('uv', new THREE.BufferAttribute(uvs2, 2));
return [mesh,mesh2];
};
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 400;
scene = new THREE.Scene();
var arrow;
var rayCaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.domElement.addEventListener("mousemove", onMouseMove);
var points = {
pointUpper1: new THREE.Vector3(-70, 0, -20),
pointUpper2: new THREE.Vector3(130, 0, -20),
pointCenter1: new THREE.Vector3(-100, 0, 0),
pointCenter2: new THREE.Vector3(150, 0, 0),
pointLower1: new THREE.Vector3(-70, 0, 20),
pointLower2: new THREE.Vector3(130, 0, 20)
};
var totalPoints = {
center : [new THREE.Vector3(-70, 0, 0),new THREE.Vector3(0, 0, 0),new THREE.Vector3(50, 0, 0),new THREE.Vector3(100, 0, 0),new THREE.Vector3(130, 0, 0)],
lower : [new THREE.Vector3(-70, 0, 20),new THREE.Vector3(0, 0, 20),new THREE.Vector3(50, 0, 20),new THREE.Vector3(100, 0, 20),new THREE.Vector3(130, 0, 20)],
upper : [new THREE.Vector3(-70, 0, -20),new THREE.Vector3(0, 0, -20),new THREE.Vector3(50, 0, -20),new THREE.Vector3(100, 0, -20),new THREE.Vector3(130, 0, -20)]
};
var sphere = new THREE.SphereGeometry(2, 10, 10);
function initPoints(){
var point1mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({color: 0xff00}));
point1mesh.position.copy(points.pointUpper1);
scene.add(point1mesh);
var point2mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({color: 0x0000ff}));
point2mesh.position.copy(points.pointUpper2);
scene.add(point2mesh);
var point3mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({color: 0xff00}));
point3mesh.position.copy(points.pointCenter1);
scene.add(point3mesh);
var point4mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({color: 0x0000ff}));
point4mesh.position.copy(points.pointCenter2);
scene.add(point4mesh);
var point5mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({color: 0xff00}));
point5mesh.position.copy(points.pointLower1);
scene.add(point5mesh);
var point6mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({color: 0x0000ff}));
point6mesh.position.copy(points.pointLower2);
scene.add(point6mesh);
}
initPoints();
var mesh = new wallsGeometry(totalPoints);
function createArrow() {
var length = 30;
arrow = new THREE.ArrowHelper(
THREE.Object3D.DefaultUp,
new THREE.Vector3(),
length,
0xffff00,
1.5 * length,
1.25 * length
);
arrow.position.z = 10;
scene.add(arrow);
}
// arrow
createArrow();
function updateArrow(object, point, face) {
arrow.position.copy(point);
var normalMatrix = new THREE.Matrix3().getNormalMatrix( object.matrixWorld );
var normal = face.normal.clone().applyMatrix3( normalMatrix ).normalize();
arrow.setDirection(normal);
}
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
rayCaster.setFromCamera(mouse, camera);
var intersects = rayCaster.intersectObject(mesh[0], true);
var i, il, intersect;
if (intersects[0]) {
var face = intersects[0].face;
var point = intersects[0].point;
var object = intersects[0].object;
updateArrow(object, point, face);
}
}
/* var textureLoader = new THREE.TextureLoader();
textureLoader.load('./textures/Capture.PNG', function (texture) {
console.log('texture loaded');
texture.minFilter = THREE.LinearFilter;
//mesh[0].material.map = texture;
var vertexNormalsHelper = new THREE.VertexNormalsHelper( mesh[0], 10 );
mesh[0].add( vertexNormalsHelper );
}); */
scene.add(mesh[0]);
/* textureLoader.load('./textures/abc.jpg', function (texture) {
console.log('texture loaded');
texture.minFilter = THREE.LinearFilter;
//mesh[1].material.map = texture;
var vertexNormalsHelper = new THREE.VertexNormalsHelper( mesh[1], 10 );
mesh[1].add( vertexNormalsHelper );
}); */
scene.add(mesh[1]);
//
var Orbitcontrols = new THREE.OrbitControls(camera,renderer.domElement);
Orbitcontrols.update();
}
// render
function render() {
renderer.render(scene, camera);
}
// animate
function animate() {
requestAnimationFrame(animate);
render();
}
https://jsfiddle.net/simar_aneja/fsmw8znq/6/
In the fiddle you can see that wall is building properly and you can increase the vertices and loop of creating those indices, in the start. Now I want to add UVs to this bufferGeometry, I tried converting to geometry and then calculated faceVertexUVs, but this is not the right way. Can anyone suggest me the further path where I can attach different textures only at the front side, different on top side. And it should be in such a way that no matter how many vertices comes, Uvs should get calculated based on length of vertices. I have some Idea but not getting how to make it different for diff sides of wall.
Thanks
Here's a UV box-unwrapping I fixed up for ya. Maybe you'll find it helpful...
I also put your fiddle into a snippet you can run below...
function boxUnwrapUVs(geometry) {
if (!geometry.boundingBox) geometry.computeBoundingBox();
var sz = geometry.boundingBox.getSize(new THREE.Vector3());
var center = geometry.boundingBox.getCenter(new THREE.Vector3())
var min = geometry.boundingBox.min;
if (geometry.faceVertexUvs[0].length == 0) {
for (var i = 0; i < geometry.faces.length; i++) {
geometry.faceVertexUvs[0].push([new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()]);
}
}
for (var i = 0; i < geometry.faces.length; i++) {
var face = geometry.faces[i];
var faceUVs = geometry.faceVertexUvs[0][i]
var va = geometry.vertices[geometry.faces[i].a]
var vb = geometry.vertices[geometry.faces[i].b]
var vc = geometry.vertices[geometry.faces[i].c]
var vab = new THREE.Vector3().copy(vb).sub(va)
var vac = new THREE.Vector3().copy(vc).sub(va)
//now we have 2 vectors to get the cross product of...
var vcross = new THREE.Vector3().copy(vab).cross(vac);
//Find the largest axis of the plane normal...
vcross.set(Math.abs(vcross.x), Math.abs(vcross.y), Math.abs(vcross.z))
var majorAxis = vcross.x > vcross.y ? (vcross.x > vcross.z ? 'x' : vcross.y > vcross.z ? 'y' : vcross.y > vcross.z) : vcross.y > vcross.z ? 'y' : 'z'
//Take the other two axis from the largest axis
var uAxis = majorAxis == 'x' ? 'y' : majorAxis == 'y' ? 'x' : 'x';
var vAxis = majorAxis == 'x' ? 'z' : majorAxis == 'y' ? 'z' : 'y';
faceUVs[0].set((va[uAxis] - min[uAxis]) / sz[uAxis], (va[vAxis] - min[vAxis]) / sz[vAxis])
faceUVs[1].set((vb[uAxis] - min[uAxis]) / sz[uAxis], (vb[vAxis] - min[vAxis]) / sz[vAxis])
faceUVs[2].set((vc[uAxis] - min[uAxis]) / sz[uAxis], (vc[vAxis] - min[vAxis]) / sz[vAxis])
}
geometry.elementsNeedUpdate = geometry.verticesNeedUpdate = true;
}
geometry = new THREE.Geometry().fromBufferGeometry(geometry)
boxUnwrapUVs(geometry)
var mesh = new THREE.Mesh(geometry, material);
/**
* Created by Singh on 7/30/2018.
*/
var renderer, scene, camera;
init();
animate();
function init() {
wallsGeometry = function(totalPoints) {
var rrnd = (min, max) => (Math.random() * (max - min)) + min
var irnd = (rng) => (Math.random() * rng) | 0
function makeRndCanvas() {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 128;
var ctx = canvas.getContext('2d');
var srnd = (rng) => (Math.random() - 0.5) * 2 * rng
var irnd = (rng) => ((Math.random() * rng) | 0)
for (var x = 0; x < canvas.width; x++) {
for (var y = 0; y < canvas.width; y++) {
ctx.fillStyle = `rgba(${irnd(256)},${irnd(256)},${irnd(256)},1.0)`
ctx.fillRect(x, y, 1, 1);
}
}
ctx.fillStyle = '#ffff00'
ctx.fillText("WAHOO", 3, 64)
return canvas;
}
function makeRndTexture() {
var tex = new THREE.Texture(makeRndCanvas())
tex.minFilter = // THREE.LinearMipMapLinearFilter;
tex.magFilter = THREE.NearestFilter; //THREE.LinearFilter;
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
//tex.repeat.set(0.01, 0.01);
tex.needsUpdate = true;
return tex;
}
var material = new THREE.MeshLambertMaterial({ /*color: 0xff0000,*/
side: THREE.DoubleSide,
wireframe: false,
map: makeRndTexture()
});
var material2 = new THREE.MeshLambertMaterial({ /*color: 0x0044400, */
side: THREE.DoubleSide,
wireframe: true
});
var geometry = new THREE.BufferGeometry();
var geometry2 = new THREE.BufferGeometry();
var wallHeight = 200;
var pointindices = [
0, 1, 2, 0, 2, 3, //left
5, 4, 7, 5, 7, 6, //right
4, 0, 3, 4, 3, 7, //back
1, 5, 6, 1, 6, 2, //front
2, 6, 7, 2, 7, 3, //top
5, 1, 0, 5, 0, 4, //bottom
];
var pointindices2 = [
1, 0, 2, 1, 3, 2, //left
4, 5, 7, 4, 6, 7, //right
0, 4, 3, 0, 7, 3, //back
5, 1, 2, 5, 2, 6, //front
6, 2, 7, 6, 3, 7, //top
1, 5, 0, 1, 4, 0, //bottom
];
var tempIndices = [];
var tempIndices2 = [];
for (var i = 0; i < 4; i++) {
for (var j = 0; j < pointindices.length; j++) {
tempIndices[pointindices.length * i + j] = 4 * i + pointindices[j];
}
}
for (var i = 0; i < 4; i++) {
for (var j = 0; j < pointindices2.length; j++) {
tempIndices2[pointindices2.length * i + j] = 4 * i + pointindices2[j];
}
}
var tempVerMesh = [];
var tempVerMesh2 = [];
var indices = new Uint32Array(tempIndices);
var pointsArray = { // for testing
pointUpper1: new THREE.Vector3(),
pointUpper2: new THREE.Vector3(),
pointCenter1: new THREE.Vector3(),
pointCenter2: new THREE.Vector3(),
pointLower1: new THREE.Vector3(),
pointLower2: new THREE.Vector3()
};
console.log(totalPoints);
/*function generateUVs(geometry) {
geometry.computeBoundingBox();
var max = geometry.boundingBox.max, min = geometry.boundingBox.min;
var offset = new THREE.Vector3(0 - min.x, 0 - min.y);
var range = new THREE.Vector3(max.x - min.x, max.y - min.y);
var faces = geometry.faces;
geometry.faceVertexUvs[0] = [];
for (var i = 0; i < faces.length ; i++) {
var v1 = geometry.vertices[faces[i].a],
v2 = geometry.vertices[faces[i].b],
v3 = geometry.vertices[faces[i].c];
geometry.faceVertexUvs[0].push([
new THREE.Vector3((v1.x + offset.x)/range.x ,(v1.y + offset.y)/range.y),
new THREE.Vector3((v2.x + offset.x)/range.x ,(v2.y + offset.y)/range.y),
new THREE.Vector3((v3.x + offset.x)/range.x ,(v3.y + offset.y)/range.y),
]);
}
geometry.uvsNeedUpdate = true;
return geometry;
}*/
for (var i = 0; i < totalPoints.lower.length; i++) {
pointsArray.pointCenter1 = totalPoints.center[i];
//pointsArray.pointCenter2 = totalPoints.center[i + 1];
pointsArray.pointLower1 = totalPoints.lower[i];
//pointsArray.pointLower2 = totalPoints.lower[i + 1];
pointsArray.pointUpper1 = totalPoints.upper[i];
//pointsArray.pointUpper2 = totalPoints.upper[i + 1];
tempVerMesh.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y, pointsArray.pointCenter1.z);
tempVerMesh.push(pointsArray.pointLower1.x, pointsArray.pointLower1.y, pointsArray.pointLower1.z);
tempVerMesh.push(pointsArray.pointLower1.x, pointsArray.pointLower1.y + wallHeight, pointsArray.pointLower1.z);
tempVerMesh.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y + wallHeight, pointsArray.pointCenter1.z);
tempVerMesh2.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y, pointsArray.pointCenter1.z);
tempVerMesh2.push(pointsArray.pointUpper1.x, pointsArray.pointUpper1.y, pointsArray.pointUpper1.z);
tempVerMesh2.push(pointsArray.pointUpper1.x, pointsArray.pointUpper1.y + wallHeight, pointsArray.pointUpper1.z);
tempVerMesh2.push(pointsArray.pointCenter1.x, pointsArray.pointCenter1.y + wallHeight, pointsArray.pointCenter1.z);
}
var vertices = new Float32Array(tempVerMesh);
var vertices2 = new Float32Array(tempVerMesh2);
//var uvs = new Float32Array(pointUVs);
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
//geometry.addAttribute('uv', new THREE.BufferAttribute(uvs, 2));
geometry.computeFaceNormals();
geometry.computeVertexNormals();
console.log(geometry);
function boxUnwrapUVs(geometry) {
if (!geometry.boundingBox) geometry.computeBoundingBox();
var sz = geometry.boundingBox.getSize(new THREE.Vector3());
var center = geometry.boundingBox.getCenter(new THREE.Vector3())
var min = geometry.boundingBox.min;
if (geometry.faceVertexUvs[0].length == 0) {
for (var i = 0; i < geometry.faces.length; i++) {
geometry.faceVertexUvs[0].push([new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()]);
}
}
for (var i = 0; i < geometry.faces.length; i++) {
var face = geometry.faces[i];
var faceUVs = geometry.faceVertexUvs[0][i]
var va = geometry.vertices[geometry.faces[i].a]
var vb = geometry.vertices[geometry.faces[i].b]
var vc = geometry.vertices[geometry.faces[i].c]
var vab = new THREE.Vector3().copy(vb).sub(va)
var vac = new THREE.Vector3().copy(vc).sub(va)
//now we have 2 vectors to get the cross product of...
var vcross = new THREE.Vector3().copy(vab).cross(vac);
//Find the largest axis of the plane normal...
vcross.set(Math.abs(vcross.x), Math.abs(vcross.y), Math.abs(vcross.z))
var majorAxis = vcross.x > vcross.y ? (vcross.x > vcross.z ? 'x' : vcross.y > vcross.z ? 'y' : vcross.y > vcross.z) : vcross.y > vcross.z ? 'y' : 'z'
//Take the other two axis from the largest axis
var uAxis = majorAxis == 'x' ? 'y' : majorAxis == 'y' ? 'x' : 'x';
var vAxis = majorAxis == 'x' ? 'z' : majorAxis == 'y' ? 'z' : 'y';
faceUVs[0].set((va[uAxis] - min[uAxis]) / sz[uAxis], (va[vAxis] - min[vAxis]) / sz[vAxis])
faceUVs[1].set((vb[uAxis] - min[uAxis]) / sz[uAxis], (vb[vAxis] - min[vAxis]) / sz[vAxis])
faceUVs[2].set((vc[uAxis] - min[uAxis]) / sz[uAxis], (vc[vAxis] - min[vAxis]) / sz[vAxis])
}
geometry.elementsNeedUpdate = geometry.verticesNeedUpdate = true;
}
geometry = new THREE.Geometry().fromBufferGeometry(geometry)
boxUnwrapUVs(geometry)
geometry = new THREE.BufferGeometry().fromGeometry(geometry)
var mesh = new THREE.Mesh(geometry, material);
var indices2 = new Uint32Array(tempIndices2);
geometry2.addAttribute('position', new THREE.BufferAttribute(vertices2, 3));
geometry2.setIndex(new THREE.BufferAttribute(indices2, 1));
geometry2.computeFaceNormals();
geometry2.computeVertexNormals();
/* var geom = new THREE.Geometry().fromBufferGeometry(geometry2);
var temp = generateUVs(geom);
geometry2 = new THREE.BufferGeometry().fromGeometry(temp);*/
var mesh2 = new THREE.Mesh(geometry2, material2);
//geometry2.addAttribute('uv', new THREE.BufferAttribute(uvs2, 2));
return [mesh, mesh2];
};
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.z = 400;
scene = new THREE.Scene();
var arrow;
var rayCaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
var light = new THREE.DirectionalLight();
light.position.set(200, 200, 200)
scene.add(light)
document.body.appendChild(renderer.domElement);
renderer.domElement.addEventListener("mousemove", onMouseMove);
var points = {
pointUpper1: new THREE.Vector3(-70, 0, -20),
pointUpper2: new THREE.Vector3(130, 0, -20),
pointCenter1: new THREE.Vector3(-100, 0, 0),
pointCenter2: new THREE.Vector3(150, 0, 0),
pointLower1: new THREE.Vector3(-70, 0, 20),
pointLower2: new THREE.Vector3(130, 0, 20)
};
var totalPoints = {
center: [new THREE.Vector3(-70, 0, 0), new THREE.Vector3(0, 0, 0), new THREE.Vector3(50, 0, 0), new THREE.Vector3(100, 0, 0), new THREE.Vector3(130, 0, 0)],
lower: [new THREE.Vector3(-70, 0, 20), new THREE.Vector3(0, 0, 20), new THREE.Vector3(50, 0, 20), new THREE.Vector3(100, 0, 20), new THREE.Vector3(130, 0, 20)],
upper: [new THREE.Vector3(-70, 0, -20), new THREE.Vector3(0, 0, -20), new THREE.Vector3(50, 0, -20), new THREE.Vector3(100, 0, -20), new THREE.Vector3(130, 0, -20)]
};
var sphere = new THREE.SphereGeometry(2, 10, 10);
function initPoints() {
var point1mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({
color: 0xff00
}));
point1mesh.position.copy(points.pointUpper1);
scene.add(point1mesh);
var point2mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({
color: 0x0000ff
}));
point2mesh.position.copy(points.pointUpper2);
scene.add(point2mesh);
var point3mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({
color: 0xff00
}));
point3mesh.position.copy(points.pointCenter1);
scene.add(point3mesh);
var point4mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({
color: 0x0000ff
}));
point4mesh.position.copy(points.pointCenter2);
scene.add(point4mesh);
var point5mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({
color: 0xff00
}));
point5mesh.position.copy(points.pointLower1);
scene.add(point5mesh);
var point6mesh = new THREE.Mesh(sphere, new THREE.MeshBasicMaterial({
color: 0x0000ff
}));
point6mesh.position.copy(points.pointLower2);
scene.add(point6mesh);
}
initPoints();
var mesh = new wallsGeometry(totalPoints);
function createArrow() {
var length = 30;
arrow = new THREE.ArrowHelper(
THREE.Object3D.DefaultUp,
new THREE.Vector3(),
length,
0xffff00,
1.5 * length,
1.25 * length
);
arrow.position.z = 10;
scene.add(arrow);
}
// arrow
createArrow();
function updateArrow(object, point, face) {
arrow.position.copy(point);
var normalMatrix = new THREE.Matrix3().getNormalMatrix(object.matrixWorld);
var normal = face.normal.clone().applyMatrix3(normalMatrix).normalize();
arrow.setDirection(normal);
}
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
rayCaster.setFromCamera(mouse, camera);
var intersects = rayCaster.intersectObject(mesh[0], true);
var i, il, intersect;
if (intersects[0]) {
var face = intersects[0].face;
var point = intersects[0].point;
var object = intersects[0].object;
updateArrow(object, point, face);
}
}
/* var textureLoader = new THREE.TextureLoader();
textureLoader.load('./textures/Capture.PNG', function (texture) {
console.log('texture loaded');
texture.minFilter = THREE.LinearFilter;
//mesh[0].material.map = texture;
var vertexNormalsHelper = new THREE.VertexNormalsHelper( mesh[0], 10 );
mesh[0].add( vertexNormalsHelper );
}); */
scene.add(mesh[0]);
/* textureLoader.load('./textures/abc.jpg', function (texture) {
console.log('texture loaded');
texture.minFilter = THREE.LinearFilter;
//mesh[1].material.map = texture;
var vertexNormalsHelper = new THREE.VertexNormalsHelper( mesh[1], 10 );
mesh[1].add( vertexNormalsHelper );
}); */
scene.add(mesh[1]);
//
var Orbitcontrols = new THREE.OrbitControls(camera, renderer.domElement);
Orbitcontrols.update();
}
// render
function render() {
renderer.render(scene, camera);
}
// animate
function animate() {
requestAnimationFrame(animate);
render();
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
I have created two examples of drawing lines in three.js, one uses different geometry and material and another uses only one geometry and one material. The links of the fidlles are: https://jsfiddle.net/sounakgarai/6reawwot/4/ and https://jsfiddle.net/sounakgarai/2xL70me3/ respectively.
I can hover on the lines in the first example and can see them change the material color on mouseover. And in the second example when I hover, the whole lineMesh gets its color changed as there is only one material.
Now what I would like to do is to use only one geometry like in the second example. But I want to see an effect which is seen in the first example: all the different pieces of the LineSegment (of the second example) I want to see those getting hovered in a way so that I can identify those distinctly (but I don't want to use different geometry for that).
Is it possible to achieve?
The important thing is that when I use multiple geometry, it makes the browser hang and go slow while rendering a large model which has considerably large number of members. And I also want to render multiple three.js models in different div elements in a page in future. So it may make the browser go more slow while rendering them.
An option with THREE.BufferGeometry(), THREE.LineSegments() and .vertexColors = THREE.VertexColors:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var points = [
new THREE.Vector3(-5, 5, 0),
new THREE.Vector3(),
new THREE.Vector3(),
new THREE.Vector3(5, 5, 0),
new THREE.Vector3(5, 5, 0),
new THREE.Vector3(5, -5, 0)
];
var geom = new THREE.BufferGeometry().setFromPoints(points);
geom.addAttribute("color", new THREE.BufferAttribute(new Float32Array([1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, .5, .5, .5, 1, 0, 1]), 3)); // we'll change this color attribute
geom.addAttribute("colorBase", new THREE.BufferAttribute(new Float32Array([1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, .5, .5, .5, 1, 0, 1]), 3)); // this attribute contains original colors for restoring
var mat = new THREE.LineBasicMaterial({
vertexColors: THREE.VertexColors
});
var line = new THREE.LineSegments(geom, mat);
scene.add(line);
// all of those variables are for re-use
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects = [];
var oldIndex = -1;
var col = new THREE.Color();
window.addEventListener("mousemove", onMouseMove, false);
function onMouseMove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObject(line);
if (intersects.length === 0) return;
let idx = intersects[0].index;
if (idx !== oldIndex) highlightSegment(idx, 0xFFFF00);
}
function highlightSegment(idx, color) {
setColor(idx, color);
if (oldIndex !== -1) {
restoreColor();
}
line.geometry.attributes.color.needsUpdate = true;
oldIndex = idx; // save current index as an old one
}
function setColor(idx, color) { // change color for the current segment
let idxNear = idx % 2 === 0 ? idx + 1 : idx - 1; //
// if 'idx' is an index of the start point in a segment, then its pair will be idx + 1,
// otherwise is idx - 1.
col.set(color);
let colors = line.geometry.attributes.color;
colors.setXYZ(idx, col.r, col.g, col.b); // a very useful method of 'THREE.BufferAttribute()'
colors.setXYZ(idxNear, col.r, col.g, col.b);
}
function restoreColor() { // restore the original color for the old segment
let oldIndexNear = oldIndex % 2 === 0 ? oldIndex + 1 : oldIndex - 1;
let colors = line.geometry.attributes.color;
let colorBase = line.geometry.attributes.colorBase;
colors.copyAt(oldIndex, colorBase, oldIndex); // another useful method of 'THREE.BufferAttribute()'
colors.copyAt(oldIndexNear, colorBase, oldIndexNear);
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
You have to provide vertex colors instead of the material color. Like this -
var lineMaterial = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors});
And for each vertex, push the corresponding color. Like this -
geometry.colors.push(new THREE.Color("rgb(0, 0, 255)"), new THREE.Color("rgb(0, 0, 255)"));
And in the mouse hover handler, you need to get the index of the currently intersected line and change the color of that vertex. Like this -
intersects[0].object.geometry.colors[intersects[0].index].setHex(0xff0000);
let nextIndex = intersects[0].index + (intersects[0].index % 2 == 0 ? 1 : -1 );
intersects[0].object.geometry.colors[nextIndex].setHex(0xff0000);
intersects[0].object.geometry.colorsNeedUpdate = true;
Here is the simplified code based on your second version -
var container, scene, camera, renderer;
var mouse = new THREE.Vector2();
var intersected;
init();
animate();
function guid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
function init() {
container = document.getElementById("myDiv");
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf7f7f7);
var WIDTH = container.clientWidth,
HEIGHT = container.clientHeight;
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(WIDTH, HEIGHT);
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 20000);
camera.up.set(0, 0, 1);
camera.lookAt(scene.position);
camera.position.set(0, 0, 100);
var nsize = 1, nspace = 5, nrange;
nrange = nsize * nspace;
var i, j, x, y, z = 0;
var lineMaterial = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors});
var geometry = new THREE.Geometry();
for (i = 0; i <= nsize; i++) {
for (j = 0, x = 0, y = 0; j <= nsize; j++) {
geometry.vertices.push(new THREE.Vector3(x, y, z), new THREE.Vector3(x, y + nspace, z));
geometry.colors.push(new THREE.Color("rgb(0, 0, 255)"), new THREE.Color("rgb(0, 0, 255)"));
x += nspace;
}
for (j = 0, x = 0, y = 0; j <= nsize; j++) {
geometry.vertices.push(new THREE.Vector3(x, y, z), new THREE.Vector3(x + nspace, y, z));
geometry.colors.push(new THREE.Color("rgb(0, 0, 255)"), new THREE.Color("rgb(0, 0, 255)"));
y += nspace;
}
z += nspace;
}
y = -10;
for (i = 0; i <= nsize; i++) {
for (j = 0, x = 0, z = 0; j <= nsize; j++) {
geometry.vertices.push(new THREE.Vector3(x, y, z), new THREE.Vector3(x, y, z + nspace));
geometry.colors.push(new THREE.Color("rgb(0, 0, 255)"), new THREE.Color("rgb(0, 0, 255)"));
x += nspace;
}
for (j = 0, x = 0, z = 0; j <= nsize; j++) {
geometry.vertices.push(new THREE.Vector3(x, y, z), new THREE.Vector3(x + nspace, y, z));
geometry.colors.push(new THREE.Color("rgb(0, 0, 255)"), new THREE.Color("rgb(0, 0, 255)"));
z += nspace;
}
y += nspace;
}
var lineMesh = new THREE.LineSegments(geometry, lineMaterial);
lineMesh.name = guid();
lineMesh.position.x = -(nsize * nspace) / 2;
lineMesh.position.y = -(nsize * nspace) / 2;
lineMesh.position.z = -(nsize * nspace) / 2;
scene.add(lineMesh);
}
function animate() {
requestAnimationFrame(animate);
// Render the scene.
render();
}
function onMouseMove(event) {
event.preventDefault();
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = (event.clientX / container.clientWidth) * 2 - 1;
mouse.y = - (event.clientY / container.clientHeight) * 2 + 1;
if (mouse !== null && camera !== null) {
var ray = new THREE.Raycaster();
ray.setFromCamera(mouse, camera);
var intersects = ray.intersectObjects(scene.children);
if (intersects.length > 0) {
intersects[0].object.geometry.colors[intersects[0].index].setHex(0xff0000);
let nextIndex = intersects[0].index + (intersects[0].index % 2 == 0 ? 1 : -1 );
intersects[0].object.geometry.colors[nextIndex].setHex(0xff0000);
intersects[0].object.geometry.colorsNeedUpdate = true;
}
}
}
function render() {
renderer.render(scene, camera);
}
container.addEventListener('mousemove', onMouseMove, false);
Alternative of the raycaster, you can use GPU picking to identify the intersected line.
It is not possible to change the material of a single LineSegment if you use a single geometry, but what you could do is overlay an extra LineSegment added dynamically on top of the segment you want to highlight, which I think will produce the effect you are looking for.
You can achieve that by creating a material with no depthTest:
material.depthTest=false
Which will always appear on top of the geometry that has a material with depthTest.
This simple test of WebGL texture rendering using the three.js library:
// Canvas dimensions
canvasW = Math.floor(0.9*window.innerWidth);
canvasH = Math.floor(0.75*canvasW);
cAR = canvasW / canvasH;
canvasWrapper = document.getElementById('canvasWrapper');
canvasWrapper.style.width=canvasW+'px';
canvasWrapper.style.height=canvasH+'px';
// Renderer
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
console.log("Renderer pixel ratio = "+window.devicePixelRatio);
renderer.setSize(canvasW, canvasH);
canvas = renderer.domElement;
canvasWrapper.appendChild(canvas);
// Set up camera
cameraDist = 24;
camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000);
cameraAngle = 0;
camera.position.x = cameraDist*Math.sin(cameraAngle);
camera.position.y = 0.3*cameraDist;
camera.position.z = cameraDist*Math.cos(cameraAngle);
camera.lookAt(new THREE.Vector3(0,0,0));
// Set up scene, consisting of texture-tiled ground
scene = new THREE.Scene();
groundWidth = 1000;
groundMaterial = null;
groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth);
groundGeom.rotateX(-Math.PI/2);
groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial());
scene.add(groundMesh);
//window.requestAnimationFrame(draw);
// Insert texture once it has loaded
function setGroundTexture(texture)
{
groundTexture = texture;
groundTexture.wrapS = THREE.RepeatWrapping;
groundTexture.wrapT = THREE.RepeatWrapping;
groundTexture.repeat.set(groundWidth, groundWidth);
groundTexture.anisotropy = renderer.getMaxAnisotropy();
console.log("Texture anisotropy = "+groundTexture.anisotropy);
groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture});
if (groundMesh)
{
groundMesh.material = groundMaterial;
window.requestAnimationFrame(draw);
};
}
// Start texture loading
//new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {});
setGroundTexture(makeTexture());
// Render a frame
function draw()
{
renderer.render(scene, camera);
}
// -------
function makeTexture() {
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 256;
ctx.canvas.height = 256;
ctx.fillStyle = "rgb(238, 238, 238)";
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = "rgb(208, 208, 208)";
ctx.fillRect(0, 0, 128, 128);
ctx.fillRect(128, 128, 128, 128);
for (var y = 0; y < 2; ++y) {
for (var x = 0; x < 2; ++x) {
ctx.save();
ctx.translate(x * 128 + 64, y * 128 + 64);
ctx.lineWidth = 3;
ctx.beginPath();
var radius = 50;
ctx.moveTo(radius, 0);
for (var i = 0; i <= 6; ++i) {
var a = i / 3 * Math.PI;
ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius);
}
ctx.stroke();
ctx.restore();
}
}
var tex = new THREE.Texture(ctx.canvas);
tex.needsUpdate = true;
return tex;
}
canvas, #canvasWrapper {margin-left: auto; margin-right: auto;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script>
<div id="canvasWrapper"></div>
renders perfectly on the desktop browsers I've tried, but is badly blurred when rendered on an iPad, as shown by the screenshot further down the page.
Desktop
iPad
In both cases, the texture is rendered with an anisotropy of 16 (the maximum supported by the renderer). The image used for the texture has dimensions 256 × 256 (a power of 2, which is necessary for repeated textures), and making it larger or smaller doesn't fix the problem.
texture:
I'm setting the renderer's pixel ratio to match the browser window, which means it is 1 for desktop systems and 2 for the iPad's retina display. This approach generally gives the best results for other aspects of rendering, and in any case setting the pixel ratio to 1 on the iPad, instead of 2, doesn't improve the appearance of the texture.
So my question is: is this a bug in iOS WebGL that I'll just have to live with, or is there something I can tweak in my own code to get better results on iOS devices?
Edit: This three.js demo page also renders much less clearly on the iPad than on desktop browsers, and the source for the demo uses the same general approach as my own code, which suggests that whatever trick I'm missing, it's not something simple and obvious.
I can't fully explain the source of the problem, but I've found a work-around that suggests that the cause is some kind of degradation of numerical precision, I guess in the GPU, that doesn't occur with every iPad graphics card.
The work-around involves splitting the plane geometry for the ground, which was originally just a single square (which three.js presumably divides into 2 triangles), into a grid of multiple squares. Presumably this changes something in the way the (u,v) coordinates on the object and the texture coordinates run up against the limits of floating point precision in the GPU. Also, reducing the size of the ground from 1000 to 200 helps.
The annoying thing is the overhead from having all those extra faces in the plane geometry, even though they're completely redundant in specifying the shape.
In any case, the result looks exactly the same on my desktop browsers, but vastly better on my iPad 4.
Edit: After more careful testing, I don't think subdividing the THREE.PlaneGeometry is making any difference, it's only reducing the overall size of the tiled plane that helps. And in fact, by making the size of the tiled plane large enough, whatever limit is being hit on the iPad 4 when the size is just 1000 can be reached on my iMac when the size is 80,000, as the second version of the code snippet shows. (The texture starts to degrade around 50,000, but 80,000 makes the distortion unmissable.) Obviously there are no real applications where you need to tile a surface with 50,000 x 50,000 copies of a texture, but a few hundred in each direction, which is where the iPad 4 starts to have problems, isn't extravagant.
First version of the code snippet, which fixes the problem on an iPad 4:
// Canvas dimensions
canvasW = Math.floor(0.9*window.innerWidth);
canvasH = Math.floor(0.75*canvasW);
cAR = canvasW / canvasH;
canvasWrapper = document.getElementById('canvasWrapper');
canvasWrapper.style.width=canvasW+'px';
canvasWrapper.style.height=canvasH+'px';
// Renderer
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
console.log("Renderer pixel ratio = "+window.devicePixelRatio);
renderer.setSize(canvasW, canvasH);
canvas = renderer.domElement;
canvasWrapper.appendChild(canvas);
// Set up camera
cameraDist = 24;
camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000);
cameraAngle = 0;
camera.position.x = cameraDist*Math.sin(cameraAngle);
camera.position.y = 0.3*cameraDist;
camera.position.z = cameraDist*Math.cos(cameraAngle);
camera.lookAt(new THREE.Vector3(0,0,0));
// Set up scene, consisting of texture-tiled ground
scene = new THREE.Scene();
// groundWidth = 1000;
// Reduce overall size of ground
groundWidth = 200;
groundMaterial = null;
// groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth);
// Split plane geometry into a grid of smaller squares
groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth,20,20);
groundGeom.rotateX(-Math.PI/2);
groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial());
scene.add(groundMesh);
//window.requestAnimationFrame(draw);
// Insert texture once it has loaded
function setGroundTexture(texture)
{
groundTexture = texture;
groundTexture.wrapS = THREE.RepeatWrapping;
groundTexture.wrapT = THREE.RepeatWrapping;
groundTexture.repeat.set(groundWidth, groundWidth);
groundTexture.anisotropy = renderer.getMaxAnisotropy();
console.log("Texture anisotropy = "+groundTexture.anisotropy);
groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture});
if (groundMesh)
{
groundMesh.material = groundMaterial;
window.requestAnimationFrame(draw);
};
}
// Start texture loading
//new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {});
setGroundTexture(makeTexture());
// Render a frame
function draw()
{
renderer.render(scene, camera);
}
// -------
function makeTexture() {
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 256;
ctx.canvas.height = 256;
ctx.fillStyle = "rgb(238, 238, 238)";
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = "rgb(208, 208, 208)";
ctx.fillRect(0, 0, 128, 128);
ctx.fillRect(128, 128, 128, 128);
for (var y = 0; y < 2; ++y) {
for (var x = 0; x < 2; ++x) {
ctx.save();
ctx.translate(x * 128 + 64, y * 128 + 64);
ctx.lineWidth = 3;
ctx.beginPath();
var radius = 50;
ctx.moveTo(radius, 0);
for (var i = 0; i <= 6; ++i) {
var a = i / 3 * Math.PI;
ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius);
}
ctx.stroke();
ctx.restore();
}
}
var tex = new THREE.Texture(ctx.canvas);
tex.needsUpdate = true;
return tex;
}
canvas, #canvasWrapper {margin-left: auto; margin-right: auto;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script>
<div id="canvasWrapper"></div>
Second version of the code snippet, which breaks the texture on a 2007 iMac:
// Canvas dimensions
canvasW = Math.floor(0.9*window.innerWidth);
canvasH = Math.floor(0.75*canvasW);
cAR = canvasW / canvasH;
canvasWrapper = document.getElementById('canvasWrapper');
canvasWrapper.style.width=canvasW+'px';
canvasWrapper.style.height=canvasH+'px';
// Renderer
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
console.log("Renderer pixel ratio = "+window.devicePixelRatio);
renderer.setSize(canvasW, canvasH);
canvas = renderer.domElement;
canvasWrapper.appendChild(canvas);
// Set up camera
cameraDist = 24;
camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000);
cameraAngle = 0;
camera.position.x = cameraDist*Math.sin(cameraAngle);
camera.position.y = 0.3*cameraDist;
camera.position.z = cameraDist*Math.cos(cameraAngle);
camera.lookAt(new THREE.Vector3(0,0,0));
// Set up scene, consisting of texture-tiled ground
scene = new THREE.Scene();
// groundWidth = 1000;
// Increase the size of the plane to trigger the problem
groundWidth = 80000;
groundMaterial = null;
groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth);
groundGeom.rotateX(-Math.PI/2);
groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial());
scene.add(groundMesh);
//window.requestAnimationFrame(draw);
// Insert texture once it has loaded
function setGroundTexture(texture)
{
groundTexture = texture;
groundTexture.wrapS = THREE.RepeatWrapping;
groundTexture.wrapT = THREE.RepeatWrapping;
groundTexture.repeat.set(groundWidth, groundWidth);
groundTexture.anisotropy = renderer.getMaxAnisotropy();
console.log("Texture anisotropy = "+groundTexture.anisotropy);
groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture});
if (groundMesh)
{
groundMesh.material = groundMaterial;
window.requestAnimationFrame(draw);
};
}
// Start texture loading
//new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {});
setGroundTexture(makeTexture());
// Render a frame
function draw()
{
renderer.render(scene, camera);
}
// -------
function makeTexture() {
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 256;
ctx.canvas.height = 256;
ctx.fillStyle = "rgb(238, 238, 238)";
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = "rgb(208, 208, 208)";
ctx.fillRect(0, 0, 128, 128);
ctx.fillRect(128, 128, 128, 128);
for (var y = 0; y < 2; ++y) {
for (var x = 0; x < 2; ++x) {
ctx.save();
ctx.translate(x * 128 + 64, y * 128 + 64);
ctx.lineWidth = 3;
ctx.beginPath();
var radius = 50;
ctx.moveTo(radius, 0);
for (var i = 0; i <= 6; ++i) {
var a = i / 3 * Math.PI;
ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius);
}
ctx.stroke();
ctx.restore();
}
}
var tex = new THREE.Texture(ctx.canvas);
tex.needsUpdate = true;
return tex;
}
canvas, #canvasWrapper {margin-left: auto; margin-right: auto;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script>
<div id="canvasWrapper"></div>
Greg Egan's observation makes a lot of sense. If you not only subdivide the plane but tile the UV coords so they repeat instead of using large numbers that might fix it.
// Canvas dimensions
canvasW = Math.floor(0.9*window.innerWidth);
canvasH = Math.floor(0.75*canvasW);
cAR = canvasW / canvasH;
canvasWrapper = document.getElementById('canvasWrapper');
canvasWrapper.style.width=canvasW+'px';
canvasWrapper.style.height=canvasH+'px';
// Renderer
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
console.log("Renderer pixel ratio = "+window.devicePixelRatio);
renderer.setSize(canvasW, canvasH);
canvas = renderer.domElement;
canvasWrapper.appendChild(canvas);
// Set up camera
cameraDist = 24;
camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000);
cameraAngle = 0;
camera.position.x = cameraDist*Math.sin(cameraAngle);
camera.position.y = 0.3*cameraDist;
camera.position.z = cameraDist*Math.cos(cameraAngle);
camera.lookAt(new THREE.Vector3(0,0,0));
// Set up scene, consisting of texture-tiled ground
scene = new THREE.Scene();
// groundWidth = 1000;
// Reduce overall size of ground
groundWidth = 200;
groundMaterial = null;
// groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth);
// Split plane geometry into a grid of smaller squares
//groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth,20,20);
var groundGeom = new THREE.BufferGeometry();
var quads = groundWidth * groundWidth;
var positions = new Float32Array( quads * 6 * 3 );
var normals = new Float32Array( quads * 6 * 3 );
var texcoords = new Float32Array( quads * 6 * 2 );
for (var yy = 0; yy < groundWidth; ++yy) {
for (var xx = 0; xx < groundWidth; ++xx) {
var qoff = (yy * groundWidth + xx) * 6;
var poff = qoff * 3;
var x = xx - groundWidth / 2;
var y = yy - groundWidth / 2;
positions[poff + 0] = x;
positions[poff + 1] = y;
positions[poff + 2] = 0;
positions[poff + 3] = x + 1;
positions[poff + 4] = y;
positions[poff + 5] = 0;
positions[poff + 6] = x;
positions[poff + 7] = y + 1;
positions[poff + 8] = 0;
positions[poff + 9] = x;
positions[poff + 10] = y + 1;
positions[poff + 11] = 0;
positions[poff + 12] = x + 1;
positions[poff + 13] = y;
positions[poff + 14] = 0;
positions[poff + 15] = x + 1;
positions[poff + 16] = y + 1;
positions[poff + 17] = 0;
normals[poff + 0] = 0;
normals[poff + 1] = 1;
normals[poff + 2] = 0;
normals[poff + 3] = 0;
normals[poff + 4] = 1;
normals[poff + 5] = 0;
normals[poff + 6] = 0;
normals[poff + 7] = 1;
normals[poff + 8] = 0;
normals[poff + 9] = 0;
normals[poff + 10] = 1;
normals[poff + 11] = 0;
normals[poff + 12] = 0;
normals[poff + 13] = 1;
normals[poff + 14] = 0;
normals[poff + 15] = 0;
normals[poff + 16] = 1;
normals[poff + 17] = 0;
var toff = qoff * 2;
texcoords[toff + 0] = 0;
texcoords[toff + 1] = 0;
texcoords[toff + 2] = 1;
texcoords[toff + 3] = 0;
texcoords[toff + 4] = 0;
texcoords[toff + 5] = 1;
texcoords[toff + 6] = 0;
texcoords[toff + 7] = 1;
texcoords[toff + 8] = 1;
texcoords[toff + 9] = 0;
texcoords[toff + 10] = 1;
texcoords[toff + 11] = 1;
}
}
groundGeom.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
groundGeom.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
groundGeom.addAttribute( 'uv', new THREE.BufferAttribute( texcoords, 2 ) );
groundGeom.computeBoundingSphere();
groundGeom.rotateX(-Math.PI/2);
groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial());
scene.add(groundMesh);
//window.requestAnimationFrame(draw);
// Insert texture once it has loaded
function setGroundTexture(texture)
{
groundTexture = texture;
groundTexture.wrapS = THREE.RepeatWrapping;
groundTexture.wrapT = THREE.RepeatWrapping;
groundTexture.repeat.set(1, 1);
groundTexture.anisotropy = renderer.getMaxAnisotropy();
console.log("Texture anisotropy = "+groundTexture.anisotropy);
groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture});
if (groundMesh)
{
groundMesh.material = groundMaterial;
window.requestAnimationFrame(draw);
};
}
// Start texture loading
//new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {});
setGroundTexture(makeTexture());
// Render a frame
function draw()
{
renderer.render(scene, camera);
}
// -------
function makeTexture() {
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 256;
ctx.canvas.height = 256;
ctx.fillStyle = "rgb(238, 238, 238)";
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = "rgb(208, 208, 208)";
ctx.fillRect(0, 0, 128, 128);
ctx.fillRect(128, 128, 128, 128);
for (var y = 0; y < 2; ++y) {
for (var x = 0; x < 2; ++x) {
ctx.save();
ctx.translate(x * 128 + 64, y * 128 + 64);
ctx.lineWidth = 3;
ctx.beginPath();
var radius = 50;
ctx.moveTo(radius, 0);
for (var i = 0; i <= 6; ++i) {
var a = i / 3 * Math.PI;
ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius);
}
ctx.stroke();
ctx.restore();
}
}
var tex = new THREE.Texture(ctx.canvas);
tex.needsUpdate = true;
return tex;
}
canvas, #canvasWrapper {margin-left: auto; margin-right: auto;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script>
<div id="canvasWrapper"></div>