@@ -22,7 +22,7 @@ OR CONDITIONS OF ANY KIND, either express or implied. See the Apache License for
2222the specific language governing permissions and limitations under the License.
2323
2424 Copyright (c) 2023 Audiokinetic Inc.
25- Copyright (c) 2024 CCP ehf.
25+ Copyright (c) 2025 CCP ehf.
2626
2727This implementation was developed by CCP Games for spatial audio object clustering
2828in EVE Online and EVE Frontier. This implementation does not grant any rights to
@@ -369,33 +369,64 @@ void ObjectClusterFX::MixToCluster(const AkAudioObject* inObject, AkAudioBuffer*
369369 AKPLATFORM::AkMemCpy (pGeneratedObject->volumeMatrix , currentVolumes, uTransmixSize);
370370}
371371
372+ void ObjectClusterFX::SafeCleanupForThresholdChange ()
373+ {
374+ auto it = m_mapInObjsToOutObjs.Begin ();
375+ while (it != m_mapInObjsToOutObjs.End ()) {
376+ if ((*it).pUserData ) {
377+ // Don't delete the object, just mark it as unclustered
378+ (*it).pUserData ->isClustered = false ;
379+ // Reset index to force reassignment
380+ (*it).pUserData ->index = -1 ;
381+ }
382+ ++it;
383+ }
384+
385+ // Clear cluster assignments but maintain existing objects
386+ m_clusters.clear ();
387+ }
388+
372389void ObjectClusterFX::FeedPositionsToKMeans (const AkAudioObjects& inObjects)
373390{
391+ bool thresholdChanged = false ;
392+ bool thresholdDecreased = false ;
374393
375394 if (m_lastDistanceThreshold != m_pParams->RTPC .distanceThreshold ) {
395+ thresholdChanged = true ;
396+ thresholdDecreased = m_pParams->RTPC .distanceThreshold < m_lastDistanceThreshold;
397+
376398 m_kmeans->setDistanceThreshold (m_pParams->RTPC .distanceThreshold );
377399 m_lastDistanceThreshold = m_pParams->RTPC .distanceThreshold ;
400+
401+ // Safe cleanup when threshold changes
402+ SafeCleanupForThresholdChange ();
378403 }
379404
380405 std::vector<ObjectPosition> objectPositions;
381406 objectPositions.reserve (inObjects.uNumObjects );
382407
408+ // Collect positions and ensure proper redistribution
383409 for (AkUInt32 i = 0 ; i < inObjects.uNumObjects ; ++i) {
384410 AkAudioObject* inobj = inObjects.ppObjects [i];
385411
386- // Check if this is either position-only or position+orientation
387412 bool shouldCluster = (inobj->positioning .behavioral .spatMode == AK_SpatializationMode_PositionOnly ||
388413 inobj->positioning .behavioral .spatMode == AK_SpatializationMode_PositionAndOrientation);
389414
390415 if (shouldCluster) {
391416 objectPositions.push_back ({ inobj->positioning .threeD .xform .Position (), inobj->key });
392417 }
393418 }
394- // Perform clustering only if there are objects
395- m_clusters.clear ();
419+
396420 if (!objectPositions.empty ()) {
421+ // Force reinitialization of KMeans when threshold decreases
422+ if (thresholdDecreased) {
423+ m_kmeans->reinitializeClustering ();
424+ }
425+
397426 m_kmeans->performClustering (objectPositions);
398427
428+ // Update clusters with new assignments
429+ m_clusters.clear ();
399430 auto tempClusters = m_kmeans->getClusters ();
400431 m_clusters.reserve (tempClusters.size ());
401432
0 commit comments