Maintain aspect on Three.js texture - three.js

I'm trying to keep a texture centered when it's shown in a different sized box.
I've seen this answer
Three.js: Make image texture fit object without distorting or repeating
But it's not quite doing it for me.
this.texture = new THREE.Texture(this.image)
const vec = new THREE.Vector3()
new THREE.Box3().setFromObject( this.rounded ).getSize(vec)
const imageAspect = this.image.width/this.image.height
const boxAspect = vec.x/vec.y
this.texture.wrapT = THREE.RepeatWrapping;
this.texture.offset.y = 0.5 * ( 1 - boxAspect/imageAspect )
//texture.wrapT = THREE.RepeatWrapping; texture.repeat.x = geometryAspectRatio / imageAspectRatio; texture.offset.x = 0.5 * ( 1 - texture.repeat.x )
this.texture.needsUpdate = true
this.rounded.material = new THREE.MeshPhongMaterial( { map: this.texture, side: THREE.DoubleSide } )
In this aspect the values are
Image: {width:399 height:275}
Texture: {width:1, height: 0.75}
In this aspect the values are
Image: {width:399 height:275}
Texture: {width:2, height: 1}
How do I fix it so the graphic is always central, maintains the aspect and is not distorted?

I hope I got you correctly, here is an option of how you can center it:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var planeGeom = new THREE.PlaneBufferGeometry(16, 9);
var planeMat = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/758px-Canestra_di_frutta_(Caravaggio).jpg", tex => {
//console.log(tex);
//console.log(tex.image.width, tex.image.height);
let imgRatio = tex.image.width / tex.image.height;
let planeRatio = planeGeom.parameters.width / planeGeom.parameters.height;
//console.log(imgRatio, planeRatio);
tex.wrapS = THREE.RepeatWrapping; // THREE.ClampToEdgeWrapping;
tex.repeat.x = planeRatio / imgRatio;
tex.offset.x = -0.5 * ((planeRatio / imgRatio) - 1);
})
});
var plane = new THREE.Mesh(planeGeom, planeMat);
scene.add(plane);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
})
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
Addition: how to re-compute UVs for ShapeBufferGeometry
var box = new THREE.Box3().setFromObject(mesh); // mesh with ShapeBufferGeometry
var size = new THREE.Vector3();
box.getSize(size);
var vec3 = new THREE.Vector3(); // temp vector
var attPos = mesh.geometry.attributes.position;
var attUv = mesh.geometry.attributes.uv;
for (let i = 0; i < attPos.count; i++){
vec3.fromBufferAttribute(attPos, i);
attUv.setXY(i,
(vec3.x - box.min.x) / size.x,
(vec3.y - box.min.y) / size.y
);
}
attUv.needsUpdate = true; // just in case

Related

Trying to "land" cylinders onto terrain in three.js

