Re: Coordinate from device to user space without a context

Donn <[email protected]>
Newsgroups gmane.comp.lib.cairo
Message-ID <[email protected]>
On 06/11/2013 21:25, Bryce W. Harrington wrote:
> But let's say your two coordinates aren't rotated or skewed, they're
> just straight multiples with an offset.  The formula simplifies to
> this:
>
> x_new = xx * x + x0; y_new = yy * y + y0;
>
(slimmer file attached.)

I gave this one a go in my move(), where mx,my are mouse coords:

public void move(double mx, double my) {
   double xnew = mymatrix.xx * mx + mymatrix.x0;
   double ynew = mymatrix.yy * my + mymatrix.y0;
   stdout.printf("mx my: %G|%G maps to xnew ynew: %G|%G\n",mx,my, xnew,
ynew);
   x=xnew; y=ynew;

In my set_matrix method, after setting a new trans/scale/rot, I do:
[line 105] cr.identity_matrix();
[line 106] mymatrix = cr.get_matrix();

The printout shows if:
I remark line 105 out (no identity call) the numbers grow rapidly, 
showing no relation to user space or mouse space.
  If I put 106 back in, the numbers are out by about half my window 
width. They also do not adhere to the initial matrix that was set in my 
initmatrix call (line 255) which sets up a zero,zero in the middle.

So, I'm close. But ...

Thanks for your math insight - I'm monkey-see-monkey-type most of the 
time.I have read about matrix math (linear?) and even had my bro in law 
'splain it, but I'm like a stone in these waters!

> Once you understand the above, then you'll understand exactly what
> the cairo_matrix_transform_point() and cairo_matrix_invert()
> functions do.
Well.. I'll do my best, but that's a tall order. ;)


Thanks!
\d

-- 
cairo mailing list
[email protected]
http://lists.cairographics.org/mailman/listinfo/cairo
matrix_maillist_debug.vala (text/x-vala, 9.9 KB)
//valac --pkg gee-1.0 --pkg gtk+-3.0 -X -lm -X -w "$1" -o test
using Gtk;
using Cairo;

public void grid(Cairo.Context cr, bool white=false) {
    cr.move_to(0,0);
    double m=0f;
    double w=0f;
    double x=0f;
    //for (x=10f; x <= 210f; x+=10f) {
    for (x=0f; x <= 500f; x+=10f) {
      m++;
      w = 10 + ( Math.fmod(m,2) * 10); //fluctuating height
      cr.rectangle (x,-w/2,1,w); //right arm
      cr.rectangle (-x,-w/2,1,w); //left arm
      cr.rectangle (-w/2,-x,w,1); //down arm
      cr.rectangle (-w/2,x,w,1); //up arm
    }
    if (white) {
      cr.set_source_rgb(1,1,1);
    } else {
      cr.set_source_rgba(0,0,0,0.2);
    }
    cr.fill();
}

public double c(int col) { return (double)col / 256.0; }

public class Dot: Object {
  public double x = 0;
  public double y = 0;
  public double sx = 1;
  public double sy = 1;
  public double rot = 0;
  public double radius = 8;
  public double r=0;
  public double g=0;
  public double b=0;
  public string name;
  public double device_x=0; 
  public double device_y=0; 
  
  //Device-space bounding box coords
  public double dbbx1;
  public double dbbx2;
  public double dbby1;
  public double dbby2;

  public bool is_hit = false;

  private Cairo.Matrix? mymatrix;


  public Dot(string n) {
    name = n;
  }
  
  public void move(double mx, double my) {

    //double xnew = mymatrix.xx * mx + mymatrix.xy * my + mymatrix.x0;
    //double ynew = mymatrix.yx * my + mymatrix.yy * my + mymatrix.y0;

    double xnew = mymatrix.xx * mx + mymatrix.x0;
    double ynew = mymatrix.yy * my + mymatrix.y0;

    stdout.printf("mx my: %G|%G maps to xnew ynew: %G|%G\n",mx,my, xnew, ynew);
   
    x=xnew; y=ynew;

    //
    /*
    double px = mx; double py=my;
    stdout.printf("BEFORE mx my: %G | %G\n", px, py);
    mymatrix.transform_point(ref px, ref py);
    stdout.printf("AFTER mx my: %G | %G\n", px, py);
    
    //Set my obj vars for a later draw()
    x=px; y=py;
    */
  }

  public bool bbox_hit( double mx, double my) {
    is_hit = (
      (mx > dbbx1) && 
      (mx < dbbx2) && 
      (my > dbby1) && 
      (my < dbby2)
    );
    return is_hit;
  }

