#!/usr/bin/ruby ############################################################################### # class Node class Node attr_reader :x, :y, :edges def initialize(x,y) @x=x @y=y @nb_edges=0 @edges=Array.new #list of edges connected to this node. In principle #there would just need to be a set, since we don't #need indexes (as long as we can iterate) end def to_svg return "\n" end def to_s return "------------\nNode: (#{@x},#{@y}). Edges:\n" + @edges.to_s + "-------------" end def add_edge(e) @nb_edges=@nb_edges+1 @edges[@nb_edges-1]=e end end ############################################################################### # class Edge class Edge attr_reader :node1, :node2, :sign, :angle1, :angle2 def initialize(n1,n2,sign) @node1=n1 @node2=n2 @sign=sign @angle1=Math.atan2(@node2.y-@node1.y, @node2.x-@node1.x) @angle2=Math.atan2(@node1.y-@node2.y, @node1.x-@node2.x) end def to_svg return " " end def to_s return "Edge: (#{@node1.x},#{@node1.y}), (#{@node2.x},#{@node2.y}). Angles: #{@angle1}, #{@angle2}\n" end def angle(n) #returns the angle of the edge at Node n if n==@node1 then @angle1 elsif n==@node2 then @angle2 else "error!" end end end ############################################################################### # class Graph class Graph attr_reader :nb_nodes, :nb_edges, :nodes, :edges def initialize() @nb_nodes=0 @nb_edges=0 @nodes=Array.new @edges=Array.new end def add_node(n) @nodes[@nb_nodes]=n @nb_nodes += 1 end def add_edge(e) @edges[@nb_edges]=e @nb_edges+=1 #for each node n of e, add to n a pointer to e e.node1.add_edge(e) e.node2.add_edge(e) end def next_edge_around(n, e, direction) #return the next edge after e around node n clockwise if direction=="clockwise" then minmax=10 else minmax=-10 end # as long as it's greater than Pi (clockwise) or less than -Pi # (anticlockwise), then we're ok next_edge = e # we must iterate through all the edges stemming from n n.edges.each do |edge| if edge != e then angle=edge.angle(n) if (direction=="clockwise" and angle < minmax) or (direction=="anticlockwise" and angle > minmax) then next_edge=edge minmax=angle end end end return next_edge end def to_svg() s=""; @nodes.each { |n| s+=n.to_svg } @edges.each { |e| s+=e.to_svg } return s end def min_x() min=1e200 @nodes.each { |n| if n.x < min then min=n.x end } return min end def max_x() max=-1e200 @nodes.each { |n| if n.x > max then max=n.x end } return max end def min_y() min=1e200 @nodes.each { |n| if n.x < min then min=n.x end } return min end def max_y() max=-1e200 @nodes.each { |n| if n.x > max then max=n.x end } return max end end ############################################################################### # class CelticPattern class CelticPattern BoundingBoxMargin=3 def initialize(graph) @graph = graph end def draw_spline(node, edge1, edge2) # P1 (x1,y1) is the middle point of edge1 x1=(edge1.node1.x+edge1.node2.x)/2.0 y1=(edge1.node1.y+edge1.node2.y)/2.0 # P2 (x2,y2) is the middle point of edge1 x4=(edge2.node1.x+edge2.node2.x)/2.0 y4=(edge2.node1.y+edge2.node2.y)/2.0 alpha=beta=0.9 # I1 sticks out from edge1 to the right (coming from node) # i.e. P1I1 = P1N /\ -alphaZ i1x = -alpha*(node.y-y1)+x1 i1y = alpha*(node.x-x1)+y1 # I2 sticks out from edge2 to the left (coming from node) # i.e. P4I2 = P1N /\ alphaZ i2x = alpha*(node.y-y4)+x4 i2y = -alpha*(node.x-x4)+y4 # P2 is drawn from I1 coming back toward N opposite NP1 and parallel to it # i.e. I1P2 = I1P1 /\ -betaZ x2 = -beta*(y1-i1y) + i1x y2 = beta*(x1-i1x) + i1y # P3 is drawn from I2 coming back toward N opposite NP4 and parallel to it # i.e. I2P3 = I2P4 /\ betaZ x3 = beta*(y4-i2y) + i2x y3 = -beta*(x4-i2x) + i2y return "\n" end def draw_spline_direction(node, edge1, edge2, direction) # P1 (x1,y1) is the middle point of edge1 x1=(edge1.node1.x+edge1.node2.x)/2.0 y1=(edge1.node1.y+edge1.node2.y)/2.0 # P2 (x2,y2) is the middle point of edge1 x4=(edge2.node1.x+edge2.node2.x)/2.0 y4=(edge2.node1.y+edge2.node2.y)/2.0 if direction == "clockwise" and (x1-node.x)*(y4-node.y)-(y1-node.y)*(x4-node.x) < 0 then draw_spline(node, edge2, edge1) else draw_spline(node, edge1, edge2) end then def to_svg_by_node s= "\n" @graph.nodes.each { |n| if n.edges current_edge=first_edge=n.edges[0] next_edge=@graph.next_edge_around(n,first_edge,"anticlockwise") while next_edge != first_edge do s+= draw_spline(n,current_edge,next_edge); current_edge=next_edge next_edge=@graph.next_edge_around(n,next_edge,"anticlockwise") end s+= draw_spline(n,current_edge,next_edge); end } return s+="\n" end # draw_spline(n,e1,e2,direction) def to_svg_by_curve first_node = node = @graph.nodes[0] first_edge = edge = @graph.nodes[0].edges[0] first_side = side = "left" turn="" begin end until node==first_side and edge==first_edge and side==first_side end def to_svg s="\n" s += @graph.to_svg s+=to_svg_by_node s+="\n" return s end end g = Graph.new n1 = Node.new(0.0,0.0) n2 = Node.new(10.0,0.0) n3 = Node.new(0.0,10.0) g.add_node(n1) g.add_node(n2) g.add_node(n3) e1 = Edge.new(n1,n2,"+") e2 = Edge.new(n2,n3,"+") e3 = Edge.new(n1,n3,"+") g.add_edge(e1) g.add_edge(e2) g.add_edge(e3) c = CelticPattern.new(g) puts c.to_svg