/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g2d; import java.io.BufferedReader; import java.io.IOException; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.Fixture; import com.badlogic.gdx.physics.box2d.RayCastCallback; import com.badlogic.gdx.physics.box2d.World; /** * @author kalle_h * * ParticleEmitterBox2D use box2d rayCast:ing to achieve continuous collision detection against box2d fixtures. * If particle detect collision it change it's direction before actual collision would occur. Velocity is 100% * reflected. * * These particles does not have any other physical attributes or functionality. Particles can't collide to * other particles. */ public class ParticleEmitterBox2D extends ParticleEmitter { final World world; final Vector2 startPoint = new Vector2(); final Vector2 endPoint = new Vector2(); /** collision flag */ boolean particleCollided; float normalAngle; /** If velocities squared is shorter than this it could lead 0 length rayCast that cause c++ assertion at box2d */ private final static float EPSILON = 0.001f; /** default visibility to prevent synthetic accesor creation */ final RayCastCallback rayCallBack = new RayCastCallback() { public float reportRayFixture(Fixture fixture, Vector2 point, Vector2 normal, float fraction) { ParticleEmitterBox2D.this.particleCollided = true; ParticleEmitterBox2D.this.normalAngle = MathUtils.atan2(normal.y, normal.x) * MathUtils.radiansToDegrees; return fraction; } }; /** * Constructs default ParticleEmitterBox2D. Box2d World is used for rayCasting. Assumes that particles use same unit * system that box2d world does. * * @param world */ public ParticleEmitterBox2D(World world) { super(); this.world = world; } /** * /**Constructs ParticleEmitterBox2D using bufferedReader. Box2d World is used for rayCasting. Assumes that * particles use same unit system that box2d world does. * * @param world * @param reader * @throws IOException */ public ParticleEmitterBox2D(World world, BufferedReader reader) throws IOException { super(reader); this.world = world; } /** * Constructs ParticleEmitterBox2D fully copying given emitter attributes. Box2d World is used for rayCasting. * Assumes that particles use same unit system that box2d world does. * * @param world * @param emitter */ public ParticleEmitterBox2D(World world, ParticleEmitter emitter) { super(emitter); this.world = world; } @Override protected Particle newParticle(Sprite sprite) { return new ParticleBox2D(sprite); } /** Particle that can collide to box2d fixtures */ private class ParticleBox2D extends Particle { public ParticleBox2D(Sprite sprite) { super(sprite); } /** * translate particle given amount. Continuous collision detection achieved by using RayCast from oldPos to * newPos. * * @param velocityX * @param velocityY */ @Override public void translate(float velocityX, float velocityY) { /** * If velocities squares summed is shorter than Epsilon it could lead ~0 length rayCast that cause nasty c++ * assertion inside box2d. This is so short distance that moving particle has no effect so this return * early. */ if ((velocityX * velocityX + velocityY * velocityY) < EPSILON) return; /** Position offset is half of sprite texture size. */ final float x = getX() + getWidth() / 2f; final float y = getY() + getHeight() / 2f; /** collision flag to false */ particleCollided = false; startPoint.set(x, y); endPoint.set(x + velocityX, y + velocityY); if (world != null) world.rayCast(rayCallBack, startPoint, endPoint); /** If ray collided boolean has set to true at rayCallBack */ if (!particleCollided) { // perfect reflection angle = 2f * normalAngle - angle - 180f; angleCos = MathUtils.cosDeg(angle); angleSin = MathUtils.sinDeg(angle); velocityX = velocity * angleCos; velocityY = velocity * angleSin; } super.translate(velocityX, velocityY); } } }