/* ---------------------- */ /* ref */ /* ---------------------- */ // https://twitter.com/monfera/status/761355001474187268 /* ---------------------- */ /* config */ /* ---------------------- */ var state = {} // state state.epsilon = 1e-6 state.tau = 2 * Math.PI state.pi = Math.PI state.distance2 = (s, t) => (t.x - s.x) * (t.x - s.x) + (t.y - s.y) * (t.y - s.y) state.distance = (s, t) => Math.sqrt(state.distance2(s, t)) state.quad = d3c.quad() // quadtree for multifind .x(function(d) {return d.x}) .y(function(d) {return d.y}) state.diabonalv = d => state.quad.diagonal(d, v = 1.1) // cords vibration state.debug = false state.started = true state.mousedown = function() { if (state.debug && state.started) {simulation.stop(), state.started = false} else {simulation.restart(), state.started = true} } state.linkId = (s, t) => s.idx + ':' + t.idx state.width = 600 state.height = 400 state.imagePath = "./space.jpg" state.raptorCount = 10 / 1 state.raptorLineWidth = 1 state.raptorFill = 'yellow' // "red" state.raptorArmsStroke = "orange" state.raptorArmsFactor = 4 / 1 // Infinity state.raptorRadius = 3 // * 10 state.raptorSearchRange = Infinity state.raptorAttractionFactor = 1.1 // plays with arms factors against impact factors state.raptorAbsorbfn = function(node) { return node.radius * state.raptorAttractionFactor} // collision effect state.raptorRadiusPostAbsorb = (s, t) => Math.sqrt((s.radius * s.radius) + (t.radius * t.radius)) state.raptorColorPostAbsorb = function(s, t) { let scal = d3.scaleLinear() .domain([state.preyCount, 0 ]) .range([state.raptorFill, state.preyFill]) return scal(state.preyLiving(state.nodes).length) } state.collideRadius = function(node) { return node.radius * state.prayImpactFactor} state.preyCount = 3000 // / 30 // Infinity state.preyRadius = 0.5 // * 10 state.preyFill = 'grey' // 'blue' state.preyStroke = 'blue' state.preyStrokeWidth = 0 state.preyPostFill = 'red' state.preyPostStroke = 'yellow' state.preyPostStrokeWidth = 0.2 state.prayImpactFactor = state.raptorAttractionFactor * 2 || 1 // collision effect behavior factor state.traceLineWidth = state.raptorRadius state.traceStrokeStyleLight = "rgba(128, 0, 255, 0.02)" state.traceStroke = 'yellow' // "red" state.traceOpacity = 0.1 state.traceFill = 'transparent' state.traceLength = 5000 state.tracePath = d => "M" + d.join("L") /* ---------------------- */ /* data */ /* ---------------------- */ state.raptorItems = d3.range(state.raptorCount).map(function(d, i) { return { radius: state.raptorRadius, x: Math.random() * state.width, y: Math.random() * state.height, vx: 0.1, vy: 0.5, links: [], // ref to prey type: 'raptor', color: state.raptorFill, idx: i }}) state.traceItems = d3.range(state.raptorCount).map(function(d, i) { var p0 = [state.raptorItems[i].x, state.raptorItems[i].y] return [p0] }) state.preyItems = d3.range(state.preyCount).map(function(d, i) { var randomAngle = Math.random() * 2 * Math.PI return { radius: state.preyRadius, x: Math.random() * state.width, y: Math.random() * state.height, vx: 5 * Math.cos(randomAngle), // browninan movement vy: 5 * Math.sin(randomAngle), // as per Robert Monfera saugs: [], // ref to raptors type: 'prey', color: state.preyFill, idx: i } }) state.preyLiving = d => d.filter(d => d.type === 'prey') // prey .filter(d => d.radius > 1e-6) state.links = state.raptorItems.reduce(function(p, c, i, a) { return p.concat(c.links) }, []) state.nodes = [...state.raptorItems, ...state.preyItems] /* ---------------------- */ /* svg layer */ /* ---------------------- */ var svg = d3c.layer() .on("mousedown", state.mousedown) /* ---------------------- */ /* image layer */ /* ---------------------- */ function showimage() { var imgLayer = d3c.layer({cls: 'background', item: 'image.image', data: ['image']}).e .attr("xlink:href", state.imagePath) .attr("x", "0") .attr("y", "0") .attr("width", "600") .attr("height", "400") } if (state.debug !== true) showimage() /* ---------------------- */ /* forces */ /* ---------------------- */ // container var container = function(alpha, nodes) { var node var i for (i = 0; i < nodes.length; i++) { node = nodes[i] if(Math.abs(node.x) < 0 + node.radius) { node.vx *= -1 } if(Math.abs(node.x) > state.width - node.radius) { node.vx *= -1 } if(Math.abs(node.y) < 0 + node.radius) { node.vy *= -1 } if(Math.abs(node.y) > state.height - node.radius) { node.vy *= -1 } } } /* ---------------------- */ /* simulation */ /* ---------------------- */ var simulation = d3.forceSimulation(state.nodes) .alphaDecay(0) .velocityDecay(5e-4) .force("collide", d3.forceCollide().radius(state.collideRadius).strength(2).iterations(1)) .force("container", d => container(d, state.nodes)) .on("tick", ticked) /* ---------------------- */ /* tick */ /* ---------------------- */ function ticked() { if (state.debug === true) console.log('preys: ', state.preyLiving(state.nodes).length) simulation .nodes(state.nodes) let quad = d3c.quad() .x(function(d) {return d.x}) .y(function(d) {return d.y}) .addAll(state.preyLiving(state.nodes)) for (let i = 0; i < state.raptorItems.length; i++) { let s = state.raptorItems[i] let ts = quad.findmany(s.x, s.y, state.raptorSearchRange, state.raptorArmsFactor) let ls = [] let hs = [] for (let i = 0; i < ts.length; i++) { let t = ts[i] let ln = {source: s, target: t, idx: state.linkId(s, t)} let hk = {source: t, target: s, idx: state.linkId(s, t)} ls.push(ln) // raptor hs.push(hk) // prey let dst = state.distance(s, t) let range = state.raptorAbsorbfn(s) if (dst < range) { // absorbed s.radius = state.raptorRadiusPostAbsorb(s, t) s.color = state.raptorColorPostAbsorb(s, t) t.radius = state.epsilon t.isvoid = true } t.saugs = hs } s.links = ls } state.links = state.raptorItems.reduce(function(p, c, i, a) { return p.concat(c.links) }, []) render(state) } /* ---------------------- */ /* render */ /* ---------------------- */ function render(s) { let state = Object.assign(s) // links var linksIds = (d, i) => state.linkId(d.source, d.target) var linksLayer = d3c.layer({cls: 'links', item: 'path.linkPath', data: state.links, idfn: linksIds}) linksLayer.m .attr("d", state.diabonalv) .style("fill", "transparent") .style("stroke", state.raptorArmsStroke) .style("stroke-width", "1px") linksLayer.x .remove() // traces var tracesLayer = d3c.layer({cls: 'traces', item: 'path.trace', data: state.traceItems}) tracesLayer.m .attr("d", function(d, i) { d.push([state.raptorItems[i].x, state.raptorItems[i].y]) // add present d.splice(0, d.length - state.traceLength) // remove past var r = d ? state.tracePath(d) : null return r }) .style("fill", function(d, i) { return state.traceFill }) .style("stroke", function(d) { return state.traceStroke }) .style("stroke-width", function(d) { return state.traceLineWidth }) .style("opacity", state.traceOpacity) // prey var preysLayer = d3c.layer({cls: 'preys', item: 'circle.prey', data: state.preyItems}) preysLayer.u .style("fill", function(d, i) { return (d.isvoid === true) ? state.preyPostFill : state.preyFill }) .style("stroke", function(d) { return (d.isvoid === true) ? state.preyPostStroke : state.preyFill }) .style("stroke-width", function(d) { return (d.isvoid === true) ? state.preyPostStrokeWidth : state.preyStrokeWidth }) .style("opacity", function(d, i) { return (d.isvoid === true) ? 0 : 1 }) .attr("cx", function(d) { return d.x }) .attr("cy", function(d) { return d.y }) .attr("r", function(d) { return (d.isvoid === true) ? 10 : d.radius }) preysLayer.e .attr("cx", function(d) { return d.x }) .attr("cy", function(d) { return d.y }) .attr("r", function(d) {return d.radius }) .style("fill", function(d, i) { return state.preyFill }) .style("stroke", function(d) { return state.preyFill }) .style("stroke-width", function(d) { return 0.2 }) preysLayer.x .attr("cx", function(d) { return d.x }) .attr("cy", function(d) { return d.y }) .style("fill", function(d, i) { return 'transparent' }) .style("stroke", function(d) { return "red" }) // raptors var raptorsLayer = d3c.layer({cls: 'particules', item: 'circle', data: state.raptorItems}) raptorsLayer.m .attr("cx", function(d) { return d.x }) .attr("cy", function(d) { return d.y }) .attr("r", function(d) { return d.radius }) .style("stroke-width", function(d) { return state.raptorLineWidth }) .style("fill", function(d) { return d.color }) if (state.debug) d3c.drag(raptorsLayer.m) }