Particle ID and retrieving multiple IDs in Three.js - select

I am trying to create particles with three.js, currently as a particle system.
The goal of the project is to create particles, with defined positions and unique IDs, and add an option to select multiple particles and retrieve their IDs.
I did some research, but found only ways to detect the position of a mouse-click.
So, I'm looking for the following information:
First: Is there an option to assign IDs to single particles (as simple as a number)?
Second: Is there a way to select multiple particles and view their IDs in a (popup) window?
Thanks for your Help!
PS: I found, that you can assign IDs to THREE.ParticleDOMMaterial (I don't know, what this function does!?) and THREE.ParticleBasicMaterial (Assign an ID to the material, but not to the particle itself).

All Object3D instances have a unique id in three.js by default and Particle inherits that. Also Object3D has a name property, if that helps.
Here's a little snippet which illustrates naming particles:
for ( var i = 0; i < 100; i++ ) {
particle = new THREE.Particle( new THREE.ParticleCanvasMaterial( { color: Math.random() * 0x808008 + 0x808080, program: program } ) );
particle.name = "particle"+i;
particle.position.x = Math.random() * 2000 - 1000;
particle.position.y = Math.random() * 2000 - 1000;
particle.position.z = Math.random() * 2000 - 1000;
particle.scale.x = particle.scale.y = Math.random() * 10 + 5;
group.add( particle );
}
and here's an example of a mouse down event handler which prints the name and id of the clicked particle in the console:
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectScene( scene );
if ( intersects.length > 0 ) {
console.log("you clicked particle named '" + intersects[0].object.name + "' with id: " + intersects[0].object.id);
}
}
Goodluck!

Related

ESP32 and MPU quaternion values are different - Unity3d shows different initial orentation of object

