Closed jamievicary closed 5 years ago
For reference, here is the code.
function SVG_draw_crossing(diagram, data, vertex, svg, defs) {
let wires = diagram.getWireDepths(vertex.level, vertex.singular_height);
// Sort the wire segments by depth
let segments = [];
let s = wires.source_depths;
let t = wires.target_depths;
for (let i = 0; i < s.length; i++) {
segments.push({ source: true, index: i, depth: s[i], edge: vertex.source_edges[i] });
}
for (let i = 0; i < t.length; i++) {
segments.push({ source: false, index: i, depth: t[i], edge: vertex.target_edges[i] });
}
segments.sort((a, b) => a.depth - b.depth );
// Group them by common depth
let groups = [];
let group = [];
let left = Number.MAX_VALUE;
let right = -Number.MAX_VALUE;
for (let i = 0; i < segments.length; i++) {
let segment = segments[i];
left = Math.min(left, segment.edge.x);
right = Math.max(right, segment.edge.x);
if (group.length == 0 || group[0].depth == segment.depth) {
group.push(segment);
} else {
groups.push(group);
group = [segment];
}
}
groups.push(group);
// Draw the groups nearest-first
if (data.mask_index == undefined) data.mask_index = 0;
var transparent_str = SVG_move_to({ x: left - 2.5, y: vertex.y - 0.5 }) + SVG_line_to({ x: left - 2.5, y: vertex.y + 0.5 })+ SVG_line_to({ x: right + 2.5, y: vertex.y + 0.5 })+ SVG_line_to({ x: right + 2.5, y: vertex.y - 0.5 })+ SVG_line_to({ x: left - 2.5, y: vertex.y - 0.5 });
let mask_id = null;
let mask_paths = [];
for (let i = 0; i < groups.length; i++) {
let group = groups[i];
/* Decide the common tangent vector for elements of this group.
If any element is vertical, use a vertical tangent.
If all elements are on left or right, use a vertical tangent.
Otherwise, find the mean incidence angle.
*/
let theta = null;
let theta_sum = 0;
let unique_sign = 0;
for (let i=0; i<group.length; i++) {
let s = group[i];
if (s.edge.x == vertex.x) {
theta = 0;
break;
}
let dx = s.edge.x - vertex.x;
let sign = dx == 0 ? 0 : dx < 0 ? -1 : +1;
if (unique_sign != null) {
if (unique_sign == 0 && sign != 0) unique_sign = sign;
if (unique_sign != 0 && sign != 0 && unique_sign != sign) unique_sign = null;
}
theta_sum += (s.source ? -1 : +1) * Math.atan(dx / 0.5);
}
if (theta == null) {
if (unique_sign != null) {
theta = 0;
} else {
theta = theta_sum / group.length;
}
}
//g.appendChild(mask[0]);
let c2l = 0.5;
let factor = 0.35;
let c2x_target = vertex.x + c2l * Math.sin(theta);
let c2x_source = vertex.x - c2l * Math.sin(theta);
let c2y_target = vertex.y + c2l * factor * Math.cos(theta);
let c2y_source = vertex.y - c2l * factor * Math.cos(theta);
let c1l = 0.4;
let mask_mult = 2.5;
let mask_url = mask_id ? "url(#" + mask_id + ")" : null;
for (let j = 0; j < group.length; j++) {
let segment = group[j];
// Draw this segment
let edge = segment.edge;
let start_y = segment.source ? edge.finish_height - 0.5 : edge.start_height + 0.5;
var segment_str = SVG_move_to({ x: edge.x, y: start_y })
+ SVG_bezier_to({ c1x: edge.x, c1y: start_y + (segment.source ? c1l : -c1l),
//c2x: edge.x, c2y: vertex.y,
c2x: segment.source ? c2x_source : c2x_target, c2y: segment.source ? c2y_source : c2y_target,
x: vertex.x, y: vertex.y });
// Draw the segment and patching circles
svg.appendChild(SVG_create_path({ string: segment_str, element_index: null, element_index_2: null, stroke: edge.colour, stroke_width: line_width, element_type: 'homotopy_edge' , mask: mask_url}));
svg.appendChild(SVG_create_circle({x: vertex.x, y: vertex.y, fill: edge.colour, radius: line_width / 2, class_name: 'dummy'}));
// Add appropriate paths to the mask to obscure later segments for a 3d effect
mask_paths.push($(SVG_create_path({ string: segment_str, stroke_width: line_width * mask_mult, stroke: 'black', element_type: 'mask_top' })));
mask_paths.push($(SVG_create_circle({x: vertex.x, y: vertex.y, fill: edge.colour, radius: line_width * mask_mult / 2, class_name: 'dummy'})));
}
// On the last pass we don't need to make the mask
if (i == groups.length - 1) break;
mask_id = 'mask' + (data.mask_index++);
let mask_obj = $('<mask>', { id: mask_id, maskUnits: 'userSpaceOnUse' });
mask_obj.append(SVG_create_path({ string: transparent_str, fill: 'white', element_type: 'mask_transparent' }));
for (let i=0; i<mask_paths.length; i++) {
mask_obj.append(mask_paths[i].clone());
}
defs.append(mask_obj);
}
// Attech the transparent circle over the homotopy to accept the mouse hover
vertex.fill = vertex.colour; //gProject.getColour(vertex.type, vertex.dimension);
vertex.radius = line_width;
vertex.element_type = 'vertex';
vertex.element_index = vertex.index;
vertex.fill_opacity = 0;
svg.appendChild(SVG_create_circle(vertex));
}
A lot of thought went into aspects of this, e.g. the trig that computes the control points leading to nice smooth curves, and I'd like to preserve this as far as possible.
And for reference here's some example SVG that it generates:
<svg preserveAspectRatio="xMidYMid meet" width="1085" height="644" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" style="overflow: hidden; ">
<defs>
<mask id="mask0" maskUnits="userSpaceOnUse">
<path d="M -2.5 0 L -2.5 1 L 4.5 1 L 4.5 0 L -2.5 0 " stroke-width="0.1" stroke="none" fill="white" stroke-opacity="1" fill-opacity="1" element_type="mask_transparent"></path>
<path d="M 0 0 C 0 0.4, 0.5527864045000421 0.4217376207875073, 1 0.5 " stroke-width="0.25" stroke="black" fill="none" stroke-opacity="1" fill-opacity="1" element_type="mask_top"></path>
<circle cx="1" cy="0.5" r="0.125" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 2 1 C 2 0.6, 1.4472135954999579 0.5782623792124927, 1 0.5 " stroke-width="0.25" stroke="black" fill="none" stroke-opacity="1" fill-opacity="1" element_type="mask_top"></path>
<circle cx="1" cy="0.5" r="0.125" fill="#000000" stroke="none" class="dummy"></circle>
</mask>
<mask id="mask1" maskUnits="userSpaceOnUse">
<path d="M -2.5 0 L -2.5 1 L 4.5 1 L 4.5 0 L -2.5 0 " stroke-width="0.1" stroke="none" fill="white" stroke-opacity="1" fill-opacity="1" element_type="mask_transparent"></path>
<path d="M 0 0 C 0 0.4, 0.5527864045000421 0.4217376207875073, 1 0.5 " stroke-width="0.25" stroke="black" fill="none" stroke-opacity="1" fill-opacity="1" element_type="mask_top"></path>
<circle cx="1" cy="0.5" r="0.125" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 2 1 C 2 0.6, 1.4472135954999579 0.5782623792124927, 1 0.5 " stroke-width="0.25" stroke="black" fill="none" stroke-opacity="1" fill-opacity="1" element_type="mask_top"></path>
<circle cx="1" cy="0.5" r="0.125" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 1 0 C 1 0.4, 1 0.325, 1 0.5 " stroke-width="0.25" stroke="black" fill="none" stroke-opacity="1" fill-opacity="1" element_type="mask_top"></path>
<circle cx="1" cy="0.5" r="0.125" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 1 1 C 1 0.6, 1 0.675, 1 0.5 " stroke-width="0.25" stroke="black" fill="none" stroke-opacity="1" fill-opacity="1" element_type="mask_top"></path>
<circle cx="1" cy="0.5" r="0.125" fill="#000000" stroke="none" class="dummy"></circle>
</mask>
</defs>
<g id="viewport-20181114130918902" class="svg-pan-zoom_viewport" transform="matrix(361.6666666666667,0,0,361.6666666666667,180.83333333333331,502.83333333333337)">
<g transform="scale (1 -1)" class="diagram_group">
<path d="M -0.5 0 L 2.5 0 L 2.5 1 L -0.5 1 " stroke-width="0.1" stroke="none" fill="#ffffff" stroke-opacity="1" fill-opacity="1" element_type="region"></path>
<path d="M 2 0 C 2 0.09999999999999998, 2 0.5, 1 0.5 C 2 0.5, 2 0.9, 2 1 L 2 1 L 2.5 1 L 2.5 0 L 2 0 " stroke-width="0.01" stroke="none" fill="#ffffff" element_type="region"></path>
<path d="M 0 0 C 0 0.09999999999999998, 0 0.5, 1 0.5 C 1 0.5, 1 0.09999999999999998, 1 0 L 1 0 L 0 0 " stroke-width="0.01" stroke="none" fill="#ffffff" element_type="region"></path>
<path d="M 1 0 C 1 0.09999999999999998, 1 0.5, 1 0.5 C 2 0.5, 2 0.09999999999999998, 2 0 L 2 0 L 1 0 " stroke-width="0.01" stroke="none" fill="#ffffff" element_type="region"></path>
<path d="M 1 0.5 C 0 0.5, 0 0.9, 0 1 L 0 1 L 1 1 C 1 0.9, 1 0.5, 1 0.5 " stroke-width="0.01" stroke="none" fill="#ffffff" element_type="region"></path>
<path d="M 1 0.5 C 1 0.5, 1 0.9, 1 1 L 1 1 L 2 1 C 2 0.9, 2 0.5, 1 0.5 " stroke-width="0.01" stroke="none" fill="#ffffff" element_type="region"></path>
<path d="M 0 1 L 0 1 " stroke="#000000" stroke-width="0.1" fill="none" element_type="edge" element_index="3"></path>
<path d="M 1 1 L 1 1 " stroke="#000000" stroke-width="0.1" fill="none" element_type="edge" element_index="4"></path>
<path d="M 2 1 L 2 1 " stroke="#000000" stroke-width="0.1" fill="none" element_type="edge" element_index="5"></path>
<path d="M 0 0 C 0 0.4, 0.5527864045000421 0.4217376207875073, 1 0.5 " stroke-width="0.1" stroke="#000000" fill="none" stroke-opacity="1" fill-opacity="1" element_type="homotopy_edge"></path>
<circle cx="1" cy="0.5" r="0.05" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 2 1 C 2 0.6, 1.4472135954999579 0.5782623792124927, 1 0.5 " stroke-width="0.1" stroke="#000000" fill="none" stroke-opacity="1" fill-opacity="1" element_type="homotopy_edge"></path>
<circle cx="1" cy="0.5" r="0.05" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 1 0 C 1 0.4, 1 0.325, 1 0.5 " stroke-width="0.1" stroke="#000000" fill="none" stroke-opacity="1" fill-opacity="1" element_type="homotopy_edge" mask="url(#mask0)"></path>
<circle cx="1" cy="0.5" r="0.05" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 1 1 C 1 0.6, 1 0.675, 1 0.5 " stroke-width="0.1" stroke="#000000" fill="none" stroke-opacity="1" fill-opacity="1" element_type="homotopy_edge" mask="url(#mask0)"></path>
<circle cx="1" cy="0.5" r="0.05" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 2 0 C 2 0.4, 1.4472135954999579 0.4217376207875073, 1 0.5 " stroke-width="0.1" stroke="#000000" fill="none" stroke-opacity="1" fill-opacity="1" element_type="homotopy_edge" mask="url(#mask1)"></path>
<circle cx="1" cy="0.5" r="0.05" fill="#000000" stroke="none" class="dummy"></circle>
<path d="M 0 1 C 0 0.6, 0.5527864045000421 0.5782623792124927, 1 0.5 " stroke-width="0.1" stroke="#000000" fill="none" stroke-opacity="1" fill-opacity="1" element_type="homotopy_edge" mask="url(#mask1)"></path>
<circle cx="1" cy="0.5" r="0.05" fill="#000000" stroke="none" class="dummy"></circle>
<circle cx="1" cy="0.5" r="0.1" fill="#777777" stroke="none" fill-opacity="0" element_index="0" element_type="vertex"></circle>
</g>
</g>
</svg>
We need to replicate in the new version the occlusion effect for tangles, which was implemented in the old ANC SVG renderer:
It works by organizing the strands into groups of common depth. It then draws these groups in order, starting with the shallowest. After drawing each group, it is drawn again as a mask with twice the line width.