Tuesday, April 24, 2012

Movimentum - Better rigid body constraints; and ToString()

Rigid body constraints rewritten


Ok - I botched it. Not really that obviously, but still, the code for the rigid body constraints was not thought out enough. The issue there is the lifetime of things: When do things start to appear in the animation?

My implicit assumption was that the first time a thing (i.e., a thing's anchor) is mentioned, it will be rendered into the animation. But then, I cannot hook all rigid body constraints to the first step - because then, all things will try to appear in the first step. However, at this point the constraints explicitly written in the script are still missing! - so the constraint solver will not be able to compute a location for the things, and hence it will complain.

I therefore rewrote the rigid body constraint code so that it now creates these constraints at the first step where a thing's anchor appears on the left side of a constraint. In the course of this, I also added code to create the 2d constraints (and I found out that the MovimentumParser.ConstAdd() method did not behave well with 2d anchors - it needed another if. The code looks now a little "over-if-ed", but for this helper method, so be it.)

Altogether, the code for these constraints is about 110 lines.

 

Debugging Output = ToString()


Moreover, I now added (hopefully) sensible code for ToString() of constraints and expressions so that they are more or less easily readable in the debugger. I will not explain that code too deeply - it is only for debugging -, but only give a short overview. If you are interested, please look into the github repository.

The main parts of the ToString() code are:
  • Each expression gets a ToString method with an additional parameter for controlling the parentheses. The code of this methods is one of the following if the expresion is a binary or a unary expression:
    protected internal override string ToString(AbstractOperator parentOp) {
        return parentOp.Wrap(_lhs, _operator, _rhs);
    }
    protected internal override string ToString(AbstractOperator parentOp) {
        return parentOp.Wrap(_operator, _inner);
    }
  • This code is used in a ToString() method for all expressions: I wasted the precious resource "base class" for this feature - both ScalarExpr and VectorExpr now derive from a class Expr which has the ToString() method in it:
public abstract class Expr {
    public override string ToString() {
        return ToString(AbstractOperator.IGNORE_OP);
    }

    protected internal abstract string ToString(AbstractOperator parentOp);
}
  • Finally, the following code inside AbstractOperator handles the parenthesizing by a mutually recursive call to the ToString(AbstractOperator parentOp) method from the first item above:
        public string Wrap(Expr lhs, AbstractOperator op, Expr rhs) {
            string s = lhs.ToString(op) + op + rhs.ToString(op);
            return op.Precedence < Precedence ? "(" + s + ")" : s;
        }

        public string Wrap(AbstractOperator op, Expr e) {
            string s = e.ToString(op) + op;
            return op.Precedence < Precedence ? "(" + s + ")" : s;
        }

So that the precedence check and output works, operators need to be defined with the correct precedence and output string. Here is an example for the binay scalar operators - the precedences are copied over from the grammar (if the operator occurs in simpleexpr2, precedence is 2; if it occurs in simpleexpr3, then 3, etc.):

    public class BinaryScalarOperator : AbstractOperator {
        private BinaryScalarOperator(int precedence, string asString) : base(precedence, asString) { }
        public static BinaryScalarOperator PLUS = new BinaryScalarOperator(1, " + ");
        public static BinaryScalarOperator MINUS = new BinaryScalarOperator(1, " - ");
        public static BinaryScalarOperator TIMES = new BinaryScalarOperator(2, " * ");
        public static BinaryScalarOperator DIVIDE = new BinaryScalarOperator(2, " / ");
    }

For the moment, this works as expected. And as it is only debugging output, I confess that I skipped the unit tests for this.

No comments:

Post a Comment