How to prevent an object from stretching after rotation? - three.js

How to prevent an object from being stretched after being rotated inside another object that has some scale?
In general, I want the child to behave as if the parent has a size of 1, 1, 1,
and I don't want to create a group for the parent and for the child.
A few letters for the system to accept the question
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Renderer
const renderer = new THREE.WebGLRenderer({
alpha: false,
antialias: true,
stencil: false,
depth: false
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera();
camera.position.z = 10;
new OrbitControls(camera, renderer.domElement)
// Parent
const parentGeometry = new THREE.BoxGeometry(1, 1, 1);
const parentMaterial = new THREE.MeshBasicMaterial({ color: "tomato" });
const parent = new THREE.Mesh(parentGeometry, parentMaterial);
parent.scale.set(3, 0.5, 3)
scene.add(parent);
// Child
const childGeometry = new THREE.BoxGeometry(1, 1, 1);
const childMaterial = new THREE.MeshBasicMaterial({ color: "lightblue" });
const child = new THREE.Mesh(childGeometry, childMaterial);
parent.add(child);
child.scale.x = 1 / parent.scale.x;
child.scale.y = 1 / parent.scale.y;
child.scale.z = 1 / parent.scale.z;
// ???
child.rotation.x = Math.PI / 4;
function resize() {
const width = document.documentElement.clientWidth;
const height = window.innerHeight;
camera.aspect = width / height;
camera.fov = 55
camera.updateProjectionMatrix();
renderer.setPixelRatio(Math.min(2, devicePixelRatio));
renderer.setSize(width, height);
}
window.addEventListener("resize", resize);
resize();
function tick(t) {
requestAnimationFrame(tick);
renderer.render(scene,camera);
}
requestAnimationFrame(tick);

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()

THREE.js Raycasting Points

I'm trying to implement raycasting for the Points object.
The problem is that the raycaster selection doesn't match the pointer position.
I took as reference these 2 examples from three:
webgl_interactive_raycasting_points
webgl_interactive_points
but i can't still figure out what i am doing wrong.
here is my code pen:
https://codepen.io/simone-tasca/pen/YzapWMN
let scene = new THREE.Scene()
const near = 0.1
const far = 5000
const fov = 30
const aspect = window.innerWidth / window.innerHeight
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
camera.position.set(-1.25, 0.8, -1.9)
let ambientLight = new THREE.AmbientLight('white', 1)
scene.add(ambientLight)
// POINTS CONTAINER ==========================================================
const worldCenter = [-1.07, 0, -6.85]
const rotationCorrection = [0.781, 4.305, 0.28]
const worldMap = new THREE.Object3D()
worldMap.position.set(...worldCenter)
worldMap.rotation.set(...rotationCorrection)
scene.add(worldMap)
// THREE.Points =============================================================
const PARTICLE_SIZE = 0.1
let particles, raycaster, INTERSECTED, pointer
let vertices = []
let names = []
let sizes = []
prepareData(sampleData).forEach(coords => {
vertices.push(...coords)
sizes.push(PARTICLE_SIZE)
})
const geometry = new THREE.BufferGeometry()
geometry.attributes.position = new THREE.Float32BufferAttribute(vertices, 3)
geometry.attributes.size = new THREE.Float32BufferAttribute(sizes, 1)
let material = new THREE.PointsMaterial({
color: 0xffffff,
transparent: true,
depthTest: true,
depthWrite: false
})
material.onBeforeCompile = shader => {
shader.vertexShader =
shader.vertexShader.replace('uniform float size;', 'attribute float size;')
}
particles = new THREE.Points(geometry, material)
worldMap.add(particles)
// RAYCASTER =============================================================
raycaster = new THREE.Raycaster()
pointer = new THREE.Vector2(99999, 99999)
document.addEventListener('pointermove', (event) => {
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
})
document.addEventListener('pointerout', () => pointer.set(99999, 99999))
// RENDERING ==================================================================
let canvas = document.querySelector('#c')
const renderer = new THREE.WebGLRenderer({
canvas: canvas, alpha: false
})
function resizeRendererToDisplaySize(renderer) {
const width = canvas.clientWidth
const height = canvas.clientHeight
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) renderer.setSize(width, height, false)
return needResize
}
function render(time) {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
const geometry = particles.geometry
const attributes = geometry.attributes
raycaster.setFromCamera(pointer, camera)
let intersects = raycaster.intersectObject(particles)
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].index) {
console.log(intersects[0].index)
attributes.size.array[INTERSECTED] = PARTICLE_SIZE
INTERSECTED = intersects[0].index
attributes.size.array[INTERSECTED] = PARTICLE_SIZE * 3
attributes.size.needsUpdate = true
}
} else if (INTERSECTED !== null) {
attributes.size.array[INTERSECTED] = PARTICLE_SIZE
attributes.size.needsUpdate = true
INTERSECTED = null
}
renderer.render(scene, camera)
requestAnimationFrame(render)
}
render()
You're very close. The only thing missing is to declare how "wide" you want your raycaster to be. Add this line after initiating the raycaster:
raycaster = new THREE.Raycaster()
raycaster.params.Points.threshold = 0.05;
The threshold is by default 1 unit wide. Think of this as painting with a very broad brush, the first particle you'll hit may not be the closest to your mouse pointer. So when you get intersects[0].index, it's going to be the first particle you hit with that broad ray (closest to the camera), not the closest one to your mouse. If you declare a narrower threshold, your ray will be more precise and you'll get more accurate results.
https://threejs.org/docs/#api/en/core/Raycaster.params

Why I cannot use 'VertexColors' parameter from Threejs?

I'm using threejs to provide a 3D animation...
this is my main.js file content:
import './style.css'
import {
AxesHelper,
BufferGeometry,
Float32BufferAttribute,
MathUtils,
PerspectiveCamera,
Points,
PointsMaterial,
Scene,
TextureLoader,
VertexColors,
WebGLRenderer,
} from "three";
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
const count = 100;
const distance = 2;
const textureLoader = new TextureLoader();
const circleTexture = textureLoader.load("ball.png");
const scene = new Scene();
scene.add(new AxesHelper());
const camera = new PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.01,1000);
camera.position.z = 2;
camera.position.y = 0.5;
camera.position.x = 0.5;
scene.add(camera);
const pts = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
for (let i = 0; i < pts.length; i++) {
pts[i] = MathUtils.randFloatSpread(distance * 2);
colors[i] = Math.random();
}
const geometry = new BufferGeometry();
geometry.setAttribute('position', new Float32BufferAttribute(pts,3));
geometry.setAttribute('colors', new Float32BufferAttribute(colors,3));
const pointMaterial = new PointsMaterial({
size: 0.1,
map : circleTexture,
alphaTest: 0.01,
transparent: true,
vertexColors : VertexColors,
});
const points = new Points(geometry, pointMaterial);
scene.add(points);
const renderer = new WebGLRenderer({
antialias: true, //removing all ugly pixels from cube's sides
alpha : true,
});
renderer.setClearColor(0x000000, 0);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
function anime() {
renderer.render(scene, camera);
controls.update();
requestAnimationFrame(anime);
}
anime();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); // mettre a jour la camera...
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
});
in PointsMaterial() constructor , I want to assign a value to vertexColor to make a variety of colors for my objects (3D looking points...), but I got the following error on the console :
Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/three.js?v=19235266' does not provide an export named 'VertexColors' (at main.js?t=1655672405709:13:5)
it seems like threejs doesn't contain this property or what ??!!!

How to project a texture to curved surface?

Screen capture
I tried to make a 3/4 cylinder surface, and I made it from extruting by a ellipe path,
but when I tried to load a texture to the surface, It does not as I exprected: uniformly painted to the surface, it's stretched
I know it's about texture projection, But I dont know how to set options.
class EllipseCurve3 extends THREE.Curve {
ellipse = null
constructor (ellipse) {
super()
this.ellipse = ellipse
}
getPoint(t, optionalTarget = new THREE.Vector3()) {
const point = this.ellipse.getPoint(t, optionalTarget)
return new THREE.Vector3(
point.x,
point.y,
0
)
}
}
// Scene
const scene = new THREE.Scene();
var shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.moveTo(0, 1);
shape.lineTo(50, 1);
shape.moveTo(50, 0);
shape.lineTo(0, 0);
// var curve = new THREE.CatmullRomCurve3([
// new THREE.Vector3(0, 0, 50),
// new THREE.Vector3(-50, 0, 0),
// new THREE.Vector3(0, 0, -50)
// ]);
const arc = new THREE.EllipseCurve(
0,
0, // ax, aY
100,
100, // xRadius, yRadius
0,
1.5 * Math.PI, // aStartAngle, aEndAngle
false, // aClockwise
0 // aRotation
),
path = new EllipseCurve3(arc)
geometry = new THREE.ExtrudeGeometry(shape, {
bevelEnabled: false,
extrudePath: path,
steps: 50,
depth: 5,
amount: 20,
material: 0,
extrudeMaterial: 1
});
// Set up lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
directionalLight.position.set(100, 200, 100); // x, y, z
scene.add(directionalLight);
// Camera
const width = 200;
const height = width * (window.innerHeight / window.innerWidth);
const camera = new THREE.OrthographicCamera(
width / -2, // left
width / 2, // right
height / 2, // top
height / -2, // bottom
0.1, // near
1000 // far
);
camera.position.set(400, 400, 400);
camera.lookAt(0, 0, 0);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
renderer.render(scene, camera);
// Add it to HTML
document.body.appendChild(renderer.domElement);
var textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = true;
const picture = 'https://threejs.org/examples/textures/uv_grid_opengl.jpg'
textureLoader.load(picture, function(texture) {
// repeat pattern
texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping;
// zoom in on pattern
texture.repeat.set(.01, .01);
// assign texture via MeshBasicMaterial
var material = new THREE.MeshBasicMaterial({
map: texture,
needsUpdate: true,
// transparent: true,
// premultipliedAlpha: true,
// side: THREE.DoubleSide,
// blending: THREE.AdditiveBlending
});
// var material = new THREE.MeshPhongMaterial({ color: 0x0048ff });
var mesh = new THREE.Mesh(geometry, material)
mesh.rotation.x = Math.PI / 2
// mesh.rotation.z = Math.PI / 2
scene.add(mesh)
scene.add(cube)
})
function render() {
renderer.render(scene, camera);
// Rotate out group
// svgGroup.rotation.y -= 0.005
controls.update();
requestAnimationFrame(render);
}
render();
Code here
https://codepen.io/mike-xu/pen/RwQeEXJ
"I know it's about texture projection" - It's about computing UV, based on vertices coordinates.
CylinderGeometry also may help to achieve the result you described. With less code, and more convenient and predictable way.
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls.js";
console.clear();
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 10, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", event => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
let controls = new OrbitControls(camera, renderer.domElement);
scene.add(new THREE.AxesHelper(10));
let g = new THREE.CylinderGeometry(5, 5, 5, 100, 20, true, 0, Math.PI * 1.5);
let m = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
map: new THREE.TextureLoader().load(
"https://threejs.org/examples/textures/uv_grid_opengl.jpg",
tex => {
tex.wrapS = tex.wrapT = THREE.MirroredRepeatWrapping;
tex.repeat.set(3, 1);
}
)
});
let c = new THREE.Mesh(g, m);
scene.add(c);
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
</script>

Rendered object looks flat even with shadows

I'm a three.js newb. I've been playing around with it for a couple of days and haven't been able to figure out how to make my objects look more realistic. I suspect there's no simple answer for this question, but is there anything I can do to improve my rendering quality without going into the depth of "rendering science"? Maybe I'm missing some configs. Thank you for any advice!
Here's the relevant code used in rendering a kitchen cabinet frame.
this.renderer = new THREE.WebGLRenderer({ antialias: true })
this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight)
this.renderer.sortObjects = false
this.renderer.setClearColor(0xf0f0f0)
this.renderer.gammaFactor = 2.2
this.renderer.gammaOutput = true
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.shadowMap.enabled = true
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
const light = new THREE.AmbientLight(0xffffff, 0.8)
const light2 = new THREE.PointLight(0xffffff, 0.3)
light2.position.set(400, 400, 400)
light2.shadow.camera.near = 10
light2.shadow.camera.far = 10000
light2.shadow.mapSize.width = 2048
light2.shadow.mapSize.height = 2048
light2.castShadow = true
this.scene.add(light2)
this.scene.add(light)
const material = new THREE.MeshStandardMaterial({ color: 0xffffff, metalness: 0, roughness: 0 })
let scene, camera, controls, ambient, point, loader, renderer, container, stats;
const targetRotation = 0;
const targetRotationOnMouseDown = 0;
const mouseX = 0;
const mouseXOnMouseDown = 0;
const windowHalfX = window.innerWidth / 2;
const windowHalfY = window.innerHeight / 2;
init();
animate();
var box, b1, b2, b3;
function init() {
// Create a scene which will hold all our meshes to be rendered
scene = new THREE.Scene();
// Create and position a camera
camera = new THREE.PerspectiveCamera(
60, // Field of view
window.innerWidth / window.innerHeight, // Aspect ratio
/*window.innerWidth / -8,
window.innerWidth / 8,
window.innerHeight / 8,
window.innerHeight / -8,
*/
0.1, // Near clipping pane
1000 // Far clipping pane
);
scene.add(camera)
// Reposition the camera
camera.position.set(0, 5, 10);
// Point the camera at a given coordinate
camera.lookAt(new THREE.Vector3(0, 0, 0));
// Add orbit control
controls = new THREE.OrbitControls(camera);
controls.target.set(0, -0.5, 0);
controls.update();
// Add an ambient lights
ambient = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambient);
// Add a point light that will cast shadows
point = new THREE.PointLight(0xffffff, 1);
point.position.set(25, 50, 25);
point.castShadow = true;
point.shadow.mapSize.width = 1024;
point.shadow.mapSize.height = 1024;
scene.add(point);
group = new THREE.Group();
group.position.y = 0;
scene.add(group);
rotationAnchor = new THREE.Object3D()
group.add(rotationAnchor);
box = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshStandardMaterial({
color: 'grey'
}))
b1 = box.clone();
b2 = box.clone();
b3 = box.clone();
b3.material = b3.material.clone()
b3.material.color.set('red')
group.add(box);
group.add(b1);
b1.position.y += 1
group.add(b2);
b2.position.z += 1
rotationAnchor.add(b3);
rotationAnchor.position.set(0.5, 0.5, 1.5)
b3.position.set(-.5, -.5, -.5)
// Create a renderer
renderer = new THREE.WebGLRenderer({
antialias: true
});
// Set size
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// Set color
renderer.setClearColor(0xf8a5c2);
renderer.gammaOutput = true;
// Enable shadow mapping
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Append to the document
container = document.createElement("div");
document.body.appendChild(container);
document.body.appendChild(renderer.domElement);
// Add resize listener
window.addEventListener("resize", onWindowResize, false);
// Enable FPS stats
stats = new Stats();
container.appendChild(stats.dom);
var gui = new dat.GUI({
height: 5 * 32 - 1
});
let params = {
'test': 4,
'bevelThickness': 1,
'bevelSize': 1.5,
'bevelSegments': 3
}
gui.add(params, 'test', 0, 10).onChange(val => {
test = val
})
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
rotationAnchor.rotation.z = (Math.cos(performance.now() * 0.001) * Math.PI * 0.25) + (Math.PI * 1.25)
requestAnimationFrame(animate);
// Re-render scene
renderer.render(scene, camera);
// Update stats
stats.update();
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/96/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/libs/stats.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>

Resources