1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.replica.replicaisland;
18
19/**
20 * Component that adds physics to its parent game object.  This component implements force
21 * calculation based on mass, impulses, friction, and collisions.
22 */
23public class PhysicsComponent extends GameComponent {
24
25    private float mMass;
26    private float mBounciness; // 1.0 = super bouncy, 0.0 = zero bounce
27    private float mInertia;
28    private float mStaticFrictionCoeffecient;
29    private float mDynamicFrictionCoeffecient;
30
31    private static final float DEFAULT_MASS = 1.0f;
32    private static final float DEFAULT_BOUNCINESS = 0.1f;
33    private static final float DEFAULT_INERTIA = 0.01f;
34    private static final float DEFAULT_STATIC_FRICTION_COEFFECIENT = 0.05f;
35    private static final float DEFAULT_DYNAMIC_FRICTION_COEFFECIENT = 0.02f;
36
37    PhysicsComponent() {
38        super();
39        reset();
40        setPhase(ComponentPhases.POST_PHYSICS.ordinal());
41    }
42
43    @Override
44    public void reset() {
45        // TODO: no reason to call accessors here locally.
46        setMass(DEFAULT_MASS);
47        setBounciness(DEFAULT_BOUNCINESS);
48        setInertia(DEFAULT_INERTIA);
49        setStaticFrictionCoeffecient(DEFAULT_STATIC_FRICTION_COEFFECIENT);
50        setDynamicFrictionCoeffecient(DEFAULT_DYNAMIC_FRICTION_COEFFECIENT);
51    }
52
53    @Override
54    public void update(float timeDelta, BaseObject parent) {
55        GameObject parentObject = (GameObject) parent;
56
57        // we look to user data so that other code can provide impulses
58        Vector2 impulseVector = parentObject.getImpulse();
59
60        final Vector2 currentVelocity = parentObject.getVelocity();
61
62        final Vector2 surfaceNormal = parentObject.getBackgroundCollisionNormal();
63        if (surfaceNormal.length2() > 0.0f) {
64            resolveCollision(currentVelocity, impulseVector, surfaceNormal, impulseVector);
65        }
66
67        VectorPool vectorPool = sSystemRegistry.vectorPool;
68
69        // if our speed is below inertia, we need to overcome inertia before we can move.
70
71        boolean physicsCausesMovement = true;
72
73        final float inertiaSquared = getInertia() * getInertia();
74
75        Vector2 newVelocity = vectorPool.allocate(currentVelocity);
76        newVelocity.add(impulseVector);
77
78        if (newVelocity.length2() < inertiaSquared) {
79            physicsCausesMovement = false;
80        }
81
82        final boolean touchingFloor = parentObject.touchingGround();
83
84        GravityComponent gravity = parentObject.findByClass(GravityComponent.class);
85
86        if (touchingFloor && currentVelocity.y <= 0.0f && Math.abs(newVelocity.x) > 0.0f
87                        && gravity != null) {
88            final Vector2 gravityVector = gravity.getGravity();
89
90            // if we were moving last frame, we'll use dynamic friction. Else
91            // static.
92            float frictionCoeffecient = Math.abs(currentVelocity.x) > 0.0f ?
93                        getDynamicFrictionCoeffecient() : getStaticFrictionCoeffecient();
94            frictionCoeffecient *= timeDelta;
95
96            // Friction = cofN, where cof = friction coefficient and N = force
97            // perpendicular to the ground.
98            final float maxFriction = Math.abs(gravityVector.y) * getMass()
99                    * frictionCoeffecient;
100
101            if (maxFriction > Math.abs(newVelocity.x)) {
102                newVelocity.x = (0.0f);
103            } else {
104                newVelocity.x = (newVelocity.x
105                        - (maxFriction * Utils.sign(newVelocity.x)));
106            }
107        }
108
109        if (Math.abs(newVelocity.x) < 0.01f) {
110            newVelocity.x = (0.0f);
111        }
112
113        if (Math.abs(newVelocity.y) < 0.01f) {
114            newVelocity.y = (0.0f);
115        }
116
117        // physics-based movements means constant acceleration, always. set the target to the
118        // velocity.
119        if (physicsCausesMovement) {
120            parentObject.setVelocity(newVelocity);
121            parentObject.setTargetVelocity(newVelocity);
122            parentObject.setAcceleration(Vector2.ZERO);
123            parentObject.setImpulse(Vector2.ZERO);
124        }
125
126        vectorPool.release(newVelocity);
127    }
128
129    protected void resolveCollision(Vector2 velocity, Vector2 impulse, Vector2 opposingNormal,
130                    Vector2 outputImpulse) {
131        VectorPool vectorPool = sSystemRegistry.vectorPool;
132
133        outputImpulse.set(impulse);
134
135        Vector2 collisionNormal = vectorPool.allocate(opposingNormal);
136
137        collisionNormal.normalize();
138
139        Vector2 relativeVelocity = vectorPool.allocate(velocity);
140        relativeVelocity.add(impulse);
141
142        final float dotRelativeAndNormal = relativeVelocity.dot(collisionNormal);
143
144        // make sure the motion of the entity requires resolution
145        if (dotRelativeAndNormal < 0.0f) {
146            final float coefficientOfRestitution = getBounciness(); // 0 = perfectly inelastic,
147                                                                    // 1 = perfectly elastic
148
149            // calculate an impulse to apply to the entity
150            float j = (-(1 + coefficientOfRestitution) * dotRelativeAndNormal);
151
152            j /= ((collisionNormal.dot(collisionNormal)) * (1 / getMass()));
153
154            Vector2 entity1Adjust = vectorPool.allocate(collisionNormal);
155
156            entity1Adjust.set(collisionNormal);
157            entity1Adjust.multiply(j);
158            entity1Adjust.divide(getMass());
159            entity1Adjust.add(impulse);
160            outputImpulse.set(entity1Adjust);
161            vectorPool.release(entity1Adjust);
162
163        }
164
165        vectorPool.release(collisionNormal);
166        vectorPool.release(relativeVelocity);
167    }
168
169    protected void resolveCollision(Vector2 velocity, Vector2 impulse, Vector2 opposingNormal,
170                    float otherMass, Vector2 otherVelocity, Vector2 otherImpulse,
171                    float otherBounciness, Vector2 outputImpulse) {
172        VectorPool vectorPool = sSystemRegistry.vectorPool;
173
174        Vector2 collisionNormal = vectorPool.allocate(opposingNormal);
175        collisionNormal.normalize();
176
177        Vector2 entity1Velocity = vectorPool.allocate(velocity);
178        entity1Velocity.add(impulse);
179
180        Vector2 entity2Velocity = vectorPool.allocate(otherVelocity);
181        entity2Velocity.add(otherImpulse);
182
183        Vector2 relativeVelocity = vectorPool.allocate(entity1Velocity);
184        relativeVelocity.subtract(entity2Velocity);
185
186        final float dotRelativeAndNormal = relativeVelocity.dot(collisionNormal);
187
188        // make sure the entities' motion requires resolution
189        if (dotRelativeAndNormal < 0.0f) {
190            final float bounciness = Math.min(getBounciness() + otherBounciness, 1.0f);
191            final float coefficientOfRestitution = bounciness;  // 0 = perfectly inelastic,
192                                                                // 1 = perfectly elastic
193
194            // calculate an impulse to apply to both entities
195            float j = (-(1 + coefficientOfRestitution) * dotRelativeAndNormal);
196
197            j /= ((collisionNormal.dot(collisionNormal)) * (1 / getMass() + 1 / otherMass));
198
199            Vector2 entity1Adjust = vectorPool.allocate(collisionNormal);
200            entity1Adjust.multiply(j);
201            entity1Adjust.divide(getMass());
202            entity1Adjust.add(impulse);
203
204            outputImpulse.set(entity1Adjust);
205
206            // TODO: Deal impulses both ways.
207            /*
208             * Vector3 entity2Adjust = (collisionNormal j);
209             * entity2Adjust[0] /= otherMass;
210             * entity2Adjust[1] /= otherMass;
211             * entity2Adjust[2] /= otherMass;
212             *
213             * const Vector3 newEntity2Impulse = otherImpulse + entity2Adjust;
214             */
215
216            vectorPool.release(entity1Adjust);
217        }
218
219        vectorPool.release(collisionNormal);
220        vectorPool.release(entity1Velocity);
221        vectorPool.release(entity2Velocity);
222        vectorPool.release(relativeVelocity);
223    }
224
225    public float getMass() {
226        return mMass;
227    }
228
229    public void setMass(float mass) {
230        mMass = mass;
231    }
232
233    public float getBounciness() {
234        return mBounciness;
235    }
236
237    public void setBounciness(float bounciness) {
238        mBounciness = bounciness;
239    }
240
241    public float getInertia() {
242        return mInertia;
243    }
244
245    public void setInertia(float inertia) {
246        mInertia = inertia;
247    }
248
249    public float getStaticFrictionCoeffecient() {
250        return mStaticFrictionCoeffecient;
251    }
252
253    public void setStaticFrictionCoeffecient(float staticFrictionCoeffecient) {
254        mStaticFrictionCoeffecient = staticFrictionCoeffecient;
255    }
256
257    public float getDynamicFrictionCoeffecient() {
258        return mDynamicFrictionCoeffecient;
259    }
260
261    public void setDynamicFrictionCoeffecient(float dynamicFrictionCoeffecient) {
262        mDynamicFrictionCoeffecient = dynamicFrictionCoeffecient;
263    }
264
265}
266