static int ax_count;
/*
+ * XXX: note that following in the case of non-primary elements must be
+ * split, so that Libale performs adjustments according to the
+ * immediate change in the parent element (likely depending on the
+ * alignment properties specified); for this and other reasons, it
+ * would be desirable to rewrite following logic more concisely, using,
+ * e.g., the recently-introduced ale_eval to facilitate abstraction, so
+ * that the split in functionality can be performed more cleanly.
+ */
+
+ /*
+ * Alignment state
+ *
+ * This structure contains alignment state information. The change
+ * between the non-default old initial alignment and old final
+ * alignment is used to adjust the non-default current initial
+ * alignment. If either the old or new initial alignment is a default
+ * alignment, the old --follow semantics are preserved.
+ */
+
+ class astate_t {
+ ale_trans old_initial_alignment;
+ ale_trans old_final_alignment;
+ ale_trans default_initial_alignment;
+ int old_is_default;
+ std::vector<int> is_default;
+ ale_image input_frame;
+
+ public:
+ astate_t() :
+ is_default(1) {
+
+ old_initial_alignment = ale_new_trans(accel::context(), NULL);
+ old_final_alignment = ale_new_trans(accel::context(), NULL);
+ default_initial_alignment = ale_new_trans(accel::context(), NULL);
+
+ input_frame = NULL;
+ is_default[0] = 1;
+ old_is_default = 1;
+ }
+
+ ale_image get_input_frame() const {
+ return input_frame;
+ }
+
+ void set_is_default(unsigned int index, int value) {
+
+ /*
+ * Expand the array, if necessary.
+ */
+ if (index == is_default.size());
+ is_default.resize(index + 1);
+
+ assert (index < is_default.size());
+ is_default[index] = value;
+ }
+
+ int get_is_default(unsigned int index) {
+ assert (index < is_default.size());
+ return is_default[index];
+ }
+
+ ale_trans get_default() {
+ return default_initial_alignment;
+ }
+
+ void set_default(ale_trans t) {
+ default_initial_alignment = t;
+ }
+
+ void default_set_original_bounds(ale_image i) {
+ ale_trans_set_original_bounds(default_initial_alignment, i);
+ }
+
+ void set_final(ale_trans t) {
+ old_final_alignment = t;
+ }
+
+ void set_input_frame(ale_image i) {
+ input_frame = i;
+ }
+
+ /*
+ * Implement new delta --follow semantics.
+ *
+ * If we have a transformation T such that
+ *
+ * prev_final == T(prev_init)
+ *
+ * Then we also have
+ *
+ * current_init_follow == T(current_init)
+ *
+ * We can calculate T as follows:
+ *
+ * T == prev_final(prev_init^-1)
+ *
+ * Where ^-1 is the inverse operator.
+ */
+ static trans_single follow(trans_single a, trans_single b, trans_single c) {
+ trans_single cc = c;
+
+ if (alignment_class == 0) {
+ /*
+ * Translational transformations
+ */
+
+ ale_pos t0 = -a.eu_get(0) + b.eu_get(0);
+ ale_pos t1 = -a.eu_get(1) + b.eu_get(1);
+
+ cc.eu_modify(0, t0);
+ cc.eu_modify(1, t1);
+
+ } else if (alignment_class == 1) {
+ /*
+ * Euclidean transformations
+ */
+
+ ale_pos t2 = -a.eu_get(2) + b.eu_get(2);
+
+ cc.eu_modify(2, t2);
+
+ point p( c.scaled_height()/2 + c.eu_get(0) - a.eu_get(0),
+ c.scaled_width()/2 + c.eu_get(1) - a.eu_get(1) );
+
+ p = b.transform_scaled(p);
+
+ cc.eu_modify(0, p[0] - c.scaled_height()/2 - c.eu_get(0));
+ cc.eu_modify(1, p[1] - c.scaled_width()/2 - c.eu_get(1));
+
+ } else if (alignment_class == 2) {
+ /*
+ * Projective transformations
+ */
+
+ point p[4];
+
+ p[0] = b.transform_scaled(a
+ . scaled_inverse_transform(c.transform_scaled(point( 0 , 0 ))));
+ p[1] = b.transform_scaled(a
+ . scaled_inverse_transform(c.transform_scaled(point(c.scaled_height(), 0 ))));
+ p[2] = b.transform_scaled(a
+ . scaled_inverse_transform(c.transform_scaled(point(c.scaled_height(), c.scaled_width()))));
+ p[3] = b.transform_scaled(a
+ . scaled_inverse_transform(c.transform_scaled(point( 0 , c.scaled_width()))));
+
+ cc.gpt_set(p);
+ }
+
+ return cc;
+ }
+
+ /*
+ * For multi-alignment following, we use the following approach, not
+ * guaranteed to work with large changes in scene or perspective, but
+ * which should be somewhat flexible:
+ *
+ * For
+ *
+ * t[][] calculated final alignments
+ * s[][] alignments as loaded from file
+ * previous frame n
+ * current frame n+1
+ * fundamental (primary) 0
+ * non-fundamental (non-primary) m!=0
+ * parent element m'
+ * follow(a, b, c) applying the (a, b) delta T=b(a^-1) to c
+ *
+ * following in the case of missing file data might be generated by
+ *
+ * t[n+1][0] = t[n][0]
+ * t[n+1][m!=0] = follow(t[n][m'], t[n+1][m'], t[n][m])
+ *
+ * cases with all noted file data present might be generated by
+ *
+ * t[n+1][0] = follow(s[n][0], t[n][0], s[n+1][0])
+ * t[n+1][m!=0] = follow(s[n+1][m'], t[n+1][m'], s[n+1][m])
+ *
+ * For non-following behavior, or where assigning the above is
+ * impossible, we assign the following default
+ *
+ * t[n+1][0] = Identity
+ * t[n+1][m!=0] = t[n+1][m']
+ */
+
+ void init_frame_alignment_primary(transformation *offset, int lod, ale_pos perturb) {
+
+ if (perturb > 0 && !old_is_default && !get_is_default(0)
+ && default_initial_alignment_type == 1) {
+
+ /*
+ * Apply following logic for the primary element.
+ */
+
+ ui::get()->following();
+
+ trans_single new_offset = follow(old_initial_alignment.get_element(0),
+ old_final_alignment.get_element(0),
+ offset->get_element(0));
+
+ old_initial_alignment = *offset;
+
+ offset->set_element(0, new_offset);
+
+ ui::get()->set_offset(new_offset);
+ } else {
+ old_initial_alignment = *offset;
+ }
+
+ is_default.resize(old_initial_alignment.stack_depth());
+ }
+
+ void init_frame_alignment_nonprimary(transformation *offset,
+ int lod, ale_pos perturb, unsigned int index) {
+
+ assert (index > 0);
+
+ unsigned int parent_index = offset->parent_index(index);
+
+ if (perturb > 0
+ && !get_is_default(parent_index)
+ && !get_is_default(index)
+ && default_initial_alignment_type == 1) {
+
+ /*
+ * Apply file-based following logic for the
+ * given element.
+ */
+
+ ui::get()->following();
+
+ trans_single new_offset = follow(old_initial_alignment.get_element(parent_index),
+ offset->get_element(parent_index),
+ offset->get_element(index));
+
+ old_initial_alignment.set_element(index, offset->get_element(index));
+ offset->set_element(index, new_offset);
+
+ ui::get()->set_offset(new_offset);
+
+ return;
+ }
+
+ offset->get_coordinate(parent_index);
+
+
+ if (perturb > 0
+ && old_final_alignment.exists(offset->get_coordinate(parent_index))
+ && old_final_alignment.exists(offset->get_current_coordinate())
+ && default_initial_alignment_type == 1) {
+
+ /*
+ * Apply nonfile-based following logic for
+ * the given element.
+ */
+
+ ui::get()->following();
+
+ /*
+ * XXX: Although it is different, the below
+ * should be equivalent to the comment
+ * description.
+ */
+
+ trans_single a = old_final_alignment.get_element(offset->get_coordinate(parent_index));
+ trans_single b = old_final_alignment.get_element(offset->get_current_coordinate());
+ trans_single c = offset->get_element(parent_index);
+
+ trans_single new_offset = follow(a, b, c);
+
+ offset->set_element(index, new_offset);
+ ui::get()->set_offset(new_offset);
+
+ return;
+ }
+
+ /*
+ * Handle other cases.
+ */
+
+ if (get_is_default(index)) {
+ offset->set_element(index, offset->get_element(parent_index));
+ ui::get()->set_offset(offset->get_element(index));
+ }
+ }
+
+ void init_default() {
+
+ if (default_initial_alignment_type == 0) {
+
+ /*
+ * Follow the transformation of the original frame,
+ * setting new image dimensions.
+ */
+
+ // astate->default_initial_alignment = orig_t;
+ default_initial_alignment.set_current_element(orig_t.get_element(0));
+ default_initial_alignment.set_dimensions(input_frame);
+
+ } else if (default_initial_alignment_type == 1)
+
+ /*
+ * Follow previous transformation, setting new image
+ * dimensions.
+ */
+
+ default_initial_alignment.set_dimensions(input_frame);
+
+ else
+ assert(0);
+
+ old_is_default = get_is_default(0);
+ }
+ };
+
+
+ /*
* Types for scale clusters.
*/