Last active
December 25, 2015 23:29
-
-
Save dermotbalson/7057101 to your computer and use it in GitHub Desktop.
smoke
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- smoke | |
function setup() | |
parameter.integer("FPS",0,60,60) | |
parameter.integer("Particles",0,500,0) | |
s=Smoke(vec2(WIDTH/2,50)) | |
end | |
function draw() | |
FPS=FPS*.9+.1/DeltaTime | |
background(167, 188, 204, 255) | |
s:draw() | |
Particles=#s.p | |
end | |
Smoke=class() | |
function Smoke:init(pos) | |
self.pos=pos | |
--particle settings -- | |
self.p={} | |
self.size=vec2(200,300) | |
self.expandTime=vec2(70,80) --particles expand to full size over this time (min, max range) | |
self.fadeTime=vec2(10,20) --particles fade in this number of secs (min, max range) | |
--texture image-- | |
local w=512 --size of image used as texture | |
self.img=Smoke.createImage(w) | |
--create base particle that will just sit at the source and mask the new particles | |
self.base=SmokeParticle(self.pos,self.img,vec2(15,15),vec2(5,5),vec2(0,0)) | |
--set time to first new particle | |
self.timer=0 | |
self.wind=math.random()*0.3-0.15 --horizontal wind speed | |
self.windShift=math.random(10,20) --time to next wind shift | |
end | |
function Smoke:draw(wind) | |
--create new particle when timer gets to 0 | |
self.timer=self.timer-DeltaTime | |
if self.timer<0 then | |
local p=SmokeParticle(self.pos,self.img,self.size,self.expandTime,self.fadeTime) | |
table.insert(self.p,p) | |
self.timer=2+math.random()*2 | |
end | |
--reset wind shift when timer reaches 0 | |
self.windShift=self.windShift-DeltaTime | |
if self.windShift<0 then | |
self.wind=math.random()*0.3-0.15 --horizontal wind speed | |
self.windShift=math.random()*10+20 | |
end | |
self.base:draw(vec2(0,0)) --draw base particle in unchanged position | |
--draw other particles | |
local velocity=vec2(self.wind,0.5) --y value is vertical drift speed | |
for i,p in pairs(self.p) do | |
if p:draw(velocity)==false then table.remove(self.p,i) end --remove particle if faded completely | |
end | |
end | |
--creates noise image with gray colours based on intensity values | |
function Smoke.createImage(size) | |
local min,max=100,255 --gray range used | |
local m=hmap.create(size,size,0.3) | |
local z1,z2=0,0 | |
for i=0,m.w do | |
for j=0,m.h do | |
z1=math.min(z1,m[i][j]) | |
z2=math.max(z2,m[i][j]) | |
end | |
end | |
local img=image(m.w,m.h) | |
local f=(max-min)/(z2-z1) | |
for i=1,m.w do | |
for j=1,m.h do | |
local intensity=min+f*(m[i][j]-z1) | |
img:set(i,j,color(intensity)) | |
end | |
end | |
return img | |
end | |
SmokeParticle=class() | |
function SmokeParticle:init(pos,img,vecSize,vecExpand,vecFade) | |
self.pos=vec2(pos.x,pos.y) --current x,y position | |
self.source=vec2(pos.x,pos.y) --starting position | |
--particle settings -- | |
local size=math.random(vecSize.x,vecSize.y) | |
self.expandTime=math.random(vecExpand.x,vecExpand.y) | |
self.fadeTime=math.random(vecFade.x,vecFade.y) | |
--create mesh -- | |
self.m=mesh() | |
self.m.texture=img | |
local w=img.width | |
local x1,y1,x2,y2=-size/2,-size/2,size/2,size/2 | |
local tx1,ty1=math.random()*(1-size/w),math.random()*(1-size/w) | |
local tx2,ty2=tx1+size/w,ty1+size/w | |
v,t={},{} | |
v[1]=vec2(x1,y1) t[1]=vec2(tx1,ty1) | |
v[2]=vec2(x2,y1) t[2]=vec2(tx2,ty1) | |
v[3]=vec2(x2,y2) t[3]=vec2(tx2,ty2) | |
v[4]=vec2(x1,y2) t[4]=vec2(tx1,ty2) | |
v[5]=vec2(x1,y1) t[5]=vec2(tx1,ty1) | |
v[6]=vec2(x2,y2) t[6]=vec2(tx2,ty2) | |
self.m.vertices=v | |
self.m.texCoords=t | |
self.m:setColors(color(255)) | |
self.m.shader=shader(smokeShader.vertexShader,smokeShader.fragmentShader) | |
self.m.shader.size=size/w | |
self.m.shader.fade=1 --0=invisible, 1=full alpha | |
self.m.shader.centre=vec2((tx1+tx2)/2,(ty1+ty2)/2) | |
self.timer=0 | |
end | |
function SmokeParticle:draw(velocity) | |
self.timer=self.timer+DeltaTime | |
--calculate how big it has got | |
self.m.shader.frac=math.max(0.04,math.min(1,self.timer/(self.expandTime+self.fadeTime))) | |
--if in the fading stage, calculate fade and return false if fade=0 | |
if self.timer>self.expandTime and self.fadeTime>0 then | |
self.m.shader.fade=1-(self.timer-self.expandTime)/self.fadeTime | |
if self.m.shader.fade<0 then return false end | |
end | |
pushMatrix() | |
--wind has little effect on smoke near the ground, phase it in over 100 pixels | |
self.pos.x=self.pos.x+velocity.x*math.min(1,(self.pos.y-self.source.y)/100) | |
--make smoke drift upwards slowly at first, then increasing | |
self.pos.y=self.pos.y+velocity.y*math.min(1,self.timer/50) | |
if self.pos.y>HEIGHT then return false end | |
translate(self.pos.x,self.pos.y) | |
self.m:draw() | |
popMatrix() | |
return true | |
end | |
smokeShader = { | |
vertexShader = [[ | |
uniform mat4 modelViewProjection; | |
attribute vec4 position; | |
attribute vec4 color; | |
attribute vec2 texCoord; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
void main() | |
{ | |
vColor = color; | |
vTexCoord = texCoord; | |
gl_Position = modelViewProjection * position; | |
} | |
]], | |
fragmentShader = [[ | |
precision highp float; | |
uniform lowp sampler2D texture; | |
uniform float size; | |
uniform float frac; | |
uniform float fade; | |
uniform vec2 centre; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
void main() | |
{ | |
lowp vec4 col = texture2D( texture, vTexCoord ); | |
float f = length( centre - vTexCoord ) / size * 2.0 / frac; | |
if (f >= 1.0 ) discard; //col.a = 0.0; | |
else col.a = 0.75*( 1.0 - f ) * fade; | |
gl_FragColor = col; | |
} | |
]]} | |
-- Heightmap module | |
-- Copyright (C) 2011 Marc Lepage | |
hmap={} | |
hmap.H=0 | |
hmap.f=0 | |
-- Create a heightmap using the specified height function (or default) | |
-- map[x][y] where x from 0 to map.w and y from 0 to map.h | |
--f = height function, can be left blank | |
--h = coarseness 0-1 | |
function hmap.create(width, height, h, f) | |
hmap.f = f and hmap.f or hmap.defaultf | |
hmap.H=h or .6 | |
-- make heightmap | |
local map = hmap.diamondsquare(hmap.pot(math.max(width, height)), hmap.f) | |
-- clip heightmap to desired size | |
for x = 0, map.w do for y = height+1, map.h do map[x][y] = nil end end | |
for x = width+1, map.w do map[x] = nil end | |
map.w, map.h = width, height | |
return map | |
end | |
-- Find power of two sufficient for size | |
function hmap.pot(size) | |
local pot = 2 | |
while true do | |
if size <= pot then return pot end | |
pot = 2*pot | |
end | |
end | |
-- Create a table with 0 to n zero values | |
function hmap.tcreate(n) | |
local t = {} | |
for i = 0, n do t[i] = 0 end | |
return t | |
end | |
-- Square step | |
-- Sets map[x][y] from square of radius d using height function f | |
function hmap.square(map, x, y, d, f) | |
local sum, num = 0, 0 | |
if 0 <= x-d then | |
if 0 <= y-d then sum, num = sum + map[x-d][y-d], num + 1 end | |
if y+d <= map.h then sum, num = sum + map[x-d][y+d], num + 1 end | |
end | |
if x+d <= map.w then | |
if 0 <= y-d then sum, num = sum + map[x+d][y-d], num + 1 end | |
if y+d <= map.h then sum, num = sum + map[x+d][y+d], num + 1 end | |
end | |
map[x][y] = f(map, x, y, d, sum/num) | |
end | |
-- Diamond step | |
-- Sets map[x][y] from diamond of radius d using height function f | |
function hmap.diamond(map, x, y, d, f) | |
local sum, num = 0, 0 | |
if 0 <= x-d then sum, num = sum + map[x-d][y], num + 1 end | |
if x+d <= map.w then sum, num = sum + map[x+d][y], num + 1 end | |
if 0 <= y-d then sum, num = sum + map[x][y-d], num + 1 end | |
if y+d <= map.h then sum, num = sum + map[x][y+d], num + 1 end | |
map[x][y] = f(map, x, y, d, sum/num) | |
end | |
-- Diamond square algorithm generates cloud/plasma fractal heightmap | |
-- http://en.wikipedia.org/wiki/Diamond-square_algorithm | |
-- Size must be power of two | |
-- Height function f must look like f(map, x, y, d, h) and return h' | |
function hmap.diamondsquare(size, f) | |
-- create map | |
local map = { w = size, h = size } | |
for c = 0, size do map[c] = hmap.tcreate(size) end | |
-- seed four corners | |
local d = size | |
local dd=2^-hmap.H | |
hmap.D=dd | |
local dx=(math.random()-0.5)*2 | |
map[0][0] = dx --f(map, 0, 0, d, 0) | |
map[0][d] = dx --f(map, 0, d, d, 0) | |
map[d][0] = dx --f(map, d, 0, d, 0) | |
map[d][d] = dx --f(map, d, d, d, 0) | |
d = d/2 hmap.D=hmap.D*dd | |
-- perform square and diamond steps | |
while 1 <= d do | |
for x = d, map.w-1, 2*d do | |
for y = d, map.h-1, 2*d do | |
hmap.square(map, x, y, d, f) | |
end | |
end | |
for x = d, map.w-1, 2*d do | |
for y = 0, map.h, 2*d do | |
hmap.diamond(map, x, y, d, f) | |
end | |
end | |
for x = 0, map.w, 2*d do | |
for y = d, map.h-1, 2*d do | |
hmap.diamond(map, x, y, d, f) | |
end | |
end | |
d = d/2 hmap.D=hmap.D*dd | |
end | |
return map | |
end | |
-- Default height function | |
-- d is depth (from size to 1 by powers of two) | |
-- h is mean height at map[x][y] (from square/diamond of radius d) | |
-- returns h' which is used to set map[x][y] | |
function hmap.defaultf(map, x, y, d, h) | |
return h + (math.random()-0.5)*hmap.D | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment