///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // // GeneralParticleSystem // ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// #include "CKAll.h" #include "GeneralParticleSystem.h" #define INACTIVE 0 #define ACTIVE 1 #define FREEZED 2 #ifdef USE_THR // ACC - July 10,2002 BlockingQueue PSqueue(40); FILE *ACCLOG; VxMutex logguard; #endif #include const CKSTRING RadialParticleOffsetStr = "Radial particle offset"; CKERROR CreateGeneralParticleSystemProto(CKBehaviorPrototype **); int GeneralParticleSystem(const CKBehaviorContext& behcontext); CKERROR GeneralParticleSystemCallback(const CKBehaviorContext& behcontext); void EmitterSetMesh(BOOL Set,CKGUID guid,CKBehavior* beh,ParticleEmitter *em); CKERROR CreateGeneralParticleSystemProto(CKBehaviorPrototype **pproto) { CKBehaviorPrototype *proto = CreateCKBehaviorPrototype(ParticleSystemsName); if(!proto) return CKERR_OUTOFMEMORY; proto->DeclareInput("On"); proto->DeclareInput("Off"); proto->DeclareInput("Freeze"); proto->DeclareOutput("Exit On"); proto->DeclareOutput("Exit Off"); proto->DeclareOutput("Exit Freeze"); proto->DeclareInParameter("Emission Delay",CKPGUID_TIME,"0m 0s 200ms"); proto->DeclareInParameter("Emission Delay Variance",CKPGUID_TIME,"0m 0s 0ms"); proto->DeclareInParameter("Yaw Variance",CKPGUID_ANGLE,"0:20"); proto->DeclareInParameter("Pitch Variance",CKPGUID_ANGLE,"0:20"); proto->DeclareInParameter("Speed",CKPGUID_FLOAT,"0.005"); proto->DeclareInParameter("Speed Variance",CKPGUID_FLOAT,"0.001"); proto->DeclareInParameter("Angular Speed/Spreading",CKPGUID_FLOAT,"0.0"); proto->DeclareInParameter("Angular Speed Variance/Spreading Variation",CKPGUID_FLOAT,"0.0"); proto->DeclareInParameter("Lifespan",CKPGUID_TIME,"0m 1s 0ms"); proto->DeclareInParameter("Lifespan Variance",CKPGUID_TIME,"0m 0s 250ms"); proto->DeclareInParameter("Maximum Number",CKPGUID_INT,"100"); proto->DeclareInParameter("Emission",CKPGUID_INT,"10"); proto->DeclareInParameter("Emission Variance",CKPGUID_INT,"5"); proto->DeclareInParameter("Initial Size",CKPGUID_FLOAT,"1.0"); proto->DeclareInParameter("Initial Size Variance",CKPGUID_FLOAT,"0.0"); proto->DeclareInParameter("Ending Size",CKPGUID_FLOAT,"0.1"); proto->DeclareInParameter("Ending Size Variance",CKPGUID_FLOAT,"0.0"); proto->DeclareInParameter("Bounce",CKPGUID_FLOAT,"0.8"); proto->DeclareInParameter("Bounce Variance",CKPGUID_FLOAT,"0"); proto->DeclareInParameter("Weight",CKPGUID_FLOAT,"1.0"); proto->DeclareInParameter("Weight Variance",CKPGUID_FLOAT,"0.0"); proto->DeclareInParameter("Surface",CKPGUID_FLOAT,"1.0"); proto->DeclareInParameter("Surface Variance",CKPGUID_FLOAT,"0.0"); proto->DeclareInParameter("Initial Color and Alpha",CKPGUID_COLOR,"255,255,255,255"); proto->DeclareInParameter("Variance",CKPGUID_COLOR,"0,0,0,0"); proto->DeclareInParameter("Ending Color and Alpha",CKPGUID_COLOR,"0,0,0,0"); proto->DeclareInParameter("Variance",CKPGUID_COLOR,"0,0,0,0"); proto->DeclareInParameter("Texture",CKPGUID_TEXTURE); proto->DeclareInParameter("Initial Texture Frame",CKPGUID_INT,"0"); proto->DeclareInParameter("Initial Texture Frame Variance",CKPGUID_INT,"0"); proto->DeclareInParameter("Texture Speed",CKPGUID_INT,"100"); proto->DeclareInParameter("Texture Speed Variance",CKPGUID_INT,"20"); proto->DeclareInParameter("Texture Frame Count",CKPGUID_INT,"1"); proto->DeclareInParameter("Texture Loop",CKPGUID_LOOPMODE,"No Loop"); proto->DeclareInParameter("Start Time",CKPGUID_TIME,"0m 0s 0ms"); proto->DeclareLocalParameter("Emitter",CKPGUID_VOIDBUF); proto->DeclareLocalParameter("Activity",CKPGUID_INT); proto->DeclareLocalParameter("EmissionTime",CKPGUID_FLOAT,"0"); // Settings proto->DeclareSetting("Maximum Number",CKPGUID_INT,"100"); proto->DeclareSetting("Particle Rendering",CKPGUID_RENDERMODES,"3"); proto->DeclareSetting("Source Blend",CKPGUID_BLENDFACTOR,"Source Alpha"); proto->DeclareSetting("Destination Blend",CKPGUID_BLENDFACTOR,"One"); proto->DeclareSetting("Objects",CKPGUID_GROUP); proto->DeclareSetting("Evolutions",CKPGUID_EVOLUTIONS,"Color,Size,Texture"); proto->DeclareSetting("Variances",CKPGUID_VARIANCES,"Speed,Angular Speed,Lifespan,Emission,Initial Size,Ending Size,Bounce,Weight,Surface,Initial Color,Ending Color,Initial Texture,Texture Speed"); proto->DeclareSetting("Manage Deflectors",CKPGUID_DEFLECTORS,"Plane,Infinite Plane,Cylinder,Sphere,Box,Object"); proto->DeclareSetting("Message To Deflectors",CKPGUID_MESSAGE,"NULL"); proto->DeclareSetting("Manage Interactors",CKPGUID_INTERACTORS,"Gravity,Global Wind,Local Wind,Magnet,Vortex,Disruption Box,Mutation Box,Atmosphere,Tunnel,Projector"); proto->DeclareSetting("Interactors/Deflectors Display",CKPGUID_BOOL,"TRUE"); proto->DeclareSetting("Output Particle Count",CKPGUID_BOOL,"FALSE"); proto->DeclareSetting("Trail Particle Count",CKPGUID_INT,"0"); proto->DeclareSetting("Visible In Pause",CKPGUID_BOOL,"FALSE"); proto->DeclareSetting(RadialParticleOffsetStr, CKPGUID_FLOAT, "0"); proto->SetFlags(CK_BEHAVIORPROTOTYPE_NORMAL); proto->SetFunction(GeneralParticleSystem); proto->SetBehaviorCallbackFct( GeneralParticleSystemCallback ); proto->SetBehaviorFlags((CK_BEHAVIOR_FLAGS)(CKBEHAVIOR_INTERNALLYCREATEDOUTPUTPARAMS|CKBEHAVIOR_INTERNALLYCREATEDINPUTS|CKBEHAVIOR_INTERNALLYCREATEDOUTPUTS)); *pproto = proto; return CK_OK; } // Helper function : test if both the script and the target behavioral object are active in the current scene static inline bool AreScriptAndTargetActiveInScene(const CKBehaviorContext& behcontext) { CKBehavior* beh = behcontext.Behavior; if (beh->IsParentScriptActiveInScene(behcontext.CurrentScene) && beh->IsActive()) { if (behcontext.CurrentScene->GetObjectFlags(beh->GetTarget()) & CK_SCENEOBJECT_ACTIVE) { return true; } } return false; } //// // Function to warm up the particles before the actual start of rendering // thanx to Daniel Fournier from Microids Canada void WarmUpParticles(const CKBehaviorContext& behcontext) { CKBehavior* beh = behcontext.Behavior; float startTime = 0; beh->GetInputParameterValue(STARTTIME,&startTime); if (startTime < EPSILON) // No warmup to do return; CKContext* ctx = behcontext.Context; CKGUID guid = beh->GetPrototypeGuid(); // We get the emitter ParticleEmitter *pe = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!pe) return; CK3dEntity* entity = (CK3dEntity*)ctx->GetObject(pe->m_Entity); // We get the activity // Reading Inputs pe->ReadInputs(beh); // shape of emitter object if(guid == OBJECTSYSTEM_GUID) { ((ObjectEmitter*)pe)->m_Shape = entity->GetCurrentMesh(); } // We create new particles only if we are active float emissiondelay = 0.0f; beh->GetInputParameterValue(EMISSIONDELAY,&emissiondelay); float deltaTime = 20.0f; // we start cum at the emission delay to have right at the start something for (float time = 0.f, cum = emissiondelay; time < startTime; time += deltaTime) { if( emissiondelay > EPSILON ) { // linked with time while (cum >= emissiondelay) { pe->AddParticles(); cum -= emissiondelay; } cum += deltaTime; } else { // Not linked with time pe->AddParticles(); } // We update the particles (position, color, size...) pe->UpdateParticles(deltaTime); } } void ShowParticles(CKBehavior* beh, CKBOOL show) { CKGUID guid = beh->GetPrototypeGuid(); // We get the emitter ParticleEmitter *pe = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!pe) return; // we now set the good mesh of the emitter if (show) EmitterSetMesh(FALSE,guid,beh,pe); CK3dEntity* entity = (CK3dEntity*)beh->GetCKObject(pe->m_Entity); if (!entity) return; entity->RemovePostRenderCallBack(RenderParticles_P,pe); entity->RemovePostRenderCallBack(RenderParticles_L,pe); entity->RemovePostRenderCallBack(RenderParticles_LL,pe); entity->RemovePostRenderCallBack(RenderParticles_S,pe); entity->RemovePostRenderCallBack(RenderParticles_LS,pe); entity->RemovePostRenderCallBack(RenderParticles_O,pe); entity->RemovePostRenderCallBack(RenderParticles_FS,pe); entity->RemovePostRenderCallBack(RenderParticles_OS,pe); entity->RemovePostRenderCallBack(RenderParticles_LOS,pe); entity->RemovePostRenderCallBack(RenderParticles_CS,pe); entity->RemovePostRenderCallBack(RenderParticles_RS,pe); entity->RemovePostRenderCallBack(RenderParticles_PS,pe); entity->RemovePostRenderCallBack(RenderParticles_FPS,pe); if (show) { entity->AddPostRenderCallBack(pe->m_RenderParticlesCallback, pe ); } } #ifdef USE_THR // ACC - July 10, 2002 int UpdateParticleSystemEnqueue(ParticleEmitter* aPE, float aDeltaTime) { ThreadParam ThrParam; ThrParam.pe = aPE; ThrParam.DeltaTime = aDeltaTime; ResetEvent(aPE->hasBeenComputedEvent); aPE->hasBeenEnqueud = true; PSqueue.Add(ThrParam); return 0; } HANDLE hPSthread; DWORD WINAPI PSWorkerThreadFunc(LPVOID junk) { // Forever loop for(;;){ #ifdef MT_VERB { VxMutexLock lock (logguard); fprintf(ACCLOG, "About to remove Thread param: count=%d\n", PSqueue.NumItems()); fflush(ACCLOG); } #endif // This can block when queue is empty ThreadParam currParam = PSqueue.Remove(); ParticleEmitter* pe = currParam.pe; CKBehavior* beh = currParam.pe->m_Behavior; #ifdef MT_VERB { VxMutexLock lock (logguard); fprintf(ACCLOG, "Removed a Thread param: count=%d\n", PSqueue.NumItems()); fflush(ACCLOG); } #endif //CKContext* ctx = beh->GetCKContext(); //ctx->OutputToConsoleEx( pe->UpdateParticles(currParam.DeltaTime); if (pe->m_CurrentImpact < pe->m_Impacts.Size()) { beh->SetOutputParameterValue(0,&pe->m_Impacts[pe->m_CurrentImpact].m_Position); beh->SetOutputParameterValue(1,&pe->m_Impacts[pe->m_CurrentImpact].m_Direction); beh->SetOutputParameterObject(2,pe->m_Impacts[pe->m_CurrentImpact].m_Object); beh->SetOutputParameterValue(3,&pe->m_Impacts[pe->m_CurrentImpact].m_UVs); pe->m_CurrentImpact++; beh->ActivateOutput(3); } pe->hasBeenEnqueud = false; SetEvent(pe->hasBeenComputedEvent); #ifdef MT_VERB { VxMutexLock lock (logguard); fprintf(ACCLOG, "Setevent pe=%p\n", pe); fflush(ACCLOG); } #endif } } #endif // USE_THR int GeneralParticleSystem(const CKBehaviorContext& behcontext) { CKBehavior* beh = behcontext.Behavior; CKContext* ctx = behcontext.Context; CKGUID guid = beh->GetPrototypeGuid(); // We get the emitter ParticleEmitter *pe = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!pe) return CKBR_PARAMETERERROR; pe->m_Behavior = beh; pe->hasBeenRendered = false; //initialize every frame CK3dEntity* entity = (CK3dEntity*)ctx->GetObject(pe->m_Entity); // We get the activity int activity = 0; beh->GetLocalParameterValue(1,&activity); if (beh->IsInputActive(3)) { beh->ActivateInput(3,FALSE); } else { // Freezed Input if (beh->IsInputActive(2)) { beh->ActivateInput(2,FALSE); beh->ActivateOutput(2); // it it was reezed, we unfreeze it if(activity & FREEZED) activity &= ~FREEZED; else activity |= FREEZED; } else { // Activate input if (beh->IsInputActive(0)) { beh->ActivateInput(0,FALSE); beh->ActivateOutput(0); #ifdef USE_THR // ACC, This is the point to create function static bool isThreadCreated = false; if(!isThreadCreated){ // Hijack this to create logfile ACCLOG = fopen("\\acclog.txt", "w"); assert(ACCLOG != NULL); hPSthread = CreateThread(NULL, NULL, PSWorkerThreadFunc, NULL, 0, NULL); // w2k/xp specific isThreadCreated=true; } #endif // we write the emission time to 0 float emissiontime = 0.0f; beh->GetInputParameterValue(EMISSIONDELAY,&emissiontime); // we init the time with the delay to have one particle emitted at the activation beh->SetLocalParameterValue(2,&emissiontime); WarmUpParticles(behcontext); activity |= ACTIVE; ShowParticles(beh); } else { // Inactivate input if (beh->IsInputActive(1)) { beh->ActivateInput(1,FALSE); activity = INACTIVE; } } } // we save the activity beh->SetLocalParameterValue(1,&activity); if(activity & FREEZED) { return CKBR_OK; } // Reading Inputs pe->ReadInputs(beh); // shape of emitter object if(guid == OBJECTSYSTEM_GUID) { ((ObjectEmitter*)pe)->m_Shape = entity->GetCurrentMesh(); } // we update the time spent since last emission float emissiontime = 0.0f; beh->GetLocalParameterValue(2,&emissiontime); emissiontime += behcontext.DeltaTime; // We create new particles only if we are active if(activity) { float emissiondelay=0; beh->GetInputParameterValue(EMISSIONDELAY,&emissiondelay); if(emissiondelay>0) { // linked with time float emissiondelayvariance=0; beh->GetInputParameterValue(EMISSIONDELAYVAR,&emissiondelayvariance); emissiondelay += emissiondelayvariance*RANDNUM; if(emissiondelay<0.1f) emissiondelay = 0.1f; //Avoid infinite loops while(emissiontime>emissiondelay) { emissiontime -= emissiondelay; pe->AddParticles(); } } else { // Not linked with time pe->AddParticles(); } } // ACC - July 10,2002 // Original Code #ifndef USE_THR // We update the particles (position, color, size...) pe->UpdateParticles(behcontext.DeltaTime); #else // CKContext* ctx = beh->GetCKContext(); // ctx->OutputToConsoleEx("Enque a Thread param: count=%d\n", PSqueue.NumItems()); // multi-thread. Enqueue to be processed #ifdef MT_VERB { VxMutexLock lock (logguard); fprintf(ACCLOG, "Enque a Thread param count=%d\n", PSqueue.NumItems()); fflush(ACCLOG); } #endif UpdateParticleSystemEnqueue(pe, behcontext.DeltaTime); #endif /// // Saving Locals // we write the time beh->SetLocalParameterValue(2,&emissiontime); } // ACC, July 10, 2002: #ifndef USE_THR // TODO //This code needs to be moved into thread function, however // it imposes a condition that the values are not ready until the next frame // potentially // Deflector Impacts Management if (pe->m_CurrentImpact < pe->m_Impacts.Size()) { beh->SetOutputParameterValue(0,&pe->m_Impacts[pe->m_CurrentImpact].m_Position); beh->SetOutputParameterValue(1,&pe->m_Impacts[pe->m_CurrentImpact].m_Direction); beh->SetOutputParameterObject(2,pe->m_Impacts[pe->m_CurrentImpact].m_Object); beh->SetOutputParameterValue(3,&pe->m_Impacts[pe->m_CurrentImpact].m_UVs); pe->m_CurrentImpact++; beh->ActivateOutput(3); } // Update the particle count output (if available) int opCount = beh->GetOutputParameterCount(); if ((opCount == 1) || (opCount == 5)) { beh->SetOutputParameterValue(opCount-1,&pe->particleCount); } // This section of code can go into the render call back, and return premature // if particle is shutdown // if there is no more particles and the behavior is inactive if(!activity && !(pe->getParticles())) { ShowParticles(beh,FALSE); beh->ActivateOutput(1); return CKBR_OK; } #endif return CKBR_ACTIVATENEXTFRAME; } CKERROR GeneralParticleSystemCallback(const CKBehaviorContext& behcontext) { CKBehavior* beh = behcontext.Behavior; CKContext* ctx = behcontext.Context; // we get the guid CKGUID guid = beh->GetPrototypeGuid(); // we get the frame entity CK3dEntity* ement = (CK3dEntity*)beh->GetOwner(); switch (behcontext.CallbackMessage) { case CKM_BEHAVIORATTACH: { if ((guid != OBJECTSYSTEM_GUID) && (guid != CURVESYSTEM_GUID)) { // we test if it's a frame : if not -> reject if (!(ement->GetFlags()&CK_3DENTITY_FRAME)) { ctx->OutputToConsole("You can only attach this particule system to a Frame"); return CKBR_OWNERERROR; } } } case CKM_BEHAVIORLOAD: { ParticleManager* pm = (ParticleManager*)ctx->GetManagerByGuid(PARTICLE_MANAGER_GUID); if(beh->GetLocalParameterCount() == 17) { // Mantis bug 3828 // Add new option to change the radial particle distance offset (0 by default for newly created particle // so that the zbuffer problem doesn't occur) CKParameterLocal *p; p = beh->CreateLocalParameter(RadialParticleOffsetStr, CKPGUID_FLOAT); const float defaultValue = 40.f; // default value for backward compatibility p->SetValue(&defaultValue); } /////////////////////////// // Interactors Diplay /////////////////////////// beh->SetLocalParameterValue(DISPLAYINTERACTORS,&pm->m_ShowInteractors); // init of the local param ParticleEmitter* em = pm->CreateNewEmitter(guid,CKOBJID(ement)); em->m_Behavior = beh; beh->SetLocalParameterValue(0, &em, sizeof(ParticleEmitter*)); // we now set the good mesh of the emitter if (!ctx->IsPlaying()) EmitterSetMesh(TRUE,guid,beh,em); ///////////////////////////////////// // CALLBACKS ///////////////////////////////////// // We read the settings em->ReadSettings(beh); // We read the inputs em->ReadInputs(beh); // we initialize the time float time = 0.0f; beh->SetLocalParameterValue(2,&time); } break; case CKM_BEHAVIORNEWSCENE: { ParticleEmitter *pe = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!pe) return CKBR_PARAMETERERROR; ement->ModifyMoveableFlags(VX_MOVEABLE_RENDERLAST,0); if (AreScriptAndTargetActiveInScene(behcontext)) { BOOL active; beh->GetLocalParameterValue(1,&active); if(active) { // entity->SetRenderCallBack( pe-è>m_RenderParticlesCallback, pe ); ShowParticles(beh,TRUE); } } else { ShowParticles(beh,FALSE); } } break; case CKM_BEHAVIORSETTINGSEDITED: { ParticleEmitter *pe = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); pe->ReadSettings(beh); if (ctx->IsPlaying() && AreScriptAndTargetActiveInScene(behcontext)) ShowParticles(beh); } break; case CKM_BEHAVIORDETACH: { ParticleEmitter *em = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(em) { // we check if it's not the cancel button // we now remove the emitter mesh of the frame EmitterSetMesh(FALSE,guid,beh,em); // We remove the render callbcak if(ement) { ement->RemovePostRenderCallBack(em->m_RenderParticlesCallback,em); } // we delete the particle system ParticleManager* pm = (ParticleManager*)ctx->GetManagerByGuid(PARTICLE_MANAGER_GUID); pm->DeleteEmitter(em); beh->SetLocalParameterObject(0,NULL); } } break; case CKM_BEHAVIORPOSTSAVE: { ParticleEmitter *em = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!em) return CKBR_PARAMETERERROR; // we now set the good mesh of the emitter EmitterSetMesh(TRUE,guid,beh,em); } break; case CKM_BEHAVIORRESET: { ParticleEmitter *em = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!em) return CKBR_PARAMETERERROR; int activity = 0; beh->GetLocalParameterValue(1,&activity); activity &= ~FREEZED; beh->SetLocalParameterValue(1,&activity); em->InitParticleSystem(); // we now set the good mesh of the emitter EmitterSetMesh(TRUE,guid,beh,em); } break; case CKM_BEHAVIORPAUSE: { ParticleEmitter *em = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!em) return CKBR_PARAMETERERROR; CKBOOL visibleOnPause = FALSE; beh->GetLocalParameterValue(VISIBLEINPAUSE, &visibleOnPause); if (visibleOnPause) return CKBR_OK; // we now set the good mesh of the emitter EmitterSetMesh(TRUE,guid,beh,em); } case CKM_BEHAVIORDEACTIVATESCRIPT: { ShowParticles(beh,FALSE); } break; case CKM_BEHAVIORPRESAVE: { ParticleEmitter *em = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!em) return CKBR_PARAMETERERROR; // we now set the good mesh of the emitter EmitterSetMesh(FALSE,guid,beh,em); } break; case CKM_BEHAVIORRESUME: { ParticleEmitter *em = *(ParticleEmitter**)beh->GetLocalParameterReadDataPtr(0); if(!em) return CKBR_PARAMETERERROR; // we now remove the emitter mesh of the frame EmitterSetMesh(FALSE,guid,beh,em); } case CKM_BEHAVIORACTIVATESCRIPT: { if (AreScriptAndTargetActiveInScene(behcontext)) { int activity = 0; beh->GetLocalParameterValue(1,&activity); ShowParticles(beh, activity != 0); } } break; } return CKBR_OK; } void EmitterSetMesh(BOOL Set,CKGUID guid,CKBehavior* beh,ParticleEmitter *em) { CKContext* ctx = beh->GetCKContext(); // does nothing if we are not in interface mode if (!ctx->IsInInterfaceMode()) { return; } // we get the frame entity CK3dEntity* ement = (CK3dEntity*)beh->GetOwner(); if (!ement) return; if(em->m_Mesh) { // If a mesh, it's not an object or a curve PS if (Set) { ement->SetCurrentMesh((CKMesh*)ctx->GetObject(em->m_Mesh)); } else { ement->RemoveMesh((CKMesh*)ctx->GetObject(em->m_Mesh)); } } }