/*---------------------------------------------------------------------------* Project: NintendoWare File: gfx_ParticleEmitter.cpp Copyright (C)2009-2010 Nintendo Co., Ltd./HAL Laboratory, Inc. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Revision: 28118 $ *---------------------------------------------------------------------------*/ #include "precompiled.h" #include #include #include #include #include #include namespace nw { namespace gfx { NW_UT_RUNTIME_TYPEINFO_DEFINITION(ParticleEmitter, TransformNode); void ParticleEmitter::GetMemorySizeInternal( os::MemorySizeCalculator* pSize, ResParticleEmitter resNode, const ParticleEmitter::Description& description) { os::MemorySizeCalculator& size = *pSize; // リソースのコピー ResParticleEmitterParameterData* resParameterData = NULL; if (resNode.GetIsResourceCopyEnabled()) { size += sizeof(ResParticleEmitterParameterData); } // フォームのリソースのコピー ResParticleFormData* resFormData = NULL; const ResParticleForm resForm = resNode.GetParticleForm(); if (resForm.IsValid() && resForm.GetIsResourceCopyEnabled()) { switch(resForm.GetTypeInfo()) { case ResParticleCubeForm::TYPE_INFO: size += sizeof(ResParticleCubeFormData); break; case ResParticleCylinderForm::TYPE_INFO: size += sizeof(ResParticleCylinderFormData); break; case ResParticleDiscForm::TYPE_INFO: size += sizeof(ResParticleDiscFormData); break; case ResParticlePointForm::TYPE_INFO: size += sizeof(ResParticlePointFormData); break; case ResParticleRectangleForm::TYPE_INFO: size += sizeof(ResParticleRectangleFormData); break; case ResParticleSphereForm::TYPE_INFO: size += sizeof(ResParticleSphereFormData); break; default: NW_FATAL_ERROR("unknown form type"); } } size += sizeof(ParticleEmitter); TransformNode::GetMemorySizeForInitialize(pSize, resNode, description); } //---------------------------------------- ParticleEmitter* ParticleEmitter::Create( SceneNode* parent, ResSceneObject resource, const ParticleEmitter::Description& description, os::IAllocator* allocator ) { NW_NULL_ASSERT(allocator); ResParticleEmitter resNode = ResDynamicCast(resource); NW_ASSERT(resNode.IsValid()); // リソースのコピー ResParticleEmitterParameterData* resParameterData = NULL; if (resNode.GetIsResourceCopyEnabled()) { void* resourceMemory = allocator->Alloc(sizeof(ResParticleEmitterParameterData)); if (resourceMemory == NULL) { return NULL; } resParameterData = new(resourceMemory) ResParticleEmitterParameterData; ::nw::os::MemCpy( resParameterData, &resNode.ptr()->m_IsResourceCopyEnabled, sizeof(ResParticleEmitterParameterData)); } // フォームのリソースのコピー ResParticleFormData* resFormData = NULL; const ResParticleForm resForm = resNode.GetParticleForm(); if (resForm.IsValid() && resForm.GetIsResourceCopyEnabled()) { int size = 0; switch(resForm.GetTypeInfo()) { case ResParticleCubeForm::TYPE_INFO: { size = sizeof(ResParticleCubeFormData); void* resourceMemory = allocator->Alloc(size); if (resourceMemory != NULL) { resFormData = new(resourceMemory) ResParticleCubeFormData; } } break; case ResParticleCylinderForm::TYPE_INFO: { size = sizeof(ResParticleCylinderFormData); void* resourceMemory = allocator->Alloc(size); if (resourceMemory != NULL) { resFormData = new(resourceMemory) ResParticleCylinderFormData; } } break; case ResParticleDiscForm::TYPE_INFO: { size = sizeof(ResParticleDiscFormData); void* resourceMemory = allocator->Alloc(size); if (resourceMemory != NULL) { resFormData = new(resourceMemory) ResParticleDiscFormData; } } break; case ResParticlePointForm::TYPE_INFO: { size = sizeof(ResParticlePointFormData); void* resourceMemory = allocator->Alloc(size); if (resourceMemory != NULL) { resFormData = new(resourceMemory) ResParticlePointFormData; } } break; case ResParticleRectangleForm::TYPE_INFO: { size = sizeof(ResParticleRectangleFormData); void* resourceMemory = allocator->Alloc(size); if (resourceMemory != NULL) { resFormData = new(resourceMemory) ResParticleRectangleFormData; } } break; case ResParticleSphereForm::TYPE_INFO: { size = sizeof(ResParticleSphereFormData); void* resourceMemory = allocator->Alloc(size); if (resourceMemory != NULL) { resFormData = new(resourceMemory) ResParticleSphereFormData; } } break; default: NW_FATAL_ERROR("unknown form type"); } if (resFormData == NULL) { if (resParameterData != NULL) { allocator->Free(resParameterData); return NULL; } } else { ::nw::os::MemCpy( resFormData, resForm.ptr(), size); } } void* memory = allocator->Alloc(sizeof(ParticleEmitter)); if (memory == NULL) { if (resParameterData != NULL) { allocator->Free(resParameterData); } if (resFormData != NULL) { allocator->Free(resFormData); } return NULL; } ParticleEmitter* node = new(memory) ParticleEmitter( allocator, resNode, description, ResParticleEmitterParameter(resParameterData), ResParticleForm(resFormData)); { Result result = node->Initialize(allocator); if (!result.IsSuccess()) { SafeDestroy(node); return NULL; } } if (parent) { bool result = parent->AttachChild(node); NW_ASSERT(result); } return node; } //---------------------------------------- ParticleEmitter::ParticleEmitter( os::IAllocator* allocator, ResParticleEmitter resObj, const ParticleEmitter::Description& description, ResParticleEmitterParameter resParameterObj, ResParticleForm resFormObj ) : TransformNode(allocator, resObj, description), m_IsFirstEmission(true), m_EmissionCount(0), m_NextEmissionTime(0), m_ParticleSet(NULL), m_ParticleAnimFrameController(0, 16777215, anim::PlayPolicy_Loop), m_ResParameter(resParameterObj), m_ResForm(resFormObj) { } //---------------------------------------- ParticleEmitter::~ParticleEmitter() { if (this->m_ResParameter.IsValid()) { this->GetAllocator().Free(this->m_ResParameter.ptr()); } if (this->m_ResForm.IsValid()) { this->GetAllocator().Free(this->m_ResForm.ptr()); } } //---------------------------------------- void ParticleEmitter::Accept( ISceneVisitor* visitor ) { visitor->VisitParticleEmitter(this); AcceptChildren(visitor); } int ParticleEmitter::GetEmissionCount( f32 prevTime, f32 time ) { #if 0 // 1.1.0 で変更しました。 NW_UNUSED_VARIABLE(prevTime); ResParticleEmitterParameter resource = this->GetResParticleEmitterParameterCopy(false); if (!resource.IsValid()) { return 0; } f32 cookedTime = time - resource.GetEmissionStart() - 1; if (cookedTime < 0) { return 0; } if (!resource.GetEmissionSpanInfinity() && cookedTime >= resource.GetEmissionSpan()) { return 0; } while (this->m_NextEmissionTime <= cookedTime) { f32 c = resource.GetEmissionRatio(); c *= 1.0f + resource.GetEmissionRatioRandom() * this->m_ParticleRandom.NextFloatSignedOne(); if (c < 0) { c = 0; } this->m_EmissionCount += c; int t = resource.GetEmissionInterval(); t = (int)(t * (1.0f + resource.GetEmissionIntervalRandom() * this->m_ParticleRandom.NextFloatSignedOne())); // 最速でも次のフレームまで出さない if (t < 1) { t = 1; } this->m_NextEmissionTime += t; } if (this->m_IsFirstEmission) { if (resource.GetEmissionRatio() != 0 && this->m_EmissionCount > 0.0f) { this->m_IsFirstEmission = false; if (this->m_EmissionCount < 1.0f) { this->m_EmissionCount = 1.0f; } } } int count = (int)this->m_EmissionCount; this->m_EmissionCount -= count; return count; #else // 逆再生時は放出しない if (prevTime >= time) { return 0; } ResParticleEmitterParameter resource = this->GetResParticleEmitterParameterCopy(false); NW_ASSERT(resource.IsValid()); f32 cookedPrevTime = prevTime - resource.GetEmissionStart() - 1; if (!resource.GetEmissionSpanInfinity() && cookedPrevTime >= resource.GetEmissionSpan() - 1) { return 0; } f32 cookedTime = time - resource.GetEmissionStart() - 1; if (cookedTime < 0) { return 0; } while (this->m_NextEmissionTime <= cookedTime) { f32 c = resource.GetEmissionRatio(); c *= 1.0f + resource.GetEmissionRatioRandom() * this->m_ParticleRandom.NextFloatSignedOne(); if (c < 0) { c = 0; } this->m_EmissionCount += c; int t = resource.GetEmissionInterval(); t = (int)(t * (1.0f + resource.GetEmissionIntervalRandom() * this->m_ParticleRandom.NextFloatSignedOne())); // 最速でも次のフレームまで出さない if (t < 1) { t = 1; } this->m_NextEmissionTime += t; } if (this->m_IsFirstEmission) { if (resource.GetEmissionRatio() != 0 && this->m_EmissionCount > 0.0f) { this->m_IsFirstEmission = false; if (this->m_EmissionCount < 1.0f) { this->m_EmissionCount = 1.0f; } } } int count = (int)this->m_EmissionCount; this->m_EmissionCount -= count; return count; #endif } int ParticleEmitter::Emission( ParticleContext* particleContext ) { NW_UNUSED_VARIABLE(particleContext); if (this->m_ParticleSet == NULL) { return 0; } f32 prevTime = m_ParticleAnimFrameController.GetAnimFrame().GetLastFrame(); f32 time = m_ParticleAnimFrameController.GetFrame(); int emissionCount = GetEmissionCount(prevTime, time); if (emissionCount < 1) { return 0; } ResParticleForm resForm = this->GetResParticleFormCopy(false); NW_ASSERT(resForm.IsValid()); #ifdef NW_GFX_PARTICLE_COMPAT_1_1 nw::math::VEC3* positions = particleContext->GetEmissionPositionWork(emissionCount); if (emissionCount > particleContext->GetEmissionWorkCapacity()) { emissionCount = particleContext->GetEmissionWorkCapacity(); } #else ParticleCollection* targetCollection = m_ParticleSet->GetParticleCollection(); bool targetIsAscendingOrder = m_ParticleSet->IsAscendingOrder(); u16* activeIndex = (u16*)targetCollection->GetStreamPtr(PARTICLEUSAGE_ACTIVEINDEX, PARTICLE_BUFFER_FRONT); NW_NULL_ASSERT(activeIndex); const int startIndex = targetIsAscendingOrder ? targetCollection->GetCount() : targetCollection->GetCapacity() - targetCollection->GetCount() - 1; activeIndex += startIndex; const int incrIndex = targetIsAscendingOrder ? 1 : -1; nw::math::VEC3* targetTranslate = (nw::math::VEC3*)targetCollection->GetStreamPtr(PARTICLEUSAGE_TRANSLATE, PARTICLE_BUFFER_FRONT); NW_NULL_ASSERT(targetTranslate); emissionCount = m_ParticleSet->AddParticles(emissionCount); #endif switch(resForm.GetTypeInfo()) { case ResParticleCubeForm::TYPE_INFO: { const ResParticleCubeForm cubeForm = ResStaticCast(resForm); #ifdef NW_GFX_PARTICLE_COMPAT_1_1 this->CalcCubeForm(cubeForm, emissionCount, &this->m_ParticleRandom, positions); #else this->CalcCubeForm(cubeForm, emissionCount, &this->m_ParticleRandom, activeIndex, incrIndex, targetTranslate); #endif } break; case ResParticleCylinderForm::TYPE_INFO: { const ResParticleCylinderForm cylinderForm = ResStaticCast(resForm); #ifdef NW_GFX_PARTICLE_COMPAT_1_1 this->CalcCylinderForm(cylinderForm, emissionCount, &this->m_ParticleRandom, positions); #else this->CalcCylinderForm(cylinderForm, emissionCount, &this->m_ParticleRandom, activeIndex, incrIndex, targetTranslate); #endif } break; case ResParticleDiscForm::TYPE_INFO: { const ResParticleDiscForm discForm = ResStaticCast(resForm); #ifdef NW_GFX_PARTICLE_COMPAT_1_1 this->CalcDiscForm(discForm, emissionCount, &this->m_ParticleRandom, positions); #else this->CalcDiscForm(discForm, emissionCount, &this->m_ParticleRandom, activeIndex, incrIndex, targetTranslate); #endif } break; case ResParticlePointForm::TYPE_INFO: { const ResParticlePointForm pointForm = ResStaticCast(resForm); #ifdef NW_GFX_PARTICLE_COMPAT_1_1 this->CalcPointForm(pointForm, emissionCount, &this->m_ParticleRandom, positions); #else this->CalcPointForm(pointForm, emissionCount, &this->m_ParticleRandom, activeIndex, incrIndex, targetTranslate); #endif } break; case ResParticleRectangleForm::TYPE_INFO: { const ResParticleRectangleForm rectangleForm = ResStaticCast(resForm); #ifdef NW_GFX_PARTICLE_COMPAT_1_1 this->CalcRectangleForm(rectangleForm, emissionCount, &this->m_ParticleRandom, positions); #else this->CalcRectangleForm(rectangleForm, emissionCount, &this->m_ParticleRandom, activeIndex, incrIndex, targetTranslate); #endif } break; case ResParticleSphereForm::TYPE_INFO: { const ResParticleSphereForm sphereForm = ResStaticCast(resForm); #ifdef NW_GFX_PARTICLE_COMPAT_1_1 this->CalcSphereForm(sphereForm, emissionCount, &this->m_ParticleRandom, positions); #else this->CalcSphereForm(sphereForm, emissionCount, &this->m_ParticleRandom, activeIndex, incrIndex, targetTranslate); #endif } break; default: NW_FATAL_ERROR("unknown form type"); } #ifdef NW_GFX_PARTICLE_COMPAT_1_1 m_ParticleSet->AddParticles(this->WorldMatrix(), positions, NULL, NULL, emissionCount); #else { ParticleModel* model = static_cast(m_ParticleSet->GetParent()); NW_NULL_ASSERT(model); ParticleTime targetTime = model->ParticleAnimFrameController().GetFrame(); m_ParticleSet->InitializeParticles(startIndex, emissionCount, incrIndex, targetTime); } if (m_ParticleSet->WorldMatrix() != this->WorldMatrix()) { nw::math::MTX34 transformMatrix; nw::math::MTX34Mult(&transformMatrix, m_ParticleSet->InverseWorldMatrix(), this->WorldMatrix()); nw::math::VEC3* translate = (nw::math::VEC3*)targetCollection->GetStreamPtr(PARTICLEUSAGE_TRANSLATE, PARTICLE_BUFFER_FRONT); nw::math::VEC3* velocity = (nw::math::VEC3*)targetCollection->GetStreamPtr(PARTICLEUSAGE_VELOCITY, PARTICLE_BUFFER_FRONT); for (int i = 0; i < emissionCount; ++i) { int dstIndex = *activeIndex; activeIndex += incrIndex; nw::math::VEC3Transform(&translate[dstIndex], transformMatrix, translate[dstIndex]); nw::math::VEC3TransformNormal(&velocity[dstIndex], transformMatrix, velocity[dstIndex]); } } #endif return emissionCount; } void ParticleEmitter::CalcCubeForm( const ResParticleCubeForm& cubeForm, int emissionCount, ParticleRandom* random, #ifdef NW_GFX_PARTICLE_COMPAT_1_1 nw::math::VEC3* positions #else u16* activeIndex, int incrIndex, nw::math::VEC3* targetTranslate #endif ) { const f32 Epsilon = 1e-5f; nw::math::VEC3 cookedScale(cubeForm.GetScale()); if (nw::math::FAbs(cookedScale.x) < Epsilon) { cookedScale.x = Epsilon; } if (nw::math::FAbs(cookedScale.y) < Epsilon) { cookedScale.y = Epsilon; } if (nw::math::FAbs(cookedScale.z) < Epsilon) { cookedScale.z = Epsilon; } // TBD:等間隔 for (int i = 0; i < emissionCount; ++i) { nw::math::VEC3 position(0.0f, 0.0f, 0.0f); if (cubeForm.GetInner() == 0.0f) { position.x = random->NextFloatSignedOne(); position.y = random->NextFloatSignedOne(); position.z = random->NextFloatSignedOne(); position.x *= cookedScale.x; position.y *= cookedScale.y; position.z *= cookedScale.z; } else if (cubeForm.GetInner() == 1.0f) { f32 d = random->NextFloat() * ((cookedScale.x * cookedScale.y) + (cookedScale.y * cookedScale.z) + (cookedScale.z * cookedScale.x)); int sign; if (random->Next(2) == 0) { sign = 1; } else { sign = -1; } f32 r = random->NextFloatSignedOne(); f32 s = random->NextFloatSignedOne(); if (d < cookedScale.x * cookedScale.y) { position.Set(r, s, (f32)sign); } else if (d < (cookedScale.x * cookedScale.y) + (cookedScale.y * cookedScale.z)) { position.Set((f32)sign, r, s); } else { position.Set(r, (f32)sign, s); } position.x *= cookedScale.x; position.y *= cookedScale.y; position.z *= cookedScale.z; } else { f32 xyz = cookedScale.x * cookedScale.y * cookedScale.z; f32 topbottom = xyz * (1.0f - cubeForm.GetInner()); f32 frontback = topbottom * cubeForm.GetInner(); f32 leftright = frontback * cubeForm.GetInner(); f32 f = random->NextFloat() * (topbottom + frontback + leftright); position.x = random->NextFloatSignedOne(); position.y = random->NextFloatSignedOne(); position.z = random->NextFloatSignedOne(); if (f < topbottom) { position.y *= 1.0f - cubeForm.GetInner(); if (position.y >= 0) { position.y = 1.0f - position.y; } else { position.y = -1.0f - position.y; } } else if (f < topbottom + frontback) { position.y *= cubeForm.GetInner(); position.z *= 1.0f - cubeForm.GetInner(); if (position.z >= 0) { position.z = 1.0f - position.z; } else { position.z = -1.0f - position.z; } } else { position.x *= 1.0f - cubeForm.GetInner(); if (position.x >= 0) { position.x = 1.0f - position.x; } else { position.x = -1.0f - position.x; } } position.x *= cookedScale.x; position.y *= cookedScale.y; position.z *= cookedScale.z; } #ifdef NW_GFX_PARTICLE_COMPAT_1_1 positions[i].Set(position.x, position.y, position.z); #else int index = *activeIndex; activeIndex += incrIndex; targetTranslate[index].Set(position.x, position.y, position.z); #endif } } void ParticleEmitter::CalcCylinderForm( const ResParticleCylinderForm& cylinderForm, int emissionCount, ParticleRandom* random, #ifdef NW_GFX_PARTICLE_COMPAT_1_1 nw::math::VEC3* positions #else u16* activeIndex, int incrIndex, nw::math::VEC3* targetTranslate #endif ) { const f32 Epsilon = 1e-5f; f32 angleOffset; if (cylinderForm.GetFixedOffset()) { angleOffset = cylinderForm.GetAngleOffset(); } else { angleOffset = random->NextFloat() * nw::math::F_PI * 2.0f; } nw::math::VEC3 cookedScale(cylinderForm.GetScale()); if (nw::math::FAbs(cookedScale.x) < Epsilon) { cookedScale.x = Epsilon; } if (nw::math::FAbs(cookedScale.y) < Epsilon) { cookedScale.y = Epsilon; } if (nw::math::FAbs(cookedScale.z) < Epsilon) { cookedScale.z = Epsilon; } // TBD:等間隔 for (int i = 0; i < emissionCount; ++i) { nw::math::VEC3 position(0.0f, 0.0f, 0.0f); f32 distance = random->NextFloat(); if (cylinderForm.GetInner() == 0.0f) { // 全体 distance = nw::math::FSqrt(distance); } else if (cylinderForm.GetInner() == 1.0f) { // 外周のみ distance = 1.0f; } else { distance = nw::math::FSqrt(distance + (cylinderForm.GetInner() * cylinderForm.GetInner() * (1.0f - distance))); } f32 angle = random->NextFloat() * cylinderForm.GetAngleWidth() + angleOffset; if (cylinderForm.GetAngleSwing() != 0.0f) { angle += random->NextFloatSignedHalf() * cylinderForm.GetAngleSwing(); } f32 sin = 0.0f; f32 cos = 1.0f; if (angle != 0.0f) { nw::math::SinCosRad(&sin, &cos, angle); } position.x = sin * distance; position.y = random->NextFloatSignedOne(); position.z = -cos * distance; position.x *= cookedScale.x; position.y *= cookedScale.y; position.z *= cookedScale.z; #ifdef NW_GFX_PARTICLE_COMPAT_1_1 positions[i].Set(position.x, position.y, position.z); #else int index = *activeIndex; activeIndex += incrIndex; targetTranslate[index].Set(position.x, position.y, position.z); #endif } } void ParticleEmitter::CalcDiscForm( const ResParticleDiscForm& discForm, int emissionCount, ParticleRandom* random, #ifdef NW_GFX_PARTICLE_COMPAT_1_1 nw::math::VEC3* positions #else u16* activeIndex, int incrIndex, nw::math::VEC3* targetTranslate #endif ) { const f32 Epsilon = 1e-5f; f32 angleOffset; if (discForm.GetFixedOffset()) { angleOffset = discForm.GetAngleOffset(); } else { angleOffset = random->NextFloat() * nw::math::F_PI * 2.0f; } f32 angleStep = 0.0f; if (discForm.GetEvenInterval() && emissionCount != 0) { f32 f = nw::math::FMod(discForm.GetAngleWidth(), nw::math::F_PI * 2.0f); f32 epsilon = 256.0f * 2.0f * nw::math::F_PI * nw::math::F_ULP; if (f < epsilon || f > (2 * nw::math::F_PI) - epsilon || emissionCount == 1) { angleStep = discForm.GetAngleWidth() / emissionCount; } else { angleStep = discForm.GetAngleWidth() / (emissionCount - 1); } } nw::math::VEC2 cookedScale(discForm.GetScale()); if (nw::math::FAbs(cookedScale.x) < Epsilon) { cookedScale.x = Epsilon; } if (nw::math::FAbs(cookedScale.y) < Epsilon) { cookedScale.y = Epsilon; } for (int i = 0; i < emissionCount; ++i) { nw::math::VEC3 position(0.0f, 0.0f, 0.0f); f32 distance = random->NextFloat(); if (discForm.GetInner() == 0.0f) { // 全体 distance = nw::math::FSqrt(distance); } else if (discForm.GetInner() == 1.0f) { // 外周のみ distance = 1.0f; } else { distance = nw::math::FSqrt(distance + (discForm.GetInner() * discForm.GetInner() * (1.0f - distance))); } f32 angle; if (angleStep != 0.0f) { angle = (angleStep * i) - (discForm.GetAngleWidth() / 2.0f); } else { angle = random->NextFloatSignedHalf() * discForm.GetAngleWidth(); } if (discForm.GetAngleSwing() != 0.0f) { angle += random->NextFloatSignedHalf() * discForm.GetAngleSwing(); } angle += angleOffset; f32 sin = 0.0f; f32 cos = 1.0f; if (angle != 0.0f) { nw::math::SinCosRad(&sin, &cos, angle); } position.x = sin * distance; position.y = 0; position.z = -cos * distance; position.x *= cookedScale.x; position.z *= cookedScale.y; if (position.x == 0.0f && position.z == 0.0f) { position.x = Epsilon; } #ifdef NW_GFX_PARTICLE_COMPAT_1_1 positions[i].Set(position.x, position.y, position.z); #else int index = *activeIndex; activeIndex += incrIndex; targetTranslate[index].Set(position.x, position.y, position.z); #endif } } void ParticleEmitter::CalcPointForm( const ResParticlePointForm& pointForm, int emissionCount, ParticleRandom* random, #ifdef NW_GFX_PARTICLE_COMPAT_1_1 nw::math::VEC3* positions #else u16* activeIndex, int incrIndex, nw::math::VEC3* targetTranslate #endif ) { NW_UNUSED_VARIABLE(pointForm) NW_UNUSED_VARIABLE(random) const float Radius = 1e-3f; for (int i = 0; i < emissionCount; ++i) { #ifdef NW_GFX_PARTICLE_COMPAT_1_1 positions[i].Set( random->NextFloatSignedHalf() * Radius, random->NextFloatSignedHalf() * Radius, random->NextFloatSignedHalf() * Radius); #else int index = *activeIndex; activeIndex += incrIndex; targetTranslate[index].x = random->NextFloatSignedHalf() * Radius; targetTranslate[index].y = random->NextFloatSignedHalf() * Radius; targetTranslate[index].z = random->NextFloatSignedHalf() * Radius; #endif } } void ParticleEmitter::CalcSphereForm( const ResParticleSphereForm& sphereForm, int emissionCount, ParticleRandom* random, #ifdef NW_GFX_PARTICLE_COMPAT_1_1 nw::math::VEC3* positions #else u16* activeIndex, int incrIndex, nw::math::VEC3* targetTranslate #endif ) { const f32 Epsilon = 1e-5f; f32 angleOffset; if (sphereForm.GetFixedOffset()) { angleOffset = sphereForm.GetAngleOffset(); } else { angleOffset = random->NextFloat() * nw::math::F_PI * 2.0f; } nw::math::VEC3 cookedScale(sphereForm.GetScale()); if (nw::math::FAbs(cookedScale.x) < Epsilon) { cookedScale.x = Epsilon; } if (nw::math::FAbs(cookedScale.y) < Epsilon) { cookedScale.y = Epsilon; } if (nw::math::FAbs(cookedScale.z) < Epsilon) { cookedScale.z = Epsilon; } // TBD:等間隔 for (int i = 0; i < emissionCount; ++i) { nw::math::VEC3 position(0.0f, 0.0f, 0.0f); f32 distance = random->NextFloat(); if (sphereForm.GetInner() == 0.0f) { // 全体 distance = 1.0f - (distance * distance * distance); } else if (sphereForm.GetInner() == 1.0f) { // 外周のみ distance = 1.0f; } else { distance = 1.0f - (distance * distance * distance); distance = distance + (sphereForm.GetInner() * (1.0f - distance)); } f32 yaw = random->NextFloat() * sphereForm.GetAngleWidth() + angleOffset; f32 pitch = nw::math::F_PI * random->NextFloatSignedHalf(); if (sphereForm.GetAngleSwing() != 0.0f) { yaw += random->NextFloatSignedHalf() * sphereForm.GetAngleSwing(); pitch += random->NextFloatSignedHalf() * sphereForm.GetAngleSwing(); } f32 sinYaw = 0.0f; f32 cosYaw = 1.0f; if (yaw != 0.0f) { nw::math::SinCosRad(&sinYaw, &cosYaw, yaw); } f32 sinPitch = 0.0f; f32 cosPitch = 1.0f; if (pitch != 0.0f) { nw::math::SinCosRad(&sinPitch, &cosPitch, pitch); } position.x = sinYaw * -cosPitch; position.y = -sinPitch; position.z = cosYaw * cosPitch; position *= distance; position.x *= cookedScale.x; position.y *= cookedScale.y; position.z *= cookedScale.z; #ifdef NW_GFX_PARTICLE_COMPAT_1_1 positions[i].Set(position.x, position.y, position.z); #else int index = *activeIndex; activeIndex += incrIndex; targetTranslate[index].Set(position.x, position.y, position.z); #endif } } void ParticleEmitter::CalcRectangleForm( const ResParticleRectangleForm& rectangleForm, int emissionCount, ParticleRandom* random, #ifdef NW_GFX_PARTICLE_COMPAT_1_1 nw::math::VEC3* positions #else u16* activeIndex, int incrIndex, nw::math::VEC3* targetTranslate #endif ) { const f32 Epsilon = 1e-5f; nw::math::VEC2 cookedScale(rectangleForm.GetScale()); if (nw::math::FAbs(cookedScale.x) < Epsilon) { cookedScale.x = Epsilon; } if (nw::math::FAbs(cookedScale.y) < Epsilon) { cookedScale.y = Epsilon; } // TBD:等間隔 for (int i = 0; i < emissionCount; ++i) { nw::math::VEC3 position(0.0f, 0.0f, 0.0f); if (rectangleForm.GetInner() == 0.0f) { position.x = random->NextFloatSignedOne(); position.z = random->NextFloatSignedOne(); position.x *= cookedScale.x; position.z *= cookedScale.y; // ScaleはVector2のため } else if (rectangleForm.GetInner() == 1.0f) { f32 r = random->NextFloatSignedOne() * 2.0f * (cookedScale.x + cookedScale.y); if (r >= 0) { if (r < cookedScale.x * 2.0f) { position.Set( r - cookedScale.x, 0, cookedScale.y); } else { position.Set( cookedScale.x, 0, r - (cookedScale.x * 2.0f) - cookedScale.y); } } else { if (r > -cookedScale.x * 2.0f) { position.Set( r + cookedScale.x, 0, -cookedScale.y); } else { position.Set( -cookedScale.x, 0, r + (cookedScale.x * 2.0f) + cookedScale.y); } } } else { // 均等に分布させるために面積比で場所を決める f32 f = random->NextFloat() * (1.0f + rectangleForm.GetInner()); position.x = random->NextFloatSignedOne(); position.z = random->NextFloatSignedOne(); if (f < 1.0f) { position.z *= 1.0f - rectangleForm.GetInner(); if (position.z >= 0) { position.z = 1.0f - position.z; } else { position.z = -1.0f - position.z; } } else { position.z *= rectangleForm.GetInner(); position.x *= 1.0f - rectangleForm.GetInner(); if (position.x >= 0) { position.x = 1.0f - position.x; } else { position.x = -1.0f - position.x; } } position.x *= cookedScale.x; position.z *= cookedScale.y; if (position.x == 0.0f && position.z == 0.0f) { position.x = Epsilon; } } #ifdef NW_GFX_PARTICLE_COMPAT_1_1 positions[i].Set(position.x, position.y, position.z); #else int index = *activeIndex; activeIndex += incrIndex; targetTranslate[index].Set(position.x, position.y, position.z); #endif } } } // namespace gfx } // namespace nw