Cannon JS — Position Vector NaN after initiating body with properties - javascript

I'm using a simple setup Cannon.js, following the examples online, but when I set any property in the constructor, the position and angular velocity x, y and z are all NaN.
This works, but does not move, as the body has no mass.
const body = new CANNON.Body();
console.log(body.position.x, body.mass); //logs 0, 0
However, this doesn't...
const body = new CANNON.Body({
mass: 1,
});
console.log(body.position.x, body.mass); //logs NaN, 1
Also if I instantiate the body, and then set the mass after, it still doesn't move.
Some more code for context (I am calling the update function in an animation loop, and it's happening A-OK).
export const init = () => {
world = new CANNON.World();
world.gravity.set(0,1,0);
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
for (let i = 0; i < BODIES_COUNT; i++) {
const shape = new CANNON.Box(new CANNON.Vec3(4,4,4));
const body = new CANNON.Body({
mass: 1,
});
const body = new CANNON.Body();
body.addShape(shape);
body.position.set(0, 0, 0);
body.mass = 1;
body.angularVelocity.set(0, 2, 0);
body.velocity.set(0, 1, 0);
body.angularDamping = 0.5;
world.addBody(body);
bodies.push(body);
const geometry = new THREE.BoxGeometry(10, 10, 10);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
const mesh = new THREE.Mesh(geometry, material);
meshes.push(mesh);
}
}
export const update = (delta) => {
world.step(TIMESTEP * delta);
}

The only thing I can think of is that you accidentally pass delta = 0 to world.step. Repro using Cannon.js v0.6.2: JSFiddle
Try changing your code to:
export const update = (delta) => {
if (delta > 0) {
world.step(TIMESTEP * delta);
}
}

Related

Three.js shader error: Varyings over maximum register limit

I'm trying to create a virtual multiplayer platform for live concerts using three.js and socket.io.
While in single-player everything works fine, but when I try to do tests to see if the website could handle a big load of players by creating multiple NPCs at the same time it all falls apart and it gives me these errors:
THREE.WebGLProgram: shader error: 0 35715 false gl.getProgramInfoLog Varyings over maximum register limit
THREE.WebGLProgram: shader error: 1282 35715 false gl.getProgramInfoLog Varyings over maximum register limit
The error comes up when I create more then 8 NPCs using this for loop:
for (let i = 0; i < 50; i++) {
var x,y,z;
x = Math.random() * 2000;
x *= Math.round(Math.random()) ? 1 : -1;
z = Math.random() * (19000-8000)+8000;
let smurf = new Npc(this, this.options, "Groupie", x, -190, -z, false, "Fan");
this.npcs.push(smurf);
this.remoteNPCsColliders.push(smurf);
}
From what I've understood through the three.js documentation, the issue consists of passing too many varyings at the same time, but at the same time it seems that varyings are additional data from shaders, which are not used in my code. This is the NPC object code:
class Npc {
constructor(game, options, identifier, x, y, z, conv, type) {
let model, colour;
const colours = ['Black', 'Brown', 'White'];
colour = colours[Math.floor(Math.random() * colours.length)];
if (options === undefined) {
const people = ['BeachBabe', 'BusinessMan', 'Doctor', 'FireFighter', 'Housewife', 'Policeman', 'Prostitute', 'Punk', 'RiotCop', 'Roadworker', 'Robber', 'Sheriff', 'Streetman', 'Waitress'];
model = people[Math.floor(Math.random() * people.length)];
}
this.model = model;
this.colour = colour;
this.game = game;
this.animations = this.game.animations;
const geometry = new THREE.BoxGeometry(100, 300, 100);
const material = new THREE.MeshBasicMaterial({
visible: false
});
const box = new THREE.Mesh(geometry, material);
box.name = "Collider";
box.position.set(0, 150, 0);
this.collider = box;
const loader = new FBXLoader();
const npc = this;
loader.load(`${game.assetsPath}fbx/people/${type}.fbx`, function(object) {
object.mixer = new THREE.AnimationMixer(object);
npc.root = object;
npc.mixer = object.mixer;
object.name = identifier;
object.conv = conv;
object.traverse(function(child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
const textureLoader = new THREE.TextureLoader();
npc.object = new THREE.Object3D();
npc.object.position.set(x, y, z);
//npc.object.position.set(0, 0, 0);
npc.object.rotation.set(0, -Math.PI / 2, 0);
npc.object.scale.set(2.3, 2.3, 2.3);
npc.object.add(object);
if (npc.deleted === undefined) game.scene.add(npc.object);
npc.object.add(box);
//if (game.animations.Idle!==undefined) npc.action = "Happy";
npc.action = object.animations[0]
});
}
set action(name){
//Make a copy of the clip if this is a remote player
if (this.actionName == name) return;
const clip = this.animations[name];
const action = this.mixer.clipAction( name );
action.time = 0;
this.mixer.stopAllAction();
action.startAt(0.03333);
this.actionName = name;
this.actionTime = Date.now();
action.fadeIn(0.5);
action.play();
}
get action(){
return this.actionName;
}
}
I've tried commenting every bit of code in here but with no success. The only thing that fixed the error was disabling the shadow map of the renderer, but that for some reason overexposed the whole map and made everything unplayable.
The only other questions on the topic that I could find put the problem on the lights, which I guess could be an explanation since disabling the shadow map fixed the error, but I've disabled every single light and the error still occurs.
I'm literally going crazy over this, I hope that someone can help me!

Write a reverse function for mesh removing

I've got a function that create mesh when you click on another. On the click, 2 or 3 mesh are created and go to their positions. Know i would like to do the reverse function : when the mesh are deployed, and you click anoter time on the mesh, the previously created mesh go back and are removed from the scene.
Here is my first function :
function sortiSphere(element, obj) {
var matStdParams = {
roughness: 1,
metalness: 0.8,
color: element.group,
emissive: element.group,
emissiveIntensity: 0.5
};
var sphereMaterial2 = new THREE.MeshStandardMaterial(matStdParams);
var mesh = new THREE.Mesh(sphereGeometry, sphereMaterial2);
mesh.position.x = x;
mesh.position.y = y;
mesh.position.z = z;
mesh.userData.children = element.children;
mesh.userData.name = element.label;
mesh.userData.rang = element.rang;
mesh.userData.def = element.définition;
mesh.userData.posx = element.posx;
mesh.userData.posy = element.posy;
mesh.userData.posz = element.posz;
mesh.userData.parent = obj;
mesh.userData.cartabs = element.cartabs;
mesh.position.normalize();
mesh.position.multiplyScalar(1 / element.rang);
mesh.scale.set(1 / (element.rang / 2), 1 / (element.rang / 2), 1 / (element.rang / 2))
mesh.position.x = obj.position.x;
mesh.position.y = obj.position.y;
mesh.position.z = obj.position.z;
var x = element.posx;
var y = element.posy;
var z = element.posz;
new TWEEN.Tween(mesh.position).to({
x: x,
y: y,
z: z
}, 1000)
.easing(TWEEN.Easing.Elastic.Out).start();
console.log(mesh);
scene.add(mesh);
lesMesh.push(mesh)
// lines
var material = new THREE.LineBasicMaterial({
color: 0xffffff
});
var geometry = new THREE.Geometry();
geometry.vertices.push(
obj.position,
new THREE.Vector3(x, y, z)
);
var line = new THREE.Line(geometry, material);
scene.add(line);
gen1 = [];
gen2 = [];
gen3 = [];
gen4 = [];
gen5 = [];
obj.userData.bool = true;
};
Notice that the mesh contain a information about their state : userData.bool (true if deployed, false if not)
Now, how do I tell them to go back ?
Here is what I got for now :
function deleteSphere(element, obj) {
console.log("--- deleteSphere / debut fonction")
for (i = 0; gen1.length > i; i++) {
if (gen1[i].userData.children) {
for (j = 0; gen1[i].userData.children.length > j; j++) {
console.log(gen2[i][j]);
scene.remove(gen2[i][j]);}
} else {
scene.remove(gen1[i])
console.log(gen1[i].userData.children)
}
}
};
Thanks for your time
When you use tween.js then you can use its .onUpdate() and .onComplete() methods.
This is not the ultimate solution, this is just an example from scratch.
Let's have a container for our base objects:
var bases = new THREE.Group();
scene.add(bases);
Now we need an event where we'll create our base objects, let it be a click on a button:
btnAdd.addEventListener("click", setBase);
and our setBase() function will look like this:
var rndBase = function() {
return THREE.Math.randFloatSpread(10);
}
function setBase() {
var base = new THREE.Mesh(new THREE.SphereGeometry(.5, 8, 4), new THREE.MeshBasicMaterial({
color: Math.random() * 0xffffff
}));
base.position.set(rndBase(), rndBase(), rndBase());
bases.add(base);
}
Everything's ready and we can start with clicking on base objects. Let it be an event listener for mousedown:
window.addEventListener("mousedown", setOrRemove, false);
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var intersects;
var obj;
function setOrRemove(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObjects(bases.children);
if (intersects.length > 0) {
obj = intersects[0].object;
if (obj.children.length != 0) { // simple check for existence of children
removeObj(obj); // if children were deployed, then remove them and their parent (base object)
} else {
setChildren(obj); // otherwise, as a base object has no children, then we'll add some
}
}
}
The most funny part begins here.
Let's set the children:
function setChildren(parent) {
let radius = Math.random() + 1;
for (let i = 0; i < THREE.Math.randInt(2, 3); i++) { //as you said there are 2 or 3 children
var child = new THREE.Mesh(new THREE.BoxGeometry(.25, .25, .25), new THREE.MeshBasicMaterial({
color: parent.material.color,
wireframe: true
}));
child.userData.direction = new THREE.Vector3(rndBase(), rndBase(), rndBase()).normalize(); // random direction (must be a normalized vector)
child.userData.radius = radius;
parent.add(child);
}
let start = { val: 0 };
let end = { val: 1 };
new TWEEN.Tween(start).to(end, 1000) // we simply change from 0 to 1 during 1 second
.easing(TWEEN.Easing.Elastic.Out)
.onUpdate( // here we'll update position of each children in accordance to direction and radius
function() {
parent.children.forEach((child) => {
child.position.copy(child.userData.direction).multiplyScalar(child.userData.radius * this.val);
})
}
)
.start();
}
And removing of a base object and its children is:
function removeObj(baseObj) { // very similar to how we created children
let start = { val: 1 };
let end = { val: 0 };
new TWEEN.Tween(start).to(end, 1000)
.easing(TWEEN.Easing.Elastic.In)
.onUpdate(
function(){
baseObj.children.forEach((child) => {
child.position.copy(child.userData.direction).multiplyScalar(child.userData.radius * this.val);
})
}
)
.onComplete( // but the difference is here, it allow us to perform something when our tweening is completed
function() {
for (let i = baseObj.children - 1; i = 0; i--) {
baseObj.remove(baseObj.children[i]); //and here we simply delete children in reverse order
}
bases.remove(baseObj); //and then we remove the base object itself
}
)
.start()
}
So, this is it. Don't forget to put TWEEN.update(); in our animation loop :)
jsfiddle example r86.

Threejs Particle System with joining lines. Programming logic?

Based on a previous question I had recently posted:
How to create lines between nearby particles in ThreeJS?
I was able to create individual lines joining nearby particles. However, the lines are being drawn twice because of the logic of the particle system. This is because of how the original 2D particle system worked:
https://awingit.github.io/particles/
This also draws the lines twice. For each pair of particles connecting a line, the line is drawn.
I do not think this is ideal for performance. How would I only draw a line once for each joining points?
P.S. Here is exactly the effect I would like to achieve, but cannot make sense of the code:
http://freelance-html-developer.com/clock/
I would like to understand the fundamental logic.
UPDATE:
I have created a jsfiddle with my progress.
var canvas, canvasDom, ctx, scene, renderer, camera, controls, geocoder, deviceOrientation = false;
var width = 800,
height = 600;
var particleCount = 20;
var pMaterial = new THREE.PointsMaterial({
color: 0x000000,
size: 0.5,
blending: THREE.AdditiveBlending,
//depthTest: false,
//transparent: true
});
var particles = new THREE.Geometry;
var particleSystem;
var line;
var lines = {};
var lineGroup = new THREE.Group();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0x000000,
linewidth: 1
});
var clock = new THREE.Clock();
var maxDistance = 15;
function init() {
canvasDom = document.getElementById('canvas');
setupStage();
setupRenderer();
setupCamera();
setupControls();
setupLights();
clock.start();
window.addEventListener('resize', onWindowResized, false);
onWindowResized(null);
createParticles();
scene.add(lineGroup);
animate();
}
function setupStage() {
scene = new THREE.Scene();
}
function setupRenderer() {
renderer = new THREE.WebGLRenderer({
canvas: canvasDom,
logarithmicDepthBuffer: true
});
renderer.setSize(width, height);
renderer.setClearColor(0xfff6e6);
}
function setupCamera() {
camera = new THREE.PerspectiveCamera(70, width / height, 1, 10000);
camera.position.set(0, 0, -60);
}
function setupControls() {
if (deviceOrientation) {
controls = new THREE.DeviceOrientationControls(camera);
controls.connect();
} else {
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target = new THREE.Vector3(0, 0, 0);
}
}
function setupLights() {
var light1 = new THREE.AmbientLight(0xffffff, 0.5); // soft white light
var light2 = new THREE.PointLight(0xffffff, 1, 0);
light2.position.set(100, 200, 100);
scene.add(light1);
scene.add(light2);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
animateParticles();
updateLines();
render();
}
function render() {
renderer.render(scene, camera);
}
function onWindowResized(event) {
width = window.innerWidth;
height = window.innerHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
function createParticles() {
for (var i = 0; i < particleCount; i++) {
var pX = Math.random() * 50 - 25,
pY = Math.random() * 50 - 25,
pZ = Math.random() * 50 - 25,
particle = new THREE.Vector3(pX, pY, pZ);
particle.diff = Math.random() + 0.2;
particle.default = new THREE.Vector3(pX, pY, pZ);
particle.offset = new THREE.Vector3(0, 0, 0);
particle.velocity = {};
particle.velocity.y = particle.diff * 0.5;
particle.nodes = [];
particles.vertices.push(particle);
}
particleSystem = new THREE.Points(particles, pMaterial);
particleSystem.position.y = 0;
scene.add(particleSystem);
}
function animateParticles() {
var pCount = particleCount;
while (pCount--) {
var particle = particles.vertices[pCount];
var move = Math.sin(clock.getElapsedTime() * (1 * particle.diff)) / 4;
particle.offset.y += move * particle.velocity.y;
particle.y = particle.default.y + particle.offset.y;
detectCloseByPoints(particle);
}
particles.verticesNeedUpdate = true;
particleSystem.rotation.y += 0.01;
lineGroup.rotation.y += 0.01;
}
function updateLines() {
for (var _lineKey in lines) {
if (!lines.hasOwnProperty(_lineKey)) {
continue;
}
lines[_lineKey].geometry.verticesNeedUpdate = true;
}
}
function detectCloseByPoints(p) {
var _pCount = particleCount;
while (_pCount--) {
var _particle = particles.vertices[_pCount];
if (p !== _particle) {
var _distance = p.distanceTo(_particle);
var _connection = checkConnection(p, _particle);
if (_distance < maxDistance) {
if (!_connection) {
createLine(p, _particle);
}
} else if (_connection) {
removeLine(_connection);
}
}
}
}
function checkConnection(p1, p2) {
var _childNode, _parentNode;
_childNode = p1.nodes[particles.vertices.indexOf(p2)] || p2.nodes[particles.vertices.indexOf(p1)];
if (_childNode && _childNode !== undefined) {
_parentNode = (_childNode == p1) ? p2 : p1;
}
if (_parentNode && _parentNode !== undefined) {
return {
parent: _parentNode,
child: _childNode,
lineId: particles.vertices.indexOf(_parentNode) + '-' + particles.vertices.indexOf(_childNode)
};
} else {
return false;
}
}
function removeLine(_connection) {
// Could animate line out
var childIndex = particles.vertices.indexOf(_connection.child);
_connection.parent.nodes.splice(childIndex, 1);
deleteLine(_connection.lineId);
}
function deleteLine(_id) {
lineGroup.remove(lines[_id]);
delete lines[_id];
}
function addLine(points) {
var points = points || [new THREE.Vector3(Math.random() * 10, Math.random() * 10, Math.random() * 10), new THREE.Vector3(0, 0, 0)];
var _lineId = particles.vertices.indexOf(points[0]) + '-' + particles.vertices.indexOf(points[1]);
var lineGeom = new THREE.Geometry();
if (!lines[_lineId]) {
lineGeom.dynamic = true;
lineGeom.vertices.push(points[0]);
lineGeom.vertices.push(points[1]);
var curLine = new THREE.Line(lineGeom, lineMaterial);
curLine.touched = false;
lines[_lineId] = curLine;
lineGroup.add(curLine);
return curLine;
} else {
return false;
}
}
function createLine(p1, p2) {
p1.nodes[particles.vertices.indexOf(p2)] = p2;
addLine([p1, p2]);
}
$(document).ready(function() {
init();
});
I am really close, but I am not sure if its optimized. There seem to be flickering lines, and sometimes a line just stays stuck in place.
So here are my thoughts. I clicked that all I have to do is make the Vector3 points of the lines equal to the relevant particle Vector3 points. I just need to update each lines geometry.verticesNeedUpdate = true;
Also, how I manage the lines is I create a unique ID using the indexes of the 2 points, e.g. lines['8-2'] = line
The problem you're actually trying to solve is that while looping through your list of points, you're doubling the number of successful matches.
Example:
Consider a list of points, [A, B, C, D]. Your looping tests each point against all other points. For this example, A and C are the only points close enough to be considered nearby.
During the first iteration, A vs. all, you find that A and C are nearby, so you add a line. But when you're doing your iteration for C, you also find that A is nearby. This causes the second line, which you want to avoid.
Fixing it:
The solution is simple: Don't re-visit nodes you already checked. This works because the answer of distance from A to C is no different from distance from C to A.
The best way to do this is adjust your indexing for your check loop:
// (Note: This is example code, and won't "just work.")
for(var check = 0, checkLength = nodes.length; check < checkLength; ++check){
for(var against = check + 1, against < checkLength; ++against){
if(nodes[check].distanceTo(nodes[against]) < delta){
buildThatLine(nodes[check], nodes[against]);
}
}
}
In the inner loop, the indexing is set to:
Skip the current node
Skip all nodes before the current node.
This is done by initializing the inner indexing to the outer index + 1.
Caveat:
This particular logic assumes that you discard all your lines for every frame. It's not the most efficient way to achieve the effect, but I'll leave making it more efficient as an exercise for you.

three.js fontloader cannot read generateshapes of undefined

I have the following code that generates some meshes. I want to add 3d text to the scene but when i do this i get the following error:
TypeError: Cannot read property 'generateShapes' of undefined
this is the code i have to generate the meshes and the 3d text:
var x = 0;
var y = 0;
var finalSize = 450;
var textureLoader = new THREE.TextureLoader();
var fontLoader = new THREE.FontLoader();
the var font is undefined why?
var font = fontLoader.load( 'css/arial_bold.json');
var fontColor = textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000, overdraw: 0.5 });
for (var i = javascriptProjects.length - 1; i >= 0; i--) {
var object = {};
object.scale = javascriptProjects[i].baseImageData["0"] / finalSize;
var geometry = new THREE.PlaneGeometry(javascriptProjects[i].baseImageData["0"]/object.scale,javascriptProjects[i].baseImageData["1"]/object.scale, 10, 10);
object.texture = textureLoader.load( "data/"+ javascriptProjects[i].base_image );
object.material = new THREE.MeshBasicMaterial( { map: object.texture, overdraw: 0.5, transparent: true } );
object.material.opacity = 0.5;
object.mesh = new THREE.Mesh(geometry, object.material);
//the line below generates the error
object.FontGeo = new THREE.TextGeometry( javascriptProjects[i].project_name , {
font: font,
size: 50,
height: 2,
curveSegments: 12,
bevelThickness: 1,
bevelSize: 1,
bevelEnabled: false
});
object.textMesh = new THREE.Mesh( object.FontGeo, fontColor );
object.textMesh.position.x = object.location.x;
object.textMesh.position.y = object.location.y;
object.textMesh.position.z = object.location.z - 100;
scene.add(object.textMesh);
object.location = new THREE.Vector3(x, 120, y);
object.id = "" + javascriptProjects[i].project_id;
object.mesh.position.x = object.location.x;
object.mesh.position.y = object.location.y;
object.mesh.position.z = object.location.z;
scene.add(object.mesh);
meshes.push(object.mesh);
objects.push(object);
x += 600;
if(i % 3 == 0){
y += 600;
x = 0;
};
};
apparently font is undefined. why i don't see what i am doing wrong. Any suggestions would be great. If something is not clear let me know.
Thanks in advance
FontLoader.load() is an asynchronous function call. This is why there is an onLoad call back function.
The library is calling font.generateShapes() before the font is loaded.
Use a pattern like this one, instead:
var loader = new THREE.FontLoader();
loader.load( 'myFontFile.json', function ( font ) {
// insert your code here
} );
See, for example, this three.js example, and the three.js documentation .
three.js r.84

