Beginning the integration of the new diagram library

bzr revid: fva@openerp.com-20120222092320-j05aesaglaglnovz
This commit is contained in:
Frédéric van der Essen 2012-02-22 10:23:20 +01:00
parent c881a2b134
commit 26fd73e419
5 changed files with 6479 additions and 7 deletions

View File

@ -9,6 +9,8 @@
'static/lib/js/dracula_graffle.js',
'static/lib/js/dracula_graph.js',
'static/lib/js/dracula_algorithms.js',
'static/src/js/vec2js/vec2.js',
'static/src/js/graph.js',
'static/src/js/diagram.js'
],
'css' : [

View File

@ -128,17 +128,54 @@ openerp.web.DiagramView = openerp.web.View.extend({
act_to: node.id
});
},
draw_diagram: function(result) {
this.selected_node = null;
var diagram = new Graph();
console.log(result);
var res_nodes = result['nodes'];
var res_edges = result['conn'];
var id_to_node = {}
var edge_list = [];
this.active_model = result['id_model'];
var res_nodes = result['nodes'];
var res_connectors = result['conn'];
this.parent_field = result.parent_field;
var style = { "background" : 'url("grid.png")',
"edge" : "#A0A0A0",
"edge_label" : "#555",
"text" : "#333",
"outline" : "#000",
"selected" : "#0097BE",
"gray" : "#DCDCDC",
"white" : "#FFF",
"node_size_x" : 90,
"node_size_y" : 60,
"edge_spacing" : 100,
"edge_label_font_size" : 9 };
var r = new Raphael(document.getElementById("dia-canvas"), '100%','500px');
var graph = new CuteGraph(r,style);
_.each(res_nodes, function(node) {
id_to_node[node.id] = new CuteNode( graph,
node.x,
node.y,
CuteGraph.wordwrap(node.name, 17),
node.shape === 'rectangle' ? 'rect' : 'circle',
node.color === 'white' ? style.white : style.gray );
});
_.each(res_edges, function(edge) {
edge_list.push( new CuteEdge( graph,
CuteGraph.wordwrap(edge.signal, 32),
id_to_node[edge.s_id],
id_to_node[edge.d_id] ));
});
/*
//Custom logic
this.selected_node = null;
this.active_model = result['id_model'];
this.parent_field = result.parent_field;
var diagram = new Graph();
var self = this;
var renderer = function(r, n) {
var shape = (n.node.shape === 'rectangle') ? 'rect' : 'ellipse';
@ -193,7 +230,7 @@ openerp.web.DiagramView = openerp.web.View.extend({
self.add_edit_node(edge_ids[index], self.connector);
});
}
});
});*/
},
add_edit_node: function(id, model, defaults) {

View File

@ -0,0 +1,485 @@
(function(window){
// this serves as the end of an edge when creating a link
function EdgeEnd(pos_x,pos_y){
this.x = pos_x;
this.y = pos_y;
this.get_pos = function(){
return new Vec2(this.x,this.y);
}
}
// connectors are start and end point of edge creation drags.
function Connector(graph,node,pos_x,pos_y){
var visible = false;
var conn_circle = graph.r.circle(node.get_pos().x + pos_x, node.get_pos().y + pos_y,4);
conn_circle.attr({'opacity':0, 'fill':graph.style.outline,'stroke':'none'});
var self = this;
this.update_pos = function(){
conn_circle.attr({'cx':node.get_pos().x + pos_x, 'cy':node.get_pos().y + pos_y});
}
this.get_pos = function(){
return new node.get_pos().add_xy(pos_x,pos_y);
}
function hover_in(){
if(!visible){ return;}
conn_circle.animate({'r':8},300,'elastic');
if(graph.creating_edge){
graph.target_node = node;
conn_circle.animate({'fill':graph.style.white,'stroke':graph.style.outline,'stroke-width':2},100,'linear');
}
}
function hover_out(){
if(!visible){ return;}
conn_circle.animate({'r':4, 'fill':graph.style.outline, 'stroke':'none'},400,'linear');
graph.target_node = null;
}
conn_circle.hover(hover_in,hover_out);
var drag_down = function(){
if(!visible){ return; }
self.ox = conn_circle.attr("cx");
self.oy = conn_circle.attr("cy");
self.edge_start = new EdgeEnd(self.ox,self.oy);
self.edge_end = new EdgeEnd(self.ox, self.oy);
self.edge_tmp = new GraphEdge(graph,'',self.edge_start,self.edge_end,true);
graph.creating_edge = true;
}
var drag_move = function(dx,dy){
if(!visible){ return; }
self.edge_end.x = self.ox + dx;
self.edge_end.y = self.oy + dy;
self.edge_tmp.update();
}
var drag_up = function(){
if(!visible){ return; }
graph.creating_edge = false;
self.edge_tmp.remove();
if(graph.target_node && graph.target_node != node){
new GraphEdge(graph,'new edge!', node,graph.target_node);
}
}
conn_circle.drag(drag_move,drag_down,drag_up);
function show(){
if(!visible){
conn_circle.animate({'opacity':1}, 100, 'linear');
visible = true;
}
}
function hide(){
if(visible){
conn_circle.animate({'opacity':0}, 100, 'linear');
visible = false;
}
}
this.show = show;
this.hide = hide;
}
function Graph(r,style){
var nodes = []; // list of all nodes in the graph
var edges = []; // list of all edges in the graph
var graph = {}; // graph[n1.uid][n2.uid] -> list of all edges from n1 to n2
var links = {} // links[n.uid] -> list of all edges from or to n
var uid = 1; // all nodes and edges have an uid used to order their display when they are curved
this.creating_edge = false; // true if we are dragging a new edge onto a node
this.target_node = null; // this holds the target node when creating an edge and hovering a connector
this.r = r; // the raphael instance
this.style = style; // definition of the colors, spacing, fonts, ... used by the elements
//adds a node to the graph and sets its uid.
this.add_node = function (n){
nodes.push(n);
n.uid = uid++;
};
//return the list of all nodes in the graph
this.get_node_list = function(){
return nodes;
};
//adds an edge to the graph and sets its uid
this.add_edge = function (n1,n2,e){
edges.push(e);
e.uid = uid++;
if(!graph[n1.uid]) graph[n1.uid] = {};
if(!graph[n1.uid][n2.uid]) graph[n1.uid][n2.uid] = [];
if(!links[n1.uid]) links[n1.uid] = [];
if(!links[n2.uid]) links[n2.uid] = [];
graph[n1.uid][n2.uid].push(e);
links[n1.uid].push(e);
links[n2.uid].push(e);
};
//return the list of edges from n1 to n2
this.get_edge_list = function(n1,n2){
var list = [];
if(!graph[n1.uid]) return list;
if(!graph[n1.uid][n2.uid]) return list;
return graph[n1.uid][n2.uid];
};
//returns the list of all edge connected to n
this.get_linked_edge_list = function(n){
if(!links[n.uid]) return [];
return links[n.uid];
}
//return a curvature index so that all edges connecting n1,n2 have different curvatures
this.get_edge_curvature = function(n1,n2,e){
var el_12 = this.get_edge_list(n1,n2);
var c12 = el_12.length;
var el_21 = this.get_edge_list(n2,n1);
var c21 = el_21.length;
if(c12 + c21 == 1){ // only one edge
return 0;
}else{
var index = 0;
for(var i = 0; i < c12; i++){
if (el_12[i].uid < e.uid){
index++;
}
}
if(c21 == 0){ // all edges in the same direction
return index - (c12-1)/2.0;
}else{
return index + 0.5;
}
}
};
}
// creates a new Graph Node on Raphael document r, centered on [pos_x,pos_y], with label 'label',
// and of type 'circle' or 'rect', and of color 'color'
// TODO pass graph in constructor
function GraphNode(graph,pos_x, pos_y,label,type,color){
var r = graph.r;
var sy = graph.style.node_size_y;
var sx = graph.style.node_size_x;
var node_fig = null;
//var node_shadow = null;
var self = this;
var selected = false;
this.update_time = 0;
this.connectors = [];
this.uid = 0;
graph.add_node(this);
if(type == 'circle'){
node_fig = r.ellipse(pos_x,pos_y,sx/2,sy/2);
}else{
node_fig = r.rect(pos_x-sx/2,pos_y-sy/2,sx,sy);
}
node_fig.attr({'fill':color, 'stroke':graph.style.outline,'stroke-width':1,'cursor':'pointer'});
var node_label = r.text(pos_x,pos_y,label);
node_label.attr({'fill':graph.style.text,'cursor':'pointer'});
// sets the center position of the node
var set_pos = function(pos){
if(type == 'circle'){
node_fig.attr({'cx':pos.x,'cy':pos.y});
}else{
node_fig.attr({'x':pos.x-sx/2,'y':pos.y-sy/2});
}
node_label.attr({'x':pos.x,'y':pos.y});
for(var i = 0; i < self.connectors.length; i++){
self.connectors[i].update_pos();
}
var edges = graph.get_linked_edge_list(self);
for(var i = 0; i < edges.length; i++){
edges[i].update();
}
}
// returns the figure used to draw the node
var get_fig = function(){
return node_fig;
}
// returns the center coordinates
var get_pos = function(){
if(type == 'circle'){
return new Vec2(node_fig.attr('cx'), node_fig.attr('cy'));
}else{
return new Vec2(node_fig.attr('x') + sx/2, node_fig.attr('y') + sy/2);
}
}
// return the label string
var get_label = function(){
return node_label.attr("text");
}
// sets the label string
var set_label = function(text){
node_label.attr({'text':text});
}
var get_bound = function(){
if(type == 'circle'){
return new BEllipse(get_pos().x,get_pos().y,sx/2,sy/2);
}else{
return BRect.new_centered(get_pos().x,get_pos().y,sx,sy);
}
}
// selects this node and deselects all other nodes
var set_selected = function(){
if(!selected){
node_fig.attr({'stroke':graph.style.selected, 'stroke-width':2});
selected = true;
var nodes = graph.get_node_list();
for(var i = 0; i < nodes.length; i++){
if(nodes[i] != self){
nodes[i].set_not_selected();
}
}
for(var i = 0; i < self.connectors.length; i++){
self.connectors[i].show();
}
}
}
// deselect this node
var set_not_selected = function(){
if(selected){
node_fig.animate({'stroke':graph.style.outline,'stroke-width':1},100,'linear');
selected = false;
}
for(var i = 0; i < self.connectors.length; i++){
self.connectors[i].hide();
}
}
this.set_pos = set_pos;
this.get_pos = get_pos;
this.set_label = set_label;
this.get_label = get_label;
this.get_bound = get_bound;
this.get_fig = get_fig;
this.set_selected = set_selected;
this.set_not_selected = set_not_selected
//select the node and play an animation when clicked
var click_action = function(){
if(type == 'circle'){
node_fig.attr({'rx':sx/2 + 3, 'ry':sy/2+ 3});
node_fig.animate({'rx':sx/2, 'ry':sy/2},500,'elastic');
}else{
var cx = get_pos().x;
var cy = get_pos().y;
node_fig.attr({'x':cx - (sx/2) - 3, 'y':cy - (sy/2) - 3, 'ẃidth':sx+6, 'height':sy+6});
node_fig.animate({'x':cx - sx/2, 'y':cy - sy/2, 'ẃidth':sx, 'height':sy},500,'elastic');
}
set_selected();
}
node_fig.click(click_action);
node_label.click(click_action);
//move the node when dragged
var drag_down = function(){
this.opos = get_pos();
}
var drag_move = function(dx,dy){
// we disable labels when moving for performance reasons,
// updating the label position is quite expensive
// we put this here because drag_down is also called on simple clicks ... and this causes unwanted flicker
var edges = graph.get_linked_edge_list(self);
for(var i = 0; i < edges.length; i++){
edges[i].label_disable();
}
set_pos(this.opos.add_new_xy(dx,dy));
}
var drag_up = function(){
//we re-enable the
var edges = graph.get_linked_edge_list(self);
for(var i = 0; i < edges.length; i++){
edges[i].label_enable();
}
}
node_fig.drag(drag_move,drag_down,drag_up);
node_label.drag(drag_move,drag_down,drag_up);
//allow the user to create edges by dragging onto the node
function hover_in(){
if(graph.creating_edge){
graph.target_node = self;
}
}
function hover_out(){
graph.target_node = null;
}
node_fig.hover(hover_in,hover_out);
node_label.hover(hover_in,hover_out);
this.connectors.push(new Connector(graph,this,-sx/2,0));
this.connectors.push(new Connector(graph,this,sx/2,0));
this.connectors.push(new Connector(graph,this,0,-sy/2));
this.connectors.push(new Connector(graph,this,0,sy/2));
}
// creates a new edge with label 'label' from start to end. start and end must implement get_pos_*,
// if tmp is true, the edge is not added to the graph, used for drag edges. TODO pass graph in constructor,
// replace tmp == false by graph == null
function GraphEdge(graph,label,start,end,tmp){
var r = graph.r;
var update_time = -1;
var curvature = 0; // 0 = straight, != 0 curved
var s,e; // positions of the start and end point of the line between start and end
var mc; // position of the middle of the curve (bezier control point)
var elfs = graph.style.edge_label_font_size || 10 ;
var label_enabled = true;
this.uid = 0; // unique id used to order the curved edges
var self = this;
if(!tmp){
graph.add_edge(start,end,this);
}
function get_label_pos(path){
var cpos = path.getTotalLength() * 0.5;
var cindex = Math.abs(Math.floor(curvature));
var mod = ((cindex % 3)) * (elfs * 3.1) - (elfs * 0.5);
var verticality = Math.abs(end.get_pos().sub_new(start.get_pos()).normalize().dot_xy(0,1));
verticality = Math.max(verticality-0.5,0)*2;
var lpos = path.getPointAtLength(cpos + mod * verticality);
return new Vec2(lpos.x,lpos.y - elfs *(1-verticality));
}
//computes new start and end line coordinates
function update_start_end_pos(){
if(!tmp){
curvature = graph.get_edge_curvature(start,end,self);
}else{
curvature = 0;
}
s = start.get_pos();
e = end.get_pos();
mc = s.lerp_new(e,0.5); //middle of the line s->e
var se = e.sub_new(s);
se.normalize();
se.rotate_deg(-90);
se.scale(curvature * graph.style.edge_spacing);
mc.add(se);
if(start.get_bound){
var col = start.get_bound().collide_segment(s,mc);
if(col.length > 0){
s = col[0];
}
}
if(end.get_bound){
var col = end.get_bound().collide_segment(mc,e);
if(col.length > 0){
e = col[0];
}
}
}
function make_line(){
return "M" + s.x + "," + s.y + "L" + e.x + "," + e.y ;
}
function make_curve(){
return "M" + s.x + "," + s.y + "Q" + mc.x + "," + mc.y + " " + e.x + "," + e.y;
}
update_start_end_pos();
var edge = r.path(make_curve()).attr({'stroke':graph.style.edge, 'stroke-width':2, 'arrow-end':'block-wide-long', 'cursor':'pointer'}).insertBefore(graph.get_node_list()[0].get_fig());
var labelpos = get_label_pos(edge);
var edge_label = r.text(labelpos.x, labelpos.y - elfs, label).attr({'fill':graph.style.edge_label, 'cursor':'pointer', 'font-size':elfs});
//since we create an edge we need to recompute the edges that have the same start and end positions as this one
if(!tmp){
var edges_start = graph.get_linked_edge_list(start);
var edges_end = graph.get_linked_edge_list(end);
var edges = edges_start.length < edges_end.length ? edges_start : edges_end;
for(var i = 0; i < edges.length; i ++){
if(edges[i] != self){
edges[i].update();
}
}
}
function label_enable(){
if(!label_enabled){
label_enabled = true;
edge_label.animate({'opacity':1},100,'linear');
self.update();
}
}
function label_disable(){
if(label_enabled){
label_enabled = false;
edge_label.animate({'opacity':0},100,'linear');
}
}
//update the positions
function update(){
update_start_end_pos();
edge.attr({'path':make_curve()});
if(label_enabled){
var labelpos = get_label_pos(edge);
edge_label.attr({'x':labelpos.x, 'y':labelpos.y - 14});
}
}
//TODO remove from graph
function remove(){
edge.remove();
edge_label.remove();
}
this.label_enable = label_enable;
this.label_disable = label_disable;
this.update = update;
this.remove = remove;
}
// returns a new string with the same content as str, but with lines of maximum 'width' characters.
// lines are broken on words, or into words if a word is longer than 'width'
function wordwrap( str, width) {
// http://james.padolsey.com/javascript/wordwrap-for-javascript/
width = width || 32;
var cut = true;
var brk = '\n';
if (!str) { return str; }
var regex = '.{1,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)');
return str.match( RegExp(regex, 'g') ).join( brk );
}
window.CuteGraph = Graph;
window.CuteNode = GraphNode;
window.CuteEdge = GraphEdge;
window.CuteGraph.wordwrap = wordwrap;
console.log("graph module fully loaded");
})(window);
/*
window.onload = function(){
var style = { "background" :'url("grid.png")',
"edge" :"#A0A0A0",
"edge_label" :"#555",
"text" :"#333",
"outline" :"#000",
"selected" :"#0097BE",
"gray" :"#DCDCDC",
"white" :"#FFF",
"node_size_x" : 110,
"node_size_y" : 80,
"edge_spacing" : 100 };
var r = new Raphael(document.getElementById("canvas_container"),'100%','100%');
var g = new CuteGraph(r,style);
var n1 = new GraphNode(g,100,250,'Hello World','circle',colors.white);
var n2 = new GraphNode(g,400,250,'Hello Planet','rect',colors.white);
var n3 = new GraphNode(g,250,400,'Lonely Node','rect',colors.gray);
var e1 = new GraphEdge(g,'test',n1,n2);
}*/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,486 @@
(function(window){
// A Javascript 2D vector library
// conventions :
// method that returns a float value do not modify the vector
// method that implement operators are applied onto the calling vector eg:
//
// v1.add(v2); // v2 is added onto v1, v2 is modified, v1 is not
//
// but the parameters are never modified
// those methods also return the result so that the calls can be chained.
// method that implement operators are usually also available with a '_new'
// suffix. those method do not modify the calling vector and return the result
// as a new vector instead.
//
// v3 = v1.add_new(v2) // v3 is set to v1 + v2. v1 and v2 are not modified
//
// methods that take a single vector as a parameter are usually also available with
// q '_xy' suffix. Those method takes two floats representing the x,y coordinates of
// the vector parameter and allow you to avoid to needlessly create a vector object :
//
// v1.add(new Vec2(3,4));
// v1.add_xy(3,4); //equivalent to previous line
//
// angles are in radians by default but method that takes angle as parameters
// or return angle values usually have a variant with a '_deg' suffix that works in degrees
//
// The 2D vector object
function Vec2(x,y){
this.x = x;
this.y = y;
}
window.Vec2 = Vec2;
// Multiply a number expressed in radiant by rad2deg to convert it in degrees
var rad2deg = 57.29577951308232;
// Multiply a number expressed in degrees by deg2rad to convert it to radiant
var deg2rad = 0.017453292519943295;
// The numerical precision used to compare vector equality
var epsilon = 0.0000001;
// This static method creates a new vector from polar coordinates with the angle expressed
// in degrees
Vec2.new_polar_deg = function(len,angle){
var v = new Vec2(len,0);
v.rotate_deg(angle);
return v;
};
// This static method creates a new vector from polar coordinates with the angle expressed in
// radians
Vec2.new_polar = function(len,angle){
var v = new Vec2(len,0);
v.rotate(angle);
return v;
};
// returns the length or modulus or magnitude of the vector
Vec2.prototype.len = function(){
return Math.sqrt(this.x*this.x + this.y*this.y);
};
// returns the squared length of the vector, this method is much faster than len()
Vec2.prototype.len_sq = function(){
return this.x*this.x + this.y*this.y;
};
// return the distance between this vector and the vector v
Vec2.prototype.dist = function(v){
var dx = this.x - v.x;
var dy = this.y - v.y;
return Math.sqrt(dx*dx + dy*dy);
};
// return the distance between this vector and the vector of coordinates (x,y)
Vec2.prototype.dist_xy = function(x,y){
var dx = this.x - x;
var dy = this.y - y;
return Math.sqrt(dx*dx + dy*dy);
};
// return the squared distance between this vector and the vector and the vector v
Vec2.prototype.dist_sq = function(v){
var dx = this.x - v.x;
var dy = this.y - v.y;
return dx*dx + dy*dy;
};
// return the squared distance between this vector and the vector of coordinates (x,y)
Vec2.prototype.dist_sq_xy = function(x,y){
var dx = this.x - x;
var dy = this.y - y;
return dx*dx + dy*dy;
};
// return the dot product between this vector and the vector v
Vec2.prototype.dot = function(v){
return this.x*v.x + this.y*v.y;
};
// return the dot product between this vector and the vector of coordinate (x,y)
Vec2.prototype.dot_xy = function(x,y){
return this.x*x + this.y*y;
};
// return a new vector with the same coordinates as this
Vec2.prototype.clone = function(){
return new Vec2(this.x,this.y);
};
// sets the coordinate of this vector to (0,0)
Vec2.prototype.zero = function(){
this.x = 0;
this.y = 0;
return this;
};
// sets the coordinates of this to be equal to the coordinates of v
Vec2.prototype.set = function(v){
this.x = v.x;
this.y = v.y;
return this;
};
// sets the coordinate of this to be equal to the vector (x,y)
Vec2.prototype.set_xy = function(x,y){
this.x = x;
this.y = y;
return this;
};
// sets this to be the sum of this and vector v
Vec2.prototype.add = function(v){
this.x += v.x;
this.y += v.y;
return this;
};
// sets this to be the sum of this and the vector (x,y)
Vec2.prototype.add_xy = function(x,y){
this.x += x;
this.y += y;
return this;
};
// return the sum of this and vector v as a new vector
Vec2.prototype.add_new = function(v){
return new Vec2(this.x+v.x,this.y+v.y);
};
// return the sum of this and vector (x,y) as a new vector
Vec2.prototype.add_new_xy = function(x,y){
return new Vec2(this.x+x,this.y+y);
};
// sets this to be (this - v) where v is a vector and - is the vector substraction
Vec2.prototype.sub = function(v){
this.x -= v.x;
this.y -= v.y;
return this;
};
// sets this to be (this - (x,y)) where - is the vector substraction
Vec2.prototype.sub_xy = function(x,y){
this.x -= x;
this.y -= y;
return this;
};
// returns (this - v) as a new vector where v is a vector and - is the vector substraction
Vec2.prototype.sub_new = function(v){
return new Vec2(this.x-v.x,this.y-v.y);
};
// returns (this - (x,y)) as a new vector where - is vector substraction
Vec2.prototype.sub_new_xy = function(x,y){
return new Vec2(this.x-x,this.y-y);
};
// sets this to be (this * v) where v is a vector and * is the by component product and
Vec2.prototype.mult = function(v){
this.x *= v.x;
this.y *= v.y;
return this;
};
// sets this to be (this * (x,y) ) where v is a vector and * is the by component product
Vec2.prototype.mult_xy = function(x,y){
this.x *= x;
this.y *= y;
return this;
};
// return (this * v) as a new vector where v is a vector and * is the by component product
Vec2.prototype.mult_new = function(v){
return new Vec2(this.x*v.x,this.y*v.y);
};
// return (this * (x,y)) as a new vector where * is the by component product
Vec2.prototype.mult_new_xy = function(x,y){
return new Vec2(this.x*x,this.y*y);
};
// multiply all components of this vector by float f
Vec2.prototype.scale = function(f){
this.x *= f;
this.y *= f;
return this;
};
// return this scaled by float f as a new fector
Vec2.prototype.scale_new = function(f){
return new Vec2(this.x*f, this.y*f);
};
//sets this vector to be the negative of itself
Vec2.prototype.neg = function(f){
this.x = -this.x;
this.y = -this.y;
return this;
};
// return the negation of this vector
Vec2.prototype.neg_new = function(f){
return new Vec2(-this.x,-this.y);
};
// normalizes this vector
Vec2.prototype.normalize = function(){
var len = this.len();
if(len == 0){
this.x = 1;
}else if(len != 1){
this.scale(1.0/len);
}
return this;
};
// return this vector normalized as a new vector
Vec2.prototype.normalize_new = function(){
var len = this.len();
if(len == 0){
return new Vec2(0,1);
}else if(len != 1){
return this.scale_new(1.0/len);
}
return new Vec2(this.x,this.y);
};
// sets the length of this vector to float l without changing its angle. (negative values of l will invert direction)
Vec2.prototype.set_len = function(l){
this.normalize();
this.scale(l);
return this;
};
// return a new vector with the same direction as this vector of length float l. (negative values of l will invert direction)
Vec2.prototype.set_len_new = function(l){
var v = this.normalize_new();
v.scale(l);
return v;
};
// projects this vector onto the vector v
Vec2.prototype.project = function(v){
var d = this.dot(v);
this.set(v);
this.normalize();
this.scale(d);
return this;
};
// return the projection of this onto the vector v as a new vector
Vec2.prototype.project_new = function(v){
var vc = this.clone();
vc.project(v);
return vc;
};
// return a string representation of this vector
Vec2.prototype.toString = function(){
var str = "";
str += "[";
str += this.x;
str += ",";
str += this.y;
str += "]";
return str;
};
// rotate this vector counterclockwise by rad radians.
Vec2.prototype.rotate = function(rad){
var c = Math.cos(rad);
var s = Math.sin(rad);
var px = this.x * c - this.y *s;
var py = this.x * s + this.y *c;
this.x = px;
this.y = py;
return this;
};
//rotate this vector counterclockwise by deg degrees
Vec2.prototype.rotate_deg = function(deg){
return this.rotate(deg * deg2rad);
};
//return this vector counterclockwise rotated by rad radians as a new vector
Vec2.prototype.rotate_new = function(rad){
var v = this.clone();
return v.rotate(rad);
};
//return this vector counterclockwise rotated by deg degrees as a new vector
Vec2.prototype.rotate_deg_new = function(deg){
var v = this.clone();
return v.rotate_deg(deg);
};
//linearly interpolate this vector towards the vector v by float factor alpha.
// alpha == 0 : does nothing
// alpha == 1 : sets this to v
Vec2.prototype.lerp = function(v,alpha){
var inv_alpha = 1 - alpha;
this.x = this.x * inv_alpha + v.x * alpha;
this.y = this.y * inv_alpha + v.y * alpha;
};
// returns this vector lerped to v by alpha as a new vector
Vec2.prototype.lerp_new = function(v,alpha){
var inv_alpha = 1 - alpha;
var v2 = new Vec2( this.x * inv_alpha + v.x * alpha,
this.y * inv_alpha + v.y * alpha );
return v2;
};
// returns the angle between this vector and the vector (1,0) in radians
Vec2.prototype.angle = function(){
return Math.atan2(this.y,this.x);
}
// returns the angle between this vector and the vector (1,0) in degrees
Vec2.prototype.angle_deg = function(){
return Math.atan2(this.y,this.x) * rad2deg;
};
// returns true if this vector is equal to the vector v, with a tolerance defined by the epsilon module constant
Vec2.prototype.equals = function(v){
if(Math.abs(this.x-v.x) > epsilon){
return false;
}else if(Math.abs(this.y-v.y) > epsilon){
return false;
}
return true;
};
// returns true if this vector is equal to the vector (x,y) with a tolerance defined by the epsilon module constant
Vec2.prototype.equals_xy = function(x,y){
if(Math.abs(this.x-x) > epsilon){
return false;
}else if(Math.abs(this.y-y) > epsilon){
return false;
}
return true;
};
console.log("vec2 module fully loaded");
})(window);
(function(window){
// A Bounding Shapes Library
// A Bounding Ellipse
// cx,cy : center of the ellipse
// rx,ry : radius of the ellipse
function BEllipse(cx,cy,rx,ry){
this.type = 'ellipse';
this.x = cx-rx; // minimum x coordinate contained in the ellipse
this.y = cy-ry; // minimum y coordinate contained in the ellipse
this.sx = 2*rx; // width of the ellipse on the x axis
this.sy = 2*ry; // width of the ellipse on the y axis
this.hx = rx; // half of the ellipse width on the x axis
this.hy = ry; // half of the ellipse width on the y axis
this.cx = cx; // x coordinate of the ellipse center
this.cy = cy; // y coordinqte of the ellipse center
this.mx = cx + rx; // maximum x coordinate contained in the ellipse
this.my = cy + ry; // maximum x coordinate contained in the ellipse
}
window.BEllipse = BEllipse;
// returns an unordered list of vector defining the positions of the intersections between the ellipse's
// boundary and a line segment defined by the start and end vectors a,b
BEllipse.prototype.collide_segment = function(a,b){
// http://paulbourke.net/geometry/sphereline/
collisions = []
if(a.equals(b)){ //we do not compute the intersection in this case. TODO ?
return collisions;
}
// make all computations in a space where the ellipse is a circle
// centered on zero
var c = new Vec2(this.cx,this.cy);
a = a.sub_new(c).mult_xy(1/this.hx,1/this.hy);
b = b.sub_new(c).mult_xy(1/this.hx,1/this.hy);
if(a.len_sq() < 1 && b.len_sq() < 1){ //both points inside the ellipse
return collisions;
}
// compute the roots of the intersection
var ab = b.sub_new(a);
var A = (ab.x*ab.x + ab.y*ab.y);
var B = 2*( ab.x*a.x + ab.y*a.y);
var C = a.x*a.x + a.y*a.y - 1;
var u = B * B - 4*A*C;
if(u < 0){
return collisions;
}
u = Math.sqrt(u);
var u1 = (-B + u) / (2*A);
var u2 = (-B - u) / (2*A);
if(u1 >= 0 && u1 <= 1){
var pos = a.clone();
pos.add(ab.scale_new(u1));
collisions.push(pos);
}
if(u1 != u2 && u2 >= 0 && u2 <= 1){
var pos = a.clone();
pos.add(ab.scale_new(u2));
collisions.push(pos);
}
for(var i = 0; i < collisions.length; i++){
collisions[i].mult_xy(this.hx,this.hy);
collisions[i].add_xy(this.cx,this.cy);
}
return collisions;
};
// A bounding rectangle
// x,y the minimum coordinate contained in the rectangle
// sx,sy the size of the rectangle along the x,y axis
function BRect(x,y,sx,sy){
this.type = 'rect';
this.x = x; // minimum x coordinate contained in the rectangle
this.y = y; // minimum y coordinate contained in the rectangle
this.sx = sx; // width of the rectangle on the x axis
this.sy = sy; // width of the rectangle on the y axis
this.hx = sx/2; // half of the rectangle width on the x axis
this.hy = sy/2; // half of the rectangle width on the y axis
this.cx = x + this.hx; // x coordinate of the rectangle center
this.cy = y + this.hy; // y coordinate of the rectangle center
this.mx = x + sx; // maximum x coordinate contained in the rectangle
this.my = y + sy; // maximum x coordinate contained in the rectangle
}
window.BRect = BRect;
// Static method creating a new bounding rectangle of size (sx,sy) centered on (cx,cy)
BRect.new_centered = function(cx,cy,sx,sy){
return new BRect(cx-sx/2,cy-sy/2,sx,sy);
};
//intersect line a,b with line c,d, returns null if no intersection
function line_intersect(a,b,c,d){
// http://paulbourke.net/geometry/lineline2d/
var f = ((d.y - c.y)*(b.x - a.x) - (d.x - c.x)*(b.y - a.y));
if(f == 0){
return null;
}
f = 1 / f;
var fab = ((d.x - c.x)*(a.y - c.y) - (d.y - c.y)*(a.x - c.x)) * f ;
if(fab < 0 || fab > 1){
return null;
}
var fcd = ((b.x - a.x)*(a.y - c.y) - (b.y - a.y)*(a.x - c.x)) * f ;
if(fcd < 0 || fcd > 1){
return null;
}
return new Vec2(a.x + fab * (b.x-a.x), a.y + fab * (b.y - a.y) );
}
// returns an unordered list of vector defining the positions of the intersections between the ellipse's
// boundary and a line segment defined by the start and end vectors a,b
BRect.prototype.collide_segment = function(a,b){
var collisions = []
var corners = [ new Vec2(this.x,this.y), new Vec2(this.x,this.my),
new Vec2(this.mx,this.my), new Vec2(this.mx,this.y) ];
var pos = line_intersect(a,b,corners[0],corners[1]);
if(pos) collisions.push(pos);
pos = line_intersect(a,b,corners[1],corners[2]);
if(pos) collisions.push(pos);
pos = line_intersect(a,b,corners[2],corners[3]);
if(pos) collisions.push(pos);
pos = line_intersect(a,b,corners[3],corners[0]);
if(pos) collisions.push(pos);
return collisions;
};
// returns true if the rectangle contains the position defined by the vector 'vec'
BRect.prototype.contains_vec = function(vec){
return ( vec.x >= this.x && vec.x <= this.mx &&
vec.y >= this.y && vec.y <= this.my );
};
// returns true if the rectangle contains the position (x,y)
BRect.prototype.contains_xy = function(x,y){
return ( x >= this.x && x <= this.mx &&
y >= this.y && y <= this.my );
};
// returns true if the ellipse contains the position defined by the vector 'vec'
BEllipse.prototype.contains_vec = function(v){
v = v.mult_new_xy(this.hx,this.hy);
return v.len_sq() <= 1;
};
// returns true if the ellipse contains the position (x,y)
BEllipse.prototype.contains_xy = function(x,y){
return this.contains(new Vec2(x,y));
};
console.log("bounds module fully loaded");
})(window);