// spawn the platforms, obstacles, power ups, and coins
    private PlatformObject spawnObjects(ObjectLocation location, Vector3 position, Vector3 direction, bool activateImmediately)
    {
        setupSection(location, false);
        int localIndex = infiniteObjectManager.getNextObjectIndex(ObjectType.Platform, spawnData);

        if (localIndex == -1)
        {
            print("Unable to spawn platform. No platforms can be spawned based on the probability rules");
            return(null);
        }
        PlatformObject platform = spawnPlatform(localIndex, location, position, direction, activateImmediately);

        if (platform.canSpawnCollidable() && Random.value >= noCollidableProbability.getValue(infiniteObjectHistory.getTotalDistance(false)))
        {
            // First try to spawn an obstacle. If there is any space remaining on the platform, then try to spawn a coin.
            // If there is still some space remaing, try to spawn a powerup.
            // An extension of this would be to randomize the order of ObjectType, but this way works if the probabilities
            // are setup fairly
            spawnCollidable(ObjectType.Obstacle, position, direction, location, platform, localIndex, activateImmediately);
            if (platform.canSpawnCollidable())
            {
                spawnCollidable(ObjectType.Coin, position, direction, location, platform, localIndex, activateImmediately);
                if (platform.canSpawnCollidable())
                {
                    spawnCollidable(ObjectType.PowerUp, position, direction, location, platform, localIndex, activateImmediately);
                }
            }
        }

        return(platform);
    }
    // returns true if there is still space on the platform for a collidable object to spawn
    private void spawnCollidable(ObjectType objectType, Vector3 position, Vector3 direction, ObjectLocation location, PlatformObject platform, int platformLocalIndex, bool activateImmediately)
    {
        int collidablePositions = platform.collidablePositions;

        // can't do anything if the platform doesn't accept any collidable object spawns
        if (collidablePositions == 0)
        {
            return;
        }

        Vector3 offset = platformSizes[platformLocalIndex] * 0.1f;
        float   zDelta = platformSizes[platformLocalIndex].z * .8f / (1 + collidablePositions);

        for (int i = 0; i < collidablePositions; ++i)
        {
            if (platform.canSpawnCollidable(i))
            {
                int localIndex = infiniteObjectManager.getNextObjectIndex(objectType, spawnData);
                if (localIndex != -1)
                {
                    InfiniteObject collidable = infiniteObjectManager.objectFromPool(localIndex, objectType);
                    collidable.orient(platform, position + (offset.z + ((i + 1) * zDelta)) * direction + platform.getRandomSlot() * slotDistance, Quaternion.LookRotation(direction));
                    int objectIndex = infiniteObjectManager.localIndexToObjectIndex(localIndex, objectType);
                    infiniteObjectHistory.objectSpawned(objectIndex, (offset.z + ((i + 1) * zDelta)), location, objectType);
                    platform.collidableSpawned(i);
                    if (activateImmediately)
                    {
                        collidable.activate();
                    }

                    // don't allow any more of the same collidable type if we are forcing a different collidable
                    if (platform.forceDifferentCollidableTypes)
                    {
                        break;
                    }
                }
            }
        }
    }
    // returns true if there is still space on the platform for a collidable object to spawn
    private void spawnCollidable(ObjectType objectType, Vector3 position, Vector3 direction, ObjectLocation location, PlatformObject platform, int platformLocalIndex, bool activateImmediately)
    {
        int collidablePositions = platform.collidablePositions;
        // can't do anything if the platform doesn't accept any collidable object spawns
        if (collidablePositions == 0)
            return;

        Vector3 offset = platformSizes[platformLocalIndex] * 0.1f;
        float zDelta = platformSizes[platformLocalIndex].z * .8f / (1 + collidablePositions);

        for (int i = 0; i < collidablePositions; ++i) {
            if (platform.canSpawnCollidable(i)) {
                spawnData.slotPositions = platform.getSlotsAvailable();
                int localIndex = infiniteObjectManager.getNextObjectIndex(objectType, spawnData);
                if (localIndex != -1) {
                    CollidableObject collidable = infiniteObjectManager.objectFromPool(localIndex, objectType) as CollidableObject;
                    Quaternion lookRotation = Quaternion.LookRotation(direction);
                    Vector3 spawnSlot = collidable.getSpawnSlot(platform.getTransform().right * slotDistance, spawnData.slotPositions);
                    collidable.orient(platform, position + (offset.z + ((i + 1) * zDelta)) * direction + spawnSlot, lookRotation);
                    if (activateImmediately)
                        collidable.activate();
                    
                    int objectIndex = infiniteObjectManager.localIndexToObjectIndex(localIndex, objectType);
                    infiniteObjectHistory.objectSpawned(objectIndex, (offset.z + ((i + 1) * zDelta)), location, lookRotation.eulerAngles.y, objectType);
                    platform.collidableSpawned(i);

                    // don't allow any more of the same collidable type if we are forcing a different collidable
                    if (platform.forceDifferentCollidableTypes)
                        break;
                }
            }
        }
        spawnData.slotPositions = 0;
    }