/** * Creates a spring between the two given nodes with the given strength. If the two nodes not * directly connected by an edge already have a spring between them, it will be replaced by this * one. * * @param node1 First of the two nodes to have a spring between them. * @param node2 Second of the two nodes to have a spring between them. * @param length The length of this spring (natural rest distance at which the two nodes would * sit). * @param strength The strength of this new spring. * @return True if the viewer contains the two nodes and a spring between them has been created. */ public boolean addSpring(N node1, N node2, float length, float strength) { Particle p1 = nodes.get(node1); if (p1 == null) { return false; } Particle p2 = nodes.get(node2); if (p2 == null) { return false; } // We may have to remove existing spring if it exists between these two nodes. for (int i = 0; i < physics.getNumSprings(); i++) { Spring spring = physics.getSpring(i); if ((((spring.getOneEnd() == p1) && (spring.getTheOtherEnd() == p2)) || ((spring.getOneEnd() == p2) && (spring.getTheOtherEnd() == p1))) && (spring.strength() != EDGE_STRENGTH)) { physics.removeSpring(spring); break; } } // Add the new force. physics.makeSpring(p1, p2, strength, DAMPING, length); return false; }
@Override public void prepare(final ParticleSystem system) { if (_wanderTargets.size() != system.getNumParticles()) { _wanderTargets = new ArrayList<Vector3>(system.getNumParticles()); for (int x = system.getNumParticles(); --x >= 0; ) { _wanderTargets.add(new Vector3(system.getEmissionDirection()).normalizeLocal()); } } }
/** * Cause this particle to reset it's color, age and size per the parent's settings. status is set * to Status.Available. Location, velocity and lifespan are set as given. Actual geometry data is * not affected by this call, only particle params. * * @param velocity new initial particle velocity * @param position new initial particle position * @param lifeSpan new particle lifespan in ms */ public void init( final ReadOnlyVector3 velocity, final ReadOnlyVector3 position, final double lifeSpan) { this.lifeSpan = lifeSpan; _velocity.set(velocity); _position.set(position); currColor.set(parent.getStartColor()); currentAge = 0; status = Status.Available; values[VAL_CURRENT_SIZE] = parent.getStartSize(); }
public void killParticle() { setStatus(Status.Dead); final Vector3 tempVec3 = Vector3.fetchTempInstance(); final FloatBuffer vertexBuffer = parent.getParticleGeometry().getMeshData().getVertexBuffer(); BufferUtils.populateFromBuffer(tempVec3, vertexBuffer, startIndex); final int verts = ParticleSystem.getVertsForParticleType(type); for (int x = 1; x < verts; x++) { BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + x); } Vector3.releaseTempInstance(tempVec3); }
/** * update position (using current position and velocity), color (interpolating between start and * end color), size (interpolating between start and end size), spin (using parent's spin speed) * and current age of particle. If this particle's age is greater than its lifespan, it is set to * status DEAD. * * <p>Note that this only changes the parameters of the Particle, not the geometry the particle is * associated with. * * @param secondsPassed number of seconds passed since last update. * @return true if this particle is not ALIVE (in other words, if it is ready to be reused.) */ public boolean updateAndCheck(final double secondsPassed) { if (status != Status.Alive) { return true; } currentAge += secondsPassed * 1000; // add ms time to age if (currentAge > lifeSpan) { killParticle(); return true; } final Vector3 temp = Vector3.fetchTempInstance(); _position.addLocal(_velocity.multiply(secondsPassed * 1000f, temp)); Vector3.releaseTempInstance(temp); // get interpolated values from appearance ramp: parent.getRamp().getValuesAtAge(currentAge, lifeSpan, currColor, values, parent); // interpolate colors final int verts = ParticleSystem.getVertsForParticleType(type); for (int x = 0; x < verts; x++) { BufferUtils.setInBuffer( currColor, parent.getParticleGeometry().getMeshData().getColorBuffer(), startIndex + x); } // check for tex animation final int newTexIndex = parent.getTexAnimation().getTexIndexAtAge(currentAge, lifeSpan, parent); // Update tex coords if applicable if (currentTexIndex != newTexIndex) { // Only supported in Quad type for now. if (ParticleType.Quad.equals(parent.getParticleType())) { // determine side final float side = (float) Math.sqrt(parent.getTexQuantity()); int index = newTexIndex; if (index >= parent.getTexQuantity()) { index %= parent.getTexQuantity(); } // figure row / col final float row = side - (int) (index / side) - 1; final float col = index % side; // set texcoords final float sU = col / side, eU = (col + 1) / side; final float sV = row / side, eV = (row + 1) / side; final FloatBuffer texs = parent.getParticleGeometry().getMeshData().getTextureCoords(0).getBuffer(); texs.position(startIndex * 2); texs.put(eU).put(sV); texs.put(eU).put(eV); texs.put(sU).put(eV); texs.put(sU).put(sV); texs.clear(); } currentTexIndex = newTexIndex; } return false; }
@Override protected void onDraw(Canvas canvas) { Paint paint = new Paint(); paint.setColor(Color.RED); paint.setTextSize(40); /* * draw the background */ canvas.drawBitmap(mPool, 0, 0, null); /* * compute the new position of our object, based on accelerometer * data and present time. */ final ParticleSystem particleSystem = mParticleSystem; final long now = mSensorTimeStamp + (System.nanoTime() - mCpuTimeStamp); final float sx = mSensorX; final float sy = mSensorY; particleSystem.update(sx, sy, now); final float xc = mXOrigin; final float yc = mYOrigin; final float xs = mMetersToPixelsX; final float ys = mMetersToPixelsY; String scor = "score: " + score.toString(); // canvas.rotate(90); canvas.drawText(scor, xc, yc, paint); int i = 0; for (Particle p : particleSystem.mBalls) { /* * We transform the canvas so that the coordinate system matches * the sensors coordinate system with the origin in the center * of the screen and the unit is the meter. */ i++; final float x = xc + particleSystem.getPosX(i) * xs; final float y = yc - particleSystem.getPosY(i) * ys; canvas.drawBitmap(p.bitmap, x, y, null); } // and make sure to redraw asap invalidate(); }
/** * Reset particle conditions. Besides the passed lifespan, we also reset color, size, and spin * angle to their starting values (as given by parent.) Status is set to Status.Available. * * @param lifeSpan the recreated particle's new lifespan */ public void recreateParticle(final double lifeSpan) { this.lifeSpan = lifeSpan; final int verts = ParticleSystem.getVertsForParticleType(type); currColor.set(parent.getStartColor()); for (int x = 0; x < verts; x++) { BufferUtils.setInBuffer( currColor, parent.getParticleGeometry().getMeshData().getColorBuffer(), startIndex + x); } values[VAL_CURRENT_SIZE] = parent.getStartSize(); currentAge = 0; values[VAL_CURRENT_MASS] = 1; status = Status.Available; }
/** * Adds the given edge to those to be displayed in the viewer. Note that the edge must connect * nodes that have already been added to the viewer. This version will use the locations of the * two nodes to calculate their distance of separation. * * @param edge Edge to add to the display. * @return True if edge was added successfully. False if edge contains nodes that have not been * added to the viewer. */ public boolean addEdge(E edge) { Particle p1 = nodes.get(edge.getNode1()); if (p1 == null) { System.err.println("Warning: Node1 not found when creating edge."); return false; } Particle p2 = nodes.get(edge.getNode2()); if (p2 == null) { System.err.println("Warning: Node2 not found when creating edge."); return false; } // Only add edge if it does not already exist in the collection if (!edges.containsKey(edge)) { float x1 = p1.position().x(); float y1 = p1.position().y(); float x2 = p2.position().x(); float y2 = p2.position().y(); // Strength, damping, reset length edges.put( edge, physics.makeSpring( p1, p2, EDGE_STRENGTH, DAMPING, (float) Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)))); } return true; }
public void draw() { background(255); lights(); smooth(); ps.run(); }
@Override protected void onDraw(Canvas canvas) { /* * draw the background */ // canvas.drawBitmap(mWood, 0, 0, null); /* * compute the new position of our object, based on accelerometer * data and present time. */ final ParticleSystem particleSystem = mParticleSystem; final long now = mSensorTimeStamp + (System.nanoTime() - mCpuTimeStamp); final float sx = mSensorX; final float sy = mSensorY; particleSystem.update(sx, sy, now); final float xc = mXOrigin; final float yc = mYOrigin; final float xs = mMetersToPixelsX; final float ys = mMetersToPixelsY; final Bitmap bitmap = mBitmap; final int count = particleSystem.getParticleCount(); for (int i = 0; i < count; i++) { /* * We transform the canvas so that the coordinate system matches * the sensors coordinate system with the origin in the center * of the screen and the unit is the meter. */ final float x = xc + particleSystem.getPosX(i) * xs; final float y = yc - particleSystem.getPosY(i) * ys; canvas.drawBitmap(bitmap, x, y, mPaint); } // and make sure to redraw asap invalidate(); }
/** * Creates a attractive or repulsive force between the two given nodes. If the two nodes already * have a force between them, it will be replaced by this one. * * @param node1 First of the two nodes to have a force between them. * @param node2 Second of the two nodes to have a force between them. * @param force Force to create between the two nodes. If positive, the nodes will attract each * other, if negative they will repulse. The larger the magnitude the stronger the force. * @param minDistance Minimum distance within which no force is applied. * @return True if the viewer contains the two nodes and a force between them has been created. */ public boolean addForce(N node1, N node2, float force, float minDistance) { Particle p1 = nodes.get(node1); if (p1 == null) { return false; } Particle p2 = nodes.get(node2); if (p2 == null) { return false; } // We may have to remove existing force if it exists between these two nodes. for (int i = 0; i < physics.getNumAttractions(); i++) { Attraction a = physics.getAttraction(i); if (((a.getOneEnd() == p1) && (a.getTheOtherEnd() == p2)) || ((a.getOneEnd() == p2) && (a.getTheOtherEnd() == p1))) { physics.removeAttraction(a); break; } } // Add the new force. physics.makeAttraction(p1, p2, force, minDistance); return false; }
/** Centres the particle view on the currently visible nodes. */ private void updateCentroid() { float xMax = Float.NEGATIVE_INFINITY, xMin = Float.POSITIVE_INFINITY, yMin = Float.POSITIVE_INFINITY, yMax = Float.NEGATIVE_INFINITY; for (int i = 0; i < physics.getNumParticles(); ++i) { Particle p = physics.getParticle(i); xMax = Math.max(xMax, p.position().x()); xMin = Math.min(xMin, p.position().x()); yMin = Math.min(yMin, p.position().y()); yMax = Math.max(yMax, p.position().y()); } float xRange = xMax - xMin; float yRange = yMax - yMin; if ((xRange == 0) && (yRange == 0)) { xRange = Math.max(1, xMax); yRange = Math.max(1, yMax); } float zScale = (float) Math.min(height / (yRange * 1.2), width / (xRange * 1.2)); centroid.setTarget(xMin + 0.5f * xRange, yMin + 0.5f * yRange, zScale); }
/** * Tethers the given node to its location with the given strength. * * @param node The node to be tethered. * @param strength Strength of the tether. * @return True if the viewer contains the given node and it was tethered successfully. */ public boolean tether(N node, float strength) { Particle p1 = nodes.get(node); if (p1 == null) { return false; } // Grab the tethering stake if it has already been created, otherwise create a new one. Particle stake = stakes.get(node); if (stake == null) { stake = physics.makeParticle(1, node.getLocation().x, node.getLocation().y, 0); stake.makeFixed(); stakes.put(node, stake); } // Grab the tether if it has already been created, otherwise create a new one. Spring tether = tethers.get(stake); if (tether == null) { tether = physics.makeSpring(stake, p1, strength, DAMPING, Float.MIN_VALUE); tethers.put(stake, tether); } else { tether.setStrength(strength); } return true; }
/** * Adds the given edge to those to be displayed in the viewer. Note that the edge must connect * nodes that have already been added to the viewer. This version will fix the distance of * separation between nodes to the given value * * @param edge Edge to add to the display. * @return True if edge was added successfully. False if edge contains nodes that have not been * added to the viewer. */ public boolean addEdge(E edge, float distance) { Particle p1 = nodes.get(edge.getNode1()); if (p1 == null) { System.err.println("Warning: Node1 not found when creating edge."); return false; } Particle p2 = nodes.get(edge.getNode2()); if (p2 == null) { System.err.println("Warning: Node2 not found when creating edge."); return false; } // Only add edge if it does not already exist in the collection if (!edges.containsKey(edge)) { // Strength, damping, reset length edges.put(edge, physics.makeSpring(p1, p2, EDGE_STRENGTH, DAMPING, distance)); } return true; }
public void setup() { mFont = createFont("ArialRoundedMTBold-36", 48); size(1440, 900, OPENGL); background(0); frameRate(120); // model = new OBJModel(this, "alyson_laugh.obj", "absolute", TRIANGLES); model = new OBJModel(this, "alyson_scared.obj", "absolute", TRIANGLES); // model = new OBJModel(this, "alyson_crying.obj", "absolute", TRIANGLES); smooth(); colorMode(HSB); strokeWeight(4); model.scale(800); model.translateToCenter(); detailValue = 3; scaleValue = 10; vertices = new ArrayList(); ps = new ParticleSystem(this, scaleValue); PVector averagePosition = new PVector(0, 0, 0); for (int i = 0; i < model.getVertexCount(); i += detailValue) { PVector destinationPoint = model.getVertex(i); vertices.add(destinationPoint); averagePosition.add(destinationPoint); } averagePosition.div(vertices.size()); for (int i = 0; i < vertices.size(); i++) { PVector destination = (PVector) vertices.get(i); Particle p = new Particle(this, averagePosition, destination); ps.addParticle(p); } }
/** * Attempts to space out non-connected nodes from one another. This is achieved by adding a strong * repulsive force between non-connected nodes. Note that this produces n-squared forces so can be * slow for large networks where many nodes are not connected to each other. */ public void spaceNodes() { ArrayList<Particle> pList = new ArrayList<Particle>(nodes.values()); for (int i = 0; i < pList.size(); i++) { for (int j = 0; j < pList.size(); j++) { if (i > j) { Particle p1 = pList.get(i); Particle p2 = pList.get(j); // See if we have a connection between nodes for (Spring spring : edges.values()) { if (((spring.getOneEnd() == p1) && (spring.getTheOtherEnd() == p2)) || ((spring.getOneEnd() == p2) && (spring.getTheOtherEnd() == p1))) { // Do nothing as we already have an edge connecting these two particles } else { // Add a small repulsive force physics.makeAttraction(p1, p2, -1000, 0.1f); } } } } } }
/** * Updates the positions of nodes and edges in the viewer. This method does not normally need to * be called as update happens every time draw() is called. Calling this method can be useful if * you wish to speed up the movement of nodes and edges by updating their position more than once * every draw cycle. */ public void updateParticles() { physics.tick(0.3f); // Advance time in the physics engine. }
/** * Adds a node to those to be displayed in the viewer. * * @param node Node to add to the viewer. */ public void addNode(N node) { Particle p = physics.makeParticle(1, node.getLocation().x, node.getLocation().y, 0); nodes.put(node, p); }
public void draw() { background(0); frame.setTitle(str((int) frameRate)); ps.update(); }
/** * Update the vertices for this particle, taking size, spin and viewer into consideration. In the * case of particle type ParticleType.GeomMesh, the original triangle normal is maintained rather * than rotating it to face the camera or parent vectors. * * @param cam Camera to use in determining viewer aspect. If null, or if parent is not set to * camera facing, parent's left and up vectors are used. */ public void updateVerts(final Camera cam) { final double orient = parent.getParticleOrientation() + values[VAL_CURRENT_SPIN]; final double currSize = values[VAL_CURRENT_SIZE]; if (type == ParticleSystem.ParticleType.GeomMesh || type == ParticleSystem.ParticleType.Point) {; // nothing to do } else if (cam != null && parent.isCameraFacing()) { final ReadOnlyVector3 camUp = cam.getUp(); final ReadOnlyVector3 camLeft = cam.getLeft(); final ReadOnlyVector3 camDir = cam.getDirection(); if (parent.isVelocityAligned()) { bbX.set(_velocity).normalizeLocal().multiplyLocal(currSize); camDir.cross(bbX, bbY).normalizeLocal().multiplyLocal(currSize); } else if (orient == 0) { bbX.set(camLeft).multiplyLocal(currSize); bbY.set(camUp).multiplyLocal(currSize); } else { final double cA = MathUtils.cos(orient) * currSize; final double sA = MathUtils.sin(orient) * currSize; bbX.set(camLeft) .multiplyLocal(cA) .addLocal(camUp.getX() * sA, camUp.getY() * sA, camUp.getZ() * sA); bbY.set(camLeft) .multiplyLocal(-sA) .addLocal(camUp.getX() * cA, camUp.getY() * cA, camUp.getZ() * cA); } } else { bbX.set(parent.getLeftVector()).multiplyLocal(0); bbY.set(parent.getUpVector()).multiplyLocal(0); } final Vector3 tempVec3 = Vector3.fetchTempInstance(); final FloatBuffer vertexBuffer = parent.getParticleGeometry().getMeshData().getVertexBuffer(); switch (type) { case Quad: { _position.subtract(bbX, tempVec3).subtractLocal(bbY); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 0); _position.subtract(bbX, tempVec3).addLocal(bbY); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1); _position.add(bbX, tempVec3).addLocal(bbY); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 2); _position.add(bbX, tempVec3).subtractLocal(bbY); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 3); break; } case GeomMesh: { final Quaternion tempQuat = Quaternion.fetchTempInstance(); final ReadOnlyVector3 norm = triModel.getNormal(); if (orient != 0) { tempQuat.fromAngleNormalAxis(orient, norm); } for (int x = 0; x < 3; x++) { if (orient != 0) { tempQuat.apply(triModel.get(x), tempVec3); } else { tempVec3.set(triModel.get(x)); } tempVec3.multiplyLocal(currSize).addLocal(_position); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + x); } Quaternion.releaseTempInstance(tempQuat); break; } case Triangle: { _position .subtract(3 * bbX.getX(), 3 * bbX.getY(), 3 * bbX.getZ(), tempVec3) .subtractLocal(bbY); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 0); _position.add(bbX, tempVec3).addLocal(3 * bbY.getX(), 3 * bbY.getY(), 3 * bbY.getZ()); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1); _position.add(bbX, tempVec3).subtractLocal(bbY); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 2); break; } case Line: { _position.subtract(bbX, tempVec3); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex); _position.add(bbX, tempVec3); BufferUtils.setInBuffer(tempVec3, vertexBuffer, startIndex + 1); break; } case Point: { BufferUtils.setInBuffer(_position, vertexBuffer, startIndex); break; } } Vector3.releaseTempInstance(tempVec3); }
/** * Cause this particle to reset it's lifespan, velocity, color, age and size per the parent's * settings. status is set to Status.Available and location is set to 0,0,0. Actual geometry data * is not affected by this call, only particle params. */ public void init() { init(parent.getRandomVelocity(_velocity), Vector3.ZERO, parent.getRandomLifeSpan()); }
/** * Normal use constructor. Sets up the parent and particle type for this particle. * * @param parent the particle collection this particle belongs to */ public Particle(final ParticleSystem parent) { this.parent = parent; type = parent.getParticleType(); }
@Override public void prepare(final ParticleSystem system) { super.prepare(system); _swarmPoint.set(system.getOriginCenter()).addLocal(_swarmOffset); }
/** * Sets the drag on all particles in the system. By default drag is set to 0.75 which is enough to * allow particles to move smoothly. * * @param drag Drag effect (larger numbers slow down movement). */ public void setDrag(float drag) { physics.setDrag(drag); }