package com.WhiteRabbit.YoYo.objects;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
public interface IObject {
void draw(Canvas c, Paint p);
boolean onTouchEvent(MotionEvent event);
}
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
public interface IObject {
void draw(Canvas c, Paint p);
boolean onTouchEvent(MotionEvent event);
}
Также, для нашего удобства, создадим абстрактный класс, предоставляющий пустую реализацию метода onTouch:
package com.WhiteRabbit.YoYo.objects;
import android.view.MotionEvent;
public abstract class AbstractObject implements IObject {
@Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}
}
import android.view.MotionEvent;
public abstract class AbstractObject implements IObject {
@Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}
}
Перенесем код создания объектов Box2d в класс Cord:
package com.WhiteRabbit.YoYo.objects;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.joints.DistanceJointDef;
import org.jbox2d.dynamics.joints.MouseJointDef;
import org.jbox2d.dynamics.joints.RevoluteJointDef;
import com.WhiteRabbit.YoYo.MainView;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
public class Cord extends AbstractObject {
private final static int CHAIN_CNT = 30;
private MainView view;
private List<Body> bodies = new ArrayList<Body>();
private List<Handle> handles = new ArrayList<Handle>();
public Cord(MainView v, float X, float Y) {
this.view = v;
World world = v.getWorld();
Body ground = v.getGround();
PolygonShape shape = new PolygonShape();
shape.setAsBox(0.5f, 0.125f);
FixtureDef fd = new FixtureDef();
fd.shape = shape;
fd.density = 20.0f;
fd.friction = 0.2f;
fd.filter.groupIndex = -1;
Body prevBody = ground;
Body firstBody = null;
for (int i = 0; i < CHAIN_CNT; i++) {
BodyDef bd = new BodyDef();
bd.type = BodyType.DYNAMIC;
bd.position.set(X - 14.5f + (1.0f * i), Y);
bd.linearDamping = 1;
Body body = world.createBody(bd);
body.setBullet(true);
body.createFixture(fd);
bodies.add(body);
if (firstBody == null) {
firstBody = body;
} else {
RevoluteJointDef jd = new RevoluteJointDef();
jd.collideConnected = false;
Vec2 anchor = new Vec2(X - 15.0f + (1.0f * i), Y);
jd.initialize(prevBody, body, anchor);
world.createJoint(jd);
DistanceJointDef dd = new DistanceJointDef();
dd.initialize(prevBody, body, prevBody.getWorldCenter(),
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.joints.DistanceJointDef;
import org.jbox2d.dynamics.joints.MouseJointDef;
import org.jbox2d.dynamics.joints.RevoluteJointDef;
import com.WhiteRabbit.YoYo.MainView;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
public class Cord extends AbstractObject {
private final static int CHAIN_CNT = 30;
private MainView view;
private List<Body> bodies = new ArrayList<Body>();
private List<Handle> handles = new ArrayList<Handle>();
public Cord(MainView v, float X, float Y) {
this.view = v;
World world = v.getWorld();
Body ground = v.getGround();
PolygonShape shape = new PolygonShape();
shape.setAsBox(0.5f, 0.125f);
FixtureDef fd = new FixtureDef();
fd.shape = shape;
fd.density = 20.0f;
fd.friction = 0.2f;
fd.filter.groupIndex = -1;
Body prevBody = ground;
Body firstBody = null;
for (int i = 0; i < CHAIN_CNT; i++) {
BodyDef bd = new BodyDef();
bd.type = BodyType.DYNAMIC;
bd.position.set(X - 14.5f + (1.0f * i), Y);
bd.linearDamping = 1;
Body body = world.createBody(bd);
body.setBullet(true);
body.createFixture(fd);
bodies.add(body);
if (firstBody == null) {
firstBody = body;
} else {
RevoluteJointDef jd = new RevoluteJointDef();
jd.collideConnected = false;
Vec2 anchor = new Vec2(X - 15.0f + (1.0f * i), Y);
jd.initialize(prevBody, body, anchor);
world.createJoint(jd);
DistanceJointDef dd = new DistanceJointDef();
dd.initialize(prevBody, body, prevBody.getWorldCenter(),
body.getWorldCenter());
dd.dampingRatio = 0;
dd.frequencyHz = 0;
dd.collideConnected = false;
world.createJoint(dd);
}
prevBody = body;
}
MouseJointDef mj = new MouseJointDef();
mj.bodyA = ground;
mj.bodyB = firstBody;
mj.target.set(new Vec2(X - 14.5f, Y));
mj.maxForce = 10000.0f * prevBody.getMass();
mj.dampingRatio = 1;
mj.frequencyHz = 100;
handles.add(new Handle(v, world.createJoint(mj), firstBody));
mj.bodyB = prevBody;
mj.target.set(new Vec2(X - 15.0f + (1.0f * CHAIN_CNT), Y));
mj.maxForce = 10000.0f * prevBody.getMass();
handles.add(new Handle(v, world.createJoint(mj), prevBody));
v.addObj(this);
}
@Override
public void draw(Canvas c, Paint p) {
p.setColor(Color.WHITE);
boolean f = true;
float sx = 0, sy = 0, ex = 0, ey = 0;
float px = 0, py = 0;
for (Body b: bodies) {
Vec2 pos = b.getPosition();
float x = view.xFromWorld(pos.x);
float y = view.yFromWorld(pos.y);
if (!f) {
c.drawLine(px, py, x, y, p);
} else {
sx = x;
sy = y;
}
px = x;
py = y;
ex = x;
ey = y;
f = false;
}
float r = view.getScaledX(Handle.RADIUS);
c.drawCircle(sx, sy, r, p);
c.drawCircle(ex, ey, r, p);
}
public boolean onTouchEvent(MotionEvent event) {
boolean r = false;
Set<Handle> listToClear = new HashSet<Handle>();
for (Handle h: handles) {
listToClear.add(h);
}
int cnt = event.getPointerCount();
if (!((cnt == 1)&&((event.getAction() == MotionEvent.ACTION_UP)||
dd.dampingRatio = 0;
dd.frequencyHz = 0;
dd.collideConnected = false;
world.createJoint(dd);
}
prevBody = body;
}
MouseJointDef mj = new MouseJointDef();
mj.bodyA = ground;
mj.bodyB = firstBody;
mj.target.set(new Vec2(X - 14.5f, Y));
mj.maxForce = 10000.0f * prevBody.getMass();
mj.dampingRatio = 1;
mj.frequencyHz = 100;
handles.add(new Handle(v, world.createJoint(mj), firstBody));
mj.bodyB = prevBody;
mj.target.set(new Vec2(X - 15.0f + (1.0f * CHAIN_CNT), Y));
mj.maxForce = 10000.0f * prevBody.getMass();
handles.add(new Handle(v, world.createJoint(mj), prevBody));
v.addObj(this);
}
@Override
public void draw(Canvas c, Paint p) {
p.setColor(Color.WHITE);
boolean f = true;
float sx = 0, sy = 0, ex = 0, ey = 0;
float px = 0, py = 0;
for (Body b: bodies) {
Vec2 pos = b.getPosition();
float x = view.xFromWorld(pos.x);
float y = view.yFromWorld(pos.y);
if (!f) {
c.drawLine(px, py, x, y, p);
} else {
sx = x;
sy = y;
}
px = x;
py = y;
ex = x;
ey = y;
f = false;
}
float r = view.getScaledX(Handle.RADIUS);
c.drawCircle(sx, sy, r, p);
c.drawCircle(ex, ey, r, p);
}
public boolean onTouchEvent(MotionEvent event) {
boolean r = false;
Set<Handle> listToClear = new HashSet<Handle>();
for (Handle h: handles) {
listToClear.add(h);
}
int cnt = event.getPointerCount();
if (!((cnt == 1)&&((event.getAction() == MotionEvent.ACTION_UP)||
(event.getAction() == MotionEvent.ACTION_CANCEL)))) {
for (int i = 0; i < cnt; i++) {
float x = event.getX(i);
float y = event.getY(i);
int pointerId = event.getPointerId(i);
boolean isFound = false;
for (Handle h: handles) {
Integer id = h.getPointerId();
if (id == null) continue;
if (pointerId == id) {
h.moveTo(x, y);
listToClear.remove(h);
isFound = true;
r = true;
}
}
if (isFound) continue;
for (Handle h: handles) {
if (h.isCapturing(x, y)) {
h.moveTo(x, y);
h.setPointerId(pointerId);
listToClear.remove(h);
r = true;
}
}
}
}
for (Handle h: listToClear) {
if (h.getPointerId() != null) {
h.clearPointId();
}
}
return r;
}
}
for (int i = 0; i < cnt; i++) {
float x = event.getX(i);
float y = event.getY(i);
int pointerId = event.getPointerId(i);
boolean isFound = false;
for (Handle h: handles) {
Integer id = h.getPointerId();
if (id == null) continue;
if (pointerId == id) {
h.moveTo(x, y);
listToClear.remove(h);
isFound = true;
r = true;
}
}
if (isFound) continue;
for (Handle h: handles) {
if (h.isCapturing(x, y)) {
h.moveTo(x, y);
h.setPointerId(pointerId);
listToClear.remove(h);
r = true;
}
}
}
}
for (Handle h: listToClear) {
if (h.getPointerId() != null) {
h.clearPointId();
}
}
return r;
}
}
В класс MainView добавляем совершенно очевидную реализацию методов, отвечающих за централизованное хранение и получение объектов world и ground:
package com.WhiteRabbit.YoYo;
...
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
...
private World world;
private Body ground;
...
...
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
...
private World world;
private Body ground;
...
public void setWorld(World world, Body ground) {
this.world = world;
this.ground = ground;
}
public World getWorld() {
return world;
}
public Body getGround() {
return ground;
}
this.world = world;
this.ground = ground;
}
public World getWorld() {
return world;
}
public Body getGround() {
return ground;
}
...
}
}
Теперь добавим новый динамический объект, который можно будет катать по нашей "веревочке":
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import com.WhiteRabbit.YoYo.MainView;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
public class YoYo extends AbstractObject {
private static final float INT_RADIUS = 0.5f;
private static final float EXT_RADIUS = 3f;
private MainView view;
private Body body = null;
public YoYo(MainView view, float x, float y) {
this.view = view;
BodyDef bd = new BodyDef();
bd.type = BodyType.DYNAMIC;
bd.position.set(x, y);
body = view.getWorld().createBody(bd);
body.setBullet(true);
CircleShape cs = new CircleShape();
FixtureDef fd = new FixtureDef();
fd.shape = cs;
fd.density = 1f;
fd.friction = 0.5f;
cs.m_radius = INT_RADIUS;
body.createFixture(fd);
cs.m_radius = EXT_RADIUS;
fd.density = 10.0f;
fd.filter.groupIndex = -1;
body.createFixture(fd);
view.addObj(this);
}
@Override
public void draw(Canvas c, Paint p) {
p.setColor(Color.GREEN);
p.setAlpha(0x7F);
Vec2 pos = body.getPosition();
float x = view.xFromWorld(pos.x);
float y = view.yFromWorld(pos.y);
c.drawCircle(x, y, view.yFromWorld(EXT_RADIUS), p);
p.setAlpha(0xFF);
c.drawCircle(x, y, view.yFromWorld(INT_RADIUS), p);
}
}
Наш объект состоит из двух вложенных окружностей, большая из которых не взаимодействует с прямоугольниками, составляющими "веревочку", благодаря установке свойства filter.groupIndex у соответствующей fixture (объекты имеющие одинаковый отрицательный groupIndex никогда не взаимодействуют). Благодаря тому, что мы перенесли большую часть кода в конструкторы объектов, реализация класса Task упростилась:
package com.WhiteRabbit.YoYo;
import java.util.Timer;
import java.util.TimerTask;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;
import com.WhiteRabbit.YoYo.objects.Cord;
import com.WhiteRabbit.YoYo.objects.YoYo;
public class Task extends TimerTask {
private final static int G_FORCE = 10;
private final static int CHAIN_CNT = 30;
private static long SECONDS_INTERVAL = 1000L;
private static long FRAME_RATE = 15L;
private YoYoActivity ctx;
private Timer timer = new Timer();
private World world;
private Body ground;
public Task(YoYoActivity ctx) {
this.ctx = ctx;
configure();
}
private void configure() {
MainView v = ctx.getView();
float X = v.SZ_X / 2;
float Y = v.SZ_Y - (v.SZ_Y / 5);
// Создать мир
Vec2 gravity = new Vec2(0.0f, G_FORCE);
world = new World(gravity, true);
BodyDef bd = new BodyDef();
ground = world.createBody(bd);
v.setWorld(world, ground);
// Создать нить
new Cord(v, X, Y);
// Создать волчок
new YoYo(v, X - 14.5f + (1.0f * CHAIN_CNT / 5), Y - 10f);
// Запустить рассчет
timer.scheduleAtFixedRate(this, 0, SECONDS_INTERVAL / FRAME_RATE);
}
public void terminate() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
@Override
public void run() {
// Выполнить расчет итерации
float timeStep = 2.0f / FRAME_RATE;
world.step(timeStep, 10, 10);
// Изменить направление силы гравитации
Vec2 gravity = ctx.getView().getGVect(G_FORCE);
if (gravity != null) {
world.setGravity(gravity);
}
// Обновить view
ctx.getView().update();
}
}
import java.util.Timer;
import java.util.TimerTask;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.World;
import com.WhiteRabbit.YoYo.objects.Cord;
import com.WhiteRabbit.YoYo.objects.YoYo;
public class Task extends TimerTask {
private final static int G_FORCE = 10;
private final static int CHAIN_CNT = 30;
private static long SECONDS_INTERVAL = 1000L;
private static long FRAME_RATE = 15L;
private YoYoActivity ctx;
private Timer timer = new Timer();
private World world;
private Body ground;
public Task(YoYoActivity ctx) {
this.ctx = ctx;
configure();
}
private void configure() {
MainView v = ctx.getView();
float X = v.SZ_X / 2;
float Y = v.SZ_Y - (v.SZ_Y / 5);
// Создать мир
Vec2 gravity = new Vec2(0.0f, G_FORCE);
world = new World(gravity, true);
BodyDef bd = new BodyDef();
ground = world.createBody(bd);
v.setWorld(world, ground);
// Создать нить
new Cord(v, X, Y);
// Создать волчок
new YoYo(v, X - 14.5f + (1.0f * CHAIN_CNT / 5), Y - 10f);
// Запустить рассчет
timer.scheduleAtFixedRate(this, 0, SECONDS_INTERVAL / FRAME_RATE);
}
public void terminate() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
@Override
public void run() {
// Выполнить расчет итерации
float timeStep = 2.0f / FRAME_RATE;
world.step(timeStep, 10, 10);
// Изменить направление силы гравитации
Vec2 gravity = ctx.getView().getGVect(G_FORCE);
if (gravity != null) {
world.setGravity(gravity);
}
// Обновить view
ctx.getView().update();
}
}
Готово :) Запускаем приложение под отладчиком и видим ... как волчок проваливается сквозь нить так, как если бы ее не существовало вовсе :( Дело в том, что и волчок и прямоугольники, составляющие собой нить представляют собой динамические объекты (в противном случае, они попросту оставались бы неподвижными). Рассчет коллизий движущихся объектов - дело крайне ресурсоемкое, и, как показывает практика, в Box2d, не работающее (во всяком случае, при взаимодействии мелких объектов). Вообще говоря, на этот случай, существует параметр setBullet, управляющий этой оптимизацией, но, как мы видим, его установка не приводит к какому-либо видимому эффекту.
Единственный способ, которым мне удалось решить эту проблему, является увеличение размеров взаимодействующих объектов. Увеличим ширину прямоугольников, составляющих нить:
package com.WhiteRabbit.YoYo.objects;
...
public class Cord extends AbstractObject {
...
public class Cord extends AbstractObject {
...
public Cord(MainView v, float X, float Y) {
this.view = v;
World world = v.getWorld();
Body ground = v.getGround();
PolygonShape shape = new PolygonShape();
shape.setAsBox(0.5f, 3 /*0.125f*/);
public Cord(MainView v, float X, float Y) {
this.view = v;
World world = v.getWorld();
Body ground = v.getGround();
PolygonShape shape = new PolygonShape();
shape.setAsBox(0.5f, 3 /*0.125f*/);
. . .
}
...
}
}
...
}
и снова запустим приложение:
Что-же, получилось явно не совсем то, что мы хотели, но зато мы узнали неприятную особенность Box2d, которую в дальнейшем будем иметь в виду.
Комментариев нет:
Отправить комментарий