I am trying to build a 3D FPS game in unity. A friend of mine bought a (replica) gun and modified it to add an ESP32 and an MPU-9250 gyroscope/accelerometer in it to track and send the rotation of the gun (using quaternions) to unity. The problem is that each time I "power on" the gun and start the game the initial rotation of the weapon in the game is different. (I don't want to use euler angles because of the gimbal lock problem.) Any ideas where the problem might be and how to fix it?
I am currently using the MPU-9250 DMP Arduino Library as in here.
I have started thinking that the problem lies on the fact that each time I turn on the power of the gun, the gyroscope axes are reinitialized. According to another library - Calibration - accel/gyro/mag offsets are NOT stored to register if the MPU has powered off. You need to set all offsets at every bootup by yourself (or calibrate at every bootup). I do not want to do that every time the ESP32 is restarted. If only I could use a fixed coordinate system no matter what the position of the gun is, when the ESP32 reboots.
Here is the code I have written so far:
#include "src\lib\SparkFunMPU9250-DMP.h"
#include <WiFi.h>
#include <WiFiUdp.h>
// -------------------------------------------------------------------------------------------
// Enter: WEAPON_* OR Handgun_*
const String gun = "Handgun_4";
char * udpAddress;
int udpPort;
int Trigger;
int Mag;
int Shutter;
// -------------------------------------------------------------------------------------------
// --- Trigger ---
//const int Trigger = 4;
int button_state_trigger = 0;
int button_state_mag = 0;
int button_state_shutter = 0;
// --- MPU9250 ---
MPU9250_DMP imu;
// --- WiFi ---
WiFiUDP udp;
// WIFI credentials
const char * networkName = "...";
const char * networkPswd = "...";
// IP address to send UDP data to:
// either use the ip address of the server or
// a network broadcast address
//const char * udpAddress = ...;
// UDP port
//const int udpPort = ...;
// payload to sent
String payload = "";
//Connection state?
boolean connected = false;
// Timers
unsigned long timer1, timer2;
// -------------------------------------------------------------------------------------------
void setup() {
GunSelect(gun);
// Initialize Serial Com
Serial.begin(115200);
// Initialize Trigger
pinMode(Trigger, INPUT);
pinMode(Mag, INPUT);
pinMode(Shutter, INPUT);
// Initialize MPU9250
while (imu.begin() != INV_SUCCESS) {
Serial.println("Unable to communicate with MPU-9250");
delay(2000);
}
imu.setSensors(INV_XYZ_GYRO | INV_XYZ_ACCEL | INV_XYZ_COMPASS); // Enable all sensors
imu.setGyroFSR(2000); // Set Gyro dps, options are +/- 250, 500, 1000, or 2000 dps
imu.setAccelFSR(16); // Set Accel g, options are +/- 2, 4, 8, or 16 g
imu.setSampleRate(100); // Set sample rate of Accel/Gyro, range between 4Hz-1kHz
imu.setLPF(5); // Set LPF corner frequency of Accel/Gyro, acceptable cvalues are 188, 98, 42, 20, 10, 5 (Hz)
imu.setCompassSampleRate(100); // Set Mag rate, range between: 1-100Hz
imu.dmpBegin(DMP_FEATURE_6X_LP_QUAT | // 6-axis (accel/gyro) quaternion calculation
DMP_FEATURE_GYRO_CAL , // Gyroscope calibration (0's out after 8 seconds of no motion)
200);
delay(10000);
// Initialize WiFi
connectToWiFi(networkName, networkPswd);
}
// -------------------------------------------------------------------------------------------
void loop() {
// Timer Start
// timer1 = micros();
if ( imu.fifoAvailable() ) {
if ( imu.dmpUpdateFifo() == INV_SUCCESS ) {
// Switches Read
button_state_trigger = buttonState(Trigger);
if (gun == "WEAPON_1" || gun == "WEAPON_2" || gun == "WEAPON_3" || gun == "WEAPON_4" || gun == "WEAPON_5" || gun == "WEAPON_Test") {
button_state_mag = buttonState(Mag);
button_state_shutter = buttonState(Shutter);
}
double x = imu.calcQuat(imu.qx);
double y = imu.calcQuat(imu.qy);
double z = imu.calcQuat(imu.qz);
double w = imu.calcQuat(imu.qw);
Serial.println();
Serial.println(button_state_trigger);
Serial.println(button_state_mag);
Serial.println(button_state_shutter);
Serial.println("x: " + String(x));
Serial.println("y: " + String(y));
Serial.println("z: " + String(z));
Serial.println("w: " + String(w));
Serial.println();
//*/
// Send data via WiFi
payload = "ACC|" + String(x,4) + "|" + String(y,4) + "|" + String(z,4) + "|" + String(w,4) + "|" + String(button_state_trigger) + "|" + String(button_state_mag) + "|" + String(button_state_shutter);
sendData(payload);
}
}
// Timer End
// timer2 = micros();
// Serial.println(timer2 - timer1);
}
In Unity I receive the data and parse it as 4 floats. Then I set the gun's rotation in the game as (y,w,-x,z) because the coordinate system of the gun is different from the one that Unity uses. So the code is like:
// Receiving data...
float x = float.Parse(data[1]);
float y = float.Parse(data[2]);
float z = float.Parse(data[3]);
float w = float.Parse(data[4]);
gun.rotation = new Quaternion(y,w,-x,z);
----------------Edit--------------------
I read about Madgwick filter (alternatively Kalman filter and Mahony filter) which is said to be able to produce a quaternion-based estimate of absolute device orientation (based on the earth's reference system ie. North etc.) If I understood correctly . But I am not really sure if that solves my problem.

Unity3D: Simultaneous Mesh update

I'm facing a problem regarding the update of several meshes materials.
I'm developing a projection mapping app, currently I have 3 planes on my surface, each has 4 points that define their position in space (each point, named DestinationPoint is located at the edge of the plane's corners representing the mesh). By moving (manually, using the OnMouseDown() and OnMouseDrag() functions) each point, the mesh is updated accordingly so that each corner follows each of the point (the goal is matches a real world surface.)
So that I don't have to do this calibration each time, I'm saving the values (local position property) of these 12 points (3 planes * 4 points) in a XML file. The saving/loading process of the XML file is working just fine, all points are automatically moved to the positions previously saved in the XML file. The problem arises when Unity updates each mesh material, it is only updating ONE mesh.
For example, this is the saved scene, saved into a XML file, with 3 planes, each having 4 points represented by the white spheres.
This is the scene loaded from the XML file. The points are moved into the proper position, yet the mesh isn't updated.
The function that loads the XML file is the following:
void LoadScenario(){
filePath = Application.dataPath + "/XmlData/"+scenario+".xml";
XmlReader reader = XmlReader.Create(filePath);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(reader);
XmlNodeList Surfaces = xmlDoc.GetElementsByTagName("Surfaces");
/* XML file structure
*
* <Surfaces>
<ProjectionSurface1>
<DestinationPoints>
<DP0>
<Position>-8.28037,18.33852,10.06745</Position>
</DP0>
<DP1>
<Position>-31.55436,-3.485761,3.270439</Position>
</DP1>
<DP2>
<Position>38.00045,-1.380948,4.482903</Position>
</DP2>
<DP3>
<Position>30.65092,19.90506,10.96985</Position>
</DP3>
</DestinationPoints>
</ProjectionSurface1>
</Surfaces>
*/
for(int surface = 0; surface < Surfaces.Count; surface++){
// There is only 1 type of Surface, so only 1 node for now...
XmlNode NodeSurface = Surfaces.Item(surface);
// Get List of Projection Surfaces
XmlNodeList allProjSurfaces = NodeSurface.ChildNodes;
//Debug.Log("Number of Proj Surfaces = " + allProjSurfaces.Count);
for(int projSurf = 0 ; projSurf < allProjSurfaces.Count; projSurf++){
XmlNode nodeProjSurface = allProjSurfaces.Item(projSurf);
string activeProjSurfStr = nodeProjSurface.Name;
//Debug.Log("Projection Surface: " + nodeProjSurface.Name);
// Get the DestinationPoints node
XmlNode nodeDestPoints = nodeProjSurface.FirstChild;
// Get all DPs node list
XmlNodeList allDPs = nodeDestPoints.ChildNodes;
for(int dp = 0; dp < allDPs.Count; dp++){
XmlNode nodeDP = allDPs.Item(dp);
XmlNode nodePos = nodeDP.FirstChild;
string dpStr = nodeDP.Name;
Debug.Log("dpStr: " + dpStr);
string[] split_position = nodePos.InnerText.Split(',');
/*Debug.Log("ProjectionSurface : " + nodeProjSurface.Name +
" with " + nodeDP.Name +
" ,with PosX: " + float.Parse(split_position[0]) +
" ,with PosY: " + float.Parse(split_position[1]) +
" ,with PosZ: " + float.Parse(split_position[2]));*/
float xmlXPos = float.Parse(split_position[0]);
float xmlYPos = float.Parse(split_position[1]);
float xmlZPos = float.Parse(split_position[2]);
Vector3 xmlPosVec = new Vector3(xmlXPos, xmlYPos, xmlZPos);
List <GameObject> dpList = sleManager.listOfDestPoints;
int multiplier = destControl.ReturnModifier(projSurf+1);
dpList[dp+multiplier].transform.localPosition = xmlPosVec;
GameObject dpActiveObj = dpList[dp+multiplier].gameObject;
}
}
}
reader.Close();
// Test to force an update to all 3 projection surfaces meshes
GameObject dp0 = sleManager.listOfDestPoints[0];
GameObject dp4 = sleManager.listOfDestPoints[4];
GameObject dp8 = sleManager.listOfDestPoints[8];
destControl.Run(dp0);
Debug.Log("0");
destControl.Run(dp4);
destControl.Run(dp8);
}
}
The function Run(GameObj obj) that I'm using in the end has the main goal of updating the material properties of obj in the argument and is as follows:
public void Run(GameObject dpObj){
Debug.Log("Run com arg obj name: " + dpObj.gameObject.name);
manager.indexChanged = true;
string activeDPStr = dpObj.name;
string activeProjSurfStr = dpObj.transform.root.ToString();
manager.activeProjSurfObj = ReturnActiveProjSurf(dpObj);
manager.activePlaneObj = ReturnActivePlaneObj(manager.activeProjSurfObj);
string activePlaneStr = manager.activePlaneObj.name;
manager.activeProjSurf = int.Parse(activeProjSurfStr[17].ToString());
manager.modifier = ReturnModifier(manager.activeProjSurf);
manager.activeIndex = int.Parse(activeDPStr[2].ToString()) + manager.modifier;
manager.meshMaterial = manager.activePlaneObj.renderer.material;
}
The list "listOfDestPoints" contains all existing destination points. Keep in mind each plane has 4 points, so every 4 members in the list, the target plane changes.
Now, if I try to run do:
destControl.Run(dp0); // Updates Plane 1, works just fine.
But if I try:
destControl.Run(dp0);
destControl.Run(dp4); // Does not update Plane 1, only Plane 2.
p.s: Sorry about the wall of text!
Just solved it with:
yield return new WaitForSeconds(.25f);
Inside the LoadScenario() function. Seems For cycle was faster than the update function so only the last member of the For cycle was truly updated.

Merging geometries using a WebWorker?

Anyone know if it's possible to merge a set of cube geometries in a web worker and pass it back to the main thread? Was thinking this could reduce the lag when merging large amounts of cubes.
Does Three.JS work okay in a web worker, and if it does, would it be possible (and faster) to do this? Not sure if passing the geometry back would take just as long as merging it normally.
At the moment I'm using a timed for loop to reduce the lag:
// This array is populated by the server and contains the chunk position and data (which I do nothing with yet).
var sectionData = data.secData;
var section = 0;
var tick = function() {
var start = new Date().getTime();
for (; section < sectionData.length && (new Date().getTime()) - start < 1; section++) {
var sectionXPos = sectionData[section][0] * 10;
var sectionZPos = sectionData[section][1] * 10;
var combinedGeometry = new THREE.Geometry();
for (var layer = 0; layer < 1; layer++) { // Only 1 layer because of the lag...
for (var x = 0; x < 10; x++) {
for (var z = 0; z < 10; z++) {
blockMesh.position.set(x-4.5, layer-.5, z-4.5);
blockMesh.updateMatrix();
THREE.GeometryUtils.merge(combinedGeometry, blockMesh);
}
}
}
var sectionMesh = new THREE.Mesh(combinedGeometry, grassBlockMat);
sectionMesh.position.set(sectionXPos, 0, sectionZPos);
sectionMesh.matrixAutoUpdate = false;
sectionMesh.updateMatrix();
scene.add(sectionMesh);
}
if (section < sectionData.length) {
setTimeout(tick, 25);
}
};
setTimeout(tick, 25);
Using Three.JS rev59-dev.
Merged cubes make up the terrain in chunks, and at the moment (due to the lag) each chunk only has 1 layer.
Any tips would be appreciated! Thanks.
THREE.JS will not work in a web worker, however you can copy those parts of the library that you need to work both in the main thread and in your web worker.
Your first problem will be that you cannot send the geometry object itself back to the main thread.
Since the web worker onmessage variable passing works only by sending copies of JSON (not javascript objects) or references to ArrayBuffers, you would have to decode the geometry down to each float, pack it in an ArrayBuffer, and send a reference back to the main thread.
Note those are called transferable objects and once sent, they are cleared in the webworker / main thread from which they came.
See here for more details:
http://www.html5rocks.com/en/tutorials/workers/basics/
https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers
Here is an example of packing position vertices into an array for a physics type system:
//length * 3 axes * 4 bytes per vertex
var posBuffer = new Float32Array(new ArrayBuffer(len * 3 * 4));
//in a loop
//... do hard work
posBuffer[i * 3] = pos.x; //pos is a threejs vector
posBuffer[i * 3 + 1] = pos.y;
posBuffer[i * 3 + 2] = pos.z;
//after loop send buffer to main thread
self.postMessage({posBuffer:posBuffer}, [posBuffer.buffer]);
I copied the THREE.JS vector class inside my web worker and cut out all the methods I didn't need to keep it nice and lean.
FYI it's not slow and for something like n-body collisions it works well.
The main thread sends a command to the web worker telling it to run the update and then listens for the response. Kind of like a producer consumer model in regular threading.

Can I use iScroll to scrollToElement + offset?

I have this code working...
myScroller.scrollTo(0, -654, 200);
I also have this working....
myScroller.scrollToElement("#p4", "1s");
Is there a way to add offset to the #id, like scrollToElement("#p4" + 100, "1s")?
No you can't.
myScroller.scrollToElement("#p4", "1s");
Because first parameter should be the CSS selector of a DOM element. Therefore can you use "#p4" + 100 for applying CSS rules? If your answer is YES then YOU CAN else YOU CANNOT.
If you wanted to scroll to next element after particular time what you can do is try with nth-child() CSS property with a appropriate timeout and incremental flag. Something like this (Assume your elements are inside a parent DIV element which have id myDiv)
var incre = 0;
function customScrollToElement(){
myScroller.scrollToElement("#myDiv:nth-child(" + incre + ")", 10);
incre ++;
}
setTimeout(function(){
customScrollToElement();
},100);
You can do it with some calculation by first using jQuery to get the element's position relative to their parent.
var left = -1 * ( $('#p4').position().left + 100 ),
top = -1 * ( $('#p4').position().top );
myScroller.scrollTo(left, top, 1000);
That will scroll 100 pixels right of the element

How to animate (moving by default) and inanimate (make stationary on tap) colliding objects

Hey tired this new code to animate objects (here bubbles) and make them stationary using sprites. The code is as below,
local ui = require("ui")
local gameUI = require("gameUI")
local easingx = require("easingx")
require "sprite"
display.setStatusBar( display.HiddenStatusBar )
local physics = require("physics")
physics.start()
physics.setScale( 60 )
local backgroundPortrait = display.newImage( "sky.png", 0, 0 )
local backgroundLandscape = display.newImage( "sky.png", 80, 80 )
backgroundLandscape.isVisible = false
local disp = backgroundLandscape
local function selectBubble( event )
local tapped = event.target --event.target is how Corona points to the tapped bubble
if ( tapped.bubbleSelected == false ) then
local vx,vy = tapped:getLinearVelocity()
tapped.xVel = vx --stores the current velocity into bubble's "xVel" variable
tapped.yVel = vy --likewise for yVel
tapped:setLinearVelocity( 0,0 ) --set bubble's velocity to 0!
tapped.currentFrame = (3)
tapped.bubbleSelected = true
elseif ( tapped.bubbleSelected == true ) then
tapped:setLinearVelocity( tapped.xVel, tapped.yVel ) --read previous velocity and set
tapped.bubbleSelected = false
end
end
--BUBBLE1
local bubble1 = sprite.newSprite( spriteSet1 )
bubble1.x = 100
bubble1.y = 100
physics.addBody(bubble1, {bounce=0.04, filter = bubbleCollisionFilter})
bubble1:setLinearVelocity( 2, 4 )
bubble1:addEventListener( "tap", selectBubble )
bubble1.bubbleSelected = false
bubble1:prepare("bubble")
bubble1:play()
--BUBBLE2
local bubble2 = sprite.newSprite( spriteSet1 )
bubble2.x = 210
bubble2.y = 20
physics.addBody(bubble2, {bounce=0.05, filter = bubbleCollisionFilter})
bubble2:setLinearVelocity( 2, 4 )
bubble2:prepare("bubble")
bubble2:play()
--BUBBLE3
local bubble3 = sprite.newSprite( spriteSet1 )
bubble3.x = 100
bubble3.y = 17
physics.addBody(bubble3, {bounce=0.02, filter = bubbleCollisionFilter})
bubble1:setLinearVelocity( 1, 2 )
bubble3:prepare("bubble")
bubble3:play()
--BUBBLE4
local bubble4 = sprite.newSprite( spriteSet1 )
bubble4.x = 310
bubble4.y = 20
physics.addBody(bubble4, {bounce=0.4, filter = bubbleCollisionFilter})
bubble4:setLinearVelocity( 2, 4 )
bubble4:prepare("bubble")
bubble4:play()
The problem is firstly the code doesn't seem to work. Secondly though the bubble changes color on tap (this color is same for most). Yet, each bubble has a unique letter on it. How to get this to work. Please help.
It's hard to know what exactly you're asking because your question is pretty vague (what does "animate" mean? And what do you mean by "doesn't seem to work"? explain these terms because they can mean multiple things) but I think you want to set the physics bodies to "static" in the tap event listener. Refer here for what I'm talking about:
http://developer.anscamobile.com/reference/index/bodybodytype
So inside the selectBubble() function you would type something like tapped.bodyType="static"