I want these cylinders to rest just on top of the terrain. I've tried using raycasters of different orientation similar to but the raytracer distance and point measurements don't seem correct - when I apply them, the cylinders fall through the bottom of the grid. It's as if they are looking at the non-transformed, non-displacement map mesh. How to get the intersects to line up perfectly with the actual displacement map mesh
https://github.com/ledlogic/terrain
/* from https://www.youtube.com/watch?v=2AQLMZwQpDo */
import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as dat from 'dat.gui'
const loader = new THREE.TextureLoader()
const heightImg = loader.load('/height.png')
const textureImg = loader.load('/texture.jpg')
const alphaImg = loader.load('/alpha-02.png')
// Debug
const gui = new dat.GUI()
// Canvas
const canvas = document.querySelector('canvas.webgl')
// Scene
const scene = new THREE.Scene()
// Objects
const width = 3;
const height = 3;
const widthSegments = 1500;
const heightSegments = 1500;
const geometry = new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments)
// Materials
const material = new THREE.MeshStandardMaterial({
color: 'gray',
map: textureImg,
displacementMap: heightImg,
displacementScale: 0.5,
depthTest: true
})
// Mesh
const planeMesh = new THREE.Mesh(geometry, material);
scene.add(planeMesh);
planeMesh.rotation.x = -Math.PI/2
// Lights
const pointLight = new THREE.PointLight('#dcdcff', 2)
pointLight.position.x = 3
pointLight.position.y = 3
pointLight.position.z = 3
scene.add(pointLight)
// Sizes
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
window.addEventListener('resize', () => {
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.y = 1
scene.add(camera)
// Renderer
const renderer = new THREE.WebGLRenderer({
canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
// Cylinder
var gridMin = -1.4
var gridMax = 1.4
var gridDelta = 0.2
var vector = new THREE.Vector3(0, 0, -1)
for (var x=gridMin;x<gridMax;x+=gridDelta) {
for (var z=gridMin;z<gridMax;z+=gridDelta) {
const cylinderGeometry = new THREE.CylinderGeometry( 0.01, 0.01, 0.05, 20 );
const cylinderMaterial = new THREE.MeshStandardMaterial( {color: 'gray'} );
const cylinderMesh = new THREE.Mesh( cylinderGeometry, cylinderMaterial );
cylinderMesh.position.x = x
cylinderMesh.position.y = 0.5
cylinderMesh.position.z = z
// fix y positions.
/*
var raycaster = new THREE.Raycaster();
raycaster.set(cylinderMesh.position, vector);
var velocity = new THREE.Vector3();
var intersects = raycaster.intersectObject(planeMesh);
if (intersects.length) {
var d = intersects[0].distance;
//cylinderMesh.translateY(-d);
}
*/
scene.add(cylinderMesh);
}
}
var mwdelta = 0;
document.addEventListener( 'mousewheel', (event) => {
mwdelta +=event.deltaY;
});
// Clock
let cameraRadius = 3
const clock = new THREE.Clock()
const tick = () => {
const elapsedTime = clock.getElapsedTime()
var t = 0.125 * elapsedTime
var zoomRadius = cameraRadius + mwdelta / 500
camera.position.x = zoomRadius * Math.sin(t)
camera.position.z = zoomRadius * Math.cos(t)
camera.rotation.y = t
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()

Selfshadow plane affected by a displacement map not working

Working on some kind of fictional treasure map. I'm cutting a large displacement map intosmaller tiles as I don't yet how wide the final terrain is going to be -- right now it's 5*5, but it could be wider in the future
For some reasons, I am having issues projecting shadows on the displaced planes.
I don't know where the problem is coming from. Maybe it's the way I push meshes into an array through a function, i'm afraid i'm not doing this the right way.
I'd like to achieve the result using a directional light
Here is a c4d draft of what i'm trying to achieve
and here is what i'm able to do in the browser (didnt manage to tile them properly yet :^)
var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
var material = [];
var texture = [];
var tile = [];
var planeRes = 128;
var planesize = 1;
var dim = 5;
var size = dim * dim;
var DispScale = 2;
var geometry = new THREE.PlaneBufferGeometry(planesize,planesize,planeRes, planeRes);
function tileGenerator(inc) {
if (inc < 10) {
texture[inc] = new THREE.TextureLoader().load('cut25lowres/image_part_00' + inc + '.jpg');
} else {
texture[inc] = new THREE.TextureLoader().load('cut25lowres/image_part_0' + inc + '.jpg');
}
material[inc] = new THREE.MeshPhongMaterial({
color: 0xffffff,
displacementMap: texture[inc],
side: THREE.DoubleSide,
receiveShadow : true,
castShadow : true
});
tile[inc] = new THREE.Mesh(geometry, material[inc]);
}
for (var i = 1; i < size + 1; i++) {
tileGenerator(i);
}
for (var i = 1; i < size + 1; i++) {
tile[i].position.set(-planesize * (i % dim)+1, 0, -planesize * Math.ceil(i / dim)+1 );
tile[i].rotation.x = Math.PI / 2 + Math.PI;
tile[i].rotation.z = Math.PI / 2;
scene.add(tile[i]);
}
var dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
dirLight.castShadow = true;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 6;
dirLight.shadow.mapSize.set( 1024, 1024 );
var targetObject = new THREE.Object3D();
targetObject.position.x = -10;
targetObject.position.z = -10;
dirLight.position.y = 3;
scene.add(targetObject);
dirLight.target = targetObject;
scene.add( dirLight );
Edit : Here is a cleaner version without the array as it's not part of the problem
jsfiddle.net/clemtre/3y9tqc6j/34
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var scene = new THREE.Scene();
var heightmap = new THREE.TextureLoader().load('https://i.imgur.com/MVYhfd7.jpeg');
var geometry = new THREE.PlaneGeometry(20, 20, 100, 100);
var material = new THREE.MeshPhongMaterial({
color: 0xffffff,
displacementMap: heightmap,
displacementScale: 10
});
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 1, 1).normalize();
light.castShadow = true;
scene.add(light);
var plane = new THREE.Mesh(geometry, material);
plane.rotation.x = -Math.PI/2;
scene.add(plane);
camera.position.z = -20;
camera.position.y = 5;
controls.update();
var animate = function() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
animate();
Many thanks!

Turning an image into Point Cloud in Three.js

I want to turn an image into a Point Cloud and offset the z-position based on the color value. So far I have:
Loaded an image, and upon load
Built a geometry and stored image colour within the geometry
Create a PointsMaterial and build a THREE.Points
Add to scene
However, the result looks nothing like the input image. I am missing something but unsure of what.
What am I missing with regards to displaying a point cloud version of the image?
Example: https://img2pointcloud.glitch.me/
<html>
<head>
<script src="https://threejs.org/build/three.js"></script>
</head>
<body style="margin:0">
<script>
var container, renderer, points;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.set(0, 0, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var imgSrc =
"https://cdn.glitch.com/c3afecd9-365c-424f-a08e-90fce02a151a%2Fimg.jpeg?v=1588102020636";
var img = new Image();
var width = 1920 / 4;
var height = 1080 / 4;
img.crossOrigin = "anonymous";
img.src = imgSrc;
img.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var imageData = ctx.getImageData(0, 0, width, height);
var data = imageData.data;
document.body.appendChild(canvas);
createPointCloud(imageData);
};
function createPointCloud(imageData) {
var geometry = new THREE.BufferGeometry();
var positions = [];
for (var x = 0; x < height; x++) {
for (var z = 0; z < width; z++) {
positions.push(x, z, x);
}
}
var color = new THREE.Color();
var colors = [];
for (let i = 0; i < imageData.data.length; i += 4) {
const r = imageData.data[i + 0];
const g = imageData.data[i + 1];
const b = imageData.data[i + 2];
const a = imageData.data[i + 3];
color.setRGB(r, g, b);
colors.push(color.r, color.b, color.c);
}
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3)
);
geometry.setAttribute(
"color",
new THREE.Float32BufferAttribute(colors, 3)
);
geometry.computeBoundingSphere();
var material = new THREE.PointsMaterial({
size: 0.1,
vertexColors: true
});
points = new THREE.Points(geometry, material);
scene.add(points);
animate();
}
function animate() {
requestAnimationFrame(animate);
render();
}
function render() {
renderer.render(scene, camera);
}
</script>
</body>
</html>
[1]: https://i.stack.imgur.com/9etFB.png
[2]: https://i.stack.imgur.com/HKM7m.png
Another approach is to modify shaders of THREE.PointsMaterial():
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(-75, 0, 1);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
new THREE.TextureLoader().load("https://cdn.glitch.com/c3afecd9-365c-424f-a08e-90fce02a151a%2Fimg.jpeg?v=1588102020636", tex => {
let img = tex.image;
console.log(img.width, img.height);
let g = new THREE.PlaneBufferGeometry(Math.floor(img.width / 4), Math.floor(img.height / 4), img.width, img.height);
let m = new THREE.PointsMaterial({
map: tex,
size: 0.1
});
m.onBeforeCompile = shader => {
shader.vertexShader = `
varying vec2 vUv;
${shader.vertexShader}
`;
shader.vertexShader = shader.vertexShader.replace(
`#include <color_vertex>`,
`
vUv = uv;
#include <color_vertex>`
);
shader.fragmentShader = `
varying vec2 vUv;
${shader.fragmentShader}
`;
shader.fragmentShader = shader.fragmentShader.replace(
`#include <map_particle_fragment>`,
`vec4 mapTexel = texture2D( map, vUv );
diffuseColor = mapTexel;
`
);
console.log(shader.vertexShader);
};
let p = new THREE.Points(g, m);
scene.add(p);
});
renderer.setAnimationLoop(() => {
renderer.render(scene, camera)
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
There's a big typo in my code where it for some reason says colors.push(color.r, color.b, color.c); and not colors.push(color.r, color.g, color.b); (notice the rbc vs rgb).
Sorted now, thanks for the comment, made me see it.

How do you plot elliptic paraboloid in ThreeJS

I am trying to create a elliptic paraboloid in threeJS using parametric curves. Here is the link to what I am looking to plot:
https://mathinsight.org/level_sets.
Here is what I have right now:
Here is the current function definition, however it seems to be producing a different result.
let planeCreator = function(u,v,w){
var height = 15;
var size = 20;
var u = u * height;
var v = (v * 2 * Math.PI);
var x = size * Math.sqrt(u) * Math.cos(v);
var y = u;
var z = size * Math.sqrt(u) * Math.sin(v);
w.set(x,-y,z);
};
let geometry = new THREE.ParametricBufferGeometry( planeCreator, 50, 50 );
geometry.center();
let material = new THREE.MeshPhongMaterial( {
color: 0xff0000,
side: THREE.DoubleSide,
wireframe: true
} );
let object = new THREE.Mesh( geometry, material );
scene.add( object );
Here is what is currently plotted:
As an option (without parametric geometries):
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(-4, 2, 5);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var planeGeom = new THREE.PlaneBufferGeometry(4, 4, 20, 20);
planeGeom.rotateX(-Math.PI * 0.5);
var v = new THREE.Vector3();
var positions = planeGeom.attributes.position;
for (var i = 0; i < positions.count; i++) {
v.fromBufferAttribute(positions, i);
positions.setY(i, (-(v.x * v.x) - (2 * v.z * v.z)) * 0.25);
}
planeGeom.center();
planeGeom.computeVertexNormals();
var ellipticParaboloidSurface = new THREE.Mesh(planeGeom, new THREE.MeshNormalMaterial({
side: THREE.DoubleSide
}));
scene.add(ellipticParaboloidSurface);
var boxHelper = new THREE.BoxHelper(ellipticParaboloidSurface, "yellow");
scene.add(boxHelper);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
})
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

set camera relative to point collection

I'm trying to set the camera to be 3 units away from a collection of points I would like this to be relative to the group of points since the points will change later on.
So far I can retrieve x,y,z coordinates from the database and are returned using djangos {{coord_x}} I will have to return the correct length, (I could do this on the python side - len()) for now the database query is limited to 20 rows. These points are brought into three.js using a for loop.
How do I set a camera relative to the objects? Do I need to calculate a bounding box?
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.001, 100000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// allow resizing of the window
window.addEventListener('resize', function()
{
var width = window.innerWidth;
var height = window.innerHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
});
//Controls
controls = new THREE.OrbitControls(camera, renderer.domElement)
//create the shape
var geometry = new THREE.BoxGeometry(1, 1, 1);
var material = new THREE.MeshBasicMaterial({color: 0x007654, wireframe: false});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
var numpoints = 20;
var dots = []; //If you want to use for other task
for (var i = 0 ; i < numpoints ; i++) {
var x = "{{coord_x}}";
var y = "{{coord_y}}";
var z = "{{coord_z}}";
// var x = Math.random() * (0 - 1) + 1
// var y = Math.random() * (0 - 1) + 1
// var z = Math.random() * (0 - 1) + 1
var dotGeometry = new THREE.Geometry();
dots.push(dotGeometry);
dotGeometry.vertices.push(new THREE.Vector3(x, y, z));
var dotMaterial = new THREE.PointsMaterial( { size: 3, sizeAttenuation: false, color: 0xFF0000 });
var dot = new THREE.Points( dotGeometry, dotMaterial);
scene.add(dot);
}
camera.position.z = 30
//game logic, allow rotation
var update = function()
{
//cube.rotation.x += 0.00;
//cube.rotation.y += 0.0025;
//dot.rotation.x += 0.00;
//dot.rotation.y += 0.005;
};
// draw scene
var render = function()
{
renderer.render(scene, camera);
};
// run game loop (update, render, repeat)
var GameLoop = function()
{
requestAnimationFrame(GameLoop);
update();
render();
};
GameLoop();
</script>
That's how you can work with THREE.Sphere() object to set the position of your camera:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 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 geom = new THREE.Geometry();
for (let i = 0; i < 100; i++) {
geom.vertices.push(
new THREE.Vector3(
Math.random() - 0.5,
Math.random() - 0.5,
Math.random() - 0.5
).multiplyScalar(10)
);
}
var points = new THREE.Points(geom, new THREE.PointsMaterial({
size: 0.25,
color: "aqua"
}));
scene.add(points);
var sphere = new THREE.Sphere().setFromPoints(geom.vertices);
console.log(sphere);
camera.position.copy(sphere.center);
camera.position.z += sphere.radius / Math.sin(THREE.Math.degToRad(camera.fov / 2));
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>

Resources