### Electric Field Shows an electric field's strength and direction David Wehr ### # Window width and height d_width = 720 d_height = 540 # Distance between each computed point of the field step = 18 # Area of a charge with value 1 charge_s = 1200 # List of active charges charges = [] # Charge currently being moved dragging_charge = null # Whether we're dragging the window or not dragging_window = null # The window that manages charges charge_window = null class ClickableArea constructor: (@x, @y, @width, @height) -> # Determine if the mouse is within the acceptable area clicked: (wnd_ofs) => if mouseX < @x + wnd_ofs[0] or mouseX > @x + wnd_ofs[0] + @width return false if mouseY < @y + wnd_ofs[1] or mouseY > @y + wnd_ofs[1] + @height return false return true class ChargeWindow @pos = [0, 0] @dims = [100, 40] @active_charge = null @clickables = [] @add_charge = () -> charges.push(new Charge(1, [d_width/2, d_height/2])) @delete_charge = () -> charges.splice(charges.indexOf(charge_window.constructor.active_charge), 1) @minus = () -> charge_window.constructor.active_charge.val -= 1 @plus = () -> charge_window.constructor.active_charge.val += 1 @window_area = new ClickableArea(0, 0, @dims[0], @dims[1]) minus_area = new ClickableArea(65, 2, 12, 12) plus_area = new ClickableArea(80, 2, 12, 12) add_area = new ClickableArea(2, 15, 28, 12) del_area = new ClickableArea(32, 15, 43, 12) @clickables.push({'area': minus_area, 'func': ChargeWindow.minus}) @clickables.push({'area': plus_area, 'func': ChargeWindow.plus}) @clickables.push({'area': add_area, 'func': ChargeWindow.add_charge}) @clickables.push({'area': del_area, 'func': ChargeWindow.delete_charge}) @paint = () -> stroke(0) fill(0.9) rect(@pos[0], @pos[1], @dims[0], @dims[1], 5) if @active_charge? info = "Charge: " + @active_charge.val fill(0) text(info, @pos[0] + 2, @pos[1] + 12) fill(0.9) stroke(0.2) # Minus rect(@pos[0] + 65, @pos[1] + 2, 12, 12, 2) # Horizontal line(@pos[0] + 67, @pos[1] + 8, @pos[0] + 75, @pos[1] + 8) # Plus rect(@pos[0] + 80, @pos[1] + 2, 12, 12, 2) # Horizontal line(@pos[0] + 82, @pos[1] + 8, @pos[0] + 90, @pos[1] + 8) # Vertical line(@pos[0] + 86, @pos[1] + 4, @pos[0] + 86, @pos[1] + 12) stroke(0) rect(@pos[0] + 2, @pos[1] + 15, 28, 12, 2) fill(0) text("Add", @pos[0] + 4, @pos[1] + 26) fill(0.9) rect(@pos[0] + 32, @pos[1] + 15, 43, 12, 2) fill(0) text("Delete", @pos[0] + 34, @pos[1] + 26) @check_clicks = () -> button_clicked = false for clickable in @clickables if clickable.area.clicked(@pos) clickable.func() button_clicked = true break if not button_clicked and @window_area.clicked(@pos) dragging_window = charge_window class Charge @pos = [0, 0] @val = 0 constructor: (val, pos) -> @val = val @pos = pos # Compute the magnitude of a vector vec_mag = (vec) -> sum_sqr = 0 for item in vec sum_sqr = sum_sqr + item*item return Math.sqrt(sum_sqr) # Find the electric field at a point for one charge computeFieldPoint = (charge, point) -> vect = [0, 0] d_x = point[0] - charge.pos[0] d_y = point[1] - charge.pos[1] # Radius squared r2 = d_x*d_x + d_y*d_y dir_vec = normVec([d_x, d_y], 1) for axis in [0, 1] vect[axis] = dir_vec[axis] * charge.val / r2 return vect # Return a normalized vector multiplied by a scalar factor normVec = (vec, factor) -> magnitude = vec_mag(vec) normalized = [0, 0] normalized[0] = factor * vec[0]/magnitude normalized[1] = factor * vec[1]/magnitude return normalized # Draw an arrow from (x1, y1) to (x2, y2) arrow = (x1, y1, x2, y2, arrow_size) -> line(x1, y1, x2, y2) angle = Math.atan2(y2 - y1 , x2 - x1) tip_angle = (angle + Math.PI + 1) tip_x = Math.cos(tip_angle) * arrow_size + x2 tip_y = Math.sin(tip_angle) * arrow_size + y2 line(x2, y2, tip_x, tip_y) tip_angle = (angle + Math.PI - 1) tip_x = Math.cos(tip_angle) * arrow_size + x2 tip_y = Math.sin(tip_angle) * arrow_size + y2 line(x2, y2, tip_x, tip_y) # Determine a hue in the range [0..1] with blue weakest and red strongest get_hue = (value) -> # max_rad is the radius from the charge where the the color is completely blue max_rad = 400 # Linearize the color gradient of strength normalized = Math.sqrt(1/value) / max_rad # Upper bound of 0.75 normalized = Math.min(0.75, normalized) normalized *= 0.75 return normalized ############################### Processing.js functions ############################### setup: -> size(720, 540) colorMode(HSB, 1.0) charge_window = new ChargeWindow() charges = [] charges.push(new Charge(1, [200, 200])) charges.push(new Charge(-1, [500, 150])) noLoop() draw: -> background(0.1) # Draw the electric field for x in [0..d_width] by step for y in [0..d_height] by step field_vec = [0, 0] for charge in charges vec = computeFieldPoint(charge, [x, y]) field_vec[0] += vec[0] field_vec[1] += vec[1] new_hue = get_hue(vec_mag(field_vec)) stroke(new_hue, 1, 1) field_vec = normVec(field_vec, step) arrow(x, y, x + field_vec[0], y + field_vec[1], 5) # Draw the charges for charge in charges if charge_window.constructor.active_charge == charge stroke(1) else stroke(0) radius = Math.sqrt(Math.abs(charge.val)*charge_s / Math.PI) if charge.val > 0 shade = color(0, 1, 1) else if charge.val < 0 shade = color(0.3, 1, 1) else shade = color(0.5) radius = Math.sqrt(charge_s/Math.PI) fill(shade) ellipse(charge.pos[0], charge.pos[1], radius, radius) charge_window.constructor.paint() return mousePressed: -> # Find the closest charge to the mouse distances = [] for charge in charges d_x = mouseX - charge.pos[0] d_y = mouseY - charge.pos[1] d = Math.sqrt(d_x*d_x + d_y*d_y) distances.push(d) min_dist = Math.min.apply(Math, distances) if charges.length charge = charges[distances.indexOf(min_dist)] if charge.val == 0 radius = Math.sqrt(charge_s / Math.PI) else radius = Math.sqrt(Math.abs(charge.val)*charge_s / Math.PI) if min_dist < (1.2 * radius) dragging_charge = charge charge_window.constructor.active_charge = charge charge_window.constructor.check_clicks() redraw() mouseDragged: -> # Update the charge's position and redraw the field if dragging_charge? dragging_charge.pos = [mouseX, mouseY] if dragging_window? dx = mouseX - pmouseX dy = mouseY - pmouseY dragging_window.constructor.pos[0] += dx dragging_window.constructor.pos[1] += dy redraw() mouseReleased: -> dragging_charge = null dragging_window = null redraw()