Commit 76b04cdd authored by Viktor Lidholt's avatar Viktor Lidholt

Refactor sprite physics, part 1 (#3711)

parent 4d88752c
......@@ -12,7 +12,6 @@ import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui' as ui show Image;
import 'package:box2d/box2d.dart' as box2d;
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
......@@ -35,13 +34,6 @@ part 'src/node.dart';
part 'src/node3d.dart';
part 'src/node_with_size.dart';
part 'src/particle_system.dart';
part 'src/physics_body.dart';
part 'src/physics_collision_groups.dart';
part 'src/physics_debug.dart';
part 'src/physics_group.dart';
part 'src/physics_joint.dart';
part 'src/physics_shape.dart';
part 'src/physics_world.dart';
part 'src/sound.dart';
part 'src/sprite.dart';
part 'src/sprite_box.dart';
......
......@@ -134,72 +134,10 @@ class Node {
void set rotation(double rotation) {
assert(rotation != null);
if (_physicsBody != null && (parent is PhysicsWorld || parent is PhysicsGroup)) {
_updatePhysicsRotation(physicsBody, rotation, parent);
return;
}
_rotation = rotation;
invalidateTransformMatrix();
}
void _updatePhysicsRotation(PhysicsBody body, double rotation, Node physicsParent) {
PhysicsWorld world = _physicsWorld(physicsParent);
if (world == null) return;
world._updateRotation(body, _rotationToPhysics(rotation, physicsParent));
}
PhysicsWorld _physicsWorld(Node parent) {
if (parent is PhysicsWorld) {
return parent;
}
else if (parent is PhysicsGroup) {
return _physicsWorld(parent.parent);
}
else {
assert(false);
return null;
}
}
double _rotationToPhysics(double rotation, Node physicsParent) {
if (physicsParent is PhysicsWorld) {
return rotation;
} else if (physicsParent is PhysicsGroup) {
return _rotationToPhysics(rotation + physicsParent.rotation, physicsParent.parent);
} else {
assert(false);
return null;
}
}
double _rotationFromPhysics(double rotation, Node physicsParent) {
if (physicsParent is PhysicsWorld) {
return rotation;
} else if (physicsParent is PhysicsGroup) {
return _rotationToPhysics(rotation - physicsParent.rotation, physicsParent.parent);
} else {
assert(false);
return null;
}
}
void _setRotationFromPhysics(double rotation, Node physicsParent) {
assert(rotation != null);
_rotation = _rotationFromPhysics(rotation, physicsParent);
invalidateTransformMatrix();
}
void teleportRotation(double rotation) {
assert(rotation != null);
if (_physicsBody != null && (parent is PhysicsWorld || parent is PhysicsGroup)) {
rotation = _rotationToPhysics(rotation, parent);
_physicsBody._body.setTransform(_physicsBody._body.position, radians(rotation));
_physicsBody._body.angularVelocity = 0.0;
_physicsBody._body.setType(box2d.BodyType.STATIC);
}
_setRotationFromPhysics(rotation, parent);
}
/// The position of this node relative to its parent.
///
......@@ -209,74 +147,10 @@ class Node {
void set position(Point position) {
assert(position != null);
if (_physicsBody != null && (parent is PhysicsWorld || parent is PhysicsGroup)) {
_updatePhysicsPosition(this.physicsBody, position, parent);
return;
}
_position = position;
invalidateTransformMatrix();
}
void _updatePhysicsPosition(PhysicsBody body, Point position, Node physicsParent) {
PhysicsWorld world = _physicsWorld(physicsParent);
if (world == null) return;
world._updatePosition(body, _positionToPhysics(position, physicsParent));
}
Point _positionToPhysics(Point position, Node physicsParent) {
if (physicsParent is PhysicsWorld) {
return position;
} else if (physicsParent is PhysicsGroup) {
// Transform the position
Vector4 parentPos = physicsParent.transformMatrix.transform(new Vector4(position.x, position.y, 0.0, 1.0));
Point newPos = new Point(parentPos.x, parentPos.y);
return _positionToPhysics(newPos, physicsParent.parent);
} else {
assert(false);
return null;
}
}
void _setPositionFromPhysics(Point position, Node physicsParent) {
assert(position != null);
_position = _positionFromPhysics(position, physicsParent);
invalidateTransformMatrix();
}
Point _positionFromPhysics(Point position, Node physicsParent) {
if (physicsParent is PhysicsWorld) {
return position;
} else if (physicsParent is PhysicsGroup) {
// Transform the position
Vector4 parentPos = physicsParent._inverseMatrix().transform(new Vector4(position.x, position.y, 0.0, 1.0));
Point newPos = new Point(parentPos.x, parentPos.y);
return _positionToPhysics(newPos, physicsParent.parent);
} else {
assert(false);
return null;
}
}
void teleportPosition(Point position) {
assert(position != null);
PhysicsWorld world = _physicsWorld(parent);
if (_physicsBody != null && (parent is PhysicsWorld || parent is PhysicsGroup)) {
position = _positionToPhysics(position, parent);
_physicsBody._body.setTransform(
new Vector2(
position.x / world.b2WorldToNodeConversionFactor,
position.y / world.b2WorldToNodeConversionFactor
),
_physicsBody._body.getAngle()
);
_physicsBody._body.linearVelocity = new Vector2.zero();
_physicsBody._body.setType(box2d.BodyType.STATIC);
}
_setPositionFromPhysics(position, parent);
}
/// The skew along the x-axis of this node in degrees.
///
/// myNode.skewX = 45.0;
......@@ -330,30 +204,10 @@ class Node {
void set scale(double scale) {
assert(scale != null);
if (_physicsBody != null && (parent is PhysicsWorld || parent is PhysicsGroup)) {
_updatePhysicsScale(physicsBody, scale, parent);
}
_scaleX = _scaleY = scale;
invalidateTransformMatrix();
}
void _updatePhysicsScale(PhysicsBody body, double scale, Node physicsParent) {
if (physicsParent == null) return;
_physicsWorld(physicsParent)._updateScale(body, _scaleToPhysics(scale, physicsParent));
}
double _scaleToPhysics(double scale, Node physicsParent) {
if (physicsParent is PhysicsWorld) {
return scale;
} else if (physicsParent is PhysicsGroup) {
return _scaleToPhysics(scale * physicsParent.scale, physicsParent.parent);
} else {
assert(false);
return null;
}
}
/// The horizontal scale of this node relative its parent.
///
/// myNode.scaleX = 5.0;
......@@ -361,7 +215,6 @@ class Node {
void set scaleX(double scaleX) {
assert(scaleX != null);
assert(physicsBody == null);
_scaleX = scaleX;
invalidateTransformMatrix();
......@@ -374,7 +227,6 @@ class Node {
void set scaleY(double scaleY) {
assert(scaleY != null);
assert(physicsBody == null);
_scaleY = scaleY;
invalidateTransformMatrix();
......@@ -403,7 +255,6 @@ class Node {
void addChild(Node child) {
assert(child != null);
assert(child._parent == null);
assert(!(child is PhysicsGroup) || this is PhysicsGroup || this is PhysicsWorld);
assert(() {
Node node = this;
......@@ -420,10 +271,6 @@ class Node {
_childrenLastAddedOrder += 1;
child._addedOrder = _childrenLastAddedOrder;
if (_spriteBox != null) _spriteBox._registerNode(child);
if (child is PhysicsGroup) {
child._attachGroup(child, child._world);
}
}
/// Removes a child from this node.
......@@ -436,10 +283,6 @@ class Node {
child._spriteBox = null;
if (_spriteBox != null) _spriteBox._deregisterNode(child);
}
if (child is PhysicsGroup) {
child._detachGroup(child);
}
}
/// Removes this node from its parent node.
......@@ -585,7 +428,7 @@ class Node {
return _transformMatrixBoxToNode;
}
Matrix4 _inverseMatrix() {
Matrix4 inverseTransformMatrix() {
if (_transformMatrixInverse == null) {
_transformMatrixInverse = new Matrix4.copy(transformMatrix);
_transformMatrixInverse.invert();
......@@ -791,31 +634,4 @@ class Node {
bool handleEvent(SpriteBoxEvent event) {
return false;
}
// Physics
PhysicsBody _physicsBody;
/// The physics body associated with this node. If a physics body is assigned,
/// and the node is a child of a [PhysicsWorld] or a [PhysicsGroup] the
/// node's position and rotation will be controlled by the body.
///
/// myNode.physicsBody = new PhysicsBody(
/// new PhysicsShapeCircle(Point.zero, 20.0)
/// );
PhysicsBody get physicsBody => _physicsBody;
void set physicsBody(PhysicsBody physicsBody) {
if (parent != null) {
assert(parent is PhysicsWorld);
if (physicsBody == null) {
physicsBody._detach();
} else {
physicsBody._attach(parent, this);
}
}
_physicsBody = physicsBody;
}
}
This diff is collapsed.
part of flutter_sprites;
class _PhysicsCollisionGroups {
_PhysicsCollisionGroups() {
// Make sure there is a default entry in the groups
getBitmaskForKeys(["Default"]);
}
Map<Object,int> keyLookup = <Object,int>{};
List<Object> getKeysForBitmask(int bitmask) {
List<Object> keys = [];
keyLookup.forEach((Object key, int value) {
if (value & bitmask != 0) {
keys.add(key);
}
});
return keys;
}
int getBitmaskForKeys(List<Object> keys) {
if (keys == null) {
return 0xffff;
}
int bitmask = 0;
for (Object key in keys) {
int value = keyLookup[key];
if (value == null) {
assert(keyLookup.length < 16);
value = 1 << keyLookup.length;
keyLookup[key] = value;
}
bitmask |= value;
}
return bitmask;
}
}
part of flutter_sprites;
class _PhysicsDebugDraw extends box2d.DebugDraw {
_PhysicsDebugDraw(
box2d.ViewportTransform transform,
this.physicsWorld
) : super(transform) {
appendFlags(
box2d.DebugDraw.JOINT_BIT |
box2d.DebugDraw.CENTER_OF_MASS_BIT |
box2d.DebugDraw.WIREFRAME_DRAWING_BIT
);
}
PhysicsWorld physicsWorld;
Canvas canvas;
@override
void drawSegment(Vector2 p1, Vector2 p2, box2d.Color3i color) {
Paint paint = new Paint()
..color = _toColor(color)
..strokeWidth = 1.0;
canvas.drawLine(_toPoint(p1), _toPoint(p2), paint);
}
@override
void drawSolidPolygon(
List<Vector2> vertices,
int vertexCount,
box2d.Color3i color
) {
Path path = new Path();
Point pt = _toPoint(vertices[0]);
path.moveTo(pt.x, pt.y);
for (int i = 1; i < vertexCount; i++) {
pt = _toPoint(vertices[i]);
path.lineTo(pt.x, pt.y);
}
Paint paint = new Paint()..color = _toColor(color);
canvas.drawPath(path, paint);
}
@override
void drawCircle(Vector2 center, num radius, box2d.Color3i color, [Vector2 axis]) {
Paint paint = new Paint()
..color = _toColor(color)
..style = PaintingStyle.stroke
..strokeWidth = 1.0;
canvas.drawCircle(_toPoint(center), _scale(radius), paint);
}
@override
void drawSolidCircle(Vector2 center, num radius, Vector2 axis, box2d.Color3i color) {
Paint paint = new Paint()
..color = _toColor(color);
canvas.drawCircle(_toPoint(center), _scale(radius), paint);
}
@override
void drawPoint(Vector2 point, num radiusOnScreen, box2d.Color3i color) {
drawSolidCircle(point, radiusOnScreen, null, color);
}
@override
void drawParticles(
List<Vector2> centers,
double radius,
List<box2d.ParticleColor> colors,
int count
) {
// TODO: Implement
}
@override
void drawParticlesWireframe(
List<Vector2> centers,
double radius,
List<box2d.ParticleColor> colors,
int count
) {
// TODO: Implement
}
@override
void drawTransform(box2d.Transform xf, box2d.Color3i color) {
drawCircle(xf.p, 0.1, color);
// TODO: Improve
}
@override
void drawStringXY(num x, num y, String s, box2d.Color3i color) {
// TODO: Implement
}
Color _toColor(box2d.Color3i color) {
return new Color.fromARGB(255, color.x, color.y, color.z);
}
Point _toPoint(Vector2 vec) {
return new Point(
vec.x * physicsWorld.b2WorldToNodeConversionFactor,
vec.y * physicsWorld.b2WorldToNodeConversionFactor
);
}
double _scale(double value) {
return value * physicsWorld.b2WorldToNodeConversionFactor;
}
}
part of flutter_sprites;
/// A [Node] that acts as a middle layer between a [PhysicsWorld] and a node
/// with an assigned [PhysicsBody]. The group's transformations are limited to
/// [position], [rotation], and uniform [scale].
///
/// PhysicsGroup group = new PhysicsGroup();
/// myWorld.addChild(group);
/// group.addChild(myNode);
class PhysicsGroup extends Node {
@override
void set scaleX(double scaleX) {
assert(false);
}
@override
void set scaleY(double scaleX) {
assert(false);
}
@override
void set skewX(double scaleX) {
assert(false);
}
@override
void set skewY(double scaleX) {
assert(false);
}
@override
void set physicsBody(PhysicsBody body) {
assert(false);
}
@override
void set position(Point position) {
super.position = position;
_invalidatePhysicsBodies(this);
}
@override
void set rotation(double rotation) {
super.rotation = rotation;
_invalidatePhysicsBodies(this);
}
@override
void set scale(double scale) {
super.scale = scale;
_invalidatePhysicsBodies(this);
}
void _invalidatePhysicsBodies(Node node) {
if (_world == null) return;
if (node.physicsBody != null) {
// TODO: Add to list
_world._bodiesScheduledForUpdate.add(node.physicsBody);
}
for (Node child in node.children) {
_invalidatePhysicsBodies(child);
}
}
@override
void addChild(Node node) {
super.addChild(node);
PhysicsWorld world = _world;
if (node.physicsBody != null && world != null) {
node.physicsBody._attach(world, node);
}
if (node is PhysicsGroup) {
_attachGroup(this, world);
}
}
void _attachGroup(PhysicsGroup group, PhysicsWorld world) {
for (Node child in group.children) {
if (child is PhysicsGroup) {
_attachGroup(child, world);
} else if (child.physicsBody != null) {
child.physicsBody._attach(world, child);
}
}
}
@override
void removeChild(Node node) {
super.removeChild(node);
if (node.physicsBody != null) {
node.physicsBody._detach();
}
if (node is PhysicsGroup) {
_detachGroup(this);
}
}
void _detachGroup(PhysicsGroup group) {
for (Node child in group.children) {
if (child is PhysicsGroup) {
_detachGroup(child);
} else if (child.physicsBody != null) {
child.physicsBody._detach();
}
}
}
PhysicsWorld get _world {
if (this.parent is PhysicsWorld)
return this.parent;
if (this.parent is PhysicsGroup) {
PhysicsGroup group = this.parent;
return group._world;
}
return null;
}
}
This diff is collapsed.
part of flutter_sprites;
/// Defines the shape of a [PhysicsBody].
abstract class PhysicsShape {
box2d.Shape _b2Shape;
Object userObject;
box2d.Shape getB2Shape(PhysicsWorld node, double scale) {
if (_b2Shape == null) {
_b2Shape = _createB2Shape(node, scale);
}
return _b2Shape;
}
box2d.Shape _createB2Shape(PhysicsWorld node, double scale);
void _invalidate() {
_b2Shape = null;
}
}
/// Defines a circle shape with a given center [point] and [radius].
///
/// var shape = PhysicsShapeCircle(Point.origin, 20.0);
class PhysicsShapeCircle extends PhysicsShape {
PhysicsShapeCircle(this.point, this.radius);
final Point point;
final double radius;
@override
box2d.Shape _createB2Shape(PhysicsWorld node, double scale) {
box2d.CircleShape shape = new box2d.CircleShape();
shape.p.x = scale * point.x / node.b2WorldToNodeConversionFactor;
shape.p.y = scale * point.y / node.b2WorldToNodeConversionFactor;
shape.radius = scale * radius / node.b2WorldToNodeConversionFactor;
return shape;
}
}
/// Defines a polygon shape from a list of [points];
///
/// var points = [
/// new Point(-10.0, 0.0),
/// new Point(0.0, 10.0),
/// new Point(10.0, 0.0)
/// ];
/// var shape = new PhysicsShapePolygon(points);
class PhysicsShapePolygon extends PhysicsShape {
PhysicsShapePolygon(this.points);
final List<Point> points;
@override
box2d.Shape _createB2Shape(PhysicsWorld node, double scale) {
List<Vector2> vectors = <Vector2>[];
for (Point point in points) {
Vector2 vec = new Vector2(
scale * point.x / node.b2WorldToNodeConversionFactor,
scale * point.y / node.b2WorldToNodeConversionFactor
);
vectors.add(vec);
}
box2d.PolygonShape shape = new box2d.PolygonShape();
shape.set(vectors, vectors.length);
return shape;
}
}
/// Defines a box shape from a [width] and [height].
///
/// var shape = new PhysicsShapeBox(50.0, 100.0);
class PhysicsShapeBox extends PhysicsShape {
PhysicsShapeBox(
this.width,
this.height, [
this.center = Point.origin,
this.rotation = 0.0
]);
final double width;
final double height;
final Point center;
final double rotation;
@override
box2d.Shape _createB2Shape(PhysicsWorld node, double scale) {
box2d.PolygonShape shape = new box2d.PolygonShape();
shape.setAsBox(
scale * width / node.b2WorldToNodeConversionFactor,
scale * height / node.b2WorldToNodeConversionFactor,
new Vector2(
scale * center.x / node.b2WorldToNodeConversionFactor,
scale * center.y / node.b2WorldToNodeConversionFactor
),
radians(rotation)
);
return shape;
}
}
/// Defines a chain shape from a set of [points]. This can be used to create
/// a continuous chain of edges or, if [loop] is set to true, concave polygons.
///
/// var points = [
/// new Point(-10.0, 0.0),
/// new Point(0.0, 10.0),
/// new Point(10.0, 0.0)
/// ];
/// var shape = new PhysicsShapeChain(points);
class PhysicsShapeChain extends PhysicsShape {
PhysicsShapeChain(this.points, [this.loop=false]);
final List<Point> points;
final bool loop;
@override
box2d.Shape _createB2Shape(PhysicsWorld node, double scale) {
List<Vector2> vectors = <Vector2>[];
for (Point point in points) {
Vector2 vec = new Vector2(
scale * point.x / node.b2WorldToNodeConversionFactor,
scale * point.y / node.b2WorldToNodeConversionFactor
);
vectors.add(vec);
}
box2d.ChainShape shape = new box2d.ChainShape();
if (loop)
shape.createLoop(vectors, vectors.length);
else
shape.createChain(vectors, vectors.length);
return shape;
}
}
/// Defines a single edge line shape from [pointA] to [pointB].
///
/// var shape = new PhysicsShapeEdge(
/// new Point(20.0, 20.0),
/// new Point(50.0, 20.0)
/// );
class PhysicsShapeEdge extends PhysicsShape {
PhysicsShapeEdge(this.pointA, this.pointB);
final Point pointA;
final Point pointB;
@override
box2d.Shape _createB2Shape(PhysicsWorld node, double scale) {
box2d.EdgeShape shape = new box2d.EdgeShape();
shape.set(
new Vector2(
scale * pointA.x / node.b2WorldToNodeConversionFactor,
scale * pointA.y / node.b2WorldToNodeConversionFactor
),
new Vector2(
scale * pointB.x / node.b2WorldToNodeConversionFactor,
scale * pointB.y / node.b2WorldToNodeConversionFactor
)
);
return shape;
}
}
/// A group combines several [shapes] into a single shape.
///
/// var s0 = new PhysicsShapeCircle(new Point(-10.0, 0.0), 20.0);
/// var s1 = new PhysicsShapeCircle(new Point(10.0, 0.0), 20.0);
/// var shape = new PhysicsShapeGroup([s0, s1]);
class PhysicsShapeGroup extends PhysicsShape {
PhysicsShapeGroup(this.shapes);
final List<PhysicsShape> shapes;
@override
box2d.Shape _createB2Shape(PhysicsWorld node, double scale) {
return null;
}
@override
void _invalidate() {
for (PhysicsShape shape in shapes) {
shape._invalidate();
}
}
}
This diff is collapsed.
......@@ -126,8 +126,6 @@ class SpriteBox extends RenderBox {
List<Node> _constrainedNodes;
List<PhysicsWorld> _physicsNodes;
Rect _visibleArea;
Rect get visibleArea {
......@@ -158,14 +156,12 @@ class SpriteBox extends RenderBox {
void _registerNode(Node node) {
_actionControllers = null;
_eventTargets = null;
_physicsNodes = null;
if (node == null || node.constraints != null) _constrainedNodes = null;
}
void _deregisterNode(Node node) {
_actionControllers = null;
_eventTargets = null;
_physicsNodes = null;
if (node == null || node.constraints != null) _constrainedNodes = null;
}
......@@ -356,17 +352,6 @@ class SpriteBox extends RenderBox {
Matrix4 totalMatrix = new Matrix4.fromFloat64List(canvas.getTotalMatrix());
_rootNode._visit(canvas, totalMatrix);
// Draw physics debug
if (_physicsNodes == null)
_rebuildActionControllersAndPhysicsNodes();
for (PhysicsWorld world in _physicsNodes) {
if (world.drawDebug) {
canvas.setMatrix(world._debugDrawTransform.storage);
world.paintDebug(canvas);
}
}
canvas.restore();
}
......@@ -398,7 +383,6 @@ class SpriteBox extends RenderBox {
_callConstraintsPreUpdate(delta);
_runActions(delta);
_callUpdate(_rootNode, delta);
_callStepPhysics(delta);
_callConstraintsConstrain(delta);
}
......@@ -420,13 +404,11 @@ class SpriteBox extends RenderBox {
void _rebuildActionControllersAndPhysicsNodes() {
_actionControllers = <ActionController>[];
_physicsNodes = <PhysicsWorld>[];
_addActionControllersAndPhysicsNodes(_rootNode);
}
void _addActionControllersAndPhysicsNodes(Node node) {
if (node._actions != null) _actionControllers.add(node._actions);
if (node is PhysicsWorld) _physicsNodes.add(node);
for (int i = node.children.length - 1; i >= 0; i--) {
Node child = node.children[i];
......@@ -444,15 +426,6 @@ class SpriteBox extends RenderBox {
}
}
void _callStepPhysics(double dt) {
if (_physicsNodes == null)
_rebuildActionControllersAndPhysicsNodes();
for (PhysicsWorld physicsNode in _physicsNodes) {
physicsNode._stepPhysics(dt);
}
}
void _callConstraintsPreUpdate(double dt) {
if (_constrainedNodes == null) {
_constrainedNodes = <Node>[];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment