Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nitaku/678b584e2f544ae7ed27 to your computer and use it in GitHub Desktop.
Save nitaku/678b584e2f544ae7ed27 to your computer and use it in GitHub Desktop.
Distance fields (edit tool)

TBC

Wheel: tool size Left: union Right: difference

Beware: it is slooooow.

`// noprotect`
### BEGIN ###
hcl_linear_rainbow = () ->
# H and L are linear, C is quadratic
domain = [0,1]
hue_range = [340, 340-480]
chroma_range = [0, 40]
luminance_range = [0, 100]
scale = (x) ->
ext = domain[1]-domain[0]
xn = (x-domain[0]) / ext
h = hue_range[0] + (hue_range[1]-hue_range[0])*xn
c = chroma_range[0] + (chroma_range[1]-chroma_range[0]) * (1 - Math.pow(1 - 2*xn, 2) )
l = luminance_range[0] + (luminance_range[1]-luminance_range[0]) *xn
# clamp
h = Math.max( Math.min(h, d3.max(hue_range)), d3.min(hue_range) )
c = Math.max( Math.min(c, d3.max(chroma_range)), d3.min(chroma_range) )
l = Math.max( Math.min(l, d3.max(luminance_range)), d3.min(luminance_range) )
return d3.hcl(h,c,l)
scale.domain = (x) ->
return domain if(!arguments.length)
domain = x
return scale
scale.hue_range = (x) ->
return hue_range if(!arguments.length)
hue_range = x
return scale
scale.chroma_range = (x) ->
return chroma_range if(!arguments.length)
chroma_range = x
return scale
scale.luminance_range = (x) ->
return luminance_range if(!arguments.length)
luminance_range = x
return scale
return scale
### END ###
canvas_left = d3.select('#left')
canvas_right = d3.select('#right')
width = canvas_left.node().getBoundingClientRect().width
height = canvas_left.node().getBoundingClientRect().height
side = Math.min(width, height) - 20
### define the distance function for the original circle ###
CX = 0.5
CY = 0.5
R = 0.25
dist = (x, y) -> R - ( Math.pow(x-CX, 2) + Math.pow(y-CY, 2) )/R
### compute the field ###
df = (( dist(pixel_x/side, pixel_y/side) for pixel_y in [0...side]) for pixel_x in [0...side])
### edit tool ###
svg = d3.select('svg')
cx_tool = 0
cy_tool = 0
r_tool = 0.125
dist_tool = (x, y) -> r_tool - ( Math.pow(x-cx_tool, 2) + Math.pow(y-cy_tool, 2) )/r_tool
tool = svg.append('circle')
.attr
id: 'tool'
r: r_tool*side
svg.on 'mousemove', () ->
[x,y] = d3.mouse(this)
[cx_tool, cy_tool] = [x/side, y/side]
tool
.attr
cx: x+0.5
cy: y+0.5
svg.on 'click', () ->
edit('union')
svg.on 'contextmenu', () ->
edit('sub')
# suppress context menu
d3.event.preventDefault()
# change tool size
svg.on 'mousewheel', () ->
if d3.event.wheelDelta > 0
r_tool *= 1.2
else
r_tool /= 1.2
tool
.attr
r: r_tool*side
edit = (op) ->
d3.select('body').classed('wait', true)
for pixel_x in [0...side]
for pixel_y in [0...side]
if op is 'union'
df[pixel_x][pixel_y] = Math.max( df[pixel_x][pixel_y], dist_tool(pixel_x/side, pixel_y/side) )
else if op is 'sub'
df[pixel_x][pixel_y] = Math.min( df[pixel_x][pixel_y], -dist_tool(pixel_x/side, pixel_y/side) )
window.requestAnimationFrame () ->
redraw()
d3.select('body').classed('wait', false)
redraw = () ->
### Draw the distance function ###
ctx = canvas_left.node().getContext('2d')
image = ctx.createImageData(side, side)
### define a default cubehelix-style hcl linear rainbow scale ###
MAX_D = Math.sqrt(2)/4
colorize_inner = hcl_linear_rainbow()
.domain([MAX_D,0])
.hue_range([200, 200+90])
colorize_outer = hcl_linear_rainbow()
.domain([-MAX_D,0])
.hue_range([200-180, 200-180+90])
console.debug 'Coloring...'
for pixel_x in [0...side]
for pixel_y in [0...side]
pixel_i = (pixel_y*side + pixel_x)*4
[r,g,b,a] = [pixel_i+0, pixel_i+1, pixel_i+2, pixel_i+3]
Fxy = df[pixel_x][pixel_y]
if Fxy > 0
color = d3.rgb colorize_inner(Fxy)
else
color = d3.rgb colorize_outer(Fxy)
image.data[r] = color.r
image.data[g] = color.g
image.data[b] = color.b
image.data[a] = 255
ctx.putImageData(image,(width-side)/2,(height-side)/2)
### Draw the reconstructed shape ###
ctx = canvas_right.node().getContext('2d')
image = ctx.createImageData(side, side)
BLUR = 3
for pixel_x in [0...side]
for pixel_y in [0...side]
pixel_i = (pixel_y*side + pixel_x)*4
[r,g,b,a] = [pixel_i+0, pixel_i+1, pixel_i+2, pixel_i+3]
Fxy = df[pixel_x][pixel_y]
value = Math.min(1+Fxy/(BLUR/side), 1)
image.data[r] = 255
image.data[g] = 255
image.data[b] = 255
image.data[a] = value*255
ctx.putImageData(image,(width-side)/2,(height-side)/2)
redraw()
html, body {
padding: 0;
margin: 0;
}
body.wait {
cursor: wait;
}
canvas {
background: #222;
position: absolute;
}
#left {
top: 0;
left: 0;
}
#right {
top: 0;
left: 480px;
}
svg {
position: absolute;
top: 20px;
left: 490px;
}
#tool {
fill: none;
stroke: magenta;
stroke-width: 1.5;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="Distance fields (edit tool)" />
<title>Distance fields (edit tool)</title>
<link rel="stylesheet" href="index.css">
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<canvas id="left" width="480" height="500"></canvas>
<canvas id="right" width="480" height="500"></canvas>
<svg width="460" height="460"></svg>
<script src="index.js"></script>
</body>
</html>
(function() {
// noprotect;
/* BEGIN
*/
var CX, CY, R, canvas_left, canvas_right, cx_tool, cy_tool, df, dist, dist_tool, edit, hcl_linear_rainbow, height, pixel_x, pixel_y, r_tool, redraw, side, svg, tool, width;
hcl_linear_rainbow = function() {
var chroma_range, domain, hue_range, luminance_range, scale;
domain = [0, 1];
hue_range = [340, 340 - 480];
chroma_range = [0, 40];
luminance_range = [0, 100];
scale = function(x) {
var c, ext, h, l, xn;
ext = domain[1] - domain[0];
xn = (x - domain[0]) / ext;
h = hue_range[0] + (hue_range[1] - hue_range[0]) * xn;
c = chroma_range[0] + (chroma_range[1] - chroma_range[0]) * (1 - Math.pow(1 - 2 * xn, 2));
l = luminance_range[0] + (luminance_range[1] - luminance_range[0]) * xn;
h = Math.max(Math.min(h, d3.max(hue_range)), d3.min(hue_range));
c = Math.max(Math.min(c, d3.max(chroma_range)), d3.min(chroma_range));
l = Math.max(Math.min(l, d3.max(luminance_range)), d3.min(luminance_range));
return d3.hcl(h, c, l);
};
scale.domain = function(x) {
if (!arguments.length) {
return domain;
}
domain = x;
return scale;
};
scale.hue_range = function(x) {
if (!arguments.length) {
return hue_range;
}
hue_range = x;
return scale;
};
scale.chroma_range = function(x) {
if (!arguments.length) {
return chroma_range;
}
chroma_range = x;
return scale;
};
scale.luminance_range = function(x) {
if (!arguments.length) {
return luminance_range;
}
luminance_range = x;
return scale;
};
return scale;
};
/* END
*/
canvas_left = d3.select('#left');
canvas_right = d3.select('#right');
width = canvas_left.node().getBoundingClientRect().width;
height = canvas_left.node().getBoundingClientRect().height;
side = Math.min(width, height) - 20;
/* define the distance function for the original circle
*/
CX = 0.5;
CY = 0.5;
R = 0.25;
dist = function(x, y) {
return R - (Math.pow(x - CX, 2) + Math.pow(y - CY, 2)) / R;
};
/* compute the field
*/
df = (function() {
var _i, _results;
_results = [];
for (pixel_x = _i = 0; 0 <= side ? _i < side : _i > side; pixel_x = 0 <= side ? ++_i : --_i) {
_results.push((function() {
var _j, _results1;
_results1 = [];
for (pixel_y = _j = 0; 0 <= side ? _j < side : _j > side; pixel_y = 0 <= side ? ++_j : --_j) {
_results1.push(dist(pixel_x / side, pixel_y / side));
}
return _results1;
})());
}
return _results;
})();
/* edit tool
*/
svg = d3.select('svg');
cx_tool = 0;
cy_tool = 0;
r_tool = 0.125;
dist_tool = function(x, y) {
return r_tool - (Math.pow(x - cx_tool, 2) + Math.pow(y - cy_tool, 2)) / r_tool;
};
tool = svg.append('circle').attr({
id: 'tool',
r: r_tool * side
});
svg.on('mousemove', function() {
var x, y, _ref, _ref1;
_ref = d3.mouse(this), x = _ref[0], y = _ref[1];
_ref1 = [x / side, y / side], cx_tool = _ref1[0], cy_tool = _ref1[1];
return tool.attr({
cx: x + 0.5,
cy: y + 0.5
});
});
svg.on('click', function() {
return edit('union');
});
svg.on('contextmenu', function() {
edit('sub');
return d3.event.preventDefault();
});
svg.on('mousewheel', function() {
if (d3.event.wheelDelta > 0) {
r_tool *= 1.2;
} else {
r_tool /= 1.2;
}
return tool.attr({
r: r_tool * side
});
});
edit = function(op) {
var _i, _j;
d3.select('body').classed('wait', true);
for (pixel_x = _i = 0; 0 <= side ? _i < side : _i > side; pixel_x = 0 <= side ? ++_i : --_i) {
for (pixel_y = _j = 0; 0 <= side ? _j < side : _j > side; pixel_y = 0 <= side ? ++_j : --_j) {
if (op === 'union') {
df[pixel_x][pixel_y] = Math.max(df[pixel_x][pixel_y], dist_tool(pixel_x / side, pixel_y / side));
} else if (op === 'sub') {
df[pixel_x][pixel_y] = Math.min(df[pixel_x][pixel_y], -dist_tool(pixel_x / side, pixel_y / side));
}
}
}
return window.requestAnimationFrame(function() {
redraw();
return d3.select('body').classed('wait', false);
});
};
redraw = function() {
/* Draw the distance function
*/
var BLUR, Fxy, MAX_D, a, b, color, colorize_inner, colorize_outer, ctx, g, image, pixel_i, r, value, _i, _j, _k, _l, _ref, _ref1;
ctx = canvas_left.node().getContext('2d');
image = ctx.createImageData(side, side);
/* define a default cubehelix-style hcl linear rainbow scale
*/
MAX_D = Math.sqrt(2) / 4;
colorize_inner = hcl_linear_rainbow().domain([MAX_D, 0]).hue_range([200, 200 + 90]);
colorize_outer = hcl_linear_rainbow().domain([-MAX_D, 0]).hue_range([200 - 180, 200 - 180 + 90]);
console.debug('Coloring...');
for (pixel_x = _i = 0; 0 <= side ? _i < side : _i > side; pixel_x = 0 <= side ? ++_i : --_i) {
for (pixel_y = _j = 0; 0 <= side ? _j < side : _j > side; pixel_y = 0 <= side ? ++_j : --_j) {
pixel_i = (pixel_y * side + pixel_x) * 4;
_ref = [pixel_i + 0, pixel_i + 1, pixel_i + 2, pixel_i + 3], r = _ref[0], g = _ref[1], b = _ref[2], a = _ref[3];
Fxy = df[pixel_x][pixel_y];
if (Fxy > 0) {
color = d3.rgb(colorize_inner(Fxy));
} else {
color = d3.rgb(colorize_outer(Fxy));
}
image.data[r] = color.r;
image.data[g] = color.g;
image.data[b] = color.b;
image.data[a] = 255;
}
}
ctx.putImageData(image, (width - side) / 2, (height - side) / 2);
/* Draw the reconstructed shape
*/
ctx = canvas_right.node().getContext('2d');
image = ctx.createImageData(side, side);
BLUR = 3;
for (pixel_x = _k = 0; 0 <= side ? _k < side : _k > side; pixel_x = 0 <= side ? ++_k : --_k) {
for (pixel_y = _l = 0; 0 <= side ? _l < side : _l > side; pixel_y = 0 <= side ? ++_l : --_l) {
pixel_i = (pixel_y * side + pixel_x) * 4;
_ref1 = [pixel_i + 0, pixel_i + 1, pixel_i + 2, pixel_i + 3], r = _ref1[0], g = _ref1[1], b = _ref1[2], a = _ref1[3];
Fxy = df[pixel_x][pixel_y];
value = Math.min(1 + Fxy / (BLUR / side), 1);
image.data[r] = 255;
image.data[g] = 255;
image.data[b] = 255;
image.data[a] = value * 255;
}
}
return ctx.putImageData(image, (width - side) / 2, (height - side) / 2);
};
redraw();
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment