| /* |
| * Copyright (C) 2004-2013 Savoir-Faire Linux Inc. |
| * |
| * Author: Adrien Beraud <adrien.beraud@gmail.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| * |
| * Additional permission under GNU GPL version 3 section 7: |
| * |
| * If you modify this program, or any covered work, by linking or |
| * combining it with the OpenSSL project's OpenSSL library (or a |
| * modified version of that library), containing parts covered by the |
| * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. |
| * grants you additional permission to convey the resulting work. |
| * Corresponding Source for a non-source form of such a combination |
| * shall include the source code for the parts of OpenSSL used as well |
| * as that of the covered work. |
| */ |
| |
| 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.y - 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)); |
| |
| 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; |
| } |
| |
| } |