blob: 97a3cd2b11a046ef1e35ab356a8a45e5ba5e3860 [file] [log] [blame]
package org.sflphone.model;
import java.util.ArrayList;
import java.util.List;
import android.graphics.PointF;
import android.util.Log;
public class BubbleModel {
private static final String TAG = BubbleModel.class.getSimpleName();
private long lastUpdate = 0;
public int width, height;
private ArrayList<Bubble> bubbles = new ArrayList<Bubble>();
private ArrayList<Attractor> attractors = new ArrayList<Attractor>();
private static final double BUBBLE_RETURN_TIME_HALF_LIFE = .3;
private static final double BUBBLE_RETURN_TIME_LAMBDA = Math.log(2) / BUBBLE_RETURN_TIME_HALF_LIFE;
private static final double FRICTION_VISCOUS = Math.log(2) / .2f; // Viscous friction factor
private static final float BUBBLE_MAX_SPEED = 2500.f; // px.s-1 : Max target speed in px/sec
private static final float ATTRACTOR_SMOOTH_DIST = 50.f; // px : Size of the "gravity hole" around the attractor
private static final float ATTRACTOR_STALL_DIST = 15.f; // px : Size of the "gravity hole" flat bottom
private static final float ATTRACTOR_DIST_SUCK = 20.f; // px
private static final float BORDER_REPULSION = 60000; // px.s^-2
private final float border_repulsion;
private final float bubble_max_speed;
private final float attractor_smooth_dist;
private final float attractor_stall_dist;
private final float attractor_dist_suck;
private float density = 1.f;
public BubbleModel(float screen_density) {
Log.d(TAG, "Creating BubbleModel");
this.density = screen_density;
attractor_dist_suck = ATTRACTOR_DIST_SUCK * density;
bubble_max_speed = BUBBLE_MAX_SPEED * density;
attractor_smooth_dist = ATTRACTOR_SMOOTH_DIST * density;
attractor_stall_dist = ATTRACTOR_STALL_DIST * density;
border_repulsion = BORDER_REPULSION * density;
}
public void addBubble(Bubble b) {
b.setDensity(density);
bubbles.add(b);
}
public List<Bubble> getBubbles() {
return bubbles;
}
public void addAttractor(Attractor a) {
a.setDensity(density);
attractors.add(a);
}
public List<Attractor> getAttractors() {
return attractors;
}
public void clearAttractors() {
attractors.clear();
}
public void clear() {
clearAttractors();
bubbles.clear();
}
public void update() {
long now = System.nanoTime();
// Do nothing if lastUpdate is in the future.
if (lastUpdate > now)
return;
double ddt = Math.min((now - lastUpdate) / 1000000000.0, .2);
lastUpdate = now;
float dt = (float) ddt;
// Log.w(TAG, "update dt="+dt);
int attr_n = attractors.size();
// Iterators should not be used in frequently called methods
// to avoid garbage collection glitches caused by iterator objects.
for (int i = 0, n = bubbles.size(); i < n; i++) {
if(i > bubbles.size()){ // prevent updating a bubble already removed
return;
}
Bubble b = bubbles.get(i);
if (b.markedToDie) {
continue;
}
if (!b.dragged) {
float bx = b.getPosX(), by = b.getPosY();
Attractor attractor = null;
PointF attractor_pos = b.attractor;
float attractor_dist = (attractor_pos.x - bx) * (attractor_pos.x - bx) + (attractor_pos.y - by) * (attractor_pos.x - by);
for (int j = 0; j < attr_n; j++) {
try {
Attractor t = attractors.get(j);
float dx = t.pos.x - bx, dy = t.pos.y - by;
float adist = dx * dx + dy * dy;
if (adist < attractor_dist) {
attractor = t;
attractor_pos = t.pos;
attractor_dist = adist;
}
} catch (IndexOutOfBoundsException e) {
// Try to update when layout was changing
}
}
// float friction_coef = 1.f-FRICTION_VISCOUS*dt;
double friction_coef = 1 + Math.expm1(-FRICTION_VISCOUS * ddt);
b.speed.x *= friction_coef;
b.speed.y *= friction_coef;
// if(attractor != null) {
float target_speed;
float tdx = attractor_pos.x - bx, tdy = attractor_pos.y - by;
float dist = Math.max(1.f, (float) Math.sqrt(tdx * tdx + tdy * tdy));
if (dist > attractor_smooth_dist)
target_speed = bubble_max_speed;
else if (dist < attractor_stall_dist)
target_speed = 0;
else {
float a = (dist - attractor_stall_dist) / (attractor_smooth_dist - attractor_stall_dist);
target_speed = bubble_max_speed * a;
}
if (attractor != null) {
if (dist > attractor_smooth_dist)
b.target_scale = 1.f;
else if (dist < attractor_stall_dist)
b.target_scale = .2f;
else {
float a = (dist - attractor_stall_dist) / (attractor_smooth_dist - attractor_stall_dist);
b.target_scale = a * .8f + .2f;
}
}
// border repulsion
if (bx < 0 && b.speed.x < 0) {
b.speed.x += dt * border_repulsion;
} else if (bx > width && b.speed.x > 0) {
b.speed.x -= dt * border_repulsion;
}
if (by < 0 && b.speed.y < 0) {
b.speed.y += dt * border_repulsion;
} else if (by > height && b.speed.y > 0) {
b.speed.y -= dt * border_repulsion;
}
b.speed.x += dt * target_speed * tdx / dist;
b.speed.y += dt * target_speed * tdy / dist;
double edt = -Math.expm1(-BUBBLE_RETURN_TIME_LAMBDA * ddt);
double dx = (attractor_pos.x - bx) * edt + Math.min(bubble_max_speed, b.speed.x) * dt;
double dy = (attractor_pos.y - by) * edt + Math.min(bubble_max_speed, b.speed.y) * dt;
// Log.w(TAG, "update dx="+dt+" dy="+dy);
b.setPos((float) (bx + dx), (float) (by + dy));
// Log.i(TAG,"Model:");
if (attractor != null && attractor_dist < attractor_dist_suck * attractor_dist_suck) {
b.dragged = false;
if (attractor.callback.onBubbleSucked(b)) {
bubbles.remove(b);
n--;
} else {
b.target_scale = 1.f;
}
}
}
b.setScale(b.getScale() + (b.target_scale - b.getScale()) * dt * 10.f);
}
}
public Bubble getBubble(String call) {
for (Bubble b : bubbles) {
if (!b.isUser && b.callIDEquals(call))
return b;
}
return null;
}
public void removeBubble(SipCall sipCall) {
bubbles.remove(getBubble(sipCall.getCallId()));
}
public Bubble getUser() {
for (Bubble b : bubbles) {
if (b.isUser)
return b;
}
return null;
}
}