  public void set_matrix(Cairo.Context cr) {

    //Super NB that we store the u2d coords BEFORE the matrix stuff
    device_x = x; 
    device_y = y; //prep in-vars
    cr.user_to_device(ref device_x, ref device_y); //stores into refs

    //NOTE - goes after the above code.
    cr.translate(x,y);
    cr.scale(sx,sy);
    cr.rotate((Math.PI/180.0)*rot);

        cr.save();
          //cr.identity_matrix();
          mymatrix = cr.get_matrix();
        cr.restore();

  }

  private void _record_bbox(Cairo.Context cr) {
    cr.save();
    cr.identity_matrix();
    cr.path_extents (out dbbx1, out dbby1, out dbbx2, out dbby2);
    cr.restore();
  }

  public void draw_geom(Cairo.Context cr) {
      cr.arc (20 ,20, radius, 0, 2 * Math.PI);
      if (is_hit) {
        cr.set_source_rgb (1, 0, 0);
      } else {
        cr.set_source_rgb (r, g, b);
      }
      cr.rectangle(-radius+20,-radius+20,radius,radius);

      //Get myy bbox:
      _record_bbox(cr);
  }

  public void draw(Cairo.Context cr) {

      //random grid
      _grid(cr);

      draw_geom(cr);
      cr.fill ();

      //random label.
      cr.move_to(radius+5,0);
      cr.save(); //isolate the text from the current rotation and scale?
        cr.identity_matrix();
        cr.select_font_face ("Adventure", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD);
        cr.show_text(@"d$(this.name) u($x,$y) d($device_x,$device_y)");
      cr.restore();

  }

  public void draw_dbb(Cairo.Context cr) {
    cr.save();
    cr.identity_matrix();

    cr.rectangle(dbbx1,dbby1,(dbbx2-dbbx1),(dbby2-dbby1));
    cr.set_line_width(2);
    cr.set_source_rgb(1,1,1);
    cr.stroke();
    cr.restore();
  }


  private void _grid(Cairo.Context cr) {
    grid(cr);
  }

}

/*-------------------------------------------------*/
public class SceneGraphWidget : Gtk.DrawingArea {

  public Dot d1;
  public Dot d2;
  public Dot d3;
  public List<Dot> dlist;


  public const uint flag_button_press = 1<<0;
  public const uint flag_button_release = 1<<1;
  public const uint flag_motion_notify = 1<<2;
  public const uint flag_in_drag = 1<<3;
  
  public const uint pattern_click = flag_button_press | flag_button_release;
  public const uint pattern_drag_start = flag_button_press | flag_motion_notify;
  public const uint pattern_in_drag = pattern_drag_start | flag_in_drag;
  public const uint pattern_drop = flag_button_press | flag_in_drag | flag_button_release;


  private uint bitstate;

  private Dot? dotBeingDragged;
  private Dot? overDot;

  construct {


    d1 = new Dot("1");
    d2 = new Dot("2");
    d3 = new Dot("3");

    d1.r=0.02; d1.g=0.5; d1.b=0.02; d1.x=55; d1.y=55;

    d2.r=0; d2.g=0; d2.b=0; d2.x=17; d2.y=-112; 
    d2.rot=0; 
    d2.radius = 40; 
    d2.sx=0.5; d2.sy=0.5;

    d3.r=0; d3.g=0.7; d3.b=0.7; d3.radius=20;
    d3.x=-50; d3.y=0;
    
    dlist = new List<Dot>();
    dlist.append(d1);
    dlist.append(d2);
    dlist.append(d3);

    this.set_double_buffered (true);

    this.set_size_request (500,500);
    this.can_focus = true; //In order to receive keybd signals

    this.add_events (
    Gdk.EventMask.BUTTON_PRESS_MASK |
    Gdk.EventMask.BUTTON_RELEASE_MASK |
    Gdk.EventMask.POINTER_MOTION_MASK
    );

    event.connect(handle_events); //!!! Check it out!
  }

  private void initmatrix ( Cairo.Context cr ) {
	/*
     Scale to window's size and shift the origin of the page to the center.
		  -y | -y
		  -x | +x
		 ----0------
		  -x | +x
		  +y | +y
		*/

    int w,h;
    double pw, ph;

    h = get_allocated_height ();
    w = get_allocated_width ();

    pw = (float) w / 500f; 
    ph = (float) h / 500f;

		var matrix = Cairo.Matrix (1, 0, 0, 1, w/2,h/2 ); //translate to middle.
		
    matrix.scale (pw, ph); //Scale to the window's size.
		cr.transform(matrix);
  }

  /* Override of the draw() callback, GTK3 requires this. */
  public override bool draw ( Cairo.Context cr ) {
    initmatrix(cr);
     
     //Just draw some guides for sanity
    cr.set_source_rgb (1,1,1);
    cr.paint ();

    cr.rectangle(0,0,-250,-250);
    cr.set_source_rgb(c(255), c(204), c(102));
    cr.fill();

    cr.rectangle(0,0,250,-250);
    cr.set_source_rgb(c(153), c(204), c(255));
    cr.fill();
    
    cr.rectangle(0,0,250,250);
    cr.set_source_rgb(c(204), c(244), c(255));
    cr.fill();
    
    cr.rectangle(0,0,-250,250);
    cr.set_source_rgb(c(51), c(255), c(153));
    cr.fill();
    

    grid(cr, true);


      //Begin the pain
      cr.save();

      cr.save();
        d1.set_matrix(cr);
        d1.draw(cr);
        d1.draw_dbb(cr);


            cr.save();
              d2.set_matrix(cr);
              d2.draw(cr);
              d2.draw_dbb(cr);

            cr.restore();
      
      cr.restore();  

      d3.set_matrix(cr);
      d3.draw(cr);
      d3.draw_dbb(cr);


  
    cr.restore(); 
    //back in device space

    return true;
  }

  private bool handle_events(Gdk.Event event) {
    //stdout.printf("%s\n", event.type.to_string());

    if (event.type == Gdk.EventType.MOTION_NOTIFY) {

      bitstate |= flag_motion_notify;

          double mx = event.motion.x;
          double my = event.motion.y;
          //stdout.printf("Mouse at:(%G | %G)\n",mx,my);

          var some_hit = false;

          foreach(Dot d in dlist) {
            if ( (bitstate == pattern_in_drag) && (dotBeingDragged == d) ) continue;
              if (d.bbox_hit(mx,my)) {
                some_hit = true;
                overDot = d;
              }
            }

          d2.rot += 1;d2.sx+=0.001;d2.sy+=0.001; //just animate d2 a bit
          if (!some_hit) overDot = null;
    }


    if (event.type == Gdk.EventType.BUTTON_PRESS) {
      //Set our flag:
      bitstate = bitstate | flag_button_press;


      Gdk.ModifierType modifiers;
      modifiers = Gtk.accelerator_get_default_mod_mask ();

      if ( (event.button.state & modifiers) == Gdk.ModifierType.CONTROL_MASK ) {
        stdout.printf (" Control Key\n");
        }
    }

    if (event.type == Gdk.EventType.BUTTON_RELEASE) {
      //Set our flag:
      bitstate = bitstate | flag_button_release;
    }

    //Use our flags!

    if (bitstate == pattern_click) {
      stdout.printf("CLICK!\n");
    }

    if (bitstate == pattern_drag_start) {
      //stdout.printf("DRAG START\n");
      bitstate |= flag_in_drag; //signal we are dragging flag
      if (overDot != null ) dotBeingDragged = overDot;
      if (dotBeingDragged != null) stdout.printf("DRAGSTART %s\n", dotBeingDragged.name);
    }
    if (bitstate == pattern_in_drag) {
      //stdout.printf("IN DRAG\n");
      if (dotBeingDragged != null) { 
        stdout.printf("DRAGGING %s\n", dotBeingDragged.name);
        dotBeingDragged.move(event.motion.x, event.motion.y);
      //d2.x = mx; d2.y = my;
      }
    }

    if (bitstate == pattern_drop) {
      //stdout.printf("DROP!!!!!!!!!!!\n");
      if (dotBeingDragged != null) {
        stdout.printf("DROPPED %s\n", dotBeingDragged.name);
        dotBeingDragged = null;
      }
    }


    //switch off flags after we've used them.
    if (event.type == Gdk.EventType.MOTION_NOTIFY) {
      bitstate &= ~flag_motion_notify;
    }

    if (event.type == Gdk.EventType.BUTTON_PRESS) {
      //self.ui_state = self.ui_state & ~Mouserizer.__FLAG_RELEASING # clear bit
      bitstate = bitstate & ~flag_button_release; //clear release flag
    }
    if (event.type == Gdk.EventType.BUTTON_RELEASE) {
      bitstate &= ~flag_button_press; //clear press state
      bitstate &= ~flag_in_drag; //no drag either.
    }


    queue_draw ();

    return true; //True - stops the event here. False propogates it up.
  }


}

int main(string[] args) {

  Gtk.init ( ref args ); //ok = Gtk.init ... 

  var sgw = new SceneGraphWidget ();

  var w = new Gtk.Window();


  w.add( sgw );
  w.show_all ();
  w.destroy.connect (Gtk.main_quit);
  w.key_press_event.connect( (e) => { 
    if (e.keyval == Gdk.Key.Escape) {
      Gtk.main_quit(); 
      return true; 
    }
    return false;
  } );

  Gtk.main ();

  return 0;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.