bool Texture2D::GetData(unsigned level, void* dest) const
{
    if (!object_.name_ || !graphics_)
    {
        ATOMIC_LOGERROR("No texture created, can not get data");
        return false;
    }

#ifndef GL_ES_VERSION_2_0
    if (!dest)
    {
        ATOMIC_LOGERROR("Null destination for getting data");
        return false;
    }

    if (level >= levels_)
    {
        ATOMIC_LOGERROR("Illegal mip level for getting data");
        return false;
    }

    if (graphics_->IsDeviceLost())
    {
        ATOMIC_LOGWARNING("Getting texture data while device is lost");
        return false;
    }

    if (multiSample_ > 1 && !autoResolve_)
    {
        ATOMIC_LOGERROR("Can not get data from multisampled texture without autoresolve");
        return false;
    }
    
    if (resolveDirty_)
        graphics_->ResolveToTexture(const_cast<Texture2D*>(this));

    graphics_->SetTextureForUpdate(const_cast<Texture2D*>(this));

    if (!IsCompressed())
        glGetTexImage(target_, level, GetExternalFormat(format_), GetDataType(format_), dest);
    else
        glGetCompressedTexImage(target_, level, dest);

    graphics_->SetTexture(0, 0);
    return true;
#else
    // Special case on GLES: if the texture is a rendertarget, can make it current and use glReadPixels()
    if (usage_ == TEXTURE_RENDERTARGET)
    {
        graphics_->SetRenderTarget(0, const_cast<Texture2D*>(this));
        // Ensure the FBO is current; this viewport is actually never rendered to
        graphics_->SetViewport(IntRect(0, 0, width_, height_));
        glReadPixels(0, 0, width_, height_, GetExternalFormat(format_), GetDataType(format_), dest);
        return true;
    }

    ATOMIC_LOGERROR("Getting texture data not supported");
    return false;
#endif
}
bool Geometry::SetDrawRange(PrimitiveType type, unsigned indexStart, unsigned indexCount, bool getUsedVertexRange)
{
    if (!indexBuffer_ && !rawIndexData_)
    {
        ATOMIC_LOGERROR("Null index buffer and no raw index data, can not define indexed draw range");
        return false;
    }
    if (indexBuffer_ && indexStart + indexCount > indexBuffer_->GetIndexCount())
    {
        ATOMIC_LOGERROR("Illegal draw range " + String(indexStart) + " to " + String(indexStart + indexCount - 1) + ", index buffer has " +
                 String(indexBuffer_->GetIndexCount()) + " indices");
        return false;
    }

    primitiveType_ = type;
    indexStart_ = indexStart;
    indexCount_ = indexCount;

    // Get min.vertex index and num of vertices from index buffer. If it fails, use full range as fallback
    if (indexCount)
    {
        vertexStart_ = 0;
        vertexCount_ = vertexBuffers_[0] ? vertexBuffers_[0]->GetVertexCount() : 0;

        if (getUsedVertexRange && indexBuffer_)
            indexBuffer_->GetUsedVertexRange(indexStart_, indexCount_, vertexStart_, vertexCount_);
    }
    else
    {
        vertexStart_ = 0;
        vertexCount_ = 0;
    }

    return true;
}
bool PListFile::BeginLoad(Deserializer& source)
{
    if (GetName().Empty())
        SetName(source.GetName());

    XMLFile xmlFile(context_);
    if (!xmlFile.Load(source))
    {
        ATOMIC_LOGERROR("Could not load property list");
        return false;
    }

    XMLElement plistElem = xmlFile.GetRoot("plist");
    if (!plistElem)
    {
        ATOMIC_LOGERROR("Invalid property list file");
        return false;
    }

    root_.Clear();

    XMLElement dictElem = plistElem.GetChild("dict");
    if (!LoadDict(root_, dictElem))
        return false;

    SetMemoryUse(source.GetSize());

    return true;
}
bool IndexBuffer::SetDataRange(const void* data, unsigned start, unsigned count, bool discard)
{
    if (start == 0 && count == indexCount_)
        return SetData(data);

    if (!data)
    {
        ATOMIC_LOGERROR("Null pointer for index buffer data");
        return false;
    }

    if (!indexSize_)
    {
        ATOMIC_LOGERROR("Index size not defined, can not set index buffer data");
        return false;
    }

    if (start + count > indexCount_)
    {
        ATOMIC_LOGERROR("Illegal range for setting new index buffer data");
        return false;
    }

    if (!count)
        return true;

    if (shadowData_ && shadowData_.Get() + start * indexSize_ != data)
        memcpy(shadowData_.Get() + start * indexSize_, data, count * indexSize_);

    if (object_.ptr_)
    {
        if (dynamic_)
        {
            void* hwData = MapBuffer(start, count, discard);
            if (hwData)
            {
                memcpy(hwData, data, count * indexSize_);
                UnmapBuffer();
            }
            else
                return false;
        }
        else
        {
            D3D11_BOX destBox;
            destBox.left = start * indexSize_;
            destBox.right = destBox.left + count * indexSize_;
            destBox.top = 0;
            destBox.bottom = 1;
            destBox.front = 0;
            destBox.back = 1;

            graphics_->GetImpl()->GetDeviceContext()->UpdateSubresource((ID3D11Buffer*)object_.ptr_, 0, &destBox, data, 0, 0);
        }
    }

    return true;
}
bool VertexBuffer::SetDataRange(const void* data, unsigned start, unsigned count, bool discard)
{
    if (start == 0 && count == vertexCount_)
        return SetData(data);

    if (!data)
    {
        ATOMIC_LOGERROR("Null pointer for vertex buffer data");
        return false;
    }

    if (!vertexSize_)
    {
        ATOMIC_LOGERROR("Vertex elements not defined, can not set vertex buffer data");
        return false;
    }

    if (start + count > vertexCount_)
    {
        ATOMIC_LOGERROR("Illegal range for setting new vertex buffer data");
        return false;
    }

    if (!count)
        return true;

    if (shadowData_ && shadowData_.Get() + start * vertexSize_ != data)
        memcpy(shadowData_.Get() + start * vertexSize_, data, count * vertexSize_);

    if (object_.ptr_)
    {
        if (graphics_->IsDeviceLost())
        {
            ATOMIC_LOGWARNING("Vertex buffer data assignment while device is lost");
            dataPending_ = true;
            return true;
        }

        void* hwData = MapBuffer(start, count, discard);
        if (hwData)
        {
            memcpy(hwData, data, count * vertexSize_);
            UnmapBuffer();
        }
        else
            return false;
    }

    return true;
}
Beispiel #6
0
void Cursor::ApplyOSCursorShape()
{
    // Mobile platforms do not support applying OS cursor shapes: comment out to avoid log error messages
#if !defined(ANDROID) && !defined(IOS)
    if (!osShapeDirty_ || !GetSubsystem<Input>()->IsMouseVisible() || GetSubsystem<SystemUI>()->GetCursor() != this)
        return;

    CursorShapeInfo& info = shapeInfos_[shape_];

    // Remove existing SDL cursor if is not a system shape while we should be using those, or vice versa
    if (info.osCursor_ && info.systemDefined_ != useSystemShapes_)
    {
        SDL_FreeCursor(info.osCursor_);
        info.osCursor_ = 0;
    }

    // Create SDL cursor now if necessary
    if (!info.osCursor_)
    {
        // Create a system default shape
        if (useSystemShapes_ && info.systemCursor_ >= 0 && info.systemCursor_ < CS_MAX_SHAPES)
        {
            info.osCursor_ = SDL_CreateSystemCursor((SDL_SystemCursor)osCursorLookup[info.systemCursor_]);
            info.systemDefined_ = true;
            if (!info.osCursor_)
                ATOMIC_LOGERROR("Could not create system cursor");
        }
        // Create from image
        else if (info.image_)
        {
            SDL_Surface* surface = info.image_->GetSDLSurface(info.imageRect_);

            if (surface)
            {
                info.osCursor_ = SDL_CreateColorCursor(surface, info.hotSpot_.x_, info.hotSpot_.y_);
                info.systemDefined_ = false;
                if (!info.osCursor_)
                    ATOMIC_LOGERROR("Could not create cursor from image " + info.image_->GetName());
                SDL_FreeSurface(surface);
            }
        }
    }

    if (info.osCursor_)
        SDL_SetCursor(info.osCursor_);

    osShapeDirty_ = false;
#endif
}
bool TextureCube::SetSize(int size, unsigned format, TextureUsage usage)
{
    if (size <= 0)
    {
        ATOMIC_LOGERROR("Zero or negative cube texture size");
        return false;
    }
    if (usage == TEXTURE_DEPTHSTENCIL)
    {
        ATOMIC_LOGERROR("Depth-stencil usage not supported for cube maps");
        return false;
    }

    // Delete the old rendersurfaces if any
    for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
    {
        renderSurfaces_[i].Reset();
        faceMemoryUse_[i] = 0;
    }

    usage_ = usage;

    if (usage == TEXTURE_RENDERTARGET)
    {
        for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i)
        {
            renderSurfaces_[i] = new RenderSurface(this);
#ifdef ATOMIC_OPENGL
            renderSurfaces_[i]->target_ = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
#endif
        }

        // Nearest filtering and mipmaps disabled by default
        filterMode_ = FILTER_NEAREST;
        requestedLevels_ = 1;
    }

    if (usage == TEXTURE_RENDERTARGET)
        SubscribeToEvent(E_RENDERSURFACEUPDATE, ATOMIC_HANDLER(TextureCube, HandleRenderSurfaceUpdate));
    else
        UnsubscribeFromEvent(E_RENDERSURFACEUPDATE);

    width_ = size;
    height_ = size;
    format_ = format;

    return Create();
}
void Player::SetCurrentScene(Scene* scene, Camera* camera)
{
    Vector<SharedPtr<Scene>>::ConstIterator citr = loadedScenes_.Find(SharedPtr<Scene>(scene));

    if (citr == loadedScenes_.End())
    {
        ATOMIC_LOGERROR("Player::UnloadScene - unknown scene");
        return;
    }

    currentScene_ = scene;

    if (!camera)
    {
        PODVector<Node*> cameraNodes;
        scene->GetChildrenWithComponent(cameraNodes, Camera::GetTypeStatic(), true);
        if (cameraNodes.Size())
        {
            camera = cameraNodes[0]->GetComponent<Camera>();
        }
    }

    viewport_->SetScene(scene);

    if (camera)
        viewport_->SetCamera(camera);

}
void Player::UnloadScene(Scene* scene)
{
    SharedPtr<Scene> keepalive(scene);

    if (currentScene_ == scene)
    {
        viewport_->SetScene(0);
        viewport_->SetCamera(0);
        currentScene_ = 0;
    }

    Vector<SharedPtr<Scene>>::ConstIterator citr = loadedScenes_.Find(keepalive);
    
    if (citr == loadedScenes_.End())
    {
        ATOMIC_LOGERROR("Player::UnloadScene - unknown scene");
        return;
    }

    VariantMap eventData;
    eventData[PlayerSceneUnload::P_SCENE] = scene;

    scene->SendEvent(E_PLAYERSCENEUNLOAD, eventData);

    loadedScenes_.Remove(keepalive);

}
bool UnknownComponent::SaveXML(XMLElement& dest) const
{
    if (dest.IsNull())
    {
        ATOMIC_LOGERROR("Could not save " + GetTypeName() + ", null destination element");
        return false;
    }

    if (!useXML_)
        ATOMIC_LOGWARNING("UnknownComponent loaded in binary or JSON mode, attributes will be empty for XML save");

    // Write type and ID
    if (!dest.SetString("type", GetTypeName()))
        return false;
    if (!dest.SetInt("id", id_))
        return false;

    for (unsigned i = 0; i < xmlAttributeInfos_.Size(); ++i)
    {
        XMLElement attrElem = dest.CreateChild("attribute");
        attrElem.SetAttribute("name", xmlAttributeInfos_[i].name_);
        attrElem.SetAttribute("value", xmlAttributes_[i]);
    }

    return true;
}
bool Geometry::SetDrawRange(PrimitiveType type, unsigned indexStart, unsigned indexCount, unsigned minVertex, unsigned vertexCount,
    bool checkIllegal)
{
    if (indexBuffer_)
    {
        // We can allow setting an illegal draw range now if the caller guarantees to resize / fill the buffer later
        if (checkIllegal && indexStart + indexCount > indexBuffer_->GetIndexCount())
        {
            ATOMIC_LOGERROR("Illegal draw range " + String(indexStart) + " to " + String(indexStart + indexCount - 1) +
                     ", index buffer has " + String(indexBuffer_->GetIndexCount()) + " indices");
            return false;
        }
    }
    else if (!rawIndexData_)
    {
        indexStart = 0;
        indexCount = 0;
    }

    primitiveType_ = type;
    indexStart_ = indexStart;
    indexCount_ = indexCount;
    vertexStart_ = minVertex;
    vertexCount_ = vertexCount;

    return true;
}
Variant ValueAnimation::SubstractAndMultiply(const Variant& value1, const Variant& value2, float t) const
{
    switch (valueType_)
    {
    case VAR_FLOAT:
        return (value1.GetFloat() - value2.GetFloat()) * t;

    case VAR_VECTOR2:
        return (value1.GetVector2() - value2.GetVector2()) * t;

    case VAR_VECTOR3:
        return (value1.GetVector3() - value2.GetVector3()) * t;

    case VAR_VECTOR4:
        return (value1.GetVector4() - value2.GetVector4()) * t;

    case VAR_QUATERNION:
        return (value1.GetQuaternion() - value2.GetQuaternion()) * t;

    case VAR_COLOR:
        return (value1.GetColor() - value2.GetColor()) * t;

    case VAR_DOUBLE:
        return (value1.GetDouble() - value2.GetDouble()) * t;

    default:
        ATOMIC_LOGERROR("Invalid value type for spline interpolation's subtract and multiply operation");
        return Variant::EMPTY;
    }
}
bool PListFile::LoadValue(PListValue& value, const XMLElement& valueElem)
{
    String valueType = valueElem.GetName();

    if (valueType == "string")
        value.SetString(valueElem.GetValue());
    else if (valueType == "real")
        value.SetFloat(ToFloat(valueElem.GetValue()));
    else if (valueType == "integer")
        value.SetInt(ToInt(valueElem.GetValue()));
    else if (valueType == "true")
        value.SetBool(true);
    else if (valueType == "false")
        value.SetBool(false);
    else if (valueType == "dict")
    {
        if (!LoadDict(value.ConvertToValueMap(), valueElem))
            return false;
    }
    else if (valueType == "array")
    {
        if (!LoadArray(value.ConvertToValueVector(), valueElem))
            return false;
    }
    else
    {
        ATOMIC_LOGERROR("Supported value type");
        return false;
    }

    return true;
}
float ValueAnimationInfo::CalculateScaledTime(float currentTime, bool& finished) const
{
    float beginTime = animation_->GetBeginTime();
    float endTime = animation_->GetEndTime();

    switch (wrapMode_)
    {
    case WM_LOOP:
        {
            float span = endTime - beginTime;
            float time = fmodf(currentTime - beginTime, span);
            if (time < 0.0f)
                time += span;
            return beginTime + time;
        }

    case WM_ONCE:
        finished = (currentTime >= endTime);
        // Fallthrough

    case WM_CLAMP:
        return Clamp(currentTime, beginTime, endTime);

    default:
        ATOMIC_LOGERROR("Unsupported attribute animation wrap mode");
        return beginTime;
    }
}
void CustomGeometry::DefineGeometry(unsigned index, PrimitiveType type, unsigned numVertices, bool hasNormals, bool hasColors,
    bool hasTexCoords, bool hasTangents)
{
    if (index > geometries_.Size())
    {
        ATOMIC_LOGERROR("Geometry index out of bounds");
        return;
    }

    geometryIndex_ = index;
    primitiveTypes_[index] = type;
    vertices_[index].Resize(numVertices);

    // If defining the first geometry, reset the element mask
    if (!index)
        elementMask_ = MASK_POSITION;
    if (hasNormals)
        elementMask_ |= MASK_NORMAL;
    if (hasColors)
        elementMask_ |= MASK_COLOR;
    if (hasTexCoords)
        elementMask_ |= MASK_TEXCOORD1;
    if (hasTangents)
        elementMask_ |= MASK_TANGENT;
}
Beispiel #16
0
bool File::Open(PackageFile* package, const String& fileName)
{
    if (!package)
        return false;

    const PackageEntry* entry = package->GetEntry(fileName);
    if (!entry)
        return false;

    bool success = OpenInternal(package->GetName(), FILE_READ, true);
    if (!success)
    {
        ATOMIC_LOGERROR("Could not open package file " + fileName);
        return false;
    }

    fileName_ = fileName;
    offset_ = entry->offset_;
    checksum_ = entry->checksum_;
    size_ = entry->size_;
    compressed_ = package->IsCompressed();

    // Seek to beginning of package entry's file data
    SeekInternal(offset_);
    return true;
}
bool IndexBuffer::SetData(const void* data)
{
    if (!data)
    {
        ATOMIC_LOGERROR("Null pointer for index buffer data");
        return false;
    }

    if (!indexSize_)
    {
        ATOMIC_LOGERROR("Index size not defined, can not set index buffer data");
        return false;
    }

    if (shadowData_ && data != shadowData_.Get())
        memcpy(shadowData_.Get(), data, indexCount_ * indexSize_);

    if (object_.ptr_)
    {
        if (dynamic_)
        {
            void* hwData = MapBuffer(0, indexCount_, true);
            if (hwData)
            {
                memcpy(hwData, data, indexCount_ * indexSize_);
                UnmapBuffer();
            }
            else
                return false;
        }
        else
        {
            D3D11_BOX destBox;
            destBox.left = 0;
            destBox.right = indexCount_ * indexSize_;
            destBox.top = 0;
            destBox.bottom = 1;
            destBox.front = 0;
            destBox.back = 1;

            graphics_->GetImpl()->GetDeviceContext()->UpdateSubresource((ID3D11Buffer*)object_.ptr_, 0, &destBox, data, 0, 0);
        }
    }

    return true;
}
bool IndexBuffer::GetUsedVertexRange(unsigned start, unsigned count, unsigned& minVertex, unsigned& vertexCount)
{
    if (!shadowData_)
    {
        ATOMIC_LOGERROR("Used vertex range can only be queried from an index buffer with shadow data");
        return false;
    }

    if (start + count > indexCount_)
    {
        ATOMIC_LOGERROR("Illegal index range for querying used vertices");
        return false;
    }

    minVertex = M_MAX_UNSIGNED;
    unsigned maxVertex = 0;

    if (indexSize_ == sizeof(unsigned))
    {
        unsigned* indices = ((unsigned*)shadowData_.Get()) + start;

        for (unsigned i = 0; i < count; ++i)
        {
            if (indices[i] < minVertex)
                minVertex = indices[i];
            if (indices[i] > maxVertex)
                maxVertex = indices[i];
        }
    }
    else
    {
        unsigned short* indices = ((unsigned short*)shadowData_.Get()) + start;

        for (unsigned i = 0; i < count; ++i)
        {
            if (indices[i] < minVertex)
                minVertex = indices[i];
            if (indices[i] > maxVertex)
                maxVertex = indices[i];
        }
    }

    vertexCount = maxVertex - minVertex + 1;
    return true;
}
Beispiel #19
0
void Cursor::DefineShape(CursorShape shape, Image* image, const IntRect& imageRect, const IntVector2& hotSpot)
{
    if (shape < CS_NORMAL || shape >= CS_MAX_SHAPES)
    {
        ATOMIC_LOGERROR("Shape index out of bounds, can not define cursor shape");
        return;
    }

    DefineShape(shapeNames[shape], image, imageRect, hotSpot);
}
void* IndexBuffer::Lock(unsigned start, unsigned count, bool discard)
{
    if (lockState_ != LOCK_NONE)
    {
        ATOMIC_LOGERROR("Index buffer already locked");
        return 0;
    }

    if (!indexSize_)
    {
        ATOMIC_LOGERROR("Index size not defined, can not lock index buffer");
        return 0;
    }

    if (start + count > indexCount_)
    {
        ATOMIC_LOGERROR("Illegal range for locking index buffer");
        return 0;
    }

    if (!count)
        return 0;

    lockStart_ = start;
    lockCount_ = count;

    // Because shadow data must be kept in sync, can only lock hardware buffer if not shadowed
    if (object_.ptr_ && !shadowData_ && dynamic_)
        return MapBuffer(start, count, discard);
    else if (shadowData_)
    {
        lockState_ = LOCK_SHADOW;
        return shadowData_.Get() + start * indexSize_;
    }
    else if (graphics_)
    {
        lockState_ = LOCK_SCRATCH;
        lockScratchData_ = graphics_->ReserveScratchBuffer(count * indexSize_);
        return lockScratchData_;
    }
    else
        return 0;
}
bool Geometry::SetVertexBuffer(unsigned index, VertexBuffer* buffer)
{
    if (index >= vertexBuffers_.Size())
    {
        ATOMIC_LOGERROR("Stream index out of bounds");
        return false;
    }

    vertexBuffers_[index] = buffer;
    return true;
}
bool CustomGeometry::SetMaterial(unsigned index, Material* material)
{
    if (index >= batches_.Size())
    {
        ATOMIC_LOGERROR("Material index out of bounds");
        return false;
    }

    batches_[index].material_ = material;
    MarkNetworkUpdate();
    return true;
}
bool Geometry::SetNumVertexBuffers(unsigned num)
{
    if (num >= MAX_VERTEX_STREAMS)
    {
        ATOMIC_LOGERROR("Too many vertex streams");
        return false;
    }

    unsigned oldSize = vertexBuffers_.Size();
    vertexBuffers_.Resize(num);

    return true;
}
bool Texture3D::SetSize(int width, int height, int depth, unsigned format, TextureUsage usage)
{
    if (width <= 0 || height <= 0 || depth <= 0)
    {
        ATOMIC_LOGERROR("Zero or negative 3D texture dimensions");
        return false;
    }
    if (usage >= TEXTURE_RENDERTARGET)
    {
        ATOMIC_LOGERROR("Rendertarget or depth-stencil usage not supported for 3D textures");
        return false;
    }

    usage_ = usage;

    width_ = width;
    height_ = height;
    depth_ = depth;
    format_ = format;

    return Create();
}
bool VertexBuffer::SetData(const void* data)
{
    if (!data)
    {
        ATOMIC_LOGERROR("Null pointer for vertex buffer data");
        return false;
    }

    if (!vertexSize_)
    {
        ATOMIC_LOGERROR("Vertex elements not defined, can not set vertex buffer data");
        return false;
    }

    if (shadowData_ && data != shadowData_.Get())
        memcpy(shadowData_.Get(), data, vertexCount_ * vertexSize_);

    if (object_.ptr_)
    {
        if (graphics_->IsDeviceLost())
        {
            ATOMIC_LOGWARNING("Vertex buffer data assignment while device is lost");
            dataPending_ = true;
            return true;
        }

        void* hwData = MapBuffer(0, vertexCount_, true);
        if (hwData)
        {
            memcpy(hwData, data, vertexCount_ * vertexSize_);
            UnmapBuffer();
        }
        else
            return false;
    }

    dataLost_ = false;
    return true;
}
Beispiel #26
0
unsigned File::Write(const void* data, unsigned size)
{
    if (!IsOpen())
    {
        // If file not open, do not log the error further here to prevent spamming the stderr stream
        return 0;
    }

    if (mode_ == FILE_READ)
    {
        ATOMIC_LOGERROR("File not opened for writing");
        return 0;
    }

    if (!size)
        return 0;

    // Need to reassign the position due to internal buffering when transitioning from reading to writing
    if (writeSyncNeeded_)
    {
        fseek((FILE*)handle_, position_ + offset_, SEEK_SET);
        writeSyncNeeded_ = false;
    }

    if (fwrite(data, size, 1, (FILE*)handle_) != 1)
    {
        // Return to the position where the write began
        fseek((FILE*)handle_, position_ + offset_, SEEK_SET);
        ATOMIC_LOGERROR("Error while writing to file " + GetName());
        return 0;
    }

    readSyncNeeded_ = true;
    position_ += size;
    if (position_ > size_)
        size_ = position_;

    return size;
}
Variant ValueAnimation::SplineInterpolation(unsigned index1, unsigned index2, float scaledTime)
{
    if (splineTangentsDirty_)
        UpdateSplineTangents();

    const VAnimKeyFrame& keyFrame1 = keyFrames_[index1];
    const VAnimKeyFrame& keyFrame2 = keyFrames_[index2];

    float t = (scaledTime - keyFrame1.time_) / (keyFrame2.time_ - keyFrame1.time_);

    float tt = t * t;
    float ttt = t * tt;

    float h1 = 2.0f * ttt - 3.0f * tt + 1.0f;
    float h2 = -2.0f * ttt + 3.0f * tt;
    float h3 = ttt - 2.0f * tt + t;
    float h4 = ttt - tt;

    const Variant& v1 = keyFrame1.value_;
    const Variant& v2 = keyFrame2.value_;
    const Variant& t1 = splineTangents_[index1];
    const Variant& t2 = splineTangents_[index2];

    switch (valueType_)
    {
    case VAR_FLOAT:
        return v1.GetFloat() * h1 + v2.GetFloat() * h2 + t1.GetFloat() * h3 + t2.GetFloat() * h4;

    case VAR_VECTOR2:
        return v1.GetVector2() * h1 + v2.GetVector2() * h2 + t1.GetVector2() * h3 + t2.GetVector2() * h4;

    case VAR_VECTOR3:
        return v1.GetVector3() * h1 + v2.GetVector3() * h2 + t1.GetVector3() * h3 + t2.GetVector3() * h4;

    case VAR_VECTOR4:
        return v1.GetVector4() * h1 + v2.GetVector4() * h2 + t1.GetVector4() * h3 + t2.GetVector4() * h4;

    case VAR_QUATERNION:
        return v1.GetQuaternion() * h1 + v2.GetQuaternion() * h2 + t1.GetQuaternion() * h3 + t2.GetQuaternion() * h4;

    case VAR_COLOR:
        return v1.GetColor() * h1 + v2.GetColor() * h2 + t1.GetColor() * h3 + t2.GetColor() * h4;

    case VAR_DOUBLE:
        return v1.GetDouble() * h1 + v2.GetDouble() * h2 + t1.GetDouble() * h3 + t2.GetDouble() * h4;

    default:
        ATOMIC_LOGERROR("Invalid value type for spline interpolation");
        return Variant::EMPTY;
    }
}
void Context::ReleaseSDL()
{
    --sdlInitCounter;

    if (sdlInitCounter == 0)
    {
        ATOMIC_LOGDEBUG("Quitting SDL");
        SDL_QuitSubSystem(SDL_INIT_EVERYTHING);
        SDL_Quit();
    }

    if (sdlInitCounter < 0)
        ATOMIC_LOGERROR("Too many calls to Context::ReleaseSDL()!");
}
void Context::ReleaseIK()
{
    --ikInitCounter;

    if (ikInitCounter == 0)
    {
        ATOMIC_LOGDEBUG("De-initialising Inverse Kinematics library");
        ik_log_unregister_listener(HandleIKLog);
        ik_log_deinit();
        ik_memory_deinit();
    }

    if (ikInitCounter < 0)
        ATOMIC_LOGERROR("Too many calls to Context::ReleaseIK()");
}
void CustomGeometry::BeginGeometry(unsigned index, PrimitiveType type)
{
    if (index > geometries_.Size())
    {
        ATOMIC_LOGERROR("Geometry index out of bounds");
        return;
    }

    geometryIndex_ = index;
    primitiveTypes_[index] = type;
    vertices_[index].Clear();

    // If beginning the first geometry, reset the element mask
    if (!index)
        elementMask_ = MASK_POSITION;
}