The calm in the eye of the binding storm

Here's my attempt to write up a description of the benefits of lazy binding with respect to the construction and composition of 2d shapes. Don't be too surprised by the naming discrepancies compared to analagous classes from javafx 1.x. For the record, this project was no fork, rather its original version predated the release of javafx 1.0 by over a year, in fact it began prior to the start of any actual development of the javafx 1.0 runtime, aka "Reprise" - but that's another story. Anyway, with our requirements it wasn't technically feasible to maintain full compatibility with what was subsequently done in javafx 1.x, so no serious attempt to fully match the naming was ever attempted. Nevertheless, regardless of the naming or specific implementation details, there's no reason the general technique I'll describe here can't eventually make its way in some form into a future version of javafx, so here you go:

2D shapes extend an abstract base class class Shape2D, whose definition is essentially:

public abstract class Shape2D extends CustomNode, AbstractPathElement {
...
    public var strokeWidth:Number = 1.0;
    public var strokeLineJoin: StrokeLineJoin = StrokeLineJoin.MITER;
    public var strokeLineCap: StrokeLineCap = StrokeLineCap.SQUARE;
    public var strokeMiterLimit: Number = 10.0;
    public var strokeDashArray: Number[] = [];
    public var strokeDashOffset: Number = 0.0;
    public var fillRule: FillRule = FillRule.EVEN_ODD;
    public var fill: Paint;
    public var stroke: Paint;
    public var extrude: Number = 0;

    // Implementation details
    protected var theShape: com.sun.javafx.geom.Shape;

    protected var transformedShape: com.sun.javafx.geom.Shape =
        bind lazy applyTransform(localTransform, theShape);

    override var pathCommand = bind lazy getPathCommand(transformedShape);

    function getPathCommand(shape:com.sun.javafx.geom.Shape) {
        return function(path:Path2D, gp:com.sun.javafx.geom.Path2D) {
            if (shape != null) {
                gp.append(shape, false);
            }
        }
    }
}


function applyTransform(t:Mat4, s:com.sun.javafx.geom.Shape):com.sun.javafx.geom.Shape
{
    if (t == Mat4.IDENTITY) {
	return s;
    }
    var a = new com.sun.javafx.geom.AffineTransform(t.affine2d());
    return a.createTransformedShape(s);
}

I'll describe AbstractPathElement shortly.

The Java package com.sun.javafx.geom.\*, which is part of the implementation, is actually nothing more than a portable version of the familiar java.awt.geom.\* package, exorcised of its external AWT dependencies.

Concrete 2d shape classes extend Shape2D and override its "theShape" variable with an appropriate lazy binding which expresses the dependencies specific to that shape, for example Rectangle:

public class Rectangle extends Shape2D {

    public var height: Number;
    public var width: Number;
    public var arcHeight: Number;
    public var arcWidth: Number;

    override var theShape = bind lazy makeRect(width, height, arcWidth, arcHeight);

    function makeRect(width: Number, height: Number, arcWidth: Number, arcHeight:Number) {
        def x = width / 2;
	def y = height / 2;
        if (arcHeight == 0 and arcWidth == 0) {
            return new com.sun.javafx.geom.Rectangle2D.Float(-x, -y, width, height);
        }
        return new com.sun.javafx.geom.RoundRectangle2D.Float(-x, -y, width, height, arcWidth, arcHeight);
    }
}


Shape2D itself will ultimately (but lazily) produce 1 or 2 mesh nodes as its (internal) children (one for the fill shape and one for the stroke shape if present), taking into account theShape and all the other dependencies involved, of which there are many: stroke, fill, strokeLineCap/Join/DashArray, etc. Higher level lazy bindings in Shape2D's implementation (omitted here) express these dependencies. Its own contentBounds is also (lazily) computed from the bounds of theShape (or the stroke shape derived from that).

2d shapes may also be produced with constructive geometry operations or 2d path commands. The Area class and Path2D class represent these use cases respectively. These classes operate on the potentially transformed version of theShape - which is therefore an additional lazily bound variable of Shape2D - transformedShape.

Area composes other shapes with union/intersection/subtraction operations and is itself an instance of Shape2D. Its definition is quite simple and similar to that of Rectangle above in terms of how it uses lazy binding:


public class Area extends Shape2D {

    public var add: Shape2D[];
    public var remove: Shape2D[];
    public var intersect: Shape2D[];

    override var theShape = bind lazy makeArea(for (x in add) x.transformedShape,
                                               for (x in remove) x.transformedShape,
                                               for (x in intersect) x.transformedShape);

    function makeArea(add: com.sun.javafx.geom.Shape[],
                      remove: com.sun.javafx.geom.Shape[],
                      intersect: com.sun.javafx.geom.Shape[]) {
        var a0:com.sun.javafx.geom.Area;
        if (sizeof add > 0) {
            a0 = new com.sun.javafx.geom.Area();
            for (x in add) {
                a0.add(new com.sun.javafx.geom.Area(x));
            }
        } else {
            return null;
        }
        for (x in remove) {
            a0.subtract(new com.sun.javafx.geom.Area(x));
	}
        for (x in intersect) {
            a0.intersect(new com.sun.javafx.geom.Area(x));
        }
        def path2d = new com.sun.javafx.geom.Path2D.Float(a0); // reduce memory by converting to path
	return path2d;
    }
}



Path2D objects are constructed from a sequence of commands which insert lines, elliptical arcs, quadratic curves, cubic curves, or other shapes.

AbstractPathElement is an interface which represents those things that may be inserted into a Path2D.

public mixin class AbstractPathElement {
    protected var pathCommand: function(path:Path2D, gp:com.sun.javafx.geom.Path2D):Void;
}

Command implementations such as Shape2D, LineTo, CurveTo, QuadTo, ArcTo, etc, extend AbstractPathElement and provide a lazy binding for its "pathCommand" variable. This is a function type variable which receives the path node and a path geometry object as input, and which must invoke appropriate operations on the geometry object and potentially update state in the path node in order to effect inserting itself into the path. The function value must lazily depend on the parameters of the command, for example in the case of LineTo, its absolute, x and y variables:


public class LineTo extends PathElement {
    /\*\* the x coordinate of the point \*/
    public var x: Number;
    /\*\* the y coordinate of the point \*/
    public var y: Number;

    override var pathCommand = bind lazy makePathCommand(absolute, x, y);

    function makePathCommand(absolute: Boolean, x:Number, y:Number) { return addTo }

    function addTo(path:Path2D, gp:com.sun.javafx.geom.Path2D):Void {
	if (absolute) {
            path.current = new Point2D.Double(x, y);
            path.center = path.current;
            gp.lineTo(x, y);
	} else {
            path.current = new Point2D.Double(path.current.getX() + x,
                                              path.current.getY() + y);
            path.center = path.current;
            var pt = gp.getCurrentPoint();
            gp.lineTo(pt.getX() + x, pt.getY() + y);
	}
    }
}


The ultimate geometry produced by these path commands is again created by means of a lazy binding analogous to how this was done in Rectangle and Area above.

public class Path2D extends Shape2D {

    protected var lastMoveTo: Point2D;
    protected var center: Point2D;
    protected var current: Point2D;
    public var content: AbstractPathElement[];

    override var theShape = bind lazy makePath(for (x in content) x.pathCommand);

    function makePath(cmds:Object[]) {
        def path2d = new com.sun.javafx.geom.Path2D.Float();
	center = null;
        lastMoveTo = null;
        current = null;
        for (x in cmds) {
            def cmd = x as function(path:Path2D, path2d:com.sun.javafx.geom.Path2D):Void;
            cmd(this, path2d);
        }
        return path2d;
    }

}


The net result (and benefit) of all this lazy wiring is that I can animate or otherwise modify the transform and/or any other public variable that affects the geometry or appearance of a shape (and possibly others that depend on it) and pay no price other than (transitively) invalidating precisely the dependent variables of my modification. Note that upon a subsequent modification which affects the same variables the propagation of such invalidation is "short-circuited" when a variable already marked invalid is reached.

None of the functions embedded in the lazy bindings above (whose operations are quite costly) will actually execute until the result of the binding is required, and the result will remain cached as long as it's valid. In typical programs (assuming changes were made) this update will occur exactly once per frame, which is ideal.

For example, if not touched earlier by any user defined event handler, then when the render pass occurs and view frustum culling is performed, the world bounds of the Shape2D node will be accessed (either directly, or indirectly from an ancestor). At that point, the value of the "theShape" variable will become (indirectly) required and its binding evaluated. However, the actual mesh nodes associated with the Shape2D object will not yet be created. If the Shape2D node is within the view frustum, then at some point its children will be requested by the renderer. As a result the lazy bindings that they in turn depend on will be evaluated, using the (now cached) value of "theShape" as one of their inputs.

This approach gives us a general, yet simple, elegant, extensible, declarative and compositional system for managing the very complex dependencies we encounter in scene graph design and elsewhere, which can at the same time be efficient.

Comments:

In my opinion, the same approach can be used for enterprise software tools integrated with Web Services as in Sun Java CAPS. Note that as above nobody is going to say "Hey, here you go, why don't you test your enterprise tools on my enterprise". Instead in each case we need to simulate the enterprise software problem that our tool is supposed to solve. http://www.aygulum.net
http://sohbetcide.com

Posted by Chat on June 17, 2010 at 09:05 AM PDT #

Actually, we do use lazy binding in the production scenegraph. We also do not recompute bounds or transforms on every change. There is certainly more to tighten up
http://www.evdenevenakliyatsec.com
http://www.cagdasnakliye.com
http://www.evdenevenakliye.us

Posted by nakliyat on July 11, 2010 at 09:40 PM PDT #

nstead in each case we need to simulate the enterprise software problem that our tool is supposed to solve

Posted by chat on September 08, 2010 at 03:09 AM PDT #

n my opinion, the same approach can be used for enterprise software tools integrated with Web Services as in Sun Java CAPS. http://www.memleketchat.com

Posted by Chat on November 01, 2010 at 03:44 PM PDT #

Note that as above nobody is going to say "Hey, here you go, why don't you test your enterprise tools on my enterprise".devam...

Posted by chat siteleri on November 01, 2010 at 03:45 PM PDT #

Instead in each case we need to simulate the enterprise software problem that our tool is supposed to solve. batınım sen oldun zahirim sensin

Posted by konyachat on November 01, 2010 at 03:45 PM PDT #

important and awake! great job bro thanks.

Posted by Egitim on December 11, 2010 at 05:48 AM PST #

Simple and Nice example !

Posted by شات on December 15, 2010 at 03:43 AM PST #

important and awake! great job bro thanks.

Posted by muhabbet on December 17, 2010 at 05:18 AM PST #

berk hayat neden arabayı yanlıs yükledin.

Posted by Oyun Oyun on March 28, 2011 at 09:36 PM PDT #

Note that as above nobody is going to

Posted by Oyun on April 08, 2011 at 11:37 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

user12610627

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today