Using a raycaster with the camera to create the crosshair Google Cardboard effect

An example of what I'm trying to achieve: https://workshop.chromeexperiments.com/examples/guiVR/#1--Basic-Usage
How could I get the Google Cardboard crosshair, (gaze)pointer, reticle, whatever you want to call it effect in three.js? I would like to make a dot, as crosshair, in the center of the screen in my scene. Like that I would like to use a raycaster to identify what I'm looking at in VR. Which way would be best to go here?
Do I fake an X and Y position of my mouse? Because I found other people have answered how to cover this by adding an event listener to the mousemove event. But this works on desktop, and I want to bring this to mobile.
Here are the main parts needed for building your own gaze cursor:
An object that serves as the indicator (cursor) for user feedback
An array of objects that you want the cursor to interact with
A loop to iterate over the array of interactive objects to test if the cursor is pointing at them
Here's an example of how this can be implemented.
Gaze cursor indicator
Using a ring here so it can be animated, as you typically want to give the user some feedback that the cursor is about to select something, instead of instantly triggering the interaction.
const cursor = new THREE.Mesh(
new THREE.RingBufferGeometry(0.1, 0.15),
new THREE.MeshBasicMaterial({ color: "white" })
);
Interactive Objects
Keep track of the objects you want to make interactive, and the actions they should execute when they are looked at.
const selectable = [];
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1, 1, 1),
new THREE.MeshNormalMaterial()
);
selectable.push({
object: cube,
action() {
console.log("Cube selected");
},
});
Checking Interactive Objects
Check for interactions on every frame and execute the action.
const raycaster = new THREE.Raycaster();
(function animate() {
for (let i = 0, length = selectable.length; i < length; i++) {
const camPosition = camera.position.clone();
const objectPosition = selectable[i].object.position.clone();
raycaster.set(camPosition, camera.getWorldDirection(objectPosition));
const intersects = raycaster.intersectObject(selectable[i].object);
const selected = intersects.length > 0;
// Visual feedback to inform the user they have selected an object
cursor.material.color.set(selected ? "crimson" : "white");
// Execute object action once
if (selected && !selectable[i].selected) {
selectable[i].action();
}
selectable[i].selected = selected;
}
})();
Here's a demo of this in action:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three#0.121.1/build/three.module.js";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three#0.121.1/examples/jsm/controls/OrbitControls.js";
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const cameraMin = 0.0001;
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, aspect, cameraMin, 1000);
const controls = new OrbitControls(camera, renderer.domElement);
const scene = new THREE.Scene();
camera.position.z = 5;
scene.add(camera);
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(),
new THREE.MeshNormalMaterial()
);
cube.position.x = 1;
cube.position.y = 0.5;
scene.add(cube);
const cursorSize = 1;
const cursorThickness = 1.5;
const cursorGeometry = new THREE.RingBufferGeometry(
cursorSize * cameraMin,
cursorSize * cameraMin * cursorThickness,
32,
0,
Math.PI * 0.5,
Math.PI * 2
);
const cursorMaterial = new THREE.MeshBasicMaterial({ color: "white" });
const cursor = new THREE.Mesh(cursorGeometry, cursorMaterial);
cursor.position.z = -cameraMin * 50;
camera.add(cursor);
const selectable = [
{
selected: false,
object: cube,
action() {
console.log("Cube selected");
},
}
];
const raycaster = new THREE.Raycaster();
let firstRun = true;
(function animate() {
requestAnimationFrame(animate);
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
controls.update();
if (!firstRun) {
for (let i = 0, length = selectable.length; i < length; i++) {
const camPosition = camera.position.clone();
const objectPosition = selectable[i].object.position.clone();
raycaster.set(camPosition, camera.getWorldDirection(objectPosition));
const intersects = raycaster.intersectObject(selectable[i].object);
const selected = intersects.length > 0;
cursor.material.color.set(selected ? "crimson" : "white");
if (selected && !selectable[i].selected) {
selectable[i].action();
}
selectable[i].selected = selected;
}
}
renderer.render(scene, camera);
firstRun = false;
})();
</script>
This library works like a charm - https://github.com/skezo/Reticulum

Categories

Resources