[Closed] CgTalk Maxscript Challenge 005: "Lightning Bolts"
hblan, it doesn’t take too much to learn this stuff, just lots of hunting down in the reference! You’ll be coding fast in no time!
Hi everyone,
Great lightning scripts! I especially like James’s clever use of splineIK.
Here is mine, I got a little carried away but have enjoyed making it. If I did it again I would do things differently. I haven’t animated it but this would be straight forward. I’ve createsd the streaks using vectors since this is an area where I need some work.
EDIT:I have uploaded a slightly more stable version with more realistic default settings, and also uploaded some images.
Cheers,
Josh.
I have uploaded a file hereor:
--------------------------------------------------------------------------------
-- Tangra - Lightning generator.
-- version 1.0
-- max version 6, 7
-- written by Joshua Newman
-- [www.joshuanewman.net]( http://www.joshuanewman.net/)
-- last updated 12/07/05
-- copyright 2005
--------------------------------------------------------------------------------
--
-- Minimum usage, select a source object and press create.
-- Best practice : use a target that is an Epoly, and select faces or vertices in the sub object for great results.
-- Be careful not to have too many strikes or a chance of fork that is too high as this will result in many lightning streaks!
-- Also be careful not to have too many faces or vertices from the source, as each selection will create a lightning strike.
-- Maximum and minimum air density controls the length of any line segment.
-- VARIABLES
global src, dst, lightning, srcs,dest
-- FUNCTIONS
fn createlightningpoints srcpos destpos lenMx lenMn lnmlt dir term= -- source, destination min, destination max, min density, max density, density multiplier, random direction, -- this function generates the lightning points
(
lpnts=#() -- initiate an array
lenmx*=lnmlt -- multiply the length max by the air density multiplier
lenmn*=lnmlt -- multiply the length min by the air density multiplier
-- calculate the average target
if srcpos==undefined then srcpos=[0,0,0]
endstreak=true
x=distance srcpos destpos -- set x as the average target distance
append lpnts srcpos
while x>lenMx do
(
v=normalize (destpos-lpnts[lpnts.count]) -- the basic direction of the lightning
d=random lenMn lenMx -- the random length of the stroke
t=random [(dir*-1./360),(dir*-1./360),(dir*-1./360)] [(dir*1./360),(dir*1./360),(dir*1./360)] -- the random direction
newpnt=(v+t)*d+lpnts[lpnts.count] -- the new point!
append lpnts newpnt -- collect the new point
x=distance newpnt destpos -- test to see how close to the target we are
if (random 0 100)<term then (x=0;endstreak=false)
)
if endstreak then append lpnts destpos
return lpnts
)
fn createlightning lightning varray= -- this function draws the strike
(
s=addnewspline lightning -- add a new spline to the lightning
for i=1 to varray.count do addknot lightning s #corner #line varray[i] -- add the first knot
return lightning
)
-- ROLLOUTS
rollout lightningparam "Parameters."
(
fn geofilter obj=superclassof obj==geometryclass
group "Source."
(
pickbutton srcbtn "Source object." width:245 filter:geofilter
radiobuttons srcrad "" labels:#("pivot","volume","faces","vertices")
)
group "Target."
(
pickbutton dstbtn "Destination object." width:245 filter:geofilter
radiobuttons dstrad "" labels:#("pivot","volume","faces","vertices")
checkbox useXYZ "Use co-ordinates:"
spinner dstx "X:" type:#float range:[-1000000,1000000,0] width:80 across:3 offset:[-20,0] enabled:false
spinner dsty "Y:" type:#float range:[-1000000,1000000,0] width:80 offset:[-15,0] enabled:false
spinner dstz "Z:" type:#float range:[-1000000,1000000,0] width:80 offset:[-10,0] enabled:false
)
group "Parameters."
(
spinner nstrk "Numer of strikes" type:#integer range:[1,100000,1]
spinner mxlne "Maximum air density" type:#float range:[1,10000000,1]
spinner mnlne "Minimun air density" type:#float range:[1,10000000,10]
spinner admlt "Air density multiplier" type:#float range:[1,10000000,1]
spinner mcdir "Change in direction" type:#float range:[0,360,200]
spinner fdpth "Fork depth" type:#integer range:[1,10,2]
spinner fchnc "Chance of fork" type:#float range:[0,100,2]
spinner fterm "Fork termination" type:#float range:[0,100,10]
)
group "Rendering."
(
spinner lrads "Thickness" type:#float range:[0,1000,1]
-- spinner lvars "Variation %" type:#float range:[0,100,0]
spinner lsdes "Sides" type:#float range:[1,1000,8]
checkbox sivpt "Show in viewport:" checked:true
)
/*
group "Material."
(
colorpicker lgnclr "Lightning colour" align:#right
)
group "Animate lightning"
(
)
group "Glow."
(
checkbox glw "Add glow effect" checked:true
)
*/
button create "Create!" width:250 height:50
label l1 "\xa9 2003 Joshua Newman" across:2 offset:[-8,0]
hyperlink hl "[www.joshuanewman.net]( http://www.joshuanewman.net/ )" address:"[www.joshuanewman.net]( http://www.joshuanewman.net/ )" offset:[4,0]
-- ROLLOUT LOCAL FUNCTIONS
fn changexyzstate c= -- this changes the enabled state of the destination UI elements.
(
if c then
(
dstbtn.enabled=false
dstrad.enabled=false
dstx.enabled=true
dsty.enabled=true
dstz.enabled=true
) else
(
dstbtn.enabled=true
dstrad.enabled=true
dstx.enabled=false
dsty.enabled=false
dstz.enabled=false
)
)
-- ROLLOUT EVENTS
on srcbtn picked c do -- pick source
(
srcbtn.text=c.name
src=c
)
on dstbtn picked c do -- pick destintion
(
dstbtn.text=c.name
dst=c
)
on srcrad changed c do
(
case c of -- change the availability of the number of strikes field based on the source selection
(
1: nstrk.enabled=true
2: nstrk.enabled=true
3: (if (classof src==Editable_mesh) or (classof src==Editable_poly) then (nstrk.enabled=false) else (srcrad.state=1))
4: (if (classof src==Editable_mesh) or (classof src==Editable_poly) then (nstrk.enabled=false) else (srcrad.state=1))
)
)
on dstrad changed c do
(
case c of -- change the availability of the number of strikes field based on the source selection
(
1: ()
2: ()
3: (if (classof dst==Editable_mesh) or (classof dst==Editable_poly) then () else (dstrad.state=1))
4: (if (classof dst==Editable_mesh) or (classof dst==Editable_poly) then () else (dstrad.state=1))
)
)
on useXYZ changed c do changexyzstate c
on lightningparam open do -- on opening the rollout
(
-- this routine takes the first two geometry objects in the selection adn makes the source and destina ion on opening.
x=1
for i=1 to selection.count do
(
if superclassof selection[i]==GeometryClass then
(
src=selection[i]
srcbtn.text=src.name
x=i
exit
)
)
for i=x+1 to selection.count do
(
if superclassof selection[i]==GeometryClass then
(
dst=selection[i]
dstbtn.text=dst.name
exit
)
)
)
on create pressed do -- lets make some lightning!
(
-- calculate the source. This routine calculates the source positions, based on the souce object, the souce objects sub object seleciot, and the number of strikes
if src==undefined then messagebox "Please pick a source object!" beep:true else
(
srcs=#()
case srcrad.state of
(
1: for i=1 to nstrk.value do append srcs src.pos -- the pivot of the source object.
2: ( -- within the volume of the object.
for i=1 to nstrk.value do
(
v=src.max-src.min
r=random [0,0,0] v
append srcs (r-(v/2)+src.center)
)
)
3: ( -- the sub object selected faces.
fsel=for i in src.selectedFaces collect i.index -- collect the selected faces.
for i in fsel do -- collect the face centers...
(
case (classof src) of
(
Editable_mesh : append srcs (meshop.getfacecenter src i) -- of a mesh object.
Editable_Poly : append srcs (polyop.getfacecenter src i) -- of a poly object.
)
)
)
4: ( -- the sub object selected vertices.
vsel=for i in src.selectedverts collect i.index -- collect the selected vertices.
for i in vsel do -- collect the vertex positions...
(
case (classof src) of
(
Editable_mesh : append srcs (meshop.getvert src i) -- of a mesh object.
Editable_Poly : append srcs (polyop.getvert src i) -- of a poly object.
)
)
)
)
-- set destinations
dest=#()
if useXYZ.state then -- test to see if the destination is an object, or a position.
(
append dest [dstx.value,dsty.value,dstz.value]
) else
(
if dst==undefined then -- test to see is the destination is properly defined
(
useXYZ.state=true
changexyzstate true
append dest [dstx.value,dsty.value,dstz.value]
) else
(
case dstrad.state of
(
1: append dest dst.pos
2: ( -- within the volume of the object.
for i=1 to srcs.count do
(
v=dst.max-dst.min
r=random [0,0,0] v
append dest (r-(v/2)+dst.center)
)
)
3: ( -- the sub object selected faces.
fsel=for i in dst.selectedFaces collect i.index -- collect the selected faces.
for i in fsel do -- collect the face centers...
(
case (classof dst) of
(
Editable_mesh : append dset (meshop.getfacecenter dst i) -- of a mesh object.
Editable_Poly : append dest (polyop.getfacecenter dst i) -- of a poly object.
)
)
)
4: ( print "getting destination verts" -- the sub object selected vertices.
vsel=for i in dst.selectedverts collect i.index -- collect the selected vertices.
for i in vsel do -- collect the vertex positions...
(
case (classof dst) of
(
Editable_mesh : append dest (meshop.getvert dst i) -- of a mesh object.
Editable_Poly : append dest (polyop.getvert dst i) -- of a poly object.
)
)
)
)
)
)
if dest.count==0 then append dest dst.pos
for i=1 to srcs.count do
(
s=random 1 dest.count -- set s random destination
l=createlightningpoints srcs[i] dest[s] mnlne.value mxlne.value admlt.value mcdir.value 0 -- create main streak
-- create forks
flgng=#()
append flgng l
x=1
for j=1 to fdpth.value do
(
for k=1 to flgng.count do
(
for gf=1 to flgng[k].count do
(
r=random 0 100
if r<fchnc.value then -- random chance of forks
(
s=random 1 dest.count -- set s random destination
f=createlightningpoints l[gf] dest[s] mnlne.value mxlne.value admlt.value mcdir.value fterm.value
append flgng f
)
x+=1
)
)
)
lshp=#()
for j=1 to flgng.count do
(
lightn=splineshape() -- create the lightning shagpe
append lshp lightn
createlightning lightn flgng[j] -- create the shape
updateshape lightn
lightn.render_renderable=true
lightn.render_auto_smooth=true
lightn.render_thickness=(lrads.value)/j
lightn.render_sides=lsdes.value
lightn.render_displayRenderMesh=sivpt.state
)
group lshp name:"lightning"
)
)
)
)
newroll=newrolloutfloater "Tangra - Lightning Generator" 270 577 850 120
addrollout lightningparam newroll
Ok, I forgot that I used some MAX7.5 code (must be from the VIZ extensions), so here is a version for Max6, 7
Download here
--------------------------------------------------------------------------------
-- Tangra - Lightning generator.
-- version 1.0b
-- max version 7.5
-- written by Joshua Newman
-- www.joshuanewman.net
-- last updated 12/07/05
-- copyright 2005
--------------------------------------------------------------------------------
--
-- Minimum usage, select a source object and press create.
-- Best practice : use a target that is an Epoly, and select faces or vertices in the sub object for great results.
-- Be careful not to have too many strikes or a chance of fork that is too high as this will result in many lightning streaks!
-- Also be careful not to have too many faces or vertices from the source, as each selection will create a lightning strike.
-- Maximum and minimum air density controls the length of any line segment.
-- VARIABLES
global src, dst, lightning, srcs,dest
-- FUNCTIONS
fn createlightningpoints srcpos destpos lenMx lenMn lnmlt dir term= -- source, destination min, destination max, min density, max density, density multiplier, random direction, -- this function generates the lightning points
(
lpnts=#() -- initiate an array
lenmx*=lnmlt -- multiply the length max by the air density multiplier
lenmn*=lnmlt -- multiply the length min by the air density multiplier
-- calculate the average target
if srcpos==undefined then srcpos=[0,0,0]
endstreak=true
x=distance srcpos destpos -- set x as the average target distance
append lpnts srcpos
while x>lenMx do
(
v=normalize (destpos-lpnts[lpnts.count]) -- the basic direction of the lightning
d=random lenMn lenMx -- the random length of the stroke
t=random [(dir*-1./360),(dir*-1./360),(dir*-1./360)] [(dir*1./360),(dir*1./360),(dir*1./360)] -- the random direction
newpnt=(v+t)*d+lpnts[lpnts.count] -- the new point!
append lpnts newpnt -- collect the new point
x=distance newpnt destpos -- test to see how close to the target we are
if (random 0 100)<term then (x=0;endstreak=false)
)
if endstreak then append lpnts destpos
return lpnts
)
fn createlightning lightning varray= -- this function draws the strike
(
s=addnewspline lightning -- add a new spline to the lightning
for i=1 to varray.count do addknot lightning s #corner #line varray[i] -- add the first knot
return lightning
)
-- ROLLOUTS
rollout lightningparam "Parameters."
(
fn geofilter obj=superclassof obj==geometryclass
group "Source."
(
pickbutton srcbtn "Source object." width:245 filter:geofilter
radiobuttons srcrad "" labels:#("pivot","volume","faces","vertices")
)
group "Target."
(
pickbutton dstbtn "Destination object." width:245 filter:geofilter
radiobuttons dstrad "" labels:#("pivot","volume","faces","vertices")
checkbox useXYZ "Use co-ordinates:"
spinner dstx "X:" type:#float range:[-1000000,1000000,0] width:80 across:3 offset:[-20,0] enabled:false
spinner dsty "Y:" type:#float range:[-1000000,1000000,0] width:80 offset:[-15,0] enabled:false
spinner dstz "Z:" type:#float range:[-1000000,1000000,0] width:80 offset:[-10,0] enabled:false
)
group "Parameters."
(
spinner nstrk "Numer of strikes" type:#integer range:[1,100000,1]
spinner mxlne "Maximum air density" type:#float range:[1,10000000,1]
spinner mnlne "Minimun air density" type:#float range:[1,10000000,10]
spinner admlt "Air density multiplier" type:#float range:[1,10000000,1]
spinner mcdir "Change in direction" type:#float range:[0,360,200]
spinner fdpth "Fork depth" type:#integer range:[1,10,2]
spinner fchnc "Chance of fork" type:#float range:[0,100,2]
spinner fterm "Fork termination" type:#float range:[0,100,10]
)
group "Rendering."
(
spinner lrads "Thickness" type:#float range:[0,1000,1]
-- spinner lvars "Variation %" type:#float range:[0,100,0]
spinner lsdes "Sides" type:#float range:[1,1000,8]
checkbox sivpt "Show in viewport:" checked:true
)
/*
group "Material."
(
colorpicker lgnclr "Lightning colour" align:#right
)
group "Animate lightning"
(
)
group "Glow."
(
checkbox glw "Add glow effect" checked:true
)
*/
button create "Create!" width:250 height:50
label l1 "\xa9 2003 Joshua Newman" across:2 offset:[-8,0]
hyperlink hl "www.joshuanewman.net" address:"www.joshuanewman.net" offset:[4,0]
-- ROLLOUT LOCAL FUNCTIONS
fn changexyzstate c= -- this changes the enabled state of the destination UI elements.
(
if c then
(
dstbtn.enabled=false
dstrad.enabled=false
dstx.enabled=true
dsty.enabled=true
dstz.enabled=true
) else
(
dstbtn.enabled=true
dstrad.enabled=true
dstx.enabled=false
dsty.enabled=false
dstz.enabled=false
)
)
-- ROLLOUT EVENTS
on srcbtn picked c do -- pick source
(
srcbtn.text=c.name
src=c
)
on dstbtn picked c do -- pick destintion
(
dstbtn.text=c.name
dst=c
)
on srcrad changed c do
(
case c of -- change the availability of the number of strikes field based on the source selection
(
1: nstrk.enabled=true
2: nstrk.enabled=true
3: (if (classof src==Editable_mesh) or (classof src==Editable_poly) then (nstrk.enabled=false) else (srcrad.state=1))
4: (if (classof src==Editable_mesh) or (classof src==Editable_poly) then (nstrk.enabled=false) else (srcrad.state=1))
)
)
on dstrad changed c do
(
case c of -- change the availability of the number of strikes field based on the source selection
(
1: ()
2: ()
3: (if (classof dst==Editable_mesh) or (classof dst==Editable_poly) then () else (dstrad.state=1))
4: (if (classof dst==Editable_mesh) or (classof dst==Editable_poly) then () else (dstrad.state=1))
)
)
on useXYZ changed c do changexyzstate c
on lightningparam open do -- on opening the rollout
(
-- this routine takes the first two geometry objects in the selection adn makes the source and destina ion on opening.
x=1
for i=1 to selection.count do
(
if superclassof selection[i]==GeometryClass then
(
src=selection[i]
srcbtn.text=src.name
x=i
exit
)
)
for i=x+1 to selection.count do
(
if superclassof selection[i]==GeometryClass then
(
dst=selection[i]
dstbtn.text=dst.name
exit
)
)
)
on create pressed do -- lets make some lightning!
(
-- calculate the source. This routine calculates the source positions, based on the souce object, the souce objects sub object seleciot, and the number of strikes
if src==undefined then messagebox "Please pick a source object!" beep:true else
(
srcs=#()
case srcrad.state of
(
1: for i=1 to nstrk.value do append srcs src.pos -- the pivot of the source object.
2: ( -- within the volume of the object.
for i=1 to nstrk.value do
(
v=src.max-src.min
r=random [0,0,0] v
append srcs (r-(v/2)+src.center)
)
)
3: ( -- the sub object selected faces.
fsel=for i in src.selectedFaces collect i.index -- collect the selected faces.
for i in fsel do -- collect the face centers...
(
case (classof src) of
(
Editable_mesh : append srcs (meshop.getfacecenter src i) -- of a mesh object.
Editable_Poly : append srcs (polyop.getfacecenter src i) -- of a poly object.
)
)
)
4: ( -- the sub object selected vertices.
vsel=for i in src.selectedverts collect i.index -- collect the selected vertices.
for i in vsel do -- collect the vertex positions...
(
case (classof src) of
(
Editable_mesh : append srcs (meshop.getvert src i) -- of a mesh object.
Editable_Poly : append srcs (polyop.getvert src i) -- of a poly object.
)
)
)
)
-- set destinations
dest=#()
if useXYZ.state then -- test to see if the destination is an object, or a position.
(
append dest [dstx.value,dsty.value,dstz.value]
) else
(
if dst==undefined then -- test to see is the destination is properly defined
(
useXYZ.state=true
changexyzstate true
append dest [dstx.value,dsty.value,dstz.value]
) else
(
case dstrad.state of
(
1: append dest dst.pos
2: ( -- within the volume of the object.
for i=1 to srcs.count do
(
v=dst.max-dst.min
r=random [0,0,0] v
append dest (r-(v/2)+dst.center)
)
)
3: ( -- the sub object selected faces.
fsel=for i in dst.selectedFaces collect i.index -- collect the selected faces.
for i in fsel do -- collect the face centers...
(
case (classof dst) of
(
Editable_mesh : append dset (meshop.getfacecenter dst i) -- of a mesh object.
Editable_Poly : append dest (polyop.getfacecenter dst i) -- of a poly object.
)
)
)
4: ( print "getting destination verts" -- the sub object selected vertices.
vsel=for i in dst.selectedverts collect i.index -- collect the selected vertices.
for i in vsel do -- collect the vertex positions...
(
case (classof dst) of
(
Editable_mesh : append dest (meshop.getvert dst i) -- of a mesh object.
Editable_Poly : append dest (polyop.getvert dst i) -- of a poly object.
)
)
)
)
)
)
if dest.count==0 then append dest dst.pos
for i=1 to srcs.count do
(
s=random 1 dest.count -- set s random destination
l=createlightningpoints srcs[i] dest[s] mnlne.value mxlne.value admlt.value mcdir.value 0 -- create main streak
-- create forks
flgng=#()
append flgng l
x=1
for j=1 to fdpth.value do
(
for k=1 to flgng.count do
(
for gf=1 to flgng[k].count do
(
r=random 0 100
if r<fchnc.value then -- random chance of forks
(
s=random 1 dest.count -- set s random destination
f=createlightningpoints l[gf] dest[s] mnlne.value mxlne.value admlt.value mcdir.value fterm.value
append flgng f
)
x+=1
)
)
)
lshp=#()
for j=1 to flgng.count do
(
lightn=splineshape() -- create the lightning shagpe
append lshp lightn
createlightning lightn flgng[j] -- create the shape
updateshape lightn
-- lightn.render_renderable=true
lightn.baseobject.renderable=true
-- lightn.render_auto_smooth=true
-- lightn.render_thickness=(lrads.value)/j
lightn.thickness=(lrads.value)/j
-- lightn.render_sides=lsdes.value
lightn.sides=lsdes.value
-- lightn.render_displayRenderMesh=sivpt.state
lightn.displayRenderSettings=true
)
group lshp name:"lightning"
)
)
)
)
newroll=newrolloutfloater "Tangra - Lightning Generator" 270 577 850 120
addrollout lightningparam newroll