I am trying to create a random point within a sphere, and I am not sure how to do this. I came up with this but I think it is returning a point within a cube I think I have to do something with Math.PI but not sure how.
#createParticlePosition() {
const shape = this.options.shape;
// shape.radius = 2;
if (shape.type === 'sphere') {
return new Three.Vector3(
(Math.random() * shape.radius - (shape.radius / 2)) * 1.0,
(Math.random() * shape.radius - (shape.radius / 2)) * 1.0,
(Math.random() * shape.radius - (shape.radius / 2)) * 1.0
);
}
}
You are indeed just creating boxes. You're only calculating the x,y,z values linearly, not spherically. Three.js has a Vector3.randomDirection method that could do these calculations for you:
const maxRadius = 2;
// Randomize to range [0, 2]
const randomRadius = Math.random() * maxRadius;
// Create vec3
const randomVec = new THREE.Vector3();
// Make vector point in a random direction with a radius of 1
randomVec.randomDirection();
// Scale vector to match random radius
randomVec.multiplyScalar(randomRadius);
This method utilizes this approach internally to avoid density accumulation in the poles.
To distribute points inside a sphere evenly, having direction and radius, the radius computes with Math.sqrt( r * r * Math.random());
In the snippet, the red point cloud utilizes simple r * Math.random(), the aqua one utilizes that I wrote above:
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";
let scene = new THREE.Scene();
scene.background = new THREE.Color(0x160016);
let camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 8);
let renderer = new THREE.WebGLRenderer({antialias: true});
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);
controls.enableDamping = true;
controls.enablePan = false;
let pts = new Array(1000).fill().map(p => {
let rMax = 2 * Math.random();
return new THREE.Vector3().randomDirection().multiplyScalar(rMax);
})
let g = new THREE.BufferGeometry().setFromPoints(pts);
let m = new THREE.PointsMaterial({size: 0.1, color: "red"})
let p = new THREE.Points(g, m);
p.position.x = -2;
scene.add(p)
let pts2 = new Array(1000).fill().map(p => {
let rMax = 2;
let r = Math.sqrt(rMax * rMax * Math.random());
return new THREE.Vector3().randomDirection().multiplyScalar(r);
})
let g2 = new THREE.BufferGeometry().setFromPoints(pts2);
let m2 = new THREE.PointsMaterial({size: 0.1, color: "aqua"})
let p2 = new THREE.Points(g2, m2);
p2.position.x = 2;
scene.add(p2)
renderer.setAnimationLoop(() => {
controls.update();
renderer.render(scene, camera);
});
</script>
For reference: https://discourse.threejs.org/t/random-points-on-surfaces/34153
Related
I am using
var geometry = new THREE.SphereGeometry( 15, 32, 16, 0, 2*Math.PI, 0, x);
with x < 2*PI to create a part of a sphere looking like that :
The thing is I need to have tens of thousands of those, all with the same center and radius but with different rotations and different and very small x values.
I have done research about instancing but can't find a way to use it the way I want to. Any suggestions ?
Use an additional InstancedBufferAttribute to pass phi angles per instance in InstancedMesh, and process those values in vertex shader to form the parts you want, using .onBeforeCompile() method.
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0/build/three.module.js";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 5000);
camera.position.set(0, 0, 50);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", (event) => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
let control = new OrbitControls(camera, renderer.domElement);
let light = new THREE.DirectionalLight(0xffffff, 0.5);
light.position.setScalar(1);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));
const MAX_COUNT = 10000;
let g = new THREE.SphereGeometry(1, 20, 10, 0, Math.PI * 2, Math.PI * 0.25, Math.PI * 0.5);
let m = new THREE.MeshLambertMaterial({
side: THREE.DoubleSide,
onBeforeCompile: shader => {
shader.vertexShader = `
attribute float instPhi;
// straight from the docs on Vector3.setFromSphericalCoords
vec3 setFromSphericalCoords( float radius, float phi, float theta ) {
float sinPhiRadius = sin( phi ) * radius;
float x = sinPhiRadius * sin( theta );
float y = cos( phi ) * radius;
float z = sinPhiRadius * cos( theta );
return vec3(x, y, z);
}
${shader.vertexShader}
`.replace(
`#include <beginnormal_vertex>`,
`#include <beginnormal_vertex>
vec3 sphPos = setFromSphericalCoords(1., instPhi * (1. - uv.y), PI * 2. * uv.x); // compute position
objectNormal = normalize(sphPos); // normal is just a normalized vector of the computed position
`).replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
transformed = sphPos; // set computed position
`
);
//console.log(shader.vertexShader);
}
});
let im = new THREE.InstancedMesh(g, m, MAX_COUNT);
scene.add(im);
let v3 = new THREE.Vector3();
let c = new THREE.Color();
let instPhi = []; // data for Phi values
let objMats = new Array(MAX_COUNT).fill().map((o, omIdx) => {
let om = new THREE.Object3D();
om.position.random().subScalar(0.5).multiplyScalar(100);
om.rotation.setFromVector3(v3.random().multiplyScalar(Math.PI));
om.updateMatrix();
im.setMatrixAt(omIdx, om.matrix);
im.setColorAt(omIdx, c.set(Math.random() * 0xffffff))
instPhi.push((Math.random() * 0.4 + 0.1) * Math.PI);
return om;
});
g.setAttribute("instPhi", new THREE.InstancedBufferAttribute(new Float32Array(instPhi), 1));
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
</script>
PS May be turned into the using of InstancedBufferGeometry. Creativity is up to you :)
This is based off a codepen that I am working on and I simplified the code to work with the specific part I need. I haven't worked with applyMatrix and Matrix4 before. For some reason the info from the part using these functions are showing up in my console and causing my browser to crash. I don't completely understand what is going on. I can guess that the values are being reassigned nonstop but I don't see a resolution to it in the codepen even though this issue isn't in it. Here is my code and the link to the codepen for reference.
https://codepen.io/Mamboleoo/pen/Bppdda?editors=0010
This codepen is out of my league and I am trying to get a better grasp of it.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
renderer.setClearColor(0x000000);
const spotLight = new THREE.SpotLight(0xFFFFFF);
scene.add(spotLight);
spotLight.position.set(0, 0, 100);
spotLight.castShadow = true;
spotLight.angle = 0.2;
spotLight.intensity = 0.2;
camera.position.set(0.27, 0, 500);
//Black center
var geom = new THREE.SphereGeometry(100, 32, 32);
var mat = new THREE.MeshPhongMaterial({
color: 0x000000
});
var core = new THREE.Mesh(geom, mat);
scene.add(core);
var geom = new THREE.SphereBufferGeometry(1, 15, 15);
var mat = new THREE.MeshBasicMaterial({
color: 0xffffff
});
var atoms = new THREE.Object3D();
scene.add(atoms);
for (var i = 0; i < 150; i++) {
var nucleus = new THREE.Mesh(geom, mat);
var size = Math.random() * 6 + 1.5;
nucleus.speedX = (Math.random() - 0.5) * 0.08;
nucleus.speedY = (Math.random() - 0.5) * 0.08;
nucleus.speedZ = (Math.random() - 0.5) * 0.08;
nucleus.applyMatrix(new THREE.Matrix4().makeScale(size, size, size));
nucleus.applyMatrix(new THREE.Matrix4().makeTranslation(0, 100 + Math.random() * 10, 0));
nucleus.applyMatrix(new THREE.Matrix4().makeRotationX(Math.random() * (Math.PI * 2)));
nucleus.applyMatrix(new THREE.Matrix4().makeRotationY(Math.random() * (Math.PI * 2)));
nucleus.applyMatrix(new THREE.Matrix4().makeRotationZ(Math.random() * (Math.PI * 2)));
atoms.add(nucleus);
}
function updateNucleus(a) {
for (var i = 0; i < atoms.children.length; i++) {
var part = atoms.children[i];
part.applyMatrix(new THREE.Matrix4().makeRotationX(part.speedX));
part.applyMatrix(new THREE.Matrix4().makeRotationY(part.speedY));
part.applyMatrix(new THREE.Matrix4().makeRotationZ(part.speedZ));
}
}
//Create scene
var necks = [];
var cubesObject = new THREE.Object3D();
scene.add(cubesObject);
function animate(a) {
requestAnimationFrame( animate );
updateNucleus(a);
renderer.render(scene,camera);
};
animate();
window.addEventListener('resize', function(){
camera.aspect = window.innerWidth / this.window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
})
part.applyMatrix(new THREE.Matrix4().makeRotationX(part.speedX));
The codepen uses an old version of three.js (r79). Since certain parts of the API have been renamed, the browser reports deprecation warnings every frame. With the latest version r138, the new code should look like so:
part.applyMatrix4(new THREE.Matrix4().makeRotationX(part.speedX));
Besides, it's recommended that you don't create instances of Matrix4 and other classes within the animation loop. Create the objects once outside of your loop and reuse them.
const _matrix = new THREE.Matrix4();
function updateNucleus(a) {
for (var i = 0; i < atoms.children.length; i++) {
var part = atoms.children[i];
part.applyMatrix4(_matrix.makeRotationX(part.speedX));
part.applyMatrix4(_matrix.makeRotationY(part.speedY));
part.applyMatrix4(_matrix.makeRotationZ(part.speedZ));
}
}
I’d like to project a texture or some shape ( transparent - ring, circle) to a mesh (but to a specific part). For example, in games we select/click an enemy or NPC then we see some circle under the character which indicates a selection. That circle changes its shape based on the mesh (height, slope), you can check the following images.
I’d like to do that, but I’m not sure how to do it, perfectly - so far I tried to raycast a mesh and got the vertices normal and applied the rotation but when it comes to more complex part of the mesh this does not work well enough. I think I need to use shaders? Is there any resource I can check?
Not as difficult as it seems.
Modify the ground material with .onBeforeCompile, passing position of the selected object in a uniform, and then process it in shaders.
In the code snippet, click a button to select the respective object, so the selection mark will follow it on the ground:
body{
overflow: hidden;
margin: 0;
}
#selections {
width: 100px;
display: flex;
flex-direction: column;
}
button.selected{
color: #00ff32;
background: blue;
}
<div id="selections" style="position: absolute;border: 1px solid yellow;"></div>
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.133";
import {
OrbitControls
} from "https://cdn.skypack.dev/three#0.133/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 5, 8);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x404040);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
let light = new THREE.DirectionalLight(0xffffff, 1);
light.position.setScalar(1);
scene.add(
light,
new THREE.AmbientLight(0xffffff, 0.5)
);
let objects = new Array(5).fill(0).map((p,idx)=>{return setObject(idx)});
//console.log(objects);
let selected = objects[0];
let g = new THREE.PlaneGeometry(10, 10, 5, 5);
g.rotateX(-Math.PI * 0.5);
for(let i = 0; i < g.attributes.position.count; i++){
g.attributes.position.setY(i, (Math.random() * 2 - 1) * 0.75);
}
g.computeVertexNormals();
let uniforms = {
selection: {value: new THREE.Vector3()}
}
let m = new THREE.MeshLambertMaterial({
color: 0x003264,
map: new THREE.TextureLoader().load("https://threejs.org/examples/textures/water.jpg"),
onBeforeCompile: shader => {
shader.uniforms.selection = uniforms.selection;
shader.vertexShader = `
varying vec3 vPos;
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
vPos = transformed;
`
);
shader.fragmentShader = `
#define ss(a, b, c) smoothstep(a, b, c)
uniform vec3 selection;
varying vec3 vPos;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
// shape
float dist = distance(selection.xz, vPos.xz);
float r = 0.25;
float shape = (ss(r-0.1, r, dist)*0.75 + 0.25) - ss(r, r + 0.1, dist);
vec3 col = mix(gl_FragColor.rgb, vec3(0, 1, 0.25), shape);
gl_FragColor = vec4(col, gl_FragColor.a);
`
);
//console.log(shader.fragmentShader)
}
});
let o = new THREE.Mesh(g, m);
scene.add(o);
window.addEventListener("resize", onResize);
let clock = new THREE.Clock();
renderer.setAnimationLoop(_ => {
let t = clock.getElapsedTime() * 0.5;
objects.forEach(obj => {
let ud = obj.userData;
obj.position.x = Math.cos(t * ud.scaleX + ud.initPhase) * 4.75;
obj.position.y = 1;
obj.position.z = Math.sin(t * ud.scaleZ + ud.initPhase) * 4.75;
})
o.worldToLocal(uniforms.selection.value.copy(selected.position));
renderer.render(scene, camera);
})
function setObject(idx){
let g = new THREE.SphereGeometry(0.25);
let m = new THREE.MeshLambertMaterial({color: 0x7f7f7f * Math.random() + 0x7f7f7f});
let o = new THREE.Mesh(g, m);
o.userData = {
initPhase: Math.PI * 2 * Math.random(),
scaleX: Math.random() * 0.5 + 0.5,
scaleZ: Math.random() * 0.5 + 0.5
}
scene.add(o);
let btn = document.createElement("button");
btn.innerText = "Object " + idx;
selections.appendChild(btn);
btn.addEventListener("click", event => {
selections.querySelectorAll("button").forEach(b => {b.classList.remove("selected")});
btn.classList.add("selected");
selected = o
});
return o;
}
function onResize(event) {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
</script>
i have simple 3d project made in three.js.
Currently its controlled by mouse motion which rotates camera around the scene (this also works by touch on mobile).
I want to implement to control this basic camera movement to be controlled by device motion / tilting / gyroscope while accesing web on mobile devices.
Here is my code:
const nearDist = 0.1;
const farDist = 10000;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
55,
window.innerWidth / window.innerHeight,
nearDist,
farDist
);
camera.position.x = farDist * -2;
camera.position.z = 500;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor("#e8a82b");
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.querySelector("#canvas-wrapper").appendChild(renderer.domElement);
// Object
const cubeSize = 300;
const geometry = new THREE.IcosahedronGeometry(cubeSize, 0 ); // BufferAttribute allows for more efficient passing of data to the GPU
const material = new THREE.MeshNormalMaterial({roughness: 1});
const group = new THREE.Group();
for (let i = 0; i < 260; i++) {
const mesh = new THREE.Mesh(geometry, material);
const dist = farDist / 3;
const distDouble = dist * 2;
const tau = 2 * Math.PI; // One turn
mesh.position.x = Math.random() * distDouble - dist;
mesh.position.y = Math.random() * distDouble - dist;
mesh.position.z = Math.random() * distDouble - dist;
mesh.rotation.x = Math.random() * tau;
mesh.rotation.y = Math.random() * tau;
mesh.rotation.z = Math.random() * tau;
// Manually control when 3D transformations recalculation occurs for better performance
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
group.add(mesh);
}
scene.add(group);
// Typo
const loader = new THREE.FontLoader();
const textMesh = new THREE.Mesh();
const createTypo = font => {
const word = "be creative";
const typoProperties = {
font: font,
size: cubeSize,
height: cubeSize / 2,
curveSegments: 16
};
const text = new THREE.TextGeometry(word, typoProperties);
textMesh.geometry = text;
textMesh.material = material;
textMesh.position.x = cubeSize * -2;
textMesh.position.z = cubeSize * -1;
scene.add(textMesh);
};
loader.load(
"https://threejs.org/examples/fonts/helvetiker_regular.typeface.json",
createTypo
);
// Mouse move
let mouseX = 0;
let mouseY = 0;
const mouseFX = {
windowHalfX: window.innerWidth / 2,
windowHalfY: window.innerHeight / 2,
coordinates: function(coordX, coordY) {
mouseX = (coordX - mouseFX.windowHalfX) * 2;
mouseY = (coordY - mouseFX.windowHalfY) * 2;
},
onMouseMove: function(e) {
mouseFX.coordinates(e.clientX, e.clientY);
},
onTouchMove: function(e) {
mouseFX.coordinates(e.changedTouches[0].clientX, e.changedTouches[0].clientY);
}
};
document.addEventListener("mousemove", mouseFX.onMouseMove, false);
document.addEventListener("touchmove", mouseFX.onTouchMove, false);
// Render
const render = () => {
requestAnimationFrame(render);
// Camera animation
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (mouseY * -1 - camera.position.y) * 0.05;
camera.lookAt(scene.position);
const t = Date.now() * 0.001;
const rx = Math.sin(t * 0.6) * 0.5;
const ry = Math.sin(t * 0.3) * 0.5;
const rz = Math.sin(t * 0.2) * 0.5;
group.rotation.x = rx;
group.rotation.y = ry;
group.rotation.z = rz;
textMesh.rotation.x = rx;
textMesh.rotation.y = ry;
textMesh.rotation.z = rx; // :)
renderer.render(scene, camera);
};
render();
const resizeCanvas = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
window.addEventListener("resize", resizeCanvas, false);
I tried to implement threeVR library but without any luck. But since this is my first three.js prject i belive i did it something wrong. I couldnt find any other solution for this.
Thanks for any ideas.
I have a situation where I use an OrbitControls to have a limited view in a room/space. The OrbitControls gets min/max values for azimuth and polar angles to control the view.
The OrbitControls' target is close by the camera to achieve the correct view, compare to the "panorama / cube" example (https://threejs.org/examples/?q=cube#webgl_panorama_cube).
In one situation this works fine. In an other situation this does not work. The reason is that because of the placement of objects, the starting azimuth is -179.999 degrees.
I have not found a way to tell the orbitcontrol to have the azimuth between -179.999 +/- 20 degrees.
I have experimented a little with the algorithm of #Αλέκος (Is angle in between two angles) and it looks like I can calculate the correct angles (not fully implemented). For that I would have set an azimuth and a delta in stead of min/max (and change the OrbitControls code).
Any suggestions for an easier solution? Thanks!
Test code:
var scene = new THREE.Scene();
var orbitControls;
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.FaceColors
});
var geometry = new THREE.BoxGeometry(1, 1, 1);
// colors
red = new THREE.Color(1, 0, 0);
green = new THREE.Color(0, 1, 0);
blue = new THREE.Color(0, 0, 1);
var colors = [red, green, blue];
console.log("FACES", geometry.faces.length)
for (var i = 0; i < 3; i++) {
geometry.faces[4 * i].color = colors[i];
geometry.faces[4 * i + 1].color = colors[i];
geometry.faces[4 * i + 2].color = colors[i];
geometry.faces[4 * i + 3].color = colors[i];
}
geometry.faces[0].color = new THREE.Color(1, 1, 1);
geometry.faces[4].color = new THREE.Color(1, 1, 1);
geometry.faces[8].color = new THREE.Color(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
cube.position.x = 0;
cube.position.y = 4;
cube.position.z = 44;
console.log("CUBE", cube.position);
var cubeAxis = new THREE.AxesHelper(20);
cube.add(cubeAxis);
scene.add(cube);
camera.position.x = 0.8;
camera.position.y = 4.5;
camera.position.z = 33;
orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
orbitControls.enablePan = false;
orbitControls.enableZoom = false;
orbitControls.minPolarAngle = (90 - 10) * Math.PI / 180;
orbitControls.maxPolarAngle = (90 + 10) * Math.PI / 180;
// These values do not work:
orbitControls.maxAzimuthAngle = 200 * Math.PI / 180;
orbitControls.minAzimuthAngle = 160 * Math.PI / 180;
orbitControls.target.x = 0.8;
orbitControls.target.y = 4.5;
orbitControls.target.z = 33.1;
orbitControls.enabled = true;
var animate = function () {
requestAnimationFrame(animate);
if (orbitControls) {
orbitControls.update();
}
renderer.render(scene, camera);
// console.log("orbitControls", "azi", orbitControls.getAzimuthalAngle() * 180 / Math.PI);
};
animate();