-- ----------------------------------------------------------------------- -- MAXScript VerletClothSim -- ----------------------------------------------------------------------- -- AUTHOR: Markus Boos -- EMAIL: relief7@projectgemini.net -- COMPANY: project|gemini -- COPYRIGHT: 2007-2010. Markus Boos. All Rights Reserved -- CREATION DATE: 2009-08-02 (YYYY-MM-DD) -- ----------------------------------------------------------------------- -- - basic setup for simulating a cloth using springs and constraints -- - dynamics calculation using verlet integrator without velocity -- - also supports rigid body dynamics (just translation, no orientation) -- - no support for collisions /* ------------------------ VERSION HISTORY ------------------------ Version 0.1 09/08/02 - Markus Boos - relief7@projectgemini.net -basic setup ------------------------ BUGS / KNOWN ISSUES ------------------------ - works only with editable poly objects, values have to be initialized manually --------------------------------------------------- TODO ITEMS / WISHLIST --------------------------------------------------- - */ ( -- constants local fTimeStep = 1.0 / 20.0 local iSubSteps = 4 local iConstraintIterations = 6 local fDragVerlet = 0.0005 ---------------------------------- -- SIM OBJECTS ---------------------------------- struct SimObjectVertex ( obj, fMass, vPos, vPrevPos, vVelocity, vResultantForce, iType, -- 0: ACTIVE, 1: STATIC iVertexId, on create do ( vPos = obj.verts [ iVertexId ].pos vPrevPos = obj.verts [ iVertexId ].pos ), function update = ( -- set vertex position in editable poly here --obj.pos = vPos obj.verts [ iVertexId ].pos = vPos ), function resetForces = ( vResultantForce = [ 0, 0, 0 ] ) ) ---------------------------------- -- INTEGRATORS ---------------------------------- struct Integrator ( -- fFixedTimeStep = 1.0 / 24.0 , -- = targetelapsedTime, function integrate vAcceleration simObject = () ) struct IntegratorEulerForward ( fFixedTimeStep = fTimeStep, -- = targetelapsedTime, function integrate vAcceleration simObject = ( -- euler integration, calculate simObject.vPos += simObject.vVelocity * fFixedTimeStep simObject.vVelocity += vAcceleration * fFixedTimeStep ) ) struct IntegratorVerletNoVelocity ( fFixedTimeStep = fTimeStep, -- = targetelapsedTime, fDrag = fDragVerlet, function integrate vAcceleration simObject = ( local newPos = ( 2.0 - fDrag ) * simObject.vPos - ( 1 - fDrag ) * simObject.vPrevPos + vAcceleration * fFixedTimeStep * fFixedTimeStep simObject.vPrevPos = simObject.vPos simObject.vPos = newPos ) ) ---------------------------------- -- FORCE GENERATORS ---------------------------------- struct ForceGenerator ( function applyForce simObject = () ) struct ForceGeneratorGravity ( vAcceleration = [ 0, 0, -9.81 ], function applyForce simObject = ( simObject.vResultantForce += simObject.fMass * vAcceleration ) ) struct ForceGeneratorWind ( fStrength = 10.0, function applyForce simObject = ( local fForceX = ( sin ( currentTime as float / 20.0 + simObject.vPos.y ) ) * fStrength local vWindAccel = [ fForceX, 0, 0 ] simObject.vResultantForce += simObject.fMass * vWindAccel ) ) struct ForceGeneratorMedium ( fDragCoefficient, function applyForce simObject = ( simObject.vResultantForce -= fDragCoefficient * simObject.vVelocity ) ) struct ForceGeneratorSpring ( fStiffness, fDamping, fRestLength, simObjectA, simObjectB, function applyForce = ( -- get direction vector local vDirection = simObjectA.vPos - simObjectB.vPos -- check for 0 vector if ( vDirection != [ 0, 0, 0 ] ) then ( -- get length local fLength = length vDirection --normalize vDirection = normalize vDirection -- add spring force local vForce = -fStiffness * ( ( fLength - fRestLength ) * vDirection ) -- add spring damping force vForce += -fDamping * ( dot ( simObjectA.vVelocity - simObjectB.vVelocity ) vDirection ) * vDirection simObjectA.vResultantForce += vForce simObjectB.vResultantForce -= vForce ) ) ) ---------------------------------- -- CONSTRAINTS ---------------------------------- struct Constraint ( function satisfyConstraint = () ) struct LengthConstraint ( fLength, simObjectA, simObjectB, function satisfyConstraint = ( -- calculate direction local vDirection = simObjectB.vPos - simObjectA.vPos -- calculate length fCurrentLength = length vDirection -- check for zero vector if ( fCurrentLength != 0.0 ) then ( -- normalize direction vector vDirection = normalize vDirection -- move both ends halfway in / out to satisfy length local vMoveVector = 0.5 * ( fCurrentLength - fLength ) * vDirection simObjectA.vPos += vMoveVector simObjectB.vPos -= vMoveVector ) ) ) struct PointConstraint ( vPoint, simObject, function satisfyConstraint = ( simObject.vPos = vPoint ) ) struct AttachConstraint ( objNode, simObject, function satisfyConstraint = ( simObject.vPos = objNode.pos ) ) ---------------------------------- -- SIMULATION ---------------------------------- struct SimulationSoftBodyCloth ( arrSimObjects = #(), arrGlobalForceGenerators = #(), arrSprings = #(), arrConstraints = #(), arrSimVertices = #(), integrator, objClothPlane, iWidthSegments, iLengthSegments, function addSpring fStiffness fDamping simObjectA simObjectB = ( local f = ForceGeneratorSpring fStiffness fDamping ( length ( ( at time 0f ( simObjectA.obj.pos ) ) - ( at time 0f ( simObjectB.obj.pos ) ) ) ) simObjectA simObjectB append arrSprings f ), function addVertexSpring fStiffness fDamping simObjectA simObjectB = ( local f = ForceGeneratorSpring fStiffness fDamping ( length ( simObjectA.vPos - simObjectB.vPos ) ) simObjectA simObjectB append arrSprings f ), function addSimObject simObject = ( append arrSimObjects simObject ), function addGlobalForceGenerator f = ( append arrGlobalForceGenerators f ), function update = ( --format "updating simulation...\n" for spring in arrSprings do ( spring.applyForce () ) for simObject in arrSimObjects do ( -- if object type is ACTIVE if ( simObject.iType == 0 ) then ( for fg in arrGlobalForceGenerators do ( fg.applyForce simObject ) ) ) for simObject in arrSimObjects do ( if ( simObject.iType == 0 ) then ( -- find acceleration local vAcceleration = simObject.vResultantForce / simObject.fMass -- integrate integrator.integrate vAcceleration simObject ) ) -- satisfy constraints for i = 1 to iConstraintIterations do ( for constraint in arrConstraints do ( constraint.satisfyConstraint () ) ) -- then update sim objects and reset forces to 0) for simObject in arrSimObjects do ( simObject.update () ) for simObject in arrSimObjects do ( if ( simObject.iType == 0 ) then ( simObject.resetForces () ) ) ), function createSimVertices obj fClothMass = ( local iNumVertices = obj.numVerts local fVertexMass = fClothMass / iNumVertices for i = 1 to iNumVertices do ( local v = ( SimObjectVertex obj fVertexMass ( obj.verts [i].pos ) ( obj.verts [i].pos ) [ 0, 0, 0 ] [ 0, 0, 0 ] 0 i ) append arrSimVertices v addSimObject ( arrSimVertices [ i ] ) ) ), function connectSprings fStructStiffness fStructDamping fShearStiffness fShearDamping fBendStiffness fBendDamping = ( --clearListener () -- structural springs horizontal for y = 1 to iLengthSegments + 1 do ( for x = 1 to iWidthSegments do ( -- structural springs, along mesh edges, horizontal local iVertexAId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x ) local iVertexBId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x + 1 ) -- pattern x y A B -- 1 1 1 2 -- 1 2 2 3 -- 1 3 3 4 -- 1 4 4 5 -- 2 1 6 7 -- 2 2 7 8 ... -- format "Y %, X %: %-%\n" y x iVertexAId iVertexBId addVertexSpring fStructStiffness fStructDamping arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] local fLength = length ( objClothPlane.verts [ iVertexAId ].pos - objClothPlane.verts [ iVertexBId ].pos ) append arrConstraints ( LengthConstraint fLength arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] ) ) ) -- structural springs vertical for y = 1 to iLengthSegments do ( for x = 1 to iWidthSegments + 1 do ( -- structural springs, along mesh edges, vertical local iVertexAId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x ) local iVertexBId = ( y ) * ( iWidthSegments + 1 ) + ( x ) -- pattern x y A B -- 1 1 1 6 -- 1 2 2 7 -- 1 3 3 8 -- 1 4 4 9 -- 1 5 5 10 -- 2 1 6 11 -- 2 2 7 12 ... -- format "Y %, X %: %-%\n" y x iVertexAId iVertexBId addVertexSpring fStructStiffness fStructDamping arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] local fLength = length ( objClothPlane.verts [ iVertexAId ].pos - objClothPlane.verts [ iVertexBId ].pos ) append arrConstraints ( LengthConstraint fLength arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] ) ) ) -- shear springs diagonal 1 for y = 1 to iLengthSegments do ( for x = 1 to iWidthSegments do ( -- structural springs, along mesh edges, vertical local iVertexAId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x ) local iVertexBId = ( y ) * ( iWidthSegments + 1 ) + ( x + 1 ) -- pattern x y A B -- 1 1 1 7 -- 1 2 2 8 -- 1 3 3 9 -- 1 4 4 10 -- 2 1 6 12 -- 2 2 7 13 ... -- format "Y %, X %: %-%\n" y x iVertexAId iVertexBId addVertexSpring fShearStiffness fShearDamping arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] local fLength = length ( objClothPlane.verts [ iVertexAId ].pos - objClothPlane.verts [ iVertexBId ].pos ) append arrConstraints ( LengthConstraint fLength arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] ) ) ) -- shear springs diagonal 2 for y = 1 to iLengthSegments do ( for x = 1 to iWidthSegments do ( -- structural springs, along mesh edges, vertical local iVertexAId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x ) + 1 local iVertexBId = ( y ) * ( iWidthSegments + 1 ) + x -- pattern x y A B -- 1 1 2 6 -- 1 2 3 7 -- 1 3 4 8 -- 1 4 5 9 -- 2 1 7 11 -- 2 2 8 12 ... -- format "Y %, X %: %-%\n" y x iVertexAId iVertexBId addVertexSpring fShearStiffness fShearDamping arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] local fLength = length ( objClothPlane.verts [ iVertexAId ].pos - objClothPlane.verts [ iVertexBId ].pos ) append arrConstraints ( LengthConstraint fLength arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] ) ) ) -- bend springs horizontal (every other) for y = 1 to iLengthSegments + 1 do ( for x = 1 to iWidthSegments - 1 do ( -- structural springs, along mesh edges, horizontal local iVertexAId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x ) local iVertexBId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x + 2 ) -- pattern x y A B -- 1 1 1 3 -- 1 2 2 4 -- 1 3 3 5 -- 2 1 6 8 -- 2 2 7 9 ... -- format "Y %, X %: %-%\n" y x iVertexAId iVertexBId addVertexSpring fBendStiffness fBendDamping arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] local fLength = length ( objClothPlane.verts [ iVertexAId ].pos - objClothPlane.verts [ iVertexBId ].pos ) append arrConstraints ( LengthConstraint fLength arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] ) ) ) -- bend springs vertical (every other) for y = 1 to iLengthSegments - 1 do ( for x = 1 to iWidthSegments + 1 do ( -- structural springs, along mesh edges, horizontal local iVertexAId = ( y - 1 ) * ( iWidthSegments + 1 ) + ( x ) local iVertexBId = ( y + 1 ) * ( iWidthSegments + 1 ) + ( x ) -- pattern x y A B -- 1 1 1 11 -- 1 2 2 12 -- 1 3 3 13 -- 1 4 4 14 -- 1 5 5 15 -- 2 1 6 16 -- 2 2 7 17 ... -- format "Y %, X %: %-%\n" y x iVertexAId iVertexBId addVertexSpring fBendStiffness fBendDamping arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] local fLength = length ( objClothPlane.verts [ iVertexAId ].pos - objClothPlane.verts [ iVertexBId ].pos ) append arrConstraints ( LengthConstraint fLength arrSimVertices [ iVertexAId ] arrSimVertices [ iVertexBId ] ) ) ) ), function initClothSim fClothMass fStructStiffness fStructDamping fShearStiffness fShearDamping fBendStiffness fBendDamping iWSegments iLSegments = ( iWidthSegments = iWSegments iLengthSegments = iLSegments createSimVertices objClothPlane fClothMass connectSprings fStructStiffness fStructDamping fShearStiffness fShearDamping fBendStiffness fBendDamping ) ) ---------------------------------- -- INITIALIZATION ---------------------------------- local integrator = IntegratorVerletNoVelocity () local f1 = ForceGeneratorGravity () local f2 = ForceGeneratorWind () -- cloth plane local iWidthSegments = 7 local iLengthSegments = 5 -- cloth properties local fClothMass = 2.0 local fStructStiffness = 2.0 local fStructDamping = 0.02 local fShearStiffness = 2.0 local fShearDamping = 0.02 local fBendStiffness = 2.0 local fBendDamping = 0.02 -- setup sim local simCloth = SimulationSoftBodyCloth #( ) #( ) #( ) #( ) #( ) integrator $Plane01 simCloth.initClothSim fClothMass fStructStiffness fStructDamping fShearStiffness fShearDamping fBendStiffness fBendDamping iWidthSegments iLengthSegments -- add force generators simCloth.addGlobalForceGenerator f1 simCloth.addGlobalForceGenerator f2 -- constrain cloth to helpers local c1 = AttachConstraint $Dummy01 simCloth.arrSimVertices [1] local c2 = AttachConstraint $Dummy02 simCloth.arrSimVertices [8] append simCloth.arrConstraints c1 append simCloth.arrConstraints c2 -- CALCULATION -- clock to measure calc time local clockStart = timeStamp() local fFrameStart = ( animationRange.start + 1f ) local fFrameEnd = animationRange.end for iFrame = fFrameStart to fFrameEnd do ( if keyboard.escPressed then exit animate on ( sliderTime = iFrame ( for i = 1 to iSubSteps do ( simCloth.update () ) ) ) ) local clockEnd = timeStamp() format "Processing took % ms.\n" ( ( clockEnd - clockStart ) ) )