Today was my last official day on my Fugu project. I made a Fulubula thing. Its code/DNA is after the jump.
module(...,package.seeall) local n, m, spike, new_spike, next_vert, simulate_to_age, make_base_mesh local sim_time = 0 function setup() m = make_base_mesh() n = meshnode(m) fgu:add(n) vertices = {} local all_vertices = vertexlist(m) each(all_vertices, function(v) v:set_uv(0,0) end) while #all_vertices>0 do vertices[#vertices+1] = next_vert(all_vertices) end spike = nil sim_time = 0 end function update(dt) sim_time = sim_time + 0.01 local age = exp(sim_time) - .99 print(age) simulate_to_age(age) end make_base_mesh = function() local m = icosahedron() m:smooth_subdivide(2) return m end -- simulate the growth of the thing until age simulate_to_age = function(age) m = make_base_mesh() vertices = {} local all_vertices = vertexlist(m) each(all_vertices, function(v) v:set_uv(0,0) end) while #all_vertices>0 do vertices[#vertices+1] = next_vert(all_vertices) end spike = nil local keep_going = true local dt = .03 while (keep_going) do if (spike~=nil) then local continue = spike:update(dt) if (not continue) then spike=nil end end if (spike==nil and #vertices>0) then local v = vertices[#vertices] remove(vertices,#vertices) spike = new_spike(m,v,age) elseif (#vertices==0) then keep_going = false end end n:set_mesh(m) end -- get the next random vertex from vertices, -- and remove its immediate neighbours next_vert = function(vertices) if #vertices==0 then return nil end local v = vertices[1] -- choose(vertices) local neighbours = loopv(v) insert(neighbours,v) each(neighbours, function(u) i,_ = find(vertices, function(x) return u==x end) if i then remove(vertices, i) end end) return v end -- create a new spike program at the specified vertex -- this creates a new object with an update(dt) function -- the update function returns false once the growth is complete -- the internal logic is a simple state machine new_spike = function(the_mesh,the_vertex,age) local states = { move = 1, inset = 2, done = 3 } local dy = (the_vertex.p.y+1.2)/2.4 local obj = { m=the_mesh, v=the_vertex, age = 0, max_age=age, normal=the_vertex.n, axis_of_rotation=cross(the_vertex.n,vec3(0,1,0)), seg = 1, SPEED = sqr((1.5-dy) * 3), -- 4, SEG_LENGTH = (.02 + sqr(1-dy)*0.5)*clamp(age*2,.1,2), NUM_SEGS = 6, -- 7, RADII = {.9,.7,.7,1.2,2,.8}, ROTATION_RAD = 5*(.1 + (1-dy)*.05)* clamp(age*2,0,1), RADII_MULT = clamp(age*2,0,1), MEANDER = .2 * clamp(age*2,0,1), v_coord = 0, state=states.move, next_state=nil, --state_change=.01 } -- don't extrude things pointing directly up or down if (length(obj.axis_of_rotation)<.01) then return nil end local actions = {} actions[states.move] = function(self,dt) if self.distance==nil then self.distance = 0 end local dist = self.SPEED*dt local q = quat(self.axis_of_rotation,self.ROTATION_RAD + random(-self.MEANDER,self.MEANDER)) local dir = q*self.v.n local u = (self.seg - 1 + self.distance/self.SEG_LENGTH)/self.NUM_SEGS self.v.p = self.v.p + dir*dist self.v:set_uv(u,self.v_coord) if (self.cap) then -- adjust the outer loop on the cap -- so it grows in the normal, -- reaches the right scale, -- becomes circular -- and then rotates to orient in the growth dir local t = self.distance/self.SEG_LENGTH -- amount done local outer = capov(self.cap) local center = vec3(0,0,0) for i,ov in ipairs(outer) do -- move in growing direction ov:set_uv(u,self.v_coord) ov.p = ov.p + dir*dist center = center + ov.p end center = center/#outer local er = self.cap_avg_radius * self.RADII[self.seg]*self.RADII_MULT for i,ov in ipairs(outer) do -- make a bit more circular -- current, start, end radii local cr = distance(ov.p,center) local sr = self.cap_radii[i] local tr = max(1,self.SPEED*dt + (cr-sr)/(er-sr)) local d = normalise(ov.p-center) ov.p = center + d*lerp(sr,er,tr) end -- finally flatten in the growth dir flattenvl(self.m,outer,self.v.p,dir) end self.distance = self.distance + dist if (self.distance > self.SEG_LENGTH) then self.seg = self.seg + 1 if (self.seg < self.NUM_SEGS) then self.state = states.inset else self.state = states.done end end end actions[states.inset] = function(self,dt) -- inset a tiny bit and store the cap for subsequent pulls self.cap = inset(self.m,self.v,.99) -- compute the avg radius of this cap so we can circulise it -- also store the current radius of each outer vert self.cap_radii = {} local outer = capov(self.cap) local center = vec3(0,0,0) for _,ov in ipairs(outer) do center = center + ov.p end center = center/#outer local avg_radius = 0 for _,ov in ipairs(outer) do local r = distance(ov.p,center) self.cap_radii[#self.cap_radii+1] = r avg_radius = avg_radius + r end avg_radius = avg_radius/#outer self.cap_avg_radius = avg_radius self.state = states.move self.distance = nil end obj.update = function(self,dt) self.age = self.age + dt if (self.age > self.max_age) then obj.state=states.done end if obj.state==states.done then return false else actions[obj.state](obj,dt) return true end end return obj end