/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2016 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.catroid.physics;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Vector2;
import org.catrobat.catroid.ProjectManager;
import org.catrobat.catroid.common.LookData;
import org.catrobat.catroid.content.Look;
import org.catrobat.catroid.content.Sprite;
import java.util.LinkedList;
public class PhysicsLook extends Look {
public static final float SCALE_FACTOR_ACCURACY = 10000.0f;
private final PhysicsObject physicsObject;
private final PhysicsObjectStateHandler physicsObjectStateHandler = new PhysicsObjectStateHandler();
public PhysicsLook(Sprite sprite, PhysicsWorld physicsWorld) {
super(sprite);
physicsObject = physicsWorld.getPhysicsObject(sprite);
}
@Override
public void setTransparencyInUserInterfaceDimensionUnit(float percent) {
super.setTransparencyInUserInterfaceDimensionUnit(percent);
updatePhysicsObjectState(true);
}
@Override
public void setLookData(LookData lookData) {
super.setLookData(lookData);
PhysicsWorld physicsWorld = ProjectManager.getInstance().getSceneToPlay().getPhysicsWorld();
physicsWorld.changeLook(physicsObject, this);
updatePhysicsObjectState(true);
}
@Override
public void setXInUserInterfaceDimensionUnit(float x) {
setX(x - getWidth() / 2f);
}
@Override
public void setPosition(float x, float y) {
super.setPosition(x, y);
if (null != physicsObject) {
physicsObject.setX(x + getWidth() / 2.0f);
physicsObject.setY(y + getHeight() / 2.0f);
}
}
@Override
public void setX(float x) {
super.setX(x);
if (null != physicsObject) {
physicsObject.setX(x + getWidth() / 2.0f);
}
}
@Override
public void setY(float y) {
super.setY(y);
if (null != physicsObject) {
physicsObject.setY(y + getHeight() / 2.0f);
}
}
@Override
public float getAngularVelocityInUserInterfaceDimensionUnit() {
return physicsObject.getRotationSpeed();
}
@Override
public float getXVelocityInUserInterfaceDimensionUnit() {
return physicsObject.getVelocity().x;
}
@Override
public float getYVelocityInUserInterfaceDimensionUnit() {
return physicsObject.getVelocity().y;
}
@Override
public float getX() {
float x = physicsObject.getX() - getWidth() / 2.0f;
super.setX(x);
return x;
}
@Override
public float getY() {
float y = physicsObject.getY() - getHeight() / 2.0f;
super.setY(y);
return y;
}
@Override
public float getRotation() {
super.setRotation((physicsObject.getDirection() % 360));
float rotation = super.getRotation();
float realRotation = physicsObject.getDirection() % 360;
if (realRotation < 0) {
realRotation += 360;
}
switch (super.getRotationMode()) {
case ROTATION_STYLE_LEFT_RIGHT_ONLY:
super.setRotation(0f);
boolean orientedRight = realRotation > 180 || realRotation == 0;
boolean orientedLeft = realRotation <= 180 && realRotation != 0;
if (((isFlipped() && orientedRight) || (!isFlipped() && orientedLeft)) && lookData != null) {
lookData.getTextureRegion().flip(true, false);
}
break;
case ROTATION_STYLE_ALL_AROUND:
super.setRotation(rotation);
break;
case ROTATION_STYLE_NONE:
super.setRotation(0f);
break;
}
return super.getRotation();
}
@Override
public void setRotation(float degrees) {
super.setRotation(degrees);
if (null != physicsObject) {
physicsObject.setDirection(super.getRotation() % 360);
}
}
@Override
public void setScale(float scaleX, float scaleY) {
Vector2 oldScales = new Vector2(getScaleX(), getScaleY());
if (scaleX < 0.0f || scaleY < 0.0f) {
scaleX = 0.0f;
scaleY = 0.0f;
}
int scaleXComp = Math.round(scaleX * SCALE_FACTOR_ACCURACY);
int scaleYComp = Math.round(scaleY * SCALE_FACTOR_ACCURACY);
if (scaleXComp == Math.round(oldScales.x * SCALE_FACTOR_ACCURACY) && scaleYComp == Math.round(oldScales.y * SCALE_FACTOR_ACCURACY)) {
return;
}
super.setScale(scaleX, scaleY);
if (physicsObject != null) {
PhysicsWorld physicsWorld = ProjectManager.getInstance().getSceneToPlay().getPhysicsWorld();
physicsWorld.changeLook(physicsObject, this);
updatePhysicsObjectState(true);
}
}
public void updatePhysicsObjectState(boolean record) {
physicsObjectStateHandler.update(record);
}
@Override
public void setLookVisible(boolean visible) {
super.setLookVisible(visible);
physicsObjectStateHandler.update(true);
}
public boolean isHangedUp() {
return physicsObjectStateHandler.isHangedUp();
}
public void setNonColliding(boolean nonColliding) {
physicsObjectStateHandler.setNonColliding(nonColliding);
}
public void startGlide() {
physicsObjectStateHandler.activateGlideTo();
}
public void stopGlide() {
physicsObjectStateHandler.deactivateGlideTo();
}
private interface PhysicsObjectStateCondition {
boolean isTrue();
}
@Override
public void draw(Batch batch, float parentAlpha) {
physicsObjectStateHandler.checkHangup(true);
super.draw(batch, parentAlpha);
}
private class PhysicsObjectStateHandler {
private LinkedList<PhysicsObjectStateCondition> hangupConditions = new LinkedList<>();
private LinkedList<PhysicsObjectStateCondition> nonCollidingConditions = new LinkedList<>();
private LinkedList<PhysicsObjectStateCondition> fixConditions = new LinkedList<>();
private PhysicsObjectStateCondition positionCondition;
private PhysicsObjectStateCondition visibleCondition;
private PhysicsObjectStateCondition transparencyCondition;
private PhysicsObjectStateCondition glideToCondition;
private boolean glideToIsActive = false;
private boolean hangedUp = false;
private boolean fixed = false;
private boolean nonColliding = false;
public PhysicsObjectStateHandler() {
positionCondition = new PhysicsObjectStateCondition() {
@Override
public boolean isTrue() {
return isOutsideActiveArea();
}
private boolean isOutsideActiveArea() {
return isXOutsideActiveArea() || isYOutsideActiveArea();
}
private boolean isXOutsideActiveArea() {
return Math.abs(PhysicsWorldConverter.convertBox2dToNormalCoordinate(physicsObject.getMassCenter().x))
- physicsObject.getCircumference() > PhysicsWorld.activeArea.x / 2.0f;
}
private boolean isYOutsideActiveArea() {
return Math.abs(PhysicsWorldConverter.convertBox2dToNormalCoordinate(physicsObject.getMassCenter().y))
- physicsObject.getCircumference() > PhysicsWorld.activeArea.y / 2.0f;
}
};
visibleCondition = new PhysicsObjectStateCondition() {
@Override
public boolean isTrue() {
return !isLookVisible();
}
};
transparencyCondition = new PhysicsObjectStateCondition() {
@Override
public boolean isTrue() {
return alpha == 0.0;
}
};
glideToCondition = new PhysicsObjectStateCondition() {
@Override
public boolean isTrue() {
return glideToIsActive;
}
};
hangupConditions.add(transparencyCondition);
hangupConditions.add(positionCondition);
hangupConditions.add(visibleCondition);
hangupConditions.add(glideToCondition);
nonCollidingConditions.add(transparencyCondition);
nonCollidingConditions.add(positionCondition);
nonCollidingConditions.add(visibleCondition);
fixConditions.add(glideToCondition);
}
private boolean checkHangup(boolean record) {
boolean shouldBeHangedUp = false;
for (PhysicsObjectStateCondition hangupCondition : hangupConditions) {
if (hangupCondition.isTrue()) {
shouldBeHangedUp = true;
break;
}
}
boolean deactivateHangup = hangedUp && !shouldBeHangedUp;
boolean activateHangup = !hangedUp && shouldBeHangedUp;
if (deactivateHangup) {
physicsObject.deactivateHangup(record);
} else if (activateHangup) {
physicsObject.activateHangup();
}
hangedUp = shouldBeHangedUp;
return hangedUp;
}
private boolean checkNonColliding(boolean record) {
boolean shouldBeNonColliding = false;
for (PhysicsObjectStateCondition nonCollideCondition : nonCollidingConditions) {
if (nonCollideCondition.isTrue()) {
shouldBeNonColliding = true;
break;
}
}
boolean deactivateNonColliding = nonColliding && !shouldBeNonColliding;
boolean activateNonColliding = !nonColliding && shouldBeNonColliding;
if (deactivateNonColliding) {
physicsObject.deactivateNonColliding(record, false);
} else if (activateNonColliding) {
physicsObject.activateNonColliding(false);
}
nonColliding = shouldBeNonColliding;
return nonColliding;
}
private boolean checkFixed(boolean record) {
boolean shouldBeFixed = false;
for (PhysicsObjectStateCondition fixedCondition : fixConditions) {
if (fixedCondition.isTrue()) {
shouldBeFixed = true;
break;
}
}
boolean deactivateFix = fixed && !shouldBeFixed;
boolean activateFix = !fixed && shouldBeFixed;
if (deactivateFix) {
physicsObject.deactivateFixed(record);
} else if (activateFix) {
physicsObject.activateFixed();
}
fixed = shouldBeFixed;
return fixed;
}
public void update(boolean record) {
checkHangup(record);
checkNonColliding(record);
checkFixed(record);
}
public void activateGlideTo() {
if (!glideToIsActive) {
glideToIsActive = true;
updatePhysicsObjectState(true);
}
}
public void deactivateGlideTo() {
glideToIsActive = false;
updatePhysicsObjectState(true);
}
public boolean isHangedUp() {
return hangedUp;
}
public void setNonColliding(boolean nonColliding) {
if (this.nonColliding != nonColliding) {
this.nonColliding = nonColliding;
update(true);
}
}
}
}