From cd08a41335a55260b6efa7658e3ce9bc45f4980b Mon Sep 17 00:00:00 2001 From: vsgopenmw-bot Date: Tue, 5 Sep 2023 16:38:36 +0200 Subject: [PATCH] vsgopenmw-components-vsgadapters --- .../vsgadapters/mygui/manualtexture.cpp | 58 + .../vsgadapters/mygui/manualtexture.hpp | 16 + components/vsgadapters/mygui/render.cpp | 299 ++++ components/vsgadapters/mygui/render.hpp | 86 + components/vsgadapters/mygui/texture.cpp | 118 ++ components/vsgadapters/mygui/texture.hpp | 68 + components/vsgadapters/nif/anim.cpp | 145 ++ components/vsgadapters/nif/anim.hpp | 141 ++ components/vsgadapters/nif/channel.hpp | 40 + components/vsgadapters/nif/kf.cpp | 69 + components/vsgadapters/nif/kf.hpp | 20 + components/vsgadapters/nif/nif.cpp | 1398 +++++++++++++++++ components/vsgadapters/nif/nif.hpp | 43 + components/vsgadapters/nif/particle.cpp | 214 +++ components/vsgadapters/nif/particle.hpp | 61 + .../vsgadapters/osg/MatrixDecomposition.cpp | 884 +++++++++++ .../vsgadapters/osg/Matrix_implementation.cpp | 1087 +++++++++++++ components/vsgadapters/osg/Matrixd.cpp | 37 + components/vsgadapters/osg/Matrixf.cpp | 37 + components/vsgadapters/osg/Quat.cpp | 351 +++++ components/vsgadapters/osgcompat.hpp | 68 + components/vsgadapters/sdl/surface.cpp | 77 + components/vsgadapters/sdl/surface.hpp | 19 + components/vsgadapters/sdl/window.cpp | 30 + components/vsgadapters/sdl/window.hpp | 23 + components/vsgadapters/vfs.cpp | 31 + components/vsgadapters/vfs.hpp | 31 + 27 files changed, 5451 insertions(+) create mode 100644 components/vsgadapters/mygui/manualtexture.cpp create mode 100644 components/vsgadapters/mygui/manualtexture.hpp create mode 100644 components/vsgadapters/mygui/render.cpp create mode 100644 components/vsgadapters/mygui/render.hpp create mode 100644 components/vsgadapters/mygui/texture.cpp create mode 100644 components/vsgadapters/mygui/texture.hpp create mode 100644 components/vsgadapters/nif/anim.cpp create mode 100644 components/vsgadapters/nif/anim.hpp create mode 100644 components/vsgadapters/nif/channel.hpp create mode 100644 components/vsgadapters/nif/kf.cpp create mode 100644 components/vsgadapters/nif/kf.hpp create mode 100644 components/vsgadapters/nif/nif.cpp create mode 100644 components/vsgadapters/nif/nif.hpp create mode 100644 components/vsgadapters/nif/particle.cpp create mode 100644 components/vsgadapters/nif/particle.hpp create mode 100644 components/vsgadapters/osg/MatrixDecomposition.cpp create mode 100644 components/vsgadapters/osg/Matrix_implementation.cpp create mode 100644 components/vsgadapters/osg/Matrixd.cpp create mode 100644 components/vsgadapters/osg/Matrixf.cpp create mode 100644 components/vsgadapters/osg/Quat.cpp create mode 100644 components/vsgadapters/osgcompat.hpp create mode 100644 components/vsgadapters/sdl/surface.cpp create mode 100644 components/vsgadapters/sdl/surface.hpp create mode 100644 components/vsgadapters/sdl/window.cpp create mode 100644 components/vsgadapters/sdl/window.hpp create mode 100644 components/vsgadapters/vfs.cpp create mode 100644 components/vsgadapters/vfs.hpp diff --git a/components/vsgadapters/mygui/manualtexture.cpp b/components/vsgadapters/mygui/manualtexture.cpp new file mode 100644 index 00000000000..2aa7f602ed6 --- /dev/null +++ b/components/vsgadapters/mygui/manualtexture.cpp @@ -0,0 +1,58 @@ +#include "manualtexture.hpp" + +#include + +namespace vsgAdapters::mygui +{ + MyGUI::PixelFormat format(VkFormat vk) + { + switch (vk) + { + case VK_FORMAT_R8G8B8A8_UNORM: + return MyGUI::PixelFormat::R8G8B8A8; + case VK_FORMAT_R8G8B8_UNORM: + return MyGUI::PixelFormat::R8G8B8; + default: + throw std::runtime_error("!MyGUI::PixelFormat(VkFormat=" + std::to_string(vk) + ")"); + } + } + + bool compatible(MyGUI::ITexture* tex, vsg::ref_ptr data) + { + auto pf = format(data->getLayout().format); + return tex->getFormat() == pf && static_cast(tex->getWidth()) == data->width() + && static_cast(tex->getHeight()) == data->height(); + } + + void fillManualTexture(MyGUI::ITexture* tex, vsg::ref_ptr data) + { + if (compatible(tex, data)) + { + auto* dst = reinterpret_cast(tex->lock(MyGUI::TextureUsage::Write)); + std::memcpy(dst, data->dataPointer(), data->dataSize()); + tex->unlock(); + } + } + + MyGUI::ITexture* createManualTexture( + const std::string& name, vsg::ref_ptr data, MyGUI::TextureUsage usage) + { + auto texture = MyGUI::RenderManager::getInstance().createTexture(name); + texture->createManual(data->width(), data->height(), usage, format(data->getLayout().format)); + fillManualTexture(texture, data); + return texture; + } + + void createOrUpdateManualTexture(MyGUI::ITexture*& tex, const std::string& name, vsg::ref_ptr data) + { + if (tex && !compatible(tex, data)) + { + MyGUI::RenderManager::getInstance().destroyTexture(tex); + tex = {}; + } + if (!tex) + tex = createManualTexture(name, data, MyGUI::TextureUsage::Dynamic); + else + fillManualTexture(tex, data); + } +} diff --git a/components/vsgadapters/mygui/manualtexture.hpp b/components/vsgadapters/mygui/manualtexture.hpp new file mode 100644 index 00000000000..9295eadd431 --- /dev/null +++ b/components/vsgadapters/mygui/manualtexture.hpp @@ -0,0 +1,16 @@ +#ifndef VSGOPENMW_VSGADAPTERS_MYGUI_MANUALTEXTURE_H +#define VSGOPENMW_VSGADAPTERS_MYGUI_MANUALTEXTURE_H + +#include + +#include + +namespace vsgAdapters::mygui +{ + void fillManualTexture(MyGUI::ITexture* tex, vsg::ref_ptr data); + MyGUI::ITexture* createManualTexture( + const std::string& name, vsg::ref_ptr data, MyGUI::TextureUsage usage = MyGUI::TextureUsage::Write); + void createOrUpdateManualTexture(MyGUI::ITexture*& tex, const std::string& name, vsg::ref_ptr data); +} + +#endif diff --git a/components/vsgadapters/mygui/render.cpp b/components/vsgadapters/mygui/render.cpp new file mode 100644 index 00000000000..ce75f448192 --- /dev/null +++ b/components/vsgadapters/mygui/render.cpp @@ -0,0 +1,299 @@ +#include "render.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //vsgopenmw-fixme +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "texture.hpp" + +namespace +{ + const int viewID = 0; + + vsg::GraphicsPipelineStates createPipelineStates(VkBlendFactor srcBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA, + VkBlendFactor dstBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA) + { + vsg::ColorBlendState::ColorBlendAttachments colorBlendAttachments{ { true, srcBlendFactor, dstBlendFactor, + VK_BLEND_OP_ADD, srcBlendFactor, dstBlendFactor, VK_BLEND_OP_ADD, + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT + | VK_COLOR_COMPONENT_A_BIT } }; + + auto depthStencilState = vsg::DepthStencilState::create(); + depthStencilState->depthTestEnable = false; + depthStencilState->depthWriteEnable = false; + + auto rasterState = vsg::RasterizationState::create(); + rasterState->cullMode = VK_CULL_MODE_NONE; + + vsg::VertexInputState::Bindings vertexBindingsDescriptions + = { VkVertexInputBindingDescription{ 0, sizeof(MyGUI::Vertex), VK_VERTEX_INPUT_RATE_VERTEX } }; + vsg::VertexInputState::Attributes vertexAttributeDescriptions + = { { 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(MyGUI::Vertex, x) }, + { 1, 0, VK_FORMAT_R8G8B8A8_UNORM, offsetof(MyGUI::Vertex, colour) }, + { 2, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(MyGUI::Vertex, u) } }; + + return { vsg::VertexInputState::create(vertexBindingsDescriptions, vertexAttributeDescriptions), + vsg::InputAssemblyState::create(), rasterState, vsg::MultisampleState::create(), + vsg::ColorBlendState::create(colorBlendAttachments), depthStencilState, + vsg::DynamicState::create(VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR) }; + } + +} + +namespace vsgAdapters::mygui +{ + + class VertexBuffer : public MyGUI::IVertexBuffer + { + const vsgUtil::CompileContext& mCompileContext; + public: + VertexBuffer(const vsgUtil::CompileContext& compile) : mCompileContext(compile) {} + ~VertexBuffer() + { + if (mCompiled) + mCompileContext.detach(mDraw); + } + size_t mNeedVertexCount = 0; + vsg::ref_ptr mArray; + vsg::ref_ptr mDraw; + bool mCompiled = false; + void setVertexCount(size_t count) override { mNeedVertexCount = count; } + size_t getVertexCount() const override { return mNeedVertexCount; } + MyGUI::Vertex* lock() override + { + if (!mArray || mArray->dataSize() < mNeedVertexCount * sizeof(MyGUI::Vertex)) + { + if (mCompiled) + mCompileContext.detach(mDraw); + mArray = vsg::ubyteArray::create(mNeedVertexCount * sizeof(MyGUI::Vertex)); + mArray->properties.dataVariance = vsg::DataVariance::DYNAMIC_DATA_TRANSFER_AFTER_RECORD; + mDraw = vsg::VertexDraw::create(); + mDraw->assignArrays(vsg::DataList{ mArray }); + mDraw->instanceCount = 1; + mCompiled = false; + } + return reinterpret_cast(mArray->dataPointer()); + } + void unlock() override + { + if (mCompiled) + mArray->dirty(); + } + }; + class Node : public vsg::Compilable + { + public: + Node(Render& Render) + : mRender(Render) + { + } + + void compile(vsg::Context& ctx) override { mRender.compilePipelines(ctx); } + void accept(vsg::RecordTraversal& visitor) const override { mRender.render(visitor); } + + private: + Render& mRender; + }; + + Render::Render(MyGUI::IntSize extent, vsg::ref_ptr compileContext, + const vsg::Options* imageOptions, const vsg::Options* shaderOptions, float scalingFactor) + : mImageOptions(imageOptions) + , mShaderOptions(shaderOptions) + , mCompileContext(compileContext) + { + mNode = vsg::ref_ptr{ new Node(*this) }; + + mCompileTraversal = vsg::CompileTraversal::create(vsg::ref_ptr{mCompileContext->device}); + // To help reduce the number of vsg::DescriptorPool that need to be allocated we'll provide a minimum requirement via ResourceHints. + for (auto& context : mCompileTraversal->contexts) + { + context->minimum_maxSets = 64u; + context->minimum_descriptorPoolSizes.push_back({ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 64u }); + } + + if (scalingFactor != 0.f) + mInvScalingFactor = 1.f / scalingFactor; + setViewSize(extent.width, extent.height); + + vsg::DescriptorSetLayoutBindings textureBindings + = { { 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr } }; + mPipelineLayout = vsg::PipelineLayout::create( + vsg::DescriptorSetLayouts{ vsg::DescriptorSetLayout::create(textureBindings) }, vsg::PushConstantRanges{}); + } + + Render::~Render() { } + + MyGUI::IVertexBuffer* Render::createVertexBuffer() + { + auto buffer = new VertexBuffer(*mCompileContext); + return buffer; + } + + void Render::destroyVertexBuffer(MyGUI::IVertexBuffer* buffer) + { + delete buffer; + } + + void Render::doRender(MyGUI::IVertexBuffer* buffer, MyGUI::ITexture* texture, size_t count) + { + auto tex = static_cast(texture); + // assert(tex->mTexture.valid(); + if (!tex->mTexture.valid()) + return; + + auto vertexBuffer = static_cast(buffer); + if (!vertexBuffer->mCompiled) + { + mCompileTraversal->apply(*(vertexBuffer->mDraw)); + mNewDynamicBufferInfo.push_back(vertexBuffer->mDraw->arrays[0]); + vertexBuffer->mCompiled = true; + mPendingCompile = mCompileTraversal; + } + if (!tex->mCompiled) + { + if (!tex->mShader.empty()) + { + auto idx = std::distance( + mPipelineNames.begin(), std::find(mPipelineNames.begin(), mPipelineNames.end(), tex->mShader)); + tex->mPipeline = mPipelines[idx]; + } + tex->createBindDescriptorSet(mPipelineLayout.get()); + mCompileTraversal->apply(*tex->mBindDescriptorSet); + if (tex->getUsage() == MyGUI::TextureUsage::Dynamic) + mNewDynamicImageInfo.push_back(tex->mTexture->imageInfoList[0]); + tex->mCompiled = true; + mPendingCompile = mCompileTraversal; + } + + if (tex->mPipeline) + mCurrentTraversal->apply(*tex->mPipeline); + else + mCurrentTraversal->apply(*mPipelines[0]); + + mCurrentTraversal->apply(*tex->mBindDescriptorSet); + vertexBuffer->mDraw->vertexCount = count; + mCurrentTraversal->apply(*(vertexBuffer->mDraw)); + } + + void Render::render(vsg::RecordTraversal& record) + { + mCurrentTraversal = &record; + record.apply(*mSetViewport); + record.getCommandBuffer()->viewID = viewID; + onRenderToTarget(this, mUpdate); + mUpdate = false; + // record.getState()->dirty(); + if (mPendingCompile) + { + if (mCompileTraversal->record()) + mCompileTraversal->waitForCompletion(); + + vsg::CompileResult res; + res.lateDynamicData.bufferInfos.swap(mNewDynamicBufferInfo); + res.earlyDynamicData.imageInfos.swap(mNewDynamicImageInfo); + mCompileContext->onCompiled(res); + + mPendingCompile = {}; + } + } + + void Render::setViewSize(int width, int height) + { + width = std::max(1, width); + height = std::max(1, height); + + if (!mSetViewport) + mSetViewport = vsgUtil::SetViewportState::create(vsg::ViewportState::create()); + mSetViewport->viewportState->set(0, 0, width, height); + + mViewSize.set(width * mInvScalingFactor, height * mInvScalingFactor); + + mInfo.maximumDepth = 1; + mInfo.hOffset = 0; + mInfo.vOffset = 0; + mInfo.aspectCoef = float(mViewSize.height) / float(mViewSize.width); + mInfo.pixScaleX = 1.0f / float(mViewSize.width); + mInfo.pixScaleY = 1.0f / float(mViewSize.height); + + onResizeView(mViewSize); + mUpdate = true; + } + + MyGUI::ITexture* Render::createTexture(const std::string& name) + { + auto item = mTextures.find(name); + if (item != mTextures.end()) + destroyTexture(item->second.get()); + const auto it = mTextures.emplace(name, std::make_unique(name, mImageOptions)).first; + return it->second.get(); + } + + void Render::destroyTexture(MyGUI::ITexture* texture) + { + auto tex = static_cast(texture); + if (tex->mCompiled) + mCompileContext->detach(tex->mBindDescriptorSet); + + mTextures.erase(texture->getName()); + } + + MyGUI::ITexture* Render::getTexture(const std::string& name) + { + auto item = mTextures.find(name); + if (item == mTextures.end()) + { + MyGUI::ITexture* tex = createTexture(name); + tex->loadFromFile(name); + return tex; + } + return item->second.get(); + } + + void Render::registerShader( + const std::string& shaderName, const std::string& vertexProgramFile, const std::string& fragmentProgramFile) + { + auto shaders = vsg::ShaderStages{ vsgUtil::readShader(vertexProgramFile, mShaderOptions), + vsgUtil::readShader(fragmentProgramFile, mShaderOptions) }; + + auto pipeline = vsg::BindGraphicsPipeline::create( + vsg::GraphicsPipeline::create(mPipelineLayout, shaders, createPipelineStates())); + mPipelineNames.emplace_back(shaderName); + mPipelines.emplace_back(pipeline); + + if (shaderName.empty()) + { + mPipelineNames.emplace_back("premult_alpha"); + mPipelines.emplace_back(vsg::BindGraphicsPipeline::create(vsg::GraphicsPipeline::create(mPipelineLayout, + shaders, createPipelineStates(VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA)))); + + mPipelineNames.emplace_back("blend_add"); + mPipelines.emplace_back(vsg::BindGraphicsPipeline::create(vsg::GraphicsPipeline::create( + mPipelineLayout, shaders, createPipelineStates(VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE)))); + } + } + + void Render::compilePipelines(vsg::Context& ctx) + { + ctx.viewID = viewID; + for (auto& p : mPipelines) + p->compile(ctx); + } + +} diff --git a/components/vsgadapters/mygui/render.hpp b/components/vsgadapters/mygui/render.hpp new file mode 100644 index 00000000000..56d058e7655 --- /dev/null +++ b/components/vsgadapters/mygui/render.hpp @@ -0,0 +1,86 @@ +#ifndef VSGOPENMW_VSGADAPTERS_MYGUI_RENDER_H +#define VSGOPENMW_VSGADAPTERS_MYGUI_RENDER_H + +#include +#include + +#include +#include + +namespace vsg +{ + class BufferInfo; + class ImageInfo; + class Context; + class CompileTraversal; + class PipelineLayout; + class Context; +} +namespace vsgUtil +{ + class SetViewportState; +} + +namespace vsgAdapters::mygui +{ + class Texture; + + class Render : public MyGUI::RenderManager, public MyGUI::IRenderTarget, public vsgUtil::Composite + { + friend class Node; + void compilePipelines(vsg::Context& ctx); + void render(vsg::RecordTraversal&); + + vsg::ref_ptr mSetViewport; + MyGUI::IntSize mViewSize; + bool mUpdate = false; + MyGUI::RenderTargetInfo mInfo; + using MapTexture = std::map>; + MapTexture mTextures; + + float mInvScalingFactor = 1.f; + vsg::ref_ptr mImageOptions; + vsg::ref_ptr mShaderOptions; + vsg::RecordTraversal* mCurrentTraversal = nullptr; + vsg::ref_ptr mCompileTraversal; + vsg::CompileTraversal* mPendingCompile{}; + std::vector> mNewDynamicBufferInfo; + std::vector> mNewDynamicImageInfo; + vsg::ref_ptr mCompileContext; + vsg::ref_ptr mPipelineLayout; + + std::vector mPipelineNames; + std::vector> mPipelines; + + public: + Render(MyGUI::IntSize size, vsg::ref_ptr compileContext, + const vsg::Options* imageOoptions, const vsg::Options* shaderOptions, float scalingFactor); + ~Render(); + + void setScalingFactor(float factor); + + const MyGUI::IntSize& getViewSize() const override { return mViewSize; } + MyGUI::VertexColourType getVertexFormat() const override { return MyGUI::VertexColourType::ColourABGR; } + MyGUI::IVertexBuffer* createVertexBuffer() override; + void destroyVertexBuffer(MyGUI::IVertexBuffer* buffer) override; + MyGUI::ITexture* createTexture(const std::string& name) override; + void destroyTexture(MyGUI::ITexture* _texture) override; + MyGUI::ITexture* getTexture(const std::string& name) override; + + void update(float dt) { onFrameEvent(dt); } + + void begin() override {} + void end() override {} + void doRender(MyGUI::IVertexBuffer* buffer, MyGUI::ITexture* texture, size_t count) override; + + const MyGUI::RenderTargetInfo& getInfo() const override { return mInfo; } + + void setViewSize(int width, int height) override; + + void registerShader(const std::string& _shaderName, const std::string& _vertexProgramFile, + const std::string& _fragmentProgramFile) override; + }; + +} + +#endif diff --git a/components/vsgadapters/mygui/texture.cpp b/components/vsgadapters/mygui/texture.cpp new file mode 100644 index 00000000000..5c9fcad10ab --- /dev/null +++ b/components/vsgadapters/mygui/texture.cpp @@ -0,0 +1,118 @@ +#include "texture.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace +{ + vsg::ref_ptr sampler() + { + auto sampler = vsg::Sampler::create(); + sampler->addressModeU = sampler->addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + return sampler; + } +} + +namespace vsgAdapters::mygui +{ + Texture::Texture(const std::string& name, vsg::ref_ptr options) + : mName(name) + , mOptions(options) + { + } + + Texture::Texture(vsg::ImageView* imageView) + { + if (imageView) + { + mTexture = vsg::DescriptorImage::create(vsg::ImageInfo::create(vsgUtil::share(sampler), + vsg::ref_ptr{ imageView }, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)); + if (auto& data = imageView->image->data) + { + if (data->dynamic()) + mUsage = MyGUI::TextureUsage::Dynamic; + } + } + } + + Texture::~Texture() {} + + void Texture::createManual(int width, int height, MyGUI::TextureUsage usage, MyGUI::PixelFormat format) + { + mWidth = width; + mHeight = height; + mFormat = format; + mUsage = usage; + + if (mFormat == MyGUI::PixelFormat::R8G8B8) + mLockedImage = vsg::ubvec3Array2D::create(mWidth, mHeight, vsg::Data::Properties{ VK_FORMAT_R8G8B8_UNORM }); + else if (mFormat == MyGUI::PixelFormat::R8G8B8A8) + mLockedImage = vsg::ubvec4Array2D::create(mWidth, mHeight, vsg::Data::Properties{ VK_FORMAT_R8G8B8A8_UNORM }); + else if (mFormat == MyGUI::PixelFormat::L8) + mLockedImage = vsg::ubyteArray2D::create(mWidth, mHeight, vsg::Data::Properties{ VK_FORMAT_R8_UNORM }); + else if (mFormat == MyGUI::PixelFormat::L8A8) + mLockedImage = vsg::ubvec2Array2D::create(mWidth, mHeight, vsg::Data::Properties{ VK_FORMAT_R8G8_UNORM }); + else + throw std::runtime_error("Texture format not supported"); + + if (usage == MyGUI::TextureUsage::Dynamic) + mLockedImage->properties.dataVariance = vsg::DataVariance::DYNAMIC_DATA; + + mTexture = vsg::DescriptorImage::create(vsgUtil::share(sampler), mLockedImage); + + if (mFormat == MyGUI::PixelFormat::L8) + mTexture->imageInfoList[0]->imageView->components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R }; + else if (mFormat == MyGUI::PixelFormat::L8A8) + mTexture->imageInfoList[0]->imageView->components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G }; + } + + void Texture::destroy() + { + mTexture = nullptr; + } + + void Texture::loadFromFile(const std::string& fname) + { + // assert(!fname.empty()); + if (fname.empty()) + return; + auto textureData = vsgUtil::readOptionalImage(fname, mOptions); + if (!textureData) + { + std::cout << "Failed to load " << fname << std::endl; + return; + } + mTexture = vsg::DescriptorImage::create(vsgUtil::share(sampler), textureData); + mWidth = textureData->width() * textureData->properties.blockWidth; + mHeight = textureData->height() * textureData->properties.blockHeight; + mBindDescriptorSet = nullptr; + mCompiled = false; + } + + void* Texture::lock(MyGUI::TextureUsage usage) + { + return mLockedImage->dataPointer(); + } + + void Texture::unlock() + { + if (mLockedImage && mCompiled) + mLockedImage->dirty(); + if (mUsage != MyGUI::TextureUsage::Dynamic) + mLockedImage = nullptr; + } + + void Texture::createBindDescriptorSet(vsg::PipelineLayout* layout) + { + mBindDescriptorSet + = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, vsg::Descriptors{ mTexture }); + } +} diff --git a/components/vsgadapters/mygui/texture.hpp b/components/vsgadapters/mygui/texture.hpp new file mode 100644 index 00000000000..395865f8dbe --- /dev/null +++ b/components/vsgadapters/mygui/texture.hpp @@ -0,0 +1,68 @@ +#ifndef VSGOPENMW_VSGADAPTERS_MYGUI_TEXTURE_H +#define VSGOPENMW_VSGADAPTERS_MYGUI_TEXTURE_H + +#include + +#include + +namespace vsg +{ + class Data; + class DescriptorImage; + class ImageView; + class BindDescriptorSet; + class BindGraphicsPipeline; + class PipelineLayout; + class Options; +} + +namespace vsgAdapters::mygui +{ + class Texture : public MyGUI::ITexture + { + std::string mName; + vsg::ref_ptr mLockedImage; + vsg::ref_ptr mOptions; + MyGUI::PixelFormat mFormat = MyGUI::PixelFormat::Unknow; + MyGUI::TextureUsage mUsage = MyGUI::TextureUsage::Static; + int mWidth = 0; + int mHeight = 0; + + public: + Texture(const std::string& name, vsg::ref_ptr options); + Texture(vsg::ImageView* image = {}); + virtual ~Texture(); + + vsg::BindGraphicsPipeline* mPipeline = nullptr; + vsg::ref_ptr mTexture; + vsg::ref_ptr mBindDescriptorSet; + void createBindDescriptorSet(vsg::PipelineLayout* layout); + + bool mCompiled = false; + std::string mShader; + + const std::string& getName() const override { return mName; } + + void createManual(int width, int height, MyGUI::TextureUsage usage, MyGUI::PixelFormat format) override; + void loadFromFile(const std::string& fname) override; + void saveToFile(const std::string& fname) override {} + + void destroy() override; + + void* lock(MyGUI::TextureUsage access) override; + void unlock() override; + bool isLocked() const override { return mLockedImage.valid(); } + + int getWidth() const override { return mWidth; } + int getHeight() const override { return mHeight; } + + MyGUI::PixelFormat getFormat() const override { return mFormat; } + MyGUI::TextureUsage getUsage() const override { return mUsage; } + size_t getNumElemBytes() const override { return mFormat.getBytesPerPixel(); } + + MyGUI::IRenderTarget* getRenderTarget() override { return nullptr; } + void setShader(const std::string& shader) override { mShader = shader; } + }; +} + +#endif diff --git a/components/vsgadapters/nif/anim.cpp b/components/vsgadapters/nif/anim.cpp new file mode 100644 index 00000000000..7efdbdef812 --- /dev/null +++ b/components/vsgadapters/nif/anim.cpp @@ -0,0 +1,145 @@ +#include "anim.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +namespace vsgAdapters +{ + std::vector orderRotations( + const std::vector& in, Nif::NiKeyframeData::AxisOrder order) + { + switch (order) + { + case Nif::NiKeyframeData::AxisOrder::Order_XYZ: + return in; + case Nif::NiKeyframeData::AxisOrder::Order_XZY: + return { in[0], in[2], in[1] }; + case Nif::NiKeyframeData::AxisOrder::Order_YZX: + return { in[1], in[2], in[0] }; + case Nif::NiKeyframeData::AxisOrder::Order_YXZ: + return { in[1], in[0], in[2] }; + case Nif::NiKeyframeData::AxisOrder::Order_ZXY: + return { in[2], in[0], in[1] }; + case Nif::NiKeyframeData::AxisOrder::Order_ZYX: + return { in[2], in[1], in[0] }; + case Nif::NiKeyframeData::AxisOrder::Order_XYX: + return { in[0], in[1], in[0] }; + case Nif::NiKeyframeData::AxisOrder::Order_YZY: + return { in[1], in[2], in[1] }; + case Nif::NiKeyframeData::AxisOrder::Order_ZXZ: + return { in[2], in[0], in[2] }; + } + return in; + } + + Anim::channel_ptr handleXYZChannels(const Nif::NiKeyframeController& keyctrl) + { + const auto& data = *keyctrl.mData.getPtr(); + auto xr = handleKeyframes(keyctrl, data.mXRotations); + auto yr = handleKeyframes(keyctrl, data.mXRotations); + auto zr = handleKeyframes(keyctrl, data.mZRotations); + if (!xr && !yr && !zr) + return {}; + std::vector rotations + = { { xr, { 1.f, 0.f, 0.f } }, { yr, { 0.f, 1.f, 0.f } }, { zr, { 0.f, 0.f, 1.f } } }; + rotations = orderRotations(rotations, data.mAxisOrder); + auto rotate = Anim::make_channel(); + for (auto& [channel, axis] : rotations) + { + if (channel) + rotate->rotations.emplace_back(channel, axis); + } + return rotate; + } + + void convertTrafo(Anim::Transform& trans, const Nif::Transformation& trafo) + { + trans.translation = toVsg(trafo.pos); + trans.setScale(trafo.scale); + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + trans.rotation(j, i) = trafo.rotation.mValues[i][j]; //[j][i]; + } + + vsg::ref_ptr handleTextKeys(const Nif::NiTextKeyExtraData& keys) + { + auto tags = vsg::ref_ptr{ new Anim::Tags }; + for (size_t i = 0; i < keys.list.size(); i++) + { + std::vector results; + Misc::StringUtils::split(keys.list[i].text, results, "\r\n"); + for (std::string& result : results) + { + Misc::StringUtils::trim(result); + Misc::StringUtils::lowerCaseInPlace(result); + if (!result.empty()) + tags->emplace(keys.list[i].time, std::move(result)); + } + } + return tags; + } + + vsg::ref_ptr handleKeyframeController(const Nif::NiKeyframeController& keyctrl) + { + if (keyctrl.mData.empty()) + return {}; + auto ctrl = Anim::TransformController::create(); + ctrl->scale = handleKeyframes(keyctrl, keyctrl.mData->mScales); + ctrl->translate = handleKeyframes(keyctrl, keyctrl.mData->mTranslations); + ctrl->rotate = handleKeyframes(keyctrl, keyctrl.mData->mRotations); + if (!ctrl->rotate) + ctrl->rotate = handleXYZChannels(keyctrl); + return ctrl; + } + + vsg::ref_ptr handlePathController(const Nif::NiPathController& pathctrl) + { + if (pathctrl.posData.empty() || pathctrl.floatData.empty()) + return {}; + auto ctrl = Anim::TransformController::create(); + auto path = Anim::make_channel(); + path->path = handleKeyframes(pathctrl, pathctrl.posData->mKeyList, { vsg::vec3() }); + path->percent = handleKeyframes(pathctrl, pathctrl.floatData->mKeyList, { 0.f }); + ctrl->translate = path; + return ctrl; + } + + vsg::ref_ptr handleRollController(const Nif::NiRollController& rollctrl) + { + if (rollctrl.mData.empty()) + return {}; + auto ctrl = Anim::Roll::create(); + // Note: in original game rotation speed is framerate-dependent + // For now consider controller's current value as an angular speed in radians per 1/60 seconds. + struct Multiply : public Anim::Channel + { + Anim::channel_ptr lhs; + float rhs{}; + float value(float time) const + { + return lhs->value(time) * rhs; + } + }; + auto mult = Anim::make_channel(); + mult->lhs = handleKeyframes(rollctrl, rollctrl.mData->mKeyList); + mult->rhs = 60.f; + ctrl->speed = mult; + return ctrl; + } + + bool hasTransformController(const Nif::Node& n) + { + bool hasTransformCtrl = false; + callActiveControllers(n.controller, [&hasTransformCtrl](auto& ctrl) { + if (ctrl.recType == Nif::RC_NiKeyframeController || ctrl.recType == Nif::RC_NiPathController || ctrl.recType == Nif::RC_NiRollController) + hasTransformCtrl = true; + }); + return hasTransformCtrl; + } +} diff --git a/components/vsgadapters/nif/anim.hpp b/components/vsgadapters/nif/anim.hpp new file mode 100644 index 00000000000..caaa3853527 --- /dev/null +++ b/components/vsgadapters/nif/anim.hpp @@ -0,0 +1,141 @@ +#ifndef VSGOPENMW_VSGADAPTERS_NIF_ANIM_H +#define VSGOPENMW_VSGADAPTERS_NIF_ANIM_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Anim +{ + class Tags; + class Transform; + class TransformController; + class Roll; +} +namespace vsgAdapters +{ + template + inline vsg::ref_ptr handleKeyData(const T& src) + { + auto ret = Anim::make_channel(); + for (auto& [key, val] : src.mKeys) + ret->keys[key] = toVsg(val.mValue); + return ret; + } + + template + inline vsg::ref_ptr handleQuadraticKeyData(const T& src) + { + auto ret = Anim::make_channel(); + for (auto& [key, val] : src.mKeys) + ret->keys[key] = { toVsg(val.mValue), toVsg(val.mInTan), toVsg(val.mOutTan) }; + return ret; + } + + template <> + inline vsg::ref_ptr> handleQuadraticKeyData(const Nif::QuaternionKeyMap& src) + { + return handleKeyData>(src); + } + + inline Anim::ExtrapolationMode convertExtrapolationMode(int mode) + { + switch (mode) + { + case Nif::Controller::ExtrapolationMode::Cycle: + return Anim::ExtrapolationMode::Cycle; + case Nif::Controller::ExtrapolationMode::Reverse: + return Anim::ExtrapolationMode::Reverse; + case Nif::Controller::ExtrapolationMode::Constant: + default: + return Anim::ExtrapolationMode::Constant; + } + } + + template + inline void addExtrapolatorIfRequired( + const Nif::Controller& ctrl, Anim::channel_ptr& channel, float startKey, float stopKey) + { + Anim::ExtrapolationMode mode = convertExtrapolationMode((ctrl.flags & 0x6) >> 1); + + if (ctrl.timeStop - ctrl.timeStart <= 0) + { + channel = Anim::make_constant(channel->value(ctrl.timeStart)); // vsgopenmw-optimization-nif-constant-channel + return; + } + if (ctrl.frequency == 1 && ctrl.phase == 0 && mode == Anim::ExtrapolationMode::Constant && ctrl.timeStart <= startKey && ctrl.timeStop >= stopKey) + return; // vsgopenmw-optimization-nif-prune-extrapolator + + channel = Anim::makeExtrapolator(channel, { ctrl.frequency, ctrl.phase, ctrl.timeStart, ctrl.timeStop }, mode); + } + + template + inline Anim::channel_ptr handleInterpolatedKeyframes(const Src& src) + { + switch (src->mInterpolationType) + { + case Nif::InterpolationType_Constant: + return handleKeyData>(*src); + case Nif::InterpolationType_Linear: + default: + return handleKeyData>(*src); + case Nif::InterpolationType_Quadratic: + return handleQuadraticKeyData>(*src); + } + } + + template + inline Anim::channel_ptr handleKeyframes( + const Nif::Controller& ctrl, const Src& src, const std::optional defaultValue = {}) + { + if (!src || src->mKeys.empty()) + { + if (defaultValue) + return Anim::make_constant(*defaultValue); + return {}; + } + if (src->mKeys.size() == 1) + return Anim::make_constant(toVsg(src->mKeys.begin()->second.mValue)); // vsgopenmw-optimization-nif-constant-channel + + auto ret = handleInterpolatedKeyframes(src); + float startKey = src->mKeys.begin()->first; + float stopKey = src->mKeys.rbegin()->first; + addExtrapolatorIfRequired(ctrl, ret, startKey, stopKey); + return ret; + } + + template + void callActiveControllers(const Nif::ControllerPtr controller, F func) + { + for (Nif::ControllerPtr ctrl = controller; !ctrl.empty() && ctrl->isActive(); ctrl = ctrl->next) + func(*(ctrl.getPtr())); + } + + template + const T* searchController(const Nif::ControllerPtr controller, int recType) + { + for (Nif::ControllerPtr ctrl = controller; !ctrl.empty() && ctrl->isActive(); ctrl = ctrl->next) + if (ctrl->recType == recType) + return static_cast(ctrl.getPtr()); + return nullptr; + } + + void convertTrafo(Anim::Transform& trans, const Nif::Transformation& trafo); + + vsg::ref_ptr handleTextKeys(const Nif::NiTextKeyExtraData& keys); + + vsg::ref_ptr handleKeyframeController(const Nif::NiKeyframeController& keyctrl); + vsg::ref_ptr handlePathController(const Nif::NiPathController& pathctrl); + vsg::ref_ptr handleRollController(const Nif::NiRollController& rollctrl); + bool hasTransformController(const Nif::Node& n); +} + +#endif diff --git a/components/vsgadapters/nif/channel.hpp b/components/vsgadapters/nif/channel.hpp new file mode 100644 index 00000000000..7a967239613 --- /dev/null +++ b/components/vsgadapters/nif/channel.hpp @@ -0,0 +1,40 @@ +#ifndef VSGOPENMW_VSGADAPTERS_NIF_CHANNEL_H +#define VSGOPENMW_VSGADAPTERS_NIF_CHANNEL_H + +#include +#include + +namespace vsgAdapters +{ + struct FlipChannel : public Anim::Channel + { + FlipChannel(float d, size_t n) + : delta(d) + , numTextures(n) + { + } + size_t value(float time) const override { return static_cast(time / delta) % numTextures; } + float delta; + size_t numTextures; + }; + + struct VisChannel : public Anim::Channel + { + VisChannel(const std::vector& d) + : data(d) + { + } + std::vector data; + bool value(float time) const override + { + for (size_t i = 1; i < data.size(); ++i) + { + if (data[i].time > time) + return data[i - 1].isSet; + } + return data.back().isSet; + } + }; +} + +#endif diff --git a/components/vsgadapters/nif/kf.cpp b/components/vsgadapters/nif/kf.cpp new file mode 100644 index 00000000000..761a034d7b9 --- /dev/null +++ b/components/vsgadapters/nif/kf.cpp @@ -0,0 +1,69 @@ +#include "kf.hpp" + +#include + +#include +#include + +#include "anim.hpp" + +namespace vsgAdapters +{ + vsg::ref_ptr kf::read(std::istream& stream, vsg::ref_ptr options) const + { + if (!vsg::compatibleExtension(options.get(), ".kf")) + return {}; + + std::string filename; + options->getValue("filename", filename); + + try + { + // vsgopenmw-nif-istream + Nif::NIFFile nifFile({}); + Nif::Reader reader(nifFile); + reader.parse(Files::IStreamPtr(new std::istream(stream.rdbuf()))); //reader.parse(stream); //vsgopenmw-fixme + Nif::FileView nif (nifFile); + + auto controllers = vsg::ref_ptr{ new Anim::ControllerMap }; + for (size_t i = 0; i < nif.numRoots(); ++i) + { + const auto root = nif.getRoot(i); + if (root && root->recType == Nif::RC_NiSequenceStreamHelper) + { + auto seq = static_cast(*root); + Nif::ExtraPtr extra = seq.extra; + controllers->tags = handleTextKeys(static_cast(*extra.getPtr())); + + extra = extra->next; + Nif::ControllerPtr ctrl = seq.controller; + for (; !extra.empty() && !ctrl.empty() /* && (ctrl->active()*/; + (extra = extra->next), (ctrl = ctrl->next)) + { + if (extra->recType != Nif::RC_NiStringExtraData + || ctrl->recType != Nif::RC_NiKeyframeController) + continue; + auto keyctrl + = handleKeyframeController(static_cast(*ctrl.getPtr())); + if (!keyctrl) + continue; + auto& strdata = static_cast(*extra.getPtr()); + controllers->map./*insert_or_assign*/ emplace(strdata.string, std::move(keyctrl)); + } + } + } + return controllers; + } + catch (std::exception& e) + { + std::cerr << "!kf::read(" << filename << "): " << e.what() << std::endl; + return {}; + } + } + + bool kf::getFeatures(Features& features) const + { + features.extensionFeatureMap[".kf"] = READ_ISTREAM; + return true; + } +} diff --git a/components/vsgadapters/nif/kf.hpp b/components/vsgadapters/nif/kf.hpp new file mode 100644 index 00000000000..2628bb15d03 --- /dev/null +++ b/components/vsgadapters/nif/kf.hpp @@ -0,0 +1,20 @@ +#ifndef VSGOPENMW_VSGADAPTERS_NIF_KF_H +#define VSGOPENMW_VSGADAPTERS_NIF_KF_H + +#include + +namespace vsgAdapters +{ + /* + * Reads VER_MW kf file into Anim::ControllerMap or throws. + * Depends on nif, animation. + */ + class kf : public vsg::ReaderWriter + { + public: + vsg::ref_ptr read(std::istream&, vsg::ref_ptr = {}) const override; + bool getFeatures(Features& features) const override; + }; +} + +#endif diff --git a/components/vsgadapters/nif/nif.cpp b/components/vsgadapters/nif/nif.cpp new file mode 100644 index 00000000000..57537802b1a --- /dev/null +++ b/components/vsgadapters/nif/nif.cpp @@ -0,0 +1,1398 @@ +#include "nif.hpp" + +#include +#include + +#include +#include +#include +#include +#include +// #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "anim.hpp" +#include "channel.hpp" +#include "particle.hpp" + +namespace Pipeline::Descriptors +{ + #include +} +namespace +{ + // vsgopenmw-fixme(find-my-place) + const int computeBin = 1; + const int depthSortedBin = 0; + + static const vsg::ref_ptr sNullArrayState = vsg::NullArrayState::create(); + + bool isTypeGeometry(int type) + { + switch (type) + { + case Nif::RC_NiTriShape: + case Nif::RC_NiTriStrips: + case Nif::RC_NiLines: + case Nif::RC_BSLODTriShape: + return true; + } + return false; + } + + VkBlendFactor convertBlendFactor(int mode) + { + switch (mode) + { + case 0: + return VK_BLEND_FACTOR_ONE; + case 1: + return VK_BLEND_FACTOR_ZERO; + case 2: + return VK_BLEND_FACTOR_SRC_COLOR; + case 3: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case 4: + return VK_BLEND_FACTOR_DST_COLOR; + case 5: + return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + case 6: + return VK_BLEND_FACTOR_SRC_ALPHA; + case 7: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case 8: + return VK_BLEND_FACTOR_DST_ALPHA; + case 9: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case 10: + return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; + default: + return VK_BLEND_FACTOR_SRC_ALPHA; + } + } + + int convertTextureSlot(size_t nifSlot, Pipeline::Mode& mode) + { + switch (nifSlot) + { + case Nif::NiTexturingProperty::DarkTexture: + mode = Pipeline::Mode::DARK_MAP; + return Pipeline::Descriptors::DARK_UNIT; + case Nif::NiTexturingProperty::BaseTexture: + mode = Pipeline::Mode::DIFFUSE_MAP; + return Pipeline::Descriptors::DIFFUSE_UNIT; + case Nif::NiTexturingProperty::DetailTexture: + mode = Pipeline::Mode::DETAIL_MAP; + return Pipeline::Descriptors::DETAIL_UNIT; + case Nif::NiTexturingProperty::DecalTexture: + mode = Pipeline::Mode::DECAL_MAP; + return Pipeline::Descriptors::DECAL_UNIT; + case Nif::NiTexturingProperty::GlowTexture: + mode = Pipeline::Mode::GLOW_MAP; + return Pipeline::Descriptors::GLOW_UNIT; + case Nif::NiTexturingProperty::BumpTexture: + mode = Pipeline::Mode::BUMP_MAP; + return Pipeline::Descriptors::BUMP_UNIT; + default: + return -1; + } + } + + template + void handleList(const List& list, Func f) + { + for (size_t i = 0; i < list.size(); ++i) + if (!list[i].empty()) + f(*list[i].getPtr()); + } + + bool canOptimize(const std::string& f) + { + std::string filename = Misc::StringUtils::lowerCase(f); + size_t slashpos = filename.find_last_of("\\/"); + if (slashpos != std::string::npos && slashpos + 1 < filename.size()) + { + std::string basename = filename.substr(slashpos + 1); + // xmesh.nif can not be optimized because there are keyframes added in post + if (!basename.empty() && basename[0] == 'x') + return false; + // NPC skeleton files can not be optimized because of keyframes added in post + // (most of them are usually named like 'xbase_anim.nif' anyway, but not all of them :( ) + if (basename.compare(0, 9, "base_anim") == 0 || basename.compare(0, 4, "skin") == 0) + return false; + // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly + // cautious - instead, decide on filename + if (basename.find("vfx_pattern") != std::string::npos) + return false; + } + return true; + } +} + +namespace vsgAdapters +{ + class nifImpl + { + const vsg::Options& mOptions; + const vsg::ref_ptr& mImageOptions; + bool mCanOptimize = true; + bool mShowMarkers = false; + uint32_t mPhaseGroups = 0; + std::string mFilename; + std::unique_ptr mNifFile; + std::unique_ptr mNif; + std::string mSkinFilter; + std::string mSkinFilter2; + const Pipeline::Builder& mBuilder; + const Pipeline::Override* mOverride; + + public: + nifImpl(std::istream& stream, const vsg::Options& options, const vsg::ref_ptr& imageOptions, + bool showMarkers, const Pipeline::Builder& builder) + : mOptions(options) + , mImageOptions(imageOptions) + , mShowMarkers(showMarkers) + , mBuilder(builder) + { + options.getValue("filename", mFilename); + options.getValue("skinfilter", mSkinFilter); + mOverride = Pipeline::Override::get(options); + if (!mSkinFilter.empty()) + mSkinFilter2 = std::string("tri ") + mSkinFilter; + mCanOptimize = canOptimize(mFilename); + mNifFile.reset(new Nif::NIFFile({})); + Nif::Reader reader(*mNifFile); + // vsgopenmw-nif-istream + reader.parse(Files::IStreamPtr(new std::istream(stream.rdbuf()))); //reader.parse(stream); //vsgopenmw-fixme + mNif.reset(new Nif::FileView(*mNifFile)); + } + + void warn(const std::string& msg) + { + std::cerr << msg << " (" << mFilename << ")" << std::endl; + } + + template + void share(vsg::ref_ptr& obj) + { + vsgUtil::share_if(mOptions.sharedObjects, obj); + } + + Anim::Contents mContents; + + using MaterialValue = vsg::Value; + struct NodeOptions : public Pipeline::Options + { + vsg::ref_ptr stateSwitch; + vsg::Descriptors descriptors; + std::vector textureSets; + vsg::ref_ptr material; + vsg::ref_ptr materialController; + float alphaTestCutoff = 1.f; + bool depthSorted = false; + bool autoPlay = false; + uint32_t phaseGroup = 0; + bool filterMatched = false; + }; + std::stack mNodeStack; + struct ScopedPushPop + { + ScopedPushPop(std::stack& stack) + : mStack(stack) + { + if (mStack.empty()) + mStack.emplace(); + else + mStack.emplace(mStack.top()); + } + ~ScopedPushPop() { mStack.pop(); } + std::stack& mStack; + }; + NodeOptions& getNodeOptions() { return mNodeStack.top(); } + + vsg::ref_ptr load() + { + vsg::Group::Children nodes; + for (size_t i = 0; i < mNif->numRoots(); ++i) + { + if (const Nif::Node* nifNode = dynamic_cast(mNif->getRoot(i))) + { + if (auto node = handleNode(*nifNode)) + nodes.emplace_back(node); + } + } + vsgUtil::removeGroup(nodes); + + vsg::ref_ptr ret; + if (!mContents.contains(Anim::Contents::TransformControllers | Anim::Contents::Skins | Anim::Contents::Particles | Anim::Contents::Placeholders)) + { + ret = vsgUtil::createCullNode(nodes); + vsgUtil::addLeafCullNodes(ret, 2); + } + else + { + ret = vsgUtil::getNode(nodes); + vsgUtil::addLeafCullNodes(ret); + } + if (!mContents.empty()) + { + auto meta = vsg::ref_ptr{ new Anim::Meta(mContents) }; + meta->attachTo(*ret); + } + return ret; + } + + bool canSkipGeometry(bool isMarker, const Nif::Node& nifNode) const + { + static const std::string markerName = "tri editormarker"; + static const std::string shadowName = "shadow"; + static const std::string shadowName2 = "tri shadow"; + isMarker = isMarker && Misc::StringUtils::ciCompareLen(nifNode.name, markerName, markerName.size()) == 0 + && !mShowMarkers; + return isMarker || Misc::StringUtils::ciCompareLen(nifNode.name, shadowName, shadowName.size()) == 0 + || Misc::StringUtils::ciCompareLen(nifNode.name, shadowName2, shadowName2.size()) == 0; + } + + bool canOptimizeTransform(const Nif::Node& nifNode) + { + if (!nifNode.trafo.isIdentity() || !this->mCanOptimize || hasTransformController(nifNode) + || nifNode.useFlags & Nif::Node::Bone) + return false; + if (Misc::StringUtils::ciEqual(nifNode.name, "BoneOffset")) + { + addAnimContents(Anim::Contents::Placeholders); + return false; + } + if (Misc::StringUtils::ciEqual(nifNode.name, "AttachLight")) + { + addAnimContents(Anim::Contents::Placeholders); + return false; + } + return true; + } + + bool canOptimizeBillboard(const Nif::Node& nifNode) + { + if (auto niNode = dynamic_cast(&nifNode)) + { + const auto& children = niNode->children; + for (size_t i = 0; i < children.size(); ++i) + { + if (children[i].empty()) + continue; + auto& child = *children[i].getPtr(); + if (!canOptimizeTransform(child) || !canOptimizeBillboard(child)) + return false; + } + } + return true; + } + + template + vsg::ref_ptr copyModeArray(const SourceType& vec, Pipeline::Mode mode) + { + getNodeOptions().shader.addMode(mode); + return copyArray(vec); + } + + template + void addModeDescriptor(vsg::ref_ptr descriptor, Mode mode) + { + auto& nodeOptions = getNodeOptions(); + nodeOptions.shader.addMode(mode); + nodeOptions.descriptors.emplace_back(descriptor); + } + + void handleSkin(const Nif::NiSkinInstance& skin, vsg::ref_ptr boneIndices, + vsg::ref_ptr weights) + { + const Nif::NiSkinData& data = *skin.data.getPtr(); + size_t numVertices = boneIndices->size(); + constexpr size_t maxInfluenceCount = 4; + using IndexWeightList = std::vector>; + std::vector vertexWeights(numVertices); + auto ctrl = Anim::Skin::create(); + auto& bones = ctrl->bones; + bones.reserve(skin.bones.size()); + if (!data.trafo.isIdentity()) + { + // openmw-4437-niskininstance-transformation + Anim::Transform skinTransform; + convertTrafo(skinTransform, data.trafo); + ctrl->transform = skinTransform.t_transform(vsg::mat4()); + } + for (unsigned char i = 0; i < skin.bones.size(); ++i) + { + const auto& boneData = data.bones[i]; + size_t boneIndex = bones.size(); + Anim::Transform t; + convertTrafo(t, boneData.trafo); + vsg::dsphere bounds(toVsg(boneData.boundSphereCenter), boneData.boundSphereRadius); + bones.push_back({ t.t_transform(vsg::mat4()), bounds, skin.bones[i].getPtr()->name }); + for (const auto& weight : boneData.weights) + { + if (weight.vertex < numVertices) + vertexWeights[weight.vertex].emplace_back(boneIndex, weight.weight); + } + } + for (size_t vertex = 0; vertex < numVertices; ++vertex) + { + auto& vertexWeight = vertexWeights[vertex]; + std::sort( + vertexWeight.begin(), vertexWeight.end(), + [](auto& left, auto& right) -> auto{ return left.second > right.second; }); + vsg::vec4& indices = (*boneIndices)[vertex]; + vsg::vec4& weight = (*weights)[vertex]; + size_t influenceCount = std::min(maxInfluenceCount, vertexWeight.size()); + for (size_t i = 0; i < influenceCount; ++i) + { + indices[i] = vertexWeight[i].first; + weight[i] = vertexWeight[i].second; + } + } + ctrl->boneIndices = boneIndices; + ctrl->boneWeights = weights; + + auto boneMatrices = vsg::mat4Array::create(bones.size()); + ctrl->attachTo(*boneMatrices); + auto descriptor = vsg::DescriptorBuffer::create( + boneMatrices, Pipeline::Descriptors::BONE_BINDING, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + addModeDescriptor(descriptor, Pipeline::Mode::SKIN); + addAnimContents(Anim::Contents::Skins | Anim::Contents::Controllers); + } + + vsg::ref_ptr handleGeometry(const Nif::NiGeometry& niGeometry) + { + if (niGeometry.data.empty()) + return {}; + + const Nif::NiGeometryData& data = *niGeometry.data.getPtr(); + handleGeometryControllers(niGeometry.controller, data.vertices.size()); + + vsg::DataList dataList; + dataList.reserve(!data.vertices.empty() + !data.normals.empty() + !data.colors.empty() + data.uvlist.size() + + (!niGeometry.skin.empty()) * 2); + + static_assert(static_cast(Pipeline::Mode::VERTEX) == 0); + if (!data.vertices.empty()) + dataList.emplace_back(copyModeArray(data.vertices, Pipeline::Mode::VERTEX)); + + static_assert(static_cast(Pipeline::Mode::NORMAL) == 1); + if (!data.normals.empty()) + { + dataList.emplace_back(copyModeArray(data.normals, Pipeline::Mode::NORMAL)); + vsgUtil::setID(*dataList.back(), static_cast(Pipeline::Mode::NORMAL)); + } + + static_assert(static_cast(Pipeline::Mode::COLOR) == 2); + if (!data.colors.empty()) + dataList.emplace_back(copyModeArray(data.colors, Pipeline::Mode::COLOR)); + + static_assert(static_cast(Pipeline::Mode::SKIN) == 3); + if (!niGeometry.skin.empty()) + { + auto weights = vsg::vec4Array::create(data.vertices.size()); + auto boneIndices = vsg::vec4Array::create(data.vertices.size()); + handleSkin(*niGeometry.skin.getPtr(), boneIndices, weights); + dataList.emplace_back(boneIndices); + dataList.emplace_back(weights); + } + + static_assert(static_cast(Pipeline::Mode::TEXCOORD) == 4); + for (size_t uvSet = 0; uvSet < data.uvlist.size(); ++uvSet) + dataList.emplace_back( + copyModeArray(data.uvlist[uvSet], Pipeline::Mode::TEXCOORD)); + auto& nodeOptions = getNodeOptions(); + nodeOptions.numUvSets = data.uvlist.size(); + + auto& topology = nodeOptions.primitiveTopology; + auto geom = vsg::VertexIndexDraw::create(); + geom->assignArrays(dataList); + vsg::ref_ptr indices; + if ((niGeometry.recType == Nif::RC_NiTriShape || niGeometry.recType == Nif::RC_BSLODTriShape) + && data.recType == Nif::RC_NiTriShapeData) + { + auto triangles = static_cast(data).triangles; + if (triangles.empty()) + return {}; + indices = copyArray(triangles); + } + else if (niGeometry.recType == Nif::RC_NiTriStrips && data.recType == Nif::RC_NiTriStripsData) + { + auto triStrips = static_cast(data); + std::vector mergedIndices; + for (const auto& strip : triStrips.strips) + { + if (strip.size() < 3) + continue; + mergedIndices.insert(mergedIndices.end(), strip.begin(), strip.end()); + } + indices = copyArray(mergedIndices); + topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + } + else if (niGeometry.recType == Nif::RC_NiLines && data.recType == Nif::RC_NiLinesData) + { + const auto& line = static_cast(data).lines; + if (line.empty()) + return {}; + indices = copyArray(line); + topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + } + else + return {}; + + geom->instanceCount = 1; + geom->indexCount = indices->size(); + geom->assignIndices(indices); + + auto sg = createStateGroup(geom); + if (nodeOptions.depthSorted) + { + // Alpha sorting uses the calculated bounding sphere centre. + return vsg::DepthSorted::create(depthSortedBin, vsgUtil::getBounds(sg), sg); + } + return sg; + } + + vsg::ref_ptr handleParticleSystemController( + const Nif::NiParticles& particles, const Nif::NiParticleSystemController& partctrl) + { + if (particles.data.empty() || particles.data->recType != Nif::RC_NiParticlesData) + return {}; + auto particledata = static_cast(*particles.data.getPtr()); + size_t maxParticles = particledata.numParticles; + if (maxParticles == 0) + return {}; + + auto sw = vsg::Switch::create(); + auto particleArray = vsg::Array::create(maxParticles); + //particleArray->properties.dataVariance = DYNAMIC_DATA_COMPUTE; + //particleArray->setObject(Anim::Controller::sAttachKey, vsg::ref_ptr()); + particleArray->properties.dataVariance = vsg::DataVariance::DYNAMIC_DATA; + + handleInitialParticles(*particleArray, particledata, partctrl); + + auto particlesDescriptor = vsg::DescriptorBuffer::create( + particleArray, Pipeline::Descriptors::PARTICLE_BINDING, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + addModeDescriptor(particlesDescriptor, Pipeline::Mode::PARTICLE); + + auto ctrl = Emitter::create(partctrl, particles.recIndex); + // ArrayEmitter + vsgUtil::setID(*sw, particles.recIndex); + + auto maxLifetime = partctrl.lifetime + partctrl.lifetimeRandom; + Pipeline::Data::SimulateArgs args{}; + args.emit = handleEmitterData(partctrl); + vsg::ref_ptr colorCurve; + auto affectors = partctrl.affectors; + Pipeline::ParticleModeFlags modes{}; + for (; !affectors.empty(); affectors = affectors->next) + { + if (affectors->recType == Nif::RC_NiParticleGrowFade) + { + const auto& gf = static_cast(*affectors.getPtr()); + args.size = { gf.growTime, gf.fadeTime, partctrl.size, 0 }; + modes |= Pipeline::ParticleMode_Size; + } + else if (affectors->recType == Nif::RC_NiGravity) + { + const auto& gr = static_cast(*affectors.getPtr()); + args.gravity = { .positionDecay={ toVsg(gr.mPosition), -gr.mDecay }, .directionForce={ vsg::normalize(toVsg(gr.mDirection)), gr.mForce*1.6f } }; + if (gr.mType == 0) + modes |= Pipeline::ParticleMode_GravityPlane; + else + modes |= Pipeline::ParticleMode_GravityPoint; + } + else if (affectors->recType == Nif::RC_NiParticleColorModifier) + { + const auto& cl = static_cast(*affectors.getPtr()); + if (!cl.data.empty()) + { + colorCurve = createColorCurve(*cl.data.getPtr(), maxLifetime, 0.04); + modes |= Pipeline::ParticleMode_Color; + } + } + else if (affectors->recType == Nif::RC_NiParticleRotation) + ; // unused + else + warn("Unhandled particle modifier " + affectors->recName); + } + + auto colliders = partctrl.colliders; + for (; !colliders.empty(); colliders = colliders->next) + { + if (colliders->recType == Nif::RC_NiPlanarCollider) + { + const auto& cl = static_cast(*colliders.getPtr()); + args.collide = { .positionBounce={ toVsg(cl.mPosition), cl.mBounceFactor }, .xVectorRadius={ toVsg(cl.mXVector), cl.mExtents.x()/2 }, .yVectorRadius={ toVsg(cl.mYVector), cl.mExtents.y()/2 }, .plane={ toVsg(cl.mPlaneNormal), cl.mPlaneDistance } }; + modes |= Pipeline::ParticleMode_CollidePlane; + } + else if (colliders->recType == Nif::RC_NiSphericalCollider) + { + ;//const auto& cl = static_cast(*colliders.getPtr()); + } + else + warn("Unhandled particle collider " + colliders->recName); + } + + auto frameArgsData = vsg::Value::create(); + auto frameArgsDescriptor = vsg::DescriptorBuffer::create( + frameArgsData, Pipeline::Descriptors::FRAME_ARGS_BINDING, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + auto argsValue = vsg::Value::create(args); + share(argsValue); + auto argsDescriptor = vsg::DescriptorBuffer::create( + argsValue, Pipeline::Descriptors::SIMULATE_ARGS_BINDING, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + share(argsDescriptor); + vsg::Descriptors descriptors{ argsDescriptor, frameArgsDescriptor, particlesDescriptor }; + //if (colorCurve) + { + if (!colorCurve) + { + static const auto dummyColorCurve = vsg::vec4Array::create(1, vsg::Data::Properties(VK_FORMAT_R32G32B32A32_SFLOAT)); + colorCurve = dummyColorCurve; + } + auto sampler = mBuilder.createSampler(); + sampler->addressModeU = sampler->addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + share(sampler); + descriptors.emplace_back(vsg::DescriptorImage::create(sampler, colorCurve, Pipeline::Descriptors::COLOR_CURVE_BINDING)); + } + + auto bindComputePipeline = mBuilder.particle->getOrCreate(modes); + auto computeBds = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_COMPUTE, bindComputePipeline->pipeline->layout, Pipeline::TEXTURE_SET, descriptors); + auto computeGroup = vsg::Commands::create(); // vsg::StateGroup::create(); + computeGroup->children = { + bindComputePipeline, + computeBds, + createParticleDispatch(maxParticles) + }; + + auto& nodeOptions = getNodeOptions(); + nodeOptions.numUvSets = 1; + nodeOptions.primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + nodeOptions.cullMode = VK_CULL_MODE_NONE; + + auto draw = vsg::Draw::create(maxParticles * 4, 1, 0, 0); + auto drawGroup = createStateGroup(draw); + drawGroup->prototypeArrayState = sNullArrayState; + double maxRadius = maxLifetime * (partctrl.velocity + partctrl.velocityRandom) + std::max(partctrl.offsetRandom.x(), std::max(partctrl.offsetRandom.y(), partctrl.offsetRandom.z())) + partctrl.size/2.f; + vsg::dsphere bound(vsg::dvec3(), maxRadius); + sw->children = { + { vsg::MASK_ALL, vsg::DepthSorted::create(depthSortedBin, bound, drawGroup) }, + { vsg::MASK_ALL, vsg::DepthSorted::create(computeBin, bound, computeGroup) } + }; + auto cullCtrl = Anim::CullParticles::create(); + cullCtrl->active = ctrl->active; + cullCtrl->maxLifetime = maxLifetime; + cullCtrl->drawIndex = 0; + cullCtrl->dispatchIndex = 1; + setup(partctrl, *cullCtrl, *sw); + + addAnimContents(Anim::Contents::Particles); + setup(partctrl, *ctrl, *frameArgsData); + // ParticleFlag_LocalSpace + + //return sw; + return vsg::CullNode::create(bound, sw); + } + + vsg::ref_ptr handleParticles(const Nif::NiParticles& particles) + { + const Nif::NiParticleSystemController* partctrl = nullptr; + callActiveControllers(particles.controller, [&partctrl](auto& ctrl) { + if (ctrl.recType == Nif::RC_NiParticleSystemController || ctrl.recType == Nif::RC_NiBSPArrayController) + partctrl = &static_cast(ctrl); + }); + if (partctrl) + return handleParticleSystemController(particles, *partctrl); + return {}; + } + + void handleFlipController(const Nif::NiFlipController& flipctrl) + { + auto& stateSwitch = getNodeOptions().stateSwitch = vsg::StateSwitch::create(); + auto ctrl = Anim::StateSwitch::create(); + ctrl->index = Anim::make_channel(flipctrl.mDelta, flipctrl.mSources.size()); + setup(flipctrl, *ctrl, *stateSwitch); + } + + vsg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture& st) + { + if (!st.external && !st.data.empty()) + { + warn("vsgopenmw-testing(!st->external)"); + // handleInternalTexture(st->data.getPtr()); + return {}; + } + else + return vsgUtil::readImage(st.filename, mImageOptions); + } + + vsg::ref_ptr handleTexture(const Nif::NiSourceTexture& st, int clamp, uint32_t binding) + { + auto textureData = handleSourceTexture(st); + if (!textureData) + return {}; + auto sampler = mBuilder.createSampler(); + sampler->addressModeU + = (clamp >> 1) ? VK_SAMPLER_ADDRESS_MODE_REPEAT : VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + sampler->addressModeV + = clamp & 0x1 ? VK_SAMPLER_ADDRESS_MODE_REPEAT : VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + + auto descriptor = vsgUtil::sharedDescriptorImage(mOptions.sharedObjects, sampler, textureData, binding); + return descriptor; + } + + void handleTextureProperty(const Nif::NiTexturingProperty& texprop) + { + auto& nodeOptions = getNodeOptions(); + nodeOptions.textureSets.clear(); + nodeOptions.nonStandardUvSets.clear(); + nodeOptions.stateSwitch = {}; + vsg::Descriptors textures; + auto flipctrl = searchController(texprop.controller, Nif::RC_NiFlipController); + size_t i = 0; + if (flipctrl) + { + handleFlipController(*flipctrl); + i = 1; + } + for (; i < texprop.textures.size(); ++i) + { + if (texprop.textures[i].inUse && !texprop.textures[i].texture.empty()) + { + Pipeline::Mode mode = Pipeline::Mode::DIFFUSE_MAP; + int binding = convertTextureSlot(i, mode); + if (binding == -1) + { + warn("!convertTextureSlot(NiTexturingProperty::" + std::to_string(i) + ")"); + continue; + } + auto tex = texprop.textures[i]; + if (tex.uvSet != 0) + { + nodeOptions.nonStandardUvSets[binding] = tex.uvSet; + warn("vsgopenmw-testing(tex.uvSet)"); + } + if (auto descriptor = handleTexture(*tex.texture.getPtr(), tex.clamp, binding)) + { + nodeOptions.shader.addMode(mode); + textures.emplace_back(descriptor); + } + } + } + if (flipctrl) + { + handleList(flipctrl->mSources, [this, &textures, &nodeOptions, texprop](auto& n) { + if (auto tex = handleTexture(n, texprop.textures[0].clamp, 0)) + { + nodeOptions.shader.addMode(Pipeline::Mode::DIFFUSE_MAP); + vsg::Descriptors textureSet{ tex }; + std::copy(textures.begin(), textures.end(), std::back_inserter(textureSet)); + nodeOptions.textureSets.emplace_back(textureSet); + } + }); + } + else + nodeOptions.textureSets.emplace_back(textures); + } + + void handleMaterialProperty(const Nif::NiMaterialProperty& matprop) + { + auto& nodeOptions = getNodeOptions(); + nodeOptions.material = MaterialValue::create(Pipeline::Material::createDefault()); + auto& val = nodeOptions.material->value(); + val.diffuse = vsg::vec4(toVsg(matprop.data.diffuse), matprop.data.alpha); + val.ambient = vsg::vec4(toVsg(matprop.data.ambient), 1.f); + val.emissive = vsg::vec4(toVsg(matprop.data.emissive), 1.f); + // if (mNif->getVersion() > Nif::NIFFile::NIFVersion::VER_MW) + // val.specular = vsg::vec4(toVsg(matprop.data.specular), 1.f); + // val.shininess = matprop.data.glossiness; + if (!matprop.controller.empty()) + handleMaterialController(matprop.controller); + else + nodeOptions.materialController = nullptr; + } + + void handleMaterialController(const Nif::ControllerPtr& ptr) + { + auto color = Anim::Color::create(); + callActiveControllers(ptr, [this, &color](auto& ctrl) { + if (ctrl.recType == Nif::RC_NiMaterialColorController) + { + auto matctrl = static_cast(ctrl); + if (matctrl.mData.empty()) + return; + auto targetColor = matctrl.mTargetColor; + if (targetColor == 2 && mNif->getVersion() <= Nif::NIFFile::NIFVersion::VER_MW) + return; + size_t offset = 0; + switch (targetColor) + { + case 0: + default: + offset = offsetof(Pipeline::Data::Material, ambient); + break; + case 1: + offset = offsetof(Pipeline::Data::Material, diffuse); + break; + case 2: + offset = offsetof(Pipeline::Data::Material, specular); + break; + case 3: + offset = offsetof(Pipeline::Data::Material, emissive); + break; + } + color->colorOffset = offset; + color->color = handleKeyframes(matctrl, matctrl.mData->mKeyList); + setHints(matctrl, color->hints); + } + else if (ctrl.recType == Nif::RC_NiAlphaController) + { + auto alphactrl = static_cast(ctrl); + if (alphactrl.mData.empty()) + return; + color->alphaOffset = offsetof(Pipeline::Data::Material, diffuse); + color->alpha = handleKeyframes(alphactrl, alphactrl.mData->mKeyList); + setHints(alphactrl, color->hints); + } + }); + addAnimContents(); + getNodeOptions().materialController = color; + } + + void handleAlphaProperty(const Nif::NiAlphaProperty& alpha) + { + auto& options = getNodeOptions(); + options.blend = alpha.useAlphaBlending(); + if (options.blend) + { + options.srcBlendFactor = options.srcAlphaBlendFactor = convertBlendFactor(alpha.sourceBlendMode()); + options.dstBlendFactor = options.dstAlphaBlendFactor = convertBlendFactor(alpha.destinationBlendMode()); + if (/* d3d_8_1_compat && */ options.dstBlendFactor == VK_BLEND_FACTOR_DST_ALPHA) + options.dstBlendFactor = options.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + options.depthSorted = !alpha.noSorter(); + } + if (alpha.useAlphaTesting()) + { + options.alphaTestMode = alpha.alphaTestMode(); + options.alphaTestCutoff = alpha.data.threshold / 255.f; + } + else + options.alphaTestMode = 0; + } + + void handleStencilProperty(const Nif::NiStencilProperty& stencilprop) + { + auto& nodeOptions = getNodeOptions(); + nodeOptions.frontFace + = stencilprop.data.drawMode == 2 ? VK_FRONT_FACE_CLOCKWISE : VK_FRONT_FACE_COUNTER_CLOCKWISE; + nodeOptions.cullMode = stencilprop.data.drawMode == 3 ? VK_CULL_MODE_NONE : VK_CULL_MODE_BACK_BIT; + } + + void handleWireframeProperty(const Nif::NiWireframeProperty& wireprop) + { + getNodeOptions().polygonMode = wireprop.isEnabled() ? VK_POLYGON_MODE_FILL : VK_POLYGON_MODE_LINE; + } + + void handleZBufferProperty(const Nif::NiZBufferProperty& zprop) + { + auto& nodeOptions = getNodeOptions(); + nodeOptions.depthTest = zprop.depthTest(); + nodeOptions.depthWrite = zprop.depthWrite(); + // if (!mw_compat) nodeOptions.depthFunc = + } + + void handleVertexColorProperty(const Nif::NiVertexColorProperty& vertprop) + { + auto vertmode = static_cast(vertprop.mVertexMode); + if (static_cast(vertprop.mLightingMode) == 0) + vertmode = 0; + getNodeOptions().colorMode = vertmode; + } + + void handleSpecularProperty(const Nif::NiSpecularProperty& specprop) + { + // if (mNif->getVersion() > Nif::NIFFile::NIFVersion::VER_MW) + // getNodeOptions().specular = specprop.isEnabled() && color != {0,0,0} + } + + void handleProperty(const Nif::Property& property) + { + switch (property.recType) + { + case Nif::RC_NiStencilProperty: + handleStencilProperty(static_cast(property)); + break; + case Nif::RC_NiWireframeProperty: + handleWireframeProperty(static_cast(property)); + break; + case Nif::RC_NiZBufferProperty: + handleZBufferProperty(static_cast(property)); + break; + case Nif::RC_NiAlphaProperty: + handleAlphaProperty(static_cast(property)); + break; + case Nif::RC_NiMaterialProperty: + handleMaterialProperty(static_cast(property)); + break; + case Nif::RC_NiVertexColorProperty: + handleVertexColorProperty(static_cast(property)); + break; + case Nif::RC_NiSpecularProperty: + handleSpecularProperty(static_cast(property)); + break; + case Nif::RC_NiTexturingProperty: + handleTextureProperty(static_cast(property)); + break; + default: + break; + } + } + + vsg::ref_ptr createMaterialData() + { + auto& options = getNodeOptions(); + vsg::ref_ptr data; + if (options.material && !Pipeline::Material::isDefault(*options.material)) + data = MaterialValue::create(*options.material); + else + data = MaterialValue::create(Pipeline::Material::createDefault()); + data->value().alphaTestCutoff = options.alphaTestCutoff; + if (mOverride && mOverride->materialData) + mOverride->materialData(data->value()); + // if (isDefault) return {}; // Increases shader variations. + return data; + } + + vsg::ref_ptr getMaterialDescriptor() + { + if (mOverride && mOverride->material) + return mOverride->material; + vsg::ref_ptr descriptor; + auto matData = createMaterialData(); + if (matData) + { + auto& matCtrl = getNodeOptions().materialController; + if (matCtrl) + matCtrl->attachTo(*matData); + else + share(matData); + + descriptor = vsg::DescriptorBuffer::create( + matData, Pipeline::Descriptors::MATERIAL_BINDING, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + if (!matCtrl) + share(descriptor); + } + return descriptor; + } + + vsg::ref_ptr createBindDescriptorSet(vsg::PipelineLayout* layout, size_t i) + { + vsg::Descriptors descriptors; + auto& nodeOptions = getNodeOptions(); + for (auto& descriptor : nodeOptions.descriptors) + descriptors.emplace_back(descriptor); + if (nodeOptions.textureSets.size() > i) + { + for (auto& texture : nodeOptions.textureSets[i]) + descriptors.emplace_back(texture); + } + + auto isDynamic = [](vsg::Descriptors& descriptors) -> bool { + for (auto& d : descriptors) + { + auto db = d->cast(); + if (db && !db->bufferInfoList.empty() && db->bufferInfoList[0]->data && db->bufferInfoList[0]->data->dynamic()) + return true; + } + return false; + }; + bool dynamic = isDynamic(descriptors); + + auto descriptorSet = vsg::DescriptorSet::create(layout->setLayouts[Pipeline::TEXTURE_SET], descriptors); + if (!dynamic) + share(descriptorSet); + + auto bds = vsg::BindDescriptorSet::create( + VK_PIPELINE_BIND_POINT_GRAPHICS, layout, Pipeline::TEXTURE_SET, descriptorSet); + if (!dynamic) + share(bds); + + return bds; + } + + vsg::ref_ptr createStateGroup(vsg::ref_ptr child) + { + auto stateGroup = vsg::StateGroup::create(); + auto& options = getNodeOptions(); + if (mOverride && mOverride->pipelineOptions) + mOverride->pipelineOptions(options); + if (auto mat = getMaterialDescriptor()) + addModeDescriptor(mat, Pipeline::Mode::MATERIAL); + auto bindGraphicsPipeline = mBuilder.graphics->getOrCreate(options); + vsg::ref_ptr layout = bindGraphicsPipeline->pipeline->layout; + vsg::ref_ptr descriptorState; + if (options.stateSwitch) + { + auto stateSwitch = vsg::StateSwitch::create(*options.stateSwitch); + for (size_t i = 0; i < options.textureSets.size(); ++i) + { + auto bds = createBindDescriptorSet(layout, i); + stateSwitch->add(vsg::boolToMask(i == 0), bds); + stateSwitch->slot = bds->slot; + } + descriptorState = stateSwitch; + } + else + descriptorState = createBindDescriptorSet(layout, 0); + stateGroup->children = { child }; + stateGroup->stateCommands = { bindGraphicsPipeline, descriptorState }; + return stateGroup; + } + + void setHints(const Nif::Controller& nictrl, Anim::Controller::Hints& hints) + { + hints.duration = std::max(hints.duration, nictrl.timeStop); + auto& nodeOptions = getNodeOptions(); + hints.autoPlay = nodeOptions.autoPlay; + hints.phaseGroup = nodeOptions.phaseGroup; + } + + void addAnimContents(int c = Anim::Contents::Controllers) { mContents.add(c); } + + template + void setup(const Nif::Controller& nictrl, Ctrl& controller, Target& target) + { + setHints(nictrl, controller.hints); + controller.attachTo(target); + addAnimContents(); + } + + void handleTransformControllers(const Nif::ControllerPtr controller, Anim::Transform& transform) + { + callActiveControllers(controller, [this, &transform](auto& ctrl) { + if (ctrl.recType == Nif::RC_NiKeyframeController) + { + if (auto keyctrl = handleKeyframeController(static_cast(ctrl))) + { + setup(ctrl, *keyctrl, transform); + addAnimContents(Anim::Contents::TransformControllers); + } + } + else if (ctrl.recType == Nif::RC_NiPathController) + { + if (auto pathctrl = handlePathController(static_cast(ctrl))) + { + setup(ctrl, *pathctrl, transform); + addAnimContents(Anim::Contents::TransformControllers); + } + } + else if (ctrl.recType == Nif::RC_NiRollController) + { + if (auto rollctrl = handleRollController(static_cast(ctrl))) + { + setup(ctrl, *rollctrl, transform); + addAnimContents(Anim::Contents::TransformControllers); + } + } + }); + } + + void handleUVController(const Nif::NiUVController& uvctrl) + { + if (uvctrl.data.empty()) + return; + auto ctrl = Anim::TexMat::create(); + for (int i = 0; i < 2; ++i) + { + ctrl->translate[i] = handleKeyframes(uvctrl, uvctrl.data->mKeyList[i], { 0.f }); + ctrl->scale[i] = handleKeyframes(uvctrl, uvctrl.data->mKeyList[i + 2], { 1.f }); + } + + if (uvctrl.uvSet != 0) + getNodeOptions().nonStandardUvSets[Pipeline::Descriptors::TEXMAT_BINDING] = uvctrl.uvSet; + auto matrix = vsg::mat4Value::create(); + setup(uvctrl, *ctrl, *matrix); + auto descriptor = vsg::DescriptorBuffer::create( + matrix, Pipeline::Descriptors::TEXMAT_BINDING, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + addModeDescriptor(descriptor, Pipeline::Mode::TEXMAT); + } + + void handleGeomMorpherController(const Nif::NiGeomMorpherController& morpher, size_t numVertices) + { + if (morpher.mData.empty()) + return; + const auto& morphs = morpher.mData->mMorphs; + if (morphs.empty()) + return; + size_t numMorphs = morphs.size(); + auto array = vsg::vec4Array::create(numMorphs * numVertices); + for (size_t vertex = 0; vertex < numVertices; ++vertex) + { + for (size_t j = 0; j < numMorphs; ++j) + { + if (vertex < std::min(numVertices, morphs[j].mVertices.size())) + (*array)[vertex * numMorphs + j] + = vsg::vec4(toVsg(morphs[j].mVertices[vertex]), static_cast(numMorphs)); + } + } + + getNodeOptions().descriptors.emplace_back(vsg::DescriptorBuffer::create( + array, Pipeline::Descriptors::MORPH_BINDING, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)); + + auto weights = vsg::floatArray::create(numMorphs, 0.f); + auto ctrl = Anim::Morph::create(); + ctrl->weights.resize(numMorphs); + for (size_t i = 0; i < numMorphs; ++i) + { + float defaultWeight = i == 0 ? 1.f : 0.f; // iat(i) = defaultWeight; + ctrl->weights[i] = handleKeyframes(morpher, morphs[i].mKeyFrames, defaultWeight); + } + auto weightsDescriptor = vsg::DescriptorBuffer::create( + weights, Pipeline::Descriptors::MORPH_WEIGHTS_BINDING, 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + setup(morpher, *ctrl, *weights); + addModeDescriptor(weightsDescriptor, Pipeline::Mode::MORPH); + } + + void handleGeometryControllers(const Nif::ControllerPtr controller, size_t numVertices) + { + callActiveControllers(controller, [this, &numVertices](auto& ctrl) { + if (ctrl.recType == Nif::RC_NiUVController) + handleUVController(static_cast(ctrl)); + else if (ctrl.recType == Nif::RC_NiGeomMorpherController) + handleGeomMorpherController(static_cast(ctrl), numVertices); + }); + } + + void handleVisController(const Nif::NiVisController& visctrl, vsg::Switch& sw) + { + auto ctrl = Anim::Switch::create(Anim::make_channel(visctrl.mData->mVis)); + setup(visctrl, *ctrl, sw); + } + + bool filterMatches(const std::string& nodeName) + { + return Misc::StringUtils::ciCompareLen(nodeName, mSkinFilter, mSkinFilter.size()) == 0 + || Misc::StringUtils::ciCompareLen(nodeName, mSkinFilter2, mSkinFilter2.size()) == 0; + } + + void handleEffect(const Nif::Node& nifNode) + { + if (nifNode.recType != Nif::RC_NiTextureEffect) + { + warn("Unhandled effect " + nifNode.recName); + return; + } + + auto& textureEffect = static_cast(nifNode); + if (textureEffect.textureType != Nif::NiTextureEffect::Environment_Map) + { + warn("Unhandled NiTextureEffect type " + std::to_string(textureEffect.textureType)); + return; + } + if (textureEffect.texture.empty()) + return; + + addModeDescriptor( + handleTexture(*textureEffect.texture.getPtr(), textureEffect.clamp, Pipeline::Descriptors::ENV_UNIT), + Pipeline::Mode::ENV_MAP); + + /* + switch (textureEffect->coordGenType) + { + case Nif::NiTextureEffect::World_Parallel: + texGen->setMode(osg::TexGen::OBJECT_LINEAR); + break; + case Nif::NiTextureEffect::World_Perspective: + texGen->setMode(osg::TexGen::EYE_LINEAR); + break; + case Nif::NiTextureEffect::Sphere_Map: + texGen->setMode(osg::TexGen::SPHERE_MAP); + break; + } + */ + } + + void handleEffects(const Nif::NodeList& effects) + { + handleList(effects, [this](auto& n) { handleEffect(n); }); + } + + void handleProperties(const Nif::PropertyList& props) + { + handleList(props, [this](auto& n) { handleProperty(n); }); + } + + vsg::ref_ptr handleNiNodeChildren( + const Nif::NodeList& children, vsg::ref_ptr group, bool hasMarkers, bool skipMeshes) + { + vsg::Group::Children vsgchildren; + for (size_t i = 0; i < children.size(); ++i) + { + if (children[i].empty() || Misc::StringUtils::ciEqual(children[i]->name, "Bounding Box")) + continue; + if (auto child = handleNode(*children[i].getPtr(), hasMarkers, skipMeshes)) + vsgchildren.emplace_back(child); + } + if (group) + std::copy(vsgchildren.begin(), vsgchildren.end(), std::back_inserter(group->children)); + else if (!vsgchildren.empty()) + return vsgUtil::getNode(vsgchildren); + return {}; + } + + vsg::ref_ptr handleTransform(const Nif::Node& nifNode) + { + auto trans = Anim::Transform::create(); + convertTrafo(*trans, nifNode.trafo); + handleTransformControllers(nifNode.controller, *trans); + vsgUtil::setName(*trans, std::string(nifNode.name)); + vsgUtil::setID(*trans, nifNode.recIndex); + return trans; + } + + void handleAnimFlags(const Nif::Node& nifNode) + { + if (nifNode.recType == Nif::RC_NiBSAnimationNode || nifNode.recType == Nif::RC_NiBSParticleNode) + { + auto& nodeOptions = getNodeOptions(); + nodeOptions.autoPlay = nifNode.flags & Nif::NiNode::AnimFlag_AutoPlay; + nodeOptions.phaseGroup = (nifNode.flags & Nif::NiNode::AnimFlag_NotRandom) ? 0u : ++mPhaseGroups; // openmw-6455-controller-random-phase + } + } + + vsg::ref_ptr handleNode(const Nif::Node& nifNode, bool hasMarkers = false, bool skipMeshes = false) + { + ScopedPushPop spp(mNodeStack); + + handleAnimFlags(nifNode); + + auto& nodeOptions = getNodeOptions(); + if (mNif->getUseSkinning() && !mSkinFilter.empty() && !nodeOptions.filterMatched) + { + nodeOptions.filterMatched = filterMatches(nifNode.name); + if (!nodeOptions.filterMatched) + { + if (const Nif::NiNode* ninode = dynamic_cast(&nifNode)) + return handleNiNodeChildren(ninode->children, {}, false, false); + else + return {}; + } + } + + handleProperties(nifNode.props); + + vsg::ref_ptr group; // node_to_add_children_to + vsg::ref_ptr retNode; // topmost_node + if (!canOptimizeTransform(nifNode)) + { + auto trans = handleTransform(nifNode); + trans->subgraphRequiresLocalFrustum = false;//nodeOptions.depthSorted; + retNode = group = trans; + } + if (nifNode.useFlags & Nif::Node::Emitter /* || animatedCollision*/) + { + if (!group) + retNode = group = vsg::Group::create(); + vsgUtil::setID(*group, nifNode.recIndex); + } + + if (nifNode.recType == Nif::RC_NiBillboardNode) + { + if (!canOptimizeBillboard(nifNode)) + { + auto billboard = Anim::Billboard::create(); + billboard->subgraphRequiresLocalFrustum = false; + if (group) + billboard->addChild(group); + else + group = billboard; + retNode = billboard; + } + else + nodeOptions.shader.addMode(Pipeline::Mode::BILLBOARD); + } + + for (Nif::ExtraPtr e = nifNode.extra; !e.empty(); e = e->next) + { + if (e->recType == Nif::RC_NiTextKeyExtraData) + { + if (!group) + group = vsg::Group::create(); + handleTextKeys(static_cast(*e.getPtr()))->attachTo(*group); + ; + } + else if (e->recType == Nif::RC_NiStringExtraData) + { + const Nif::NiStringExtraData* sd = static_cast(e.getPtr()); + if (sd->string == "MRK") + { + // Marker objects. These meshes are only visible in the editor. + hasMarkers = true; + } + else if (sd->string == "BONE") + { + //; + } + } + } + + if (const Nif::NiNode* ninode = dynamic_cast(&nifNode)) + { + handleEffects(ninode->effects); + + if (nifNode.recType == Nif::RC_NiSwitchNode) + { + auto& niSwitchNode = static_cast(nifNode); + auto switchNode = vsg::Switch::create(); + switchNode->setValue("switch", std::string(nifNode.name)); + const Nif::NodeList& children = niSwitchNode.children; + switchNode->children.reserve(children.size()); + for (size_t i = 0; i < children.size(); ++i) + { + auto child = children[i].empty() ? vsg::Node::create() + : handleNode(*children[i].getPtr(), hasMarkers, skipMeshes); + if (!child) + child = vsg::Node::create(); + switchNode->addChild(i == niSwitchNode.initialIndex ? true : false, child); + } + if (group) + group->addChild(switchNode); + else + retNode = switchNode; + } + else if (nifNode.recType == Nif::RC_RootCollisionNode) + { + // Hide collision shapes, but don't skip the subgraph + // We still need to animate the hidden bones so the physics system can access them + // addOffSwitch( + skipMeshes = true; + } + /* + else if (nifNode.recType == Nif::RC_NiLODNode) + { + auto &niLodNode = static_cast(nifNode); + vsg::ref_ptr lodNode = vsg::LOD::create(); + const double pixel_ratio = 1.0 / 1080.0; + const double angle_ratio = 1.0 / osg::DegreesToRadians(30.0); // assume a 60 fovy for reference + const Nif::NodeList &children = niLodNode.children; + double minimumScreenHeightRatio = (atan2(radius, static_cast(lod.getMaxRange(i))) * + angle_ratio); + + for(size_t i = 0;i < children.size();++i) + { + auto child = handleNode(*children[i].getPtr(), hasMarkers, skipMeshes); + if (!child) + child = vsg::Node::create(); + vsg::LOD::Child lodChild; + lodChild.node = child; + lodNode->addChild(lodChild); + } + if (group) + group->addChild(lodNode); + else + retNode = lodNode; + }*/ + else if (auto createdNode = handleNiNodeChildren(ninode->children, group, hasMarkers, skipMeshes)) + retNode = createdNode; + } + else if (isTypeGeometry(nifNode.recType) && !skipMeshes && !canSkipGeometry(hasMarkers, nifNode)) + { + auto child = handleGeometry(static_cast(nifNode)); + if (group && child) + group->addChild(child); + else + retNode = child; + } + else if (nifNode.recType == Nif::RC_NiParticles) + { + auto child = handleParticles(static_cast(nifNode)); + if (group && child) + group->addChild(child); + else + retNode = child; + } + if (group && !retNode) + retNode = group; + if (!retNode) + return {}; + + bool hidden = nifNode.isHidden(); + auto visctrl = searchController(nifNode.controller, Nif::RC_NiVisController); + if (hidden || (visctrl && !visctrl->mData.empty())) + { + auto sw = vsg::Switch::create(); + sw->children = { { !hidden, retNode } }; + if (visctrl) + handleVisController(*visctrl, *sw); + retNode = sw; + } + return retNode; + } + }; + + vsg::ref_ptr nif::read(std::istream& stream, vsg::ref_ptr options) const + { + if (!vsg::compatibleExtension(options.get(), ".nif")) + return {}; + try + { + return nifImpl(stream, *options, mImageOptions, showMarkers, mBuilder).load(); + } + catch (std::exception& e) + { + std::string filename; + options->getValue("filename", filename); + std::cerr << "!nif::read(" << filename << "): " << e.what() << std::endl; + return {}; + } + } + + bool nif::getFeatures(Features& features) const + { + features.extensionFeatureMap[".nif"] = READ_ISTREAM; + // features.supportedOptions = "skinfilter" + return true; + } +} diff --git a/components/vsgadapters/nif/nif.hpp b/components/vsgadapters/nif/nif.hpp new file mode 100644 index 00000000000..cd1ded50034 --- /dev/null +++ b/components/vsgadapters/nif/nif.hpp @@ -0,0 +1,43 @@ +#ifndef VSGOPENMW_VSGADAPTERS_NIF_NIF_H +#define VSGOPENMW_VSGADAPTERS_NIF_NIF_H + +#include + +namespace Pipeline +{ + class Builder; +} + +namespace vsgAdapters +{ + /* + * Reads VER_MW nif file into vsg::Node or throws. + * Depends on nif, animation, pipeline. + */ + // template + class nif : public vsg::ReaderWriter + { + const Pipeline::Builder& mBuilder; + vsg::ref_ptr mImageOptions; + + public: + nif(const Pipeline::Builder& builder, vsg::ref_ptr imageOptions) + : mBuilder(builder) + , mImageOptions(imageOptions) + { + } + vsg::ref_ptr read(std::istream&, vsg::ref_ptr = {}) const override; + + bool getFeatures(Features& features) const override; + + /// Whether or not nodes marked as "MRK" should be shown. + /// These should be hidden ingame, but visible in the editor. + bool showMarkers = false; + + // Mask to use for nodes that ignore the crosshair intersection. + // This is used for NiCollisionSwitch nodes with NiCollisionSwitch state set to disabled. + unsigned int intersectionDisabledMask = ~0; + }; +} + +#endif diff --git a/components/vsgadapters/nif/particle.cpp b/components/vsgadapters/nif/particle.cpp new file mode 100644 index 00000000000..aea8e4bfc8f --- /dev/null +++ b/components/vsgadapters/nif/particle.cpp @@ -0,0 +1,214 @@ +#include "particle.hpp" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "anim.hpp" + +namespace vsgAdapters +{ + float getParticlesPerSecond(const Nif::NiParticleSystemController& partctrl) + { + if (partctrl.noAutoAdjust()) + return partctrl.emitRate; + else if (partctrl.lifetime == 0 && partctrl.lifetimeRandom == 0) + return 0.f; + else + return (partctrl.numParticles / (partctrl.lifetime + partctrl.lifetimeRandom / 2)); + } + + Pipeline::Data::EmitArgs handleEmitterData(const Nif::NiParticleSystemController& partctrl) + { + Pipeline::Data::EmitArgs data{ + .offsetRandom = { toVsg(partctrl.offsetRandom), 0 }, + .direction = { partctrl.horizontalDir, partctrl.horizontalAngle, partctrl.verticalDir, partctrl.verticalAngle }, + .velocityLifetime = { partctrl.velocity, partctrl.velocityRandom, partctrl.lifetime, partctrl.lifetimeRandom } + }; + return data; + } + + void handleInitialParticles(vsg::Array& array, const Nif::NiParticlesData& data, + const Nif::NiParticleSystemController& partctrl) + { + for (int i = 0; i < data.numParticles; ++i) + { + auto& in = partctrl.particles[i]; + float age = std::max(0.f, in.lifetime); + if (i >= data.activeCount || in.vertex >= data.vertices.size()) + age = in.lifespan; + auto& out = array.at(i); + out.velocityAge = vsg::vec4(toVsg(in.velocity), age); + out.maxAge.x = in.lifespan; + out.color = toVsg(partctrl.color); + out.color.a = 1; + if (in.vertex < data.vertices.size()) + out.positionSize = vsg::vec4(toVsg(data.vertices[in.vertex]), 1.f); + float size = partctrl.size; + if (in.vertex < data.sizes.size()) + size *= data.sizes[in.vertex]; + out.positionSize.w = size; + } + } + + vsg::ref_ptr createColorCurve(const Nif::NiColorData& clrdata, float maxDuration, float timeStep) + { + auto channel = handleInterpolatedKeyframes(clrdata.mKeyMap); + int numSteps = maxDuration / timeStep; + auto data = vsg::vec4Array::create(numSteps, vsg::Data::Properties(VK_FORMAT_R32G32B32A32_SFLOAT)); + for (int i = 0; i < numSteps; ++i) + data->at(i) = channel->value(timeStep * i); + return data; + } + + class LinkEmitter : public vsg::ConstVisitor + { + Emitter& mEmitter; + vsgUtil::AccumulatePath mTransformPath; + vsgUtil::AccumulatePath mSwitchPath; + + public: + bool foundEmitterNode = false; + bool foundParticleNode = false; + LinkEmitter(vsgAdapters::Emitter& e) + : mEmitter(e) + { + overrideMask = vsg::MASK_ALL; + } + using vsg::ConstVisitor::apply; + void apply(const vsg::Switch& sw) override + { + auto ppn = mSwitchPath.pushPop(&sw); + check(sw); + sw.traverse(*this); + } + void apply(const vsg::Transform& t) override + { + auto trans = dynamic_cast(&t); + if (!trans) + { + check(t); + t.traverse(*this); + return; + } + auto ppn = mTransformPath.pushPop(trans); + check(t); + trans->traverse(*this); + } + void apply(const vsg::Node& n) override + { + check(n); + n.traverse(*this); + } + void check(const vsg::Node& n) + { + if (auto id = vsgUtil::ID::get(n)) + { + int index = id->id; + if (index == mEmitter.mEmitterNodeIndex) + { + foundEmitterNode = true; + mEmitter.mPathToEmitter = mTransformPath.path; + mEmitter.mSwitches = mSwitchPath.path; + } + else if (index == mEmitter.mParticleNodeIndex) + { + foundParticleNode = true; + mEmitter.mPathToParticle = mTransformPath.path; + } + } + } + }; + + Emitter::Emitter(const Nif::NiParticleSystemController& partctrl, int particleNodeIndex) + : mParticleNodeIndex(particleNodeIndex) + { + mEmitArgs = handleEmitterData(partctrl); + mParticlesPerSecond = getParticlesPerSecond(partctrl); + + if (partctrl.timeStop <= partctrl.stopTime && partctrl.timeStart >= partctrl.startTime) + active = Anim::make_constant(true); + else + { + active = Anim::make_channel(partctrl.startTime, partctrl.stopTime); + addExtrapolatorIfRequired(partctrl, active, partctrl.startTime, partctrl.stopTime); + } + if (!partctrl.emitter.empty()) + mEmitterNodeIndex = partctrl.emitter->recIndex; + } + + void Emitter::apply(vsg::Value& val, float time) + { + // float dt = scene.dt; + // openmw-7238-particle-system-time + float dt = mDt.get(time); + auto& data = val.value(); + data.time.x = dt; + data.time.y = time; + if (active->value(time) && emitterVisible()) + { + data.emitMatrix = calculateEmitMatrix(); + data.emitCount = { calculateEmitCount(dt), 0, 0, 0 }; + } + else + data.emitCount = {}; + } + + void Emitter::link(Anim::Context& ctx, vsg::Object&) + { + LinkEmitter visitor(*this); + ctx.attachmentPath.back()->accept(visitor); + if (mEmitterNodeIndex != -1 && !visitor.foundEmitterNode) + std::cerr << "!foundEmitterNode(" << mEmitterNodeIndex << ")" << std::endl; + if (mParticleNodeIndex!= -1 && !visitor.foundParticleNode) + std::cerr << "!foundParticleNode(" << mParticleNodeIndex << ")" << std::endl; + + vsgUtil::trim(mPathToEmitter, mPathToParticle); + } + + int Emitter::calculateEmitCount(float dt) + { + auto v = dt * mParticlesPerSecond * 0.01; + int i = static_cast(v); + mCarryOver += v - static_cast(i); + if (mCarryOver > 1.f) + { + ++i; + mCarryOver -= 1.f; + } + return i; + } + + vsg::mat4 Emitter::calculateEmitMatrix() const + { + auto emitterToWorld = vsgUtil::computeTransform(mPathToEmitter); + auto worldToPs = vsg::inverse_4x3(vsgUtil::computeTransform(mPathToParticle)); + return /*orthoNormalize(*/ worldToPs * emitterToWorld; + } + + bool Emitter::emitterVisible() const + { + for (auto sw : mSwitches) + for (auto& switchChild : sw->children) + if (switchChild.mask == vsg::MASK_OFF) + return false; + return true; + } + + vsg::ref_ptr createParticleDispatch(uint32_t numParticles) + { + #include + // Vulkan guarantees a minimum of 128 maxComputeWorkGroupInvocations. + static_assert(workGroupSizeX <= 128); + return vsg::Dispatch::create(std::ceil(numParticles/static_cast(workGroupSizeX)), 1, 1); + } +} diff --git a/components/vsgadapters/nif/particle.hpp b/components/vsgadapters/nif/particle.hpp new file mode 100644 index 00000000000..9490612ea3b --- /dev/null +++ b/components/vsgadapters/nif/particle.hpp @@ -0,0 +1,61 @@ +#ifndef VSGOPENMW_VSGADAPTERS_NIF_PARTICLE_H +#define VSGOPENMW_VSGADAPTERS_NIF_PARTICLE_H + +#include +#include +#include + +#include +#include +#include +#include + +namespace Nif +{ + class NiParticleSystemController; + class NiColorData; + class NiParticlesData; +} +namespace Anim +{ + class Transform; +} +namespace Pipeline::Data +{ + #include +} +namespace vsgAdapters +{ + class Emitter : public Anim::MUpdateData> + { + Anim::DeltaTime mDt; + friend class LinkEmitter; + int mCarryOver{}; + std::vector mPathToParticle; + std::vector mPathToEmitter; + std::vector mSwitches; + int mEmitterNodeIndex = -1; + int mParticleNodeIndex = -1; + float mParticlesPerSecond{}; + int calculateEmitCount(float dt); + vsg::mat4 calculateEmitMatrix() const; + bool emitterVisible() const; + Pipeline::Data::EmitArgs mEmitArgs; + public: + Emitter(const Nif::NiParticleSystemController& partctrl, int particleNodeIndex); + Anim::channel_ptr active; + + void apply(vsg::Value& val, float time); + void link(Anim::Context& ctx, vsg::Object&) override; + }; + + Pipeline::Data::EmitArgs handleEmitterData(const Nif::NiParticleSystemController& partctrl); + + void handleInitialParticles(vsg::Array& array, const Nif::NiParticlesData& data, + const Nif::NiParticleSystemController& partctrl); + + vsg::ref_ptr createColorCurve(const Nif::NiColorData& clrdata, float maxDuration, float timeStep); + vsg::ref_ptr createParticleDispatch(uint32_t numParticles); +} + +#endif diff --git a/components/vsgadapters/osg/MatrixDecomposition.cpp b/components/vsgadapters/osg/MatrixDecomposition.cpp new file mode 100644 index 00000000000..8c0bf1453a6 --- /dev/null +++ b/components/vsgadapters/osg/MatrixDecomposition.cpp @@ -0,0 +1,884 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. + */ +// osgManipulator - Copyright (C) 2007 Fugro-Jason B.V. + +// Matrix decomposition code taken from Graphics Gems IV +// http://www.acm.org/pubs/tog/GraphicsGems/gemsiv/polar_decomp +// Copyright (C) 1993 Ken Shoemake + +#include +#include + +/** Copy nxn matrix A to C using "gets" for assignment **/ +#define matrixCopy(C, gets, A, n) \ + { \ + int i, j; \ + for (i = 0; i < n; i++) \ + for (j = 0; j < n; j++) \ + C[i][j] gets(A[i][j]); \ + } + +/** Copy transpose of nxn matrix A to C using "gets" for assignment **/ +#define mat_tpose(AT, gets, A, n) \ + { \ + int i, j; \ + for (i = 0; i < n; i++) \ + for (j = 0; j < n; j++) \ + AT[i][j] gets(A[j][i]); \ + } + +/** Fill out 3x3 matrix to 4x4 **/ +#define mat_pad(A) (A[W][X] = A[X][W] = A[W][Y] = A[Y][W] = A[W][Z] = A[Z][W] = 0, A[W][W] = 1) + +/** Assign nxn matrix C the element-wise combination of A and B using "op" **/ +#define matBinop(C, gets, A, op, B, n) \ + { \ + int i, j; \ + for (i = 0; i < n; i++) \ + for (j = 0; j < n; j++) \ + C[i][j] gets(A[i][j]) op(B[i][j]); \ + } + +/** Copy nxn matrix A to C using "gets" for assignment **/ +#define mat_copy(C, gets, A, n) \ + { \ + int i, j; \ + for (i = 0; i < n; i++) \ + for (j = 0; j < n; j++) \ + C[i][j] gets(A[i][j]); \ + } + +namespace MatrixDecomposition +{ + + typedef struct + { + double x, y, z, w; + } Quat; // Quaternion + enum QuatPart + { + X, + Y, + Z, + W + }; + typedef double _HMatrix[4][4]; + typedef Quat HVect; // Homogeneous 3D vector + typedef struct + { + osg::Vec4d t; // Translation Component; + Quat q; // Essential Rotation + Quat u; // Stretch rotation + HVect k; // Sign of determinant + double f; // Sign of determinant + } _affineParts; + + HVect spectDecomp(_HMatrix S, _HMatrix U); + Quat snuggle(Quat q, HVect* k); + double polarDecomp(_HMatrix M, _HMatrix Q, _HMatrix S); + + static _HMatrix mat_id = { { 1, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 1 } }; + +#define SQRTHALF (0.7071067811865475244) + static Quat qxtoz = { 0, SQRTHALF, 0, SQRTHALF }; + static Quat qytoz = { SQRTHALF, 0, 0, SQRTHALF }; + static Quat qppmm = { 0.5, 0.5, -0.5, -0.5 }; + static Quat qpppp = { 0.5, 0.5, 0.5, 0.5 }; + static Quat qmpmm = { -0.5, 0.5, -0.5, -0.5 }; + static Quat qpppm = { 0.5, 0.5, 0.5, -0.5 }; + static Quat q0001 = { 0.0, 0.0, 0.0, 1.0 }; + static Quat q1000 = { 1.0, 0.0, 0.0, 0.0 }; + + /* Return product of quaternion q by scalar w. */ + Quat Qt_Scale(Quat q, double w) + { + Quat qq; + qq.w = q.w * w; + qq.x = q.x * w; + qq.y = q.y * w; + qq.z = q.z * w; + return (qq); + } + + /* Return quaternion product qL * qR. Note: order is important! + * To combine rotations, use the product Mul(qSecond, qFirst), + * which gives the effect of rotating by qFirst then qSecond. */ + Quat Qt_Mul(Quat qL, Quat qR) + { + Quat qq; + qq.w = qL.w * qR.w - qL.x * qR.x - qL.y * qR.y - qL.z * qR.z; + qq.x = qL.w * qR.x + qL.x * qR.w + qL.y * qR.z - qL.z * qR.y; + qq.y = qL.w * qR.y + qL.y * qR.w + qL.z * qR.x - qL.x * qR.z; + qq.z = qL.w * qR.z + qL.z * qR.w + qL.x * qR.y - qL.y * qR.x; + return (qq); + } + + /* Return conjugate of quaternion. */ + Quat Qt_Conj(Quat q) + { + Quat qq; + qq.x = -q.x; + qq.y = -q.y; + qq.z = -q.z; + qq.w = q.w; + return (qq); + } + + /* Construct a (possibly non-unit) quaternion from real components. */ + Quat Qt_(double x, double y, double z, double w) + { + Quat qq; + qq.x = x; + qq.y = y; + qq.z = z; + qq.w = w; + return (qq); + } + + /** Multiply the upper left 3x3 parts of A and B to get AB **/ + void mat_mult(_HMatrix A, _HMatrix B, _HMatrix AB) + { + int i, j; + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + AB[i][j] = A[i][0] * B[0][j] + A[i][1] * B[1][j] + A[i][2] * B[2][j]; + } + + /** Set v to cross product of length 3 vectors va and vb **/ + void vcross(double* va, double* vb, double* v) + { + v[0] = va[1] * vb[2] - va[2] * vb[1]; + v[1] = va[2] * vb[0] - va[0] * vb[2]; + v[2] = va[0] * vb[1] - va[1] * vb[0]; + } + + /** Return dot product of length 3 vectors va and vb **/ + double vdot(double* va, double* vb) + { + return (va[0] * vb[0] + va[1] * vb[1] + va[2] * vb[2]); + } + + /** Set MadjT to transpose of inverse of M times determinant of M **/ + void adjoint_transpose(_HMatrix M, _HMatrix MadjT) + { + vcross(M[1], M[2], MadjT[0]); + vcross(M[2], M[0], MadjT[1]); + vcross(M[0], M[1], MadjT[2]); + } + + /** Return index of column of M containing maximum abs entry, or -1 if M=0 **/ + int find_max_col(_HMatrix M) + { + double abs, max; + int i, j, col; + max = 0.0; + col = -1; + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + { + abs = M[i][j]; + if (abs < 0.0) + abs = -abs; + if (abs > max) + { + max = abs; + col = j; + } + } + return col; + } + + /** Setup u for Household reflection to zero all v components but first **/ + void make_reflector(double* v, double* u) + { + double s = sqrt(vdot(v, v)); + u[0] = v[0]; + u[1] = v[1]; + u[2] = v[2] + ((v[2] < 0.0) ? -s : s); + s = sqrt(2.0 / vdot(u, u)); + u[0] = u[0] * s; + u[1] = u[1] * s; + u[2] = u[2] * s; + } + + /** Apply Householder reflection represented by u to column vectors of M **/ + void reflect_cols(_HMatrix M, double* u) + { + int i, j; + for (i = 0; i < 3; i++) + { + double s = u[0] * M[0][i] + u[1] * M[1][i] + u[2] * M[2][i]; + for (j = 0; j < 3; j++) + M[j][i] -= u[j] * s; + } + } + + /** Apply Householder reflection represented by u to row vectors of M **/ + void reflect_rows(_HMatrix M, double* u) + { + int i, j; + for (i = 0; i < 3; i++) + { + double s = vdot(u, M[i]); + for (j = 0; j < 3; j++) + M[i][j] -= u[j] * s; + } + } + + /** Find orthogonal factor Q of rank 1 (or less) M **/ + void do_rank1(_HMatrix M, _HMatrix Q) + { + double v1[3], v2[3], s; + int col; + mat_copy(Q, =, mat_id, 4); + /* If rank(M) is 1, we should find a non-zero column in M */ + col = find_max_col(M); + if (col < 0) + return; /* Rank is 0 */ + v1[0] = M[0][col]; + v1[1] = M[1][col]; + v1[2] = M[2][col]; + make_reflector(v1, v1); + reflect_cols(M, v1); + v2[0] = M[2][0]; + v2[1] = M[2][1]; + v2[2] = M[2][2]; + make_reflector(v2, v2); + reflect_rows(M, v2); + s = M[2][2]; + if (s < 0.0) + Q[2][2] = -1.0; + reflect_cols(Q, v1); + reflect_rows(Q, v2); + } + + /** Find orthogonal factor Q of rank 2 (or less) M using adjoint transpose **/ + void do_rank2(_HMatrix M, _HMatrix MadjT, _HMatrix Q) + { + double v1[3], v2[3]; + double w, x, y, z, c, s, d; + int col; + /* If rank(M) is 2, we should find a non-zero column in MadjT */ + col = find_max_col(MadjT); + if (col < 0) + { + do_rank1(M, Q); + return; + } /* Rank<2 */ + v1[0] = MadjT[0][col]; + v1[1] = MadjT[1][col]; + v1[2] = MadjT[2][col]; + make_reflector(v1, v1); + reflect_cols(M, v1); + vcross(M[0], M[1], v2); + make_reflector(v2, v2); + reflect_rows(M, v2); + w = M[0][0]; + x = M[0][1]; + y = M[1][0]; + z = M[1][1]; + if (w * z > x * y) + { + c = z + w; + s = y - x; + d = sqrt(c * c + s * s); + c = c / d; + s = s / d; + Q[0][0] = Q[1][1] = c; + Q[0][1] = -(Q[1][0] = s); + } + else + { + c = z - w; + s = y + x; + d = sqrt(c * c + s * s); + c = c / d; + s = s / d; + Q[0][0] = -(Q[1][1] = c); + Q[0][1] = Q[1][0] = s; + } + Q[0][2] = Q[2][0] = Q[1][2] = Q[2][1] = 0.0; + Q[2][2] = 1.0; + reflect_cols(Q, v1); + reflect_rows(Q, v2); + } + + double mat_norm(_HMatrix M, int tpose) + { + int i; + double sum, max; + max = 0.0; + for (i = 0; i < 3; i++) + { + if (tpose) + sum = fabs(M[0][i]) + fabs(M[1][i]) + fabs(M[2][i]); + else + sum = fabs(M[i][0]) + fabs(M[i][1]) + fabs(M[i][2]); + if (max < sum) + max = sum; + } + return max; + } + + double norm_inf(_HMatrix M) + { + return mat_norm(M, 0); + } + double norm_one(_HMatrix M) + { + return mat_norm(M, 1); + } + + /* Construct a unit quaternion from rotation matrix. Assumes matrix is + * used to multiply column vector on the left: vnew = mat vold. Works + * correctly for right-handed coordinate system and right-handed rotations. + * Translation and perspective components ignored. */ + + Quat quatFromMatrix(_HMatrix mat) + { + /* This algorithm avoids near-zero divides by looking for a large component + * - first w, then x, y, or z. When the trace is greater than zero, + * |w| is greater than 1/2, which is as small as a largest component can be. + * Otherwise, the largest diagonal entry corresponds to the largest of |x|, + * |y|, or |z|, one of which must be larger than |w|, and at least 1/2. */ + Quat qu = q0001; + double tr, s; + + tr = mat[X][X] + mat[Y][Y] + mat[Z][Z]; + if (tr >= 0.0) + { + s = sqrt(tr + mat[W][W]); + qu.w = s * 0.5; + s = 0.5 / s; + qu.x = (mat[Z][Y] - mat[Y][Z]) * s; + qu.y = (mat[X][Z] - mat[Z][X]) * s; + qu.z = (mat[Y][X] - mat[X][Y]) * s; + } + else + { + int h = X; + if (mat[Y][Y] > mat[X][X]) + h = Y; + if (mat[Z][Z] > mat[h][h]) + h = Z; + switch (h) + { +#define caseMacro(i, j, k, I, J, K) \ + case I: \ + s = sqrt((mat[I][I] - (mat[J][J] + mat[K][K])) + mat[W][W]); \ + qu.i = s * 0.5; \ + s = 0.5 / s; \ + qu.j = (mat[I][J] + mat[J][I]) * s; \ + qu.k = (mat[K][I] + mat[I][K]) * s; \ + qu.w = (mat[K][J] - mat[J][K]) * s; \ + break + caseMacro(x, y, z, X, Y, Z); + caseMacro(y, z, x, Y, Z, X); + caseMacro(z, x, y, Z, X, Y); + } + } + if (mat[W][W] != 1.0) + qu = Qt_Scale(qu, 1 / sqrt(mat[W][W])); + return (qu); + } + + /******* Decompose Affine Matrix *******/ + + /* Decompose 4x4 affine matrix A as TFRUK(U transpose), where t contains the + * translation components, q contains the rotation R, u contains U, k contains + * scale factors, and f contains the sign of the determinant. + * Assumes A transforms column vectors in right-handed coordinates. + * See Ken Shoemake and Tom Duff. Matrix Animation and Polar Decomposition. + * Proceedings of Graphics Interface 1992. + */ + void decompAffine(_HMatrix A, _affineParts* parts) + { + _HMatrix Q, S, U; + Quat p; + + // Translation component. + parts->t = osg::Vec4d(A[X][W], A[Y][W], A[Z][W], 0); + double det = polarDecomp(A, Q, S); + if (det < 0.0) + { + matrixCopy(Q, =, -Q, 3); + parts->f = -1; + } + else + parts->f = 1; + + parts->q = quatFromMatrix(Q); + parts->k = spectDecomp(S, U); + parts->u = quatFromMatrix(U); + p = snuggle(parts->u, &parts->k); + parts->u = Qt_Mul(parts->u, p); + } + + /******* Polar Decomposition *******/ + /* Polar Decomposition of 3x3 matrix in 4x4, + * M = QS. See Nicholas Higham and Robert S. Schreiber, + * Fast Polar Decomposition of An Arbitrary Matrix, + * Technical Report 88-942, October 1988, + * Department of Computer Science, Cornell University. + */ + + double polarDecomp(_HMatrix M, _HMatrix Q, _HMatrix S) + { + +#define TOL 1.0e-6 + _HMatrix Mk, MadjTk, Ek; + double det, M_one, M_inf, MadjT_one, MadjT_inf, E_one, gamma, g1, g2; + + mat_tpose(Mk, =, M, 3); + M_one = norm_one(Mk); + M_inf = norm_inf(Mk); + + do + { + adjoint_transpose(Mk, MadjTk); + det = vdot(Mk[0], MadjTk[0]); + if (det == 0.0) + { + do_rank2(Mk, MadjTk, Mk); + break; + } + + MadjT_one = norm_one(MadjTk); + MadjT_inf = norm_inf(MadjTk); + + gamma = sqrt(sqrt((MadjT_one * MadjT_inf) / (M_one * M_inf)) / fabs(det)); + g1 = gamma * 0.5; + g2 = 0.5 / (gamma * det); + matrixCopy(Ek, =, Mk, 3); + matBinop(Mk, =, g1 * Mk, +, g2 * MadjTk, 3); + mat_copy(Ek, -=, Mk, 3); + E_one = norm_one(Ek); + M_one = norm_one(Mk); + M_inf = norm_inf(Mk); + + } while (E_one > (M_one * TOL)); + + mat_tpose(Q, =, Mk, 3); + mat_pad(Q); + mat_mult(Mk, M, S); + mat_pad(S); + + for (int i = 0; i < 3; i++) + for (int j = i; j < 3; j++) + S[i][j] = S[j][i] = 0.5 * (S[i][j] + S[j][i]); + return (det); + } + + /******* Spectral Decomposition *******/ + /* Compute the spectral decomposition of symmetric positive semi-definite S. + * Returns rotation in U and scale factors in result, so that if K is a diagonal + * matrix of the scale factors, then S = U K (U transpose). Uses Jacobi method. + * See Gene H. Golub and Charles F. Van Loan. Matrix Computations. Hopkins 1983. + */ + HVect spectDecomp(_HMatrix S, _HMatrix U) + { + HVect kv; + double Diag[3], OffD[3]; /* OffD is off-diag (by omitted index) */ + double g, h, fabsh, fabsOffDi, t, theta, c, s, tau, ta, OffDq, a, b; + static char nxt[] = { Y, Z, X }; + int sweep; + mat_copy(U, =, mat_id, 4); + Diag[X] = S[X][X]; + Diag[Y] = S[Y][Y]; + Diag[Z] = S[Z][Z]; + OffD[X] = S[Y][Z]; + OffD[Y] = S[Z][X]; + OffD[Z] = S[X][Y]; + for (sweep = 20; sweep > 0; sweep--) + { + double sm = fabs(OffD[X]) + fabs(OffD[Y]) + fabs(OffD[Z]); + if (sm == 0.0) + break; + for (int i = Z; i >= X; i--) + { + int p = nxt[i]; + int q = nxt[p]; + fabsOffDi = fabs(OffD[i]); + g = 100.0 * fabsOffDi; + if (fabsOffDi > 0.0) + { + h = Diag[q] - Diag[p]; + fabsh = fabs(h); + if (fabsh + g == fabsh) + { + t = OffD[i] / h; + } + else + { + theta = 0.5 * h / OffD[i]; + t = 1.0 / (fabs(theta) + sqrt(theta * theta + 1.0)); + if (theta < 0.0) + t = -t; + } + c = 1.0 / sqrt(t * t + 1.0); + s = t * c; + tau = s / (c + 1.0); + ta = t * OffD[i]; + OffD[i] = 0.0; + Diag[p] -= ta; + Diag[q] += ta; + OffDq = OffD[q]; + OffD[q] -= s * (OffD[p] + tau * OffD[q]); + OffD[p] += s * (OffDq - tau * OffD[p]); + for (int j = Z; j >= X; j--) + { + a = U[j][p]; + b = U[j][q]; + U[j][p] -= s * (b + tau * a); + U[j][q] += s * (a - tau * b); + } + } + } + } + kv.x = Diag[X]; + kv.y = Diag[Y]; + kv.z = Diag[Z]; + kv.w = 1.0; + return (kv); + } + + /******* Spectral Axis Adjustment *******/ + + /* Given a unit quaternion, q, and a scale vector, k, find a unit quaternion, p, + * which permutes the axes and turns freely in the plane of duplicate scale + * factors, such that q p has the largest possible w component, i.e. the + * smallest possible angle. Permutes k's components to go with q p instead of q. + * See Ken Shoemake and Tom Duff. Matrix Animation and Polar Decomposition. + * Proceedings of Graphics Interface 1992. Details on p. 262-263. + */ + Quat snuggle(Quat q, HVect* k) + { +#define sgn(n, v) ((n) ? -(v) : (v)) +#define swap(a, i, j) \ + { \ + a[3] = a[i]; \ + a[i] = a[j]; \ + a[j] = a[3]; \ + } +#define cycle(a, p) \ + if (p) \ + { \ + a[3] = a[0]; \ + a[0] = a[1]; \ + a[1] = a[2]; \ + a[2] = a[3]; \ + } \ + else \ + { \ + a[3] = a[2]; \ + a[2] = a[1]; \ + a[1] = a[0]; \ + a[0] = a[3]; \ + } + + Quat p = q0001; + double ka[4]; + int turn = -1; + ka[X] = k->x; + ka[Y] = k->y; + ka[Z] = k->z; + + if (ka[X] == ka[Y]) + { + if (ka[X] == ka[Z]) + turn = W; + else + turn = Z; + } + else + { + if (ka[X] == ka[Z]) + turn = Y; + else if (ka[Y] == ka[Z]) + turn = X; + } + if (turn >= 0) + { + Quat qtoz, qp; + unsigned int win; + double mag[3], t; + switch (turn) + { + default: + return (Qt_Conj(q)); + case X: + q = Qt_Mul(q, qtoz = qxtoz); + swap(ka, X, Z) break; + case Y: + q = Qt_Mul(q, qtoz = qytoz); + swap(ka, Y, Z) break; + case Z: + qtoz = q0001; + break; + } + q = Qt_Conj(q); + mag[0] = (double)q.z * q.z + (double)q.w * q.w - 0.5; + mag[1] = (double)q.x * q.z - (double)q.y * q.w; + mag[2] = (double)q.y * q.z + (double)q.x * q.w; + + bool neg[3]; + for (int i = 0; i < 3; i++) + { + neg[i] = (mag[i] < 0.0); + if (neg[i]) + mag[i] = -mag[i]; + } + + if (mag[0] > mag[1]) + { + if (mag[0] > mag[2]) + win = 0; + else + win = 2; + } + else + { + if (mag[1] > mag[2]) + win = 1; + else + win = 2; + } + + switch (win) + { + case 0: + if (neg[0]) + p = q1000; + else + p = q0001; + break; + case 1: + if (neg[1]) + p = qppmm; + else + p = qpppp; + cycle(ka, 0) break; + case 2: + if (neg[2]) + p = qmpmm; + else + p = qpppm; + cycle(ka, 1) break; + } + + qp = Qt_Mul(q, p); + t = sqrt(mag[win] + 0.5); + p = Qt_Mul(p, Qt_(0.0, 0.0, -qp.z / t, qp.w / t)); + p = Qt_Mul(qtoz, Qt_Conj(p)); + } + else + { + double qa[4], pa[4]; + unsigned int lo, hi; + bool par = false; + bool neg[4]; + double all, big, two; + qa[0] = q.x; + qa[1] = q.y; + qa[2] = q.z; + qa[3] = q.w; + for (int i = 0; i < 4; i++) + { + pa[i] = 0.0; + neg[i] = (qa[i] < 0.0); + if (neg[i]) + qa[i] = -qa[i]; + par ^= neg[i]; + } + + /* Find two largest components, indices in hi and lo */ + if (qa[0] > qa[1]) + lo = 0; + else + lo = 1; + + if (qa[2] > qa[3]) + hi = 2; + else + hi = 3; + + if (qa[lo] > qa[hi]) + { + if (qa[lo ^ 1] > qa[hi]) + { + hi = lo; + lo ^= 1; + } + else + { + hi ^= lo; + lo ^= hi; + hi ^= lo; + } + } + else + { + if (qa[hi ^ 1] > qa[lo]) + lo = hi ^ 1; + } + + all = (qa[0] + qa[1] + qa[2] + qa[3]) * 0.5; + two = (qa[hi] + qa[lo]) * SQRTHALF; + big = qa[hi]; + if (all > two) + { + if (all > big) + { /*all*/ + { + for (int i = 0; i < 4; i++) + pa[i] = sgn(neg[i], 0.5); + } + cycle(ka, par); + } + else + { /*big*/ + pa[hi] = sgn(neg[hi], 1.0); + } + } + else + { + if (two > big) + { /*two*/ + pa[hi] = sgn(neg[hi], SQRTHALF); + pa[lo] = sgn(neg[lo], SQRTHALF); + if (lo > hi) + { + hi ^= lo; + lo ^= hi; + hi ^= lo; + } + if (hi == W) + { + hi = "\001\002\000"[lo]; + lo = 3 - hi - lo; + } + swap(ka, hi, lo); + } + else + { /*big*/ + pa[hi] = sgn(neg[hi], 1.0); + } + } + p.x = -pa[0]; + p.y = -pa[1]; + p.z = -pa[2]; + p.w = pa[3]; + } + k->x = ka[X]; + k->y = ka[Y]; + k->z = ka[Z]; + return (p); + } + +} + +void osg::Matrixf::decompose(osg::Vec3f& t, osg::Quat& r, osg::Vec3f& s, osg::Quat& so) const +{ + Vec3d temp_trans; + Vec3d temp_scale; + decompose(temp_trans, r, temp_scale, so); + t = temp_trans; + s = temp_scale; +} + +void osg::Matrixf::decompose(osg::Vec3d& t, osg::Quat& r, osg::Vec3d& s, osg::Quat& so) const +{ + MatrixDecomposition::_affineParts parts; + MatrixDecomposition::_HMatrix hmatrix; + + // Transpose copy of LTW + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + hmatrix[i][j] = (*this)(j, i); + } + } + + MatrixDecomposition::decompAffine(hmatrix, &parts); + + double mul = 1.0; + if (parts.t[MatrixDecomposition::W] != 0.0) + mul = 1.0 / parts.t[MatrixDecomposition::W]; + + t[0] = parts.t[MatrixDecomposition::X] * mul; + t[1] = parts.t[MatrixDecomposition::Y] * mul; + t[2] = parts.t[MatrixDecomposition::Z] * mul; + + r.set(parts.q.x, parts.q.y, parts.q.z, parts.q.w); + + mul = 1.0; + if (parts.k.w != 0.0) + mul = 1.0 / parts.k.w; + + // mul be sign of determinant to support negative scales. + mul *= parts.f; + s[0] = parts.k.x * mul; + s[1] = parts.k.y * mul; + s[2] = parts.k.z * mul; + + so.set(parts.u.x, parts.u.y, parts.u.z, parts.u.w); +} + +void osg::Matrixd::decompose(osg::Vec3f& t, osg::Quat& r, osg::Vec3f& s, osg::Quat& so) const +{ + Vec3d temp_trans; + Vec3d temp_scale; + decompose(temp_trans, r, temp_scale, so); + t = temp_trans; + s = temp_scale; +} + +void osg::Matrixd::decompose(osg::Vec3d& t, osg::Quat& r, osg::Vec3d& s, osg::Quat& so) const +{ + MatrixDecomposition::_affineParts parts; + MatrixDecomposition::_HMatrix hmatrix; + + // Transpose copy of LTW + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + hmatrix[i][j] = (*this)(j, i); + } + } + + MatrixDecomposition::decompAffine(hmatrix, &parts); + + double mul = 1.0; + if (parts.t[MatrixDecomposition::W] != 0.0) + mul = 1.0 / parts.t[MatrixDecomposition::W]; + + t[0] = parts.t[MatrixDecomposition::X] * mul; + t[1] = parts.t[MatrixDecomposition::Y] * mul; + t[2] = parts.t[MatrixDecomposition::Z] * mul; + + r.set(parts.q.x, parts.q.y, parts.q.z, parts.q.w); + + mul = 1.0; + if (parts.k.w != 0.0) + mul = 1.0 / parts.k.w; + + // mul be sign of determinant to support negative scales. + mul *= parts.f; + s[0] = parts.k.x * mul; + s[1] = parts.k.y * mul; + s[2] = parts.k.z * mul; + + so.set(parts.u.x, parts.u.y, parts.u.z, parts.u.w); +} diff --git a/components/vsgadapters/osg/Matrix_implementation.cpp b/components/vsgadapters/osg/Matrix_implementation.cpp new file mode 100644 index 00000000000..592c02084db --- /dev/null +++ b/components/vsgadapters/osg/Matrix_implementation.cpp @@ -0,0 +1,1087 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. + */ + +#include +#include +#include +#include + +// #include + +#include +#include +#include + +using namespace osg; + +#define SET_ROW(row, v1, v2, v3, v4) \ + _mat[(row)][0] = (v1); \ + _mat[(row)][1] = (v2); \ + _mat[(row)][2] = (v3); \ + _mat[(row)][3] = (v4); + +#define INNER_PRODUCT(a, b, r, c) \ + ((a)._mat[r][0] * (b)._mat[0][c]) + ((a)._mat[r][1] * (b)._mat[1][c]) + ((a)._mat[r][2] * (b)._mat[2][c]) \ + + ((a)._mat[r][3] * (b)._mat[3][c]) + +Matrix_implementation::Matrix_implementation(value_type a00, value_type a01, value_type a02, value_type a03, + value_type a10, value_type a11, value_type a12, value_type a13, value_type a20, value_type a21, value_type a22, + value_type a23, value_type a30, value_type a31, value_type a32, value_type a33) +{ + SET_ROW(0, a00, a01, a02, a03) + SET_ROW(1, a10, a11, a12, a13) + SET_ROW(2, a20, a21, a22, a23) + SET_ROW(3, a30, a31, a32, a33) +} + +void Matrix_implementation::set(value_type a00, value_type a01, value_type a02, value_type a03, value_type a10, + value_type a11, value_type a12, value_type a13, value_type a20, value_type a21, value_type a22, value_type a23, + value_type a30, value_type a31, value_type a32, value_type a33) +{ + SET_ROW(0, a00, a01, a02, a03) + SET_ROW(1, a10, a11, a12, a13) + SET_ROW(2, a20, a21, a22, a23) + SET_ROW(3, a30, a31, a32, a33) +} + +#define QX q._v[0] +#define QY q._v[1] +#define QZ q._v[2] +#define QW q._v[3] + +void Matrix_implementation::setRotate(const Quat& q) +{ + double length2 = q.length2(); + if (fabs(length2) <= std::numeric_limits::min()) + { + _mat[0][0] = 0.0; + _mat[1][0] = 0.0; + _mat[2][0] = 0.0; + _mat[0][1] = 0.0; + _mat[1][1] = 0.0; + _mat[2][1] = 0.0; + _mat[0][2] = 0.0; + _mat[1][2] = 0.0; + _mat[2][2] = 0.0; + } + else + { + double rlength2; + // normalize quat if required. + // We can avoid the expensive sqrt in this case since all 'coefficients' below are products of two q components. + // That is a square of a square root, so it is possible to avoid that + if (length2 != 1.0) + { + rlength2 = 2.0 / length2; + } + else + { + rlength2 = 2.0; + } + + // Source: Gamasutra, Rotating Objects Using Quaternions + // + // http://www.gamasutra.com/features/19980703/quaternions_01.htm + + double wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; + + // calculate coefficients + x2 = rlength2 * QX; + y2 = rlength2 * QY; + z2 = rlength2 * QZ; + + xx = QX * x2; + xy = QX * y2; + xz = QX * z2; + + yy = QY * y2; + yz = QY * z2; + zz = QZ * z2; + + wx = QW * x2; + wy = QW * y2; + wz = QW * z2; + + // Note. Gamasutra gets the matrix assignments inverted, resulting + // in left-handed rotations, which is contrary to OpenGL and OSG's + // methodology. The matrix assignment has been altered in the next + // few lines of code to do the right thing. + // Don Burns - Oct 13, 2001 + _mat[0][0] = 1.0 - (yy + zz); + _mat[1][0] = xy - wz; + _mat[2][0] = xz + wy; + + _mat[0][1] = xy + wz; + _mat[1][1] = 1.0 - (xx + zz); + _mat[2][1] = yz - wx; + + _mat[0][2] = xz - wy; + _mat[1][2] = yz + wx; + _mat[2][2] = 1.0 - (xx + yy); + } + +#if 0 + _mat[0][3] = 0.0; + _mat[1][3] = 0.0; + _mat[2][3] = 0.0; + + _mat[3][0] = 0.0; + _mat[3][1] = 0.0; + _mat[3][2] = 0.0; + _mat[3][3] = 1.0; +#endif +} + +#define COMPILE_getRotate_David_Spillings_Mk1 +// #define COMPILE_getRotate_David_Spillings_Mk2 +// #define COMPILE_getRotate_Original + +#ifdef COMPILE_getRotate_David_Spillings_Mk1 +// David Spillings implementation Mk 1 +Quat Matrix_implementation::getRotate() const +{ + Quat q; + + value_type s; + value_type tq[4]; + int i, j; + + // Use tq to store the largest trace + tq[0] = 1 + _mat[0][0] + _mat[1][1] + _mat[2][2]; + tq[1] = 1 + _mat[0][0] - _mat[1][1] - _mat[2][2]; + tq[2] = 1 - _mat[0][0] + _mat[1][1] - _mat[2][2]; + tq[3] = 1 - _mat[0][0] - _mat[1][1] + _mat[2][2]; + + // Find the maximum (could also use stacked if's later) + j = 0; + for (i = 1; i < 4; i++) + j = (tq[i] > tq[j]) ? i : j; + + // check the diagonal + if (j == 0) + { + /* perform instant calculation */ + QW = tq[0]; + QX = _mat[1][2] - _mat[2][1]; + QY = _mat[2][0] - _mat[0][2]; + QZ = _mat[0][1] - _mat[1][0]; + } + else if (j == 1) + { + QW = _mat[1][2] - _mat[2][1]; + QX = tq[1]; + QY = _mat[0][1] + _mat[1][0]; + QZ = _mat[2][0] + _mat[0][2]; + } + else if (j == 2) + { + QW = _mat[2][0] - _mat[0][2]; + QX = _mat[0][1] + _mat[1][0]; + QY = tq[2]; + QZ = _mat[1][2] + _mat[2][1]; + } + else /* if (j==3) */ + { + QW = _mat[0][1] - _mat[1][0]; + QX = _mat[2][0] + _mat[0][2]; + QY = _mat[1][2] + _mat[2][1]; + QZ = tq[3]; + } + + s = sqrt(0.25 / tq[j]); + QW *= s; + QX *= s; + QY *= s; + QZ *= s; + + return q; +} +#endif + +#ifdef COMPILE_getRotate_David_Spillings_Mk2 +// David Spillings implementation Mk 2 +Quat Matrix_implementation::getRotate() const +{ + Quat q; + + // From http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + QW = 0.5 * sqrt(osg::maximum(0.0, 1.0 + _mat[0][0] + _mat[1][1] + _mat[2][2])); + QX = 0.5 * sqrt(osg::maximum(0.0, 1.0 + _mat[0][0] - _mat[1][1] - _mat[2][2])); + QY = 0.5 * sqrt(osg::maximum(0.0, 1.0 - _mat[0][0] + _mat[1][1] - _mat[2][2])); + QZ = 0.5 * sqrt(osg::maximum(0.0, 1.0 - _mat[0][0] - _mat[1][1] + _mat[2][2])); + +#if 0 + // Robert Osfield, June 7th 2007, arggg this new implementation produces many many errors, so have to revert to sign(..) original below. + QX = QX * osg::signOrZero( _mat[1][2] - _mat[2][1]) ; + QY = QY * osg::signOrZero( _mat[2][0] - _mat[0][2]) ; + QZ = QZ * osg::signOrZero( _mat[0][1] - _mat[1][0]) ; +#else + QX = QX * osg::sign(_mat[1][2] - _mat[2][1]); + QY = QY * osg::sign(_mat[2][0] - _mat[0][2]); + QZ = QZ * osg::sign(_mat[0][1] - _mat[1][0]); +#endif + + return q; +} +#endif + +#ifdef COMPILE_getRotate_Original +// Original implementation +Quat Matrix_implementation::getRotate() const +{ + Quat q; + + // Source: Gamasutra, Rotating Objects Using Quaternions + // + // http://www.gamasutra.com/features/programming/19980703/quaternions_01.htm + + value_type tr, s; + value_type tq[4]; + int i, j, k; + + int nxt[3] = { 1, 2, 0 }; + + tr = _mat[0][0] + _mat[1][1] + _mat[2][2] + 1.0; + + // check the diagonal + if (tr > 0.0) + { + s = (value_type)sqrt(tr); + QW = s / 2.0; + s = 0.5 / s; + QX = (_mat[1][2] - _mat[2][1]) * s; + QY = (_mat[2][0] - _mat[0][2]) * s; + QZ = (_mat[0][1] - _mat[1][0]) * s; + } + else + { + // diagonal is negative + i = 0; + if (_mat[1][1] > _mat[0][0]) + i = 1; + if (_mat[2][2] > _mat[i][i]) + i = 2; + j = nxt[i]; + k = nxt[j]; + + s = (value_type)sqrt((_mat[i][i] - (_mat[j][j] + _mat[k][k])) + 1.0); + + tq[i] = s * 0.5; + + if (s != 0.0) + s = 0.5 / s; + + tq[3] = (_mat[j][k] - _mat[k][j]) * s; + tq[j] = (_mat[i][j] + _mat[j][i]) * s; + tq[k] = (_mat[i][k] + _mat[k][i]) * s; + + QX = tq[0]; + QY = tq[1]; + QZ = tq[2]; + QW = tq[3]; + } + + return q; +} +#endif + +int Matrix_implementation::compare(const Matrix_implementation& m) const +{ + const Matrix_implementation::value_type* lhs = reinterpret_cast(_mat); + const Matrix_implementation::value_type* end_lhs = lhs + 16; + const Matrix_implementation::value_type* rhs = reinterpret_cast(m._mat); + for (; lhs != end_lhs; ++lhs, ++rhs) + { + if (*lhs < *rhs) + return -1; + if (*rhs < *lhs) + return 1; + } + return 0; +} + +void Matrix_implementation::setTrans(value_type tx, value_type ty, value_type tz) +{ + _mat[3][0] = tx; + _mat[3][1] = ty; + _mat[3][2] = tz; +} + +void Matrix_implementation::setTrans(const Vec3f& v) +{ + _mat[3][0] = v[0]; + _mat[3][1] = v[1]; + _mat[3][2] = v[2]; +} +void Matrix_implementation::setTrans(const Vec3d& v) +{ + _mat[3][0] = v[0]; + _mat[3][1] = v[1]; + _mat[3][2] = v[2]; +} + +void Matrix_implementation::makeIdentity() +{ + SET_ROW(0, 1, 0, 0, 0) + SET_ROW(1, 0, 1, 0, 0) + SET_ROW(2, 0, 0, 1, 0) + SET_ROW(3, 0, 0, 0, 1) +} + +void Matrix_implementation::makeScale(const Vec3f& v) +{ + makeScale(v[0], v[1], v[2]); +} + +void Matrix_implementation::makeScale(const Vec3d& v) +{ + makeScale(v[0], v[1], v[2]); +} + +void Matrix_implementation::makeScale(value_type x, value_type y, value_type z) +{ + SET_ROW(0, x, 0, 0, 0) + SET_ROW(1, 0, y, 0, 0) + SET_ROW(2, 0, 0, z, 0) + SET_ROW(3, 0, 0, 0, 1) +} + +void Matrix_implementation::makeTranslate(const Vec3f& v) +{ + makeTranslate(v[0], v[1], v[2]); +} + +void Matrix_implementation::makeTranslate(const Vec3d& v) +{ + makeTranslate(v[0], v[1], v[2]); +} + +void Matrix_implementation::makeTranslate(value_type x, value_type y, value_type z) +{ + SET_ROW(0, 1, 0, 0, 0) + SET_ROW(1, 0, 1, 0, 0) + SET_ROW(2, 0, 0, 1, 0) + SET_ROW(3, x, y, z, 1) +} + +void Matrix_implementation::makeRotate(const Vec3f& from, const Vec3f& to) +{ + makeIdentity(); + + Quat quat; + quat.makeRotate(from, to); + setRotate(quat); +} +void Matrix_implementation::makeRotate(const Vec3d& from, const Vec3d& to) +{ + makeIdentity(); + + Quat quat; + quat.makeRotate(from, to); + setRotate(quat); +} + +void Matrix_implementation::makeRotate(value_type angle, const Vec3f& axis) +{ + makeIdentity(); + + Quat quat; + quat.makeRotate(angle, axis); + setRotate(quat); +} +void Matrix_implementation::makeRotate(value_type angle, const Vec3d& axis) +{ + makeIdentity(); + + Quat quat; + quat.makeRotate(angle, axis); + setRotate(quat); +} + +void Matrix_implementation::makeRotate(value_type angle, value_type x, value_type y, value_type z) +{ + makeIdentity(); + + Quat quat; + quat.makeRotate(angle, x, y, z); + setRotate(quat); +} + +void Matrix_implementation::makeRotate(const Quat& quat) +{ + makeIdentity(); + + setRotate(quat); +} + +void Matrix_implementation::makeRotate( + value_type angle1, const Vec3f& axis1, value_type angle2, const Vec3f& axis2, value_type angle3, const Vec3f& axis3) +{ + makeIdentity(); + + Quat quat; + quat.makeRotate(angle1, axis1, angle2, axis2, angle3, axis3); + setRotate(quat); +} + +void Matrix_implementation::makeRotate( + value_type angle1, const Vec3d& axis1, value_type angle2, const Vec3d& axis2, value_type angle3, const Vec3d& axis3) +{ + makeIdentity(); + + Quat quat; + quat.makeRotate(angle1, axis1, angle2, axis2, angle3, axis3); + setRotate(quat); +} + +void Matrix_implementation::mult(const Matrix_implementation& lhs, const Matrix_implementation& rhs) +{ + if (&lhs == this) + { + postMult(rhs); + return; + } + if (&rhs == this) + { + preMult(lhs); + return; + } + + // PRECONDITION: We assume neither &lhs nor &rhs == this + // if it did, use preMult or postMult instead + _mat[0][0] = INNER_PRODUCT(lhs, rhs, 0, 0); + _mat[0][1] = INNER_PRODUCT(lhs, rhs, 0, 1); + _mat[0][2] = INNER_PRODUCT(lhs, rhs, 0, 2); + _mat[0][3] = INNER_PRODUCT(lhs, rhs, 0, 3); + _mat[1][0] = INNER_PRODUCT(lhs, rhs, 1, 0); + _mat[1][1] = INNER_PRODUCT(lhs, rhs, 1, 1); + _mat[1][2] = INNER_PRODUCT(lhs, rhs, 1, 2); + _mat[1][3] = INNER_PRODUCT(lhs, rhs, 1, 3); + _mat[2][0] = INNER_PRODUCT(lhs, rhs, 2, 0); + _mat[2][1] = INNER_PRODUCT(lhs, rhs, 2, 1); + _mat[2][2] = INNER_PRODUCT(lhs, rhs, 2, 2); + _mat[2][3] = INNER_PRODUCT(lhs, rhs, 2, 3); + _mat[3][0] = INNER_PRODUCT(lhs, rhs, 3, 0); + _mat[3][1] = INNER_PRODUCT(lhs, rhs, 3, 1); + _mat[3][2] = INNER_PRODUCT(lhs, rhs, 3, 2); + _mat[3][3] = INNER_PRODUCT(lhs, rhs, 3, 3); +} + +void Matrix_implementation::preMult(const Matrix_implementation& other) +{ + // brute force method requiring a copy + // Matrix_implementation tmp(other* *this); + // *this = tmp; + + // more efficient method just use a value_type[4] for temporary storage. + value_type t[4]; + for (int col = 0; col < 4; ++col) + { + t[0] = INNER_PRODUCT(other, *this, 0, col); + t[1] = INNER_PRODUCT(other, *this, 1, col); + t[2] = INNER_PRODUCT(other, *this, 2, col); + t[3] = INNER_PRODUCT(other, *this, 3, col); + _mat[0][col] = t[0]; + _mat[1][col] = t[1]; + _mat[2][col] = t[2]; + _mat[3][col] = t[3]; + } +} + +void Matrix_implementation::postMult(const Matrix_implementation& other) +{ + // brute force method requiring a copy + // Matrix_implementation tmp(*this * other); + // *this = tmp; + + // more efficient method just use a value_type[4] for temporary storage. + value_type t[4]; + for (int row = 0; row < 4; ++row) + { + t[0] = INNER_PRODUCT(*this, other, row, 0); + t[1] = INNER_PRODUCT(*this, other, row, 1); + t[2] = INNER_PRODUCT(*this, other, row, 2); + t[3] = INNER_PRODUCT(*this, other, row, 3); + SET_ROW(row, t[0], t[1], t[2], t[3]) + } +} + +#undef INNER_PRODUCT + +// orthoNormalize the 3x3 rotation matrix +void Matrix_implementation::orthoNormalize(const Matrix_implementation& rhs) +{ + value_type x_colMag + = (rhs._mat[0][0] * rhs._mat[0][0]) + (rhs._mat[1][0] * rhs._mat[1][0]) + (rhs._mat[2][0] * rhs._mat[2][0]); + value_type y_colMag + = (rhs._mat[0][1] * rhs._mat[0][1]) + (rhs._mat[1][1] * rhs._mat[1][1]) + (rhs._mat[2][1] * rhs._mat[2][1]); + value_type z_colMag + = (rhs._mat[0][2] * rhs._mat[0][2]) + (rhs._mat[1][2] * rhs._mat[1][2]) + (rhs._mat[2][2] * rhs._mat[2][2]); + + if (!equivalent((double)x_colMag, 1.0) && !equivalent((double)x_colMag, 0.0)) + { + x_colMag = sqrt(x_colMag); + _mat[0][0] = rhs._mat[0][0] / x_colMag; + _mat[1][0] = rhs._mat[1][0] / x_colMag; + _mat[2][0] = rhs._mat[2][0] / x_colMag; + } + else + { + _mat[0][0] = rhs._mat[0][0]; + _mat[1][0] = rhs._mat[1][0]; + _mat[2][0] = rhs._mat[2][0]; + } + + if (!equivalent((double)y_colMag, 1.0) && !equivalent((double)y_colMag, 0.0)) + { + y_colMag = sqrt(y_colMag); + _mat[0][1] = rhs._mat[0][1] / y_colMag; + _mat[1][1] = rhs._mat[1][1] / y_colMag; + _mat[2][1] = rhs._mat[2][1] / y_colMag; + } + else + { + _mat[0][1] = rhs._mat[0][1]; + _mat[1][1] = rhs._mat[1][1]; + _mat[2][1] = rhs._mat[2][1]; + } + + if (!equivalent((double)z_colMag, 1.0) && !equivalent((double)z_colMag, 0.0)) + { + z_colMag = sqrt(z_colMag); + _mat[0][2] = rhs._mat[0][2] / z_colMag; + _mat[1][2] = rhs._mat[1][2] / z_colMag; + _mat[2][2] = rhs._mat[2][2] / z_colMag; + } + else + { + _mat[0][2] = rhs._mat[0][2]; + _mat[1][2] = rhs._mat[1][2]; + _mat[2][2] = rhs._mat[2][2]; + } + + _mat[3][0] = rhs._mat[3][0]; + _mat[3][1] = rhs._mat[3][1]; + _mat[3][2] = rhs._mat[3][2]; + + _mat[0][3] = rhs._mat[0][3]; + _mat[1][3] = rhs._mat[1][3]; + _mat[2][3] = rhs._mat[2][3]; + _mat[3][3] = rhs._mat[3][3]; +} + +/****************************************** + Matrix inversion technique: +Given a matrix mat, we want to invert it. +mat = [ r00 r01 r02 a + r10 r11 r12 b + r20 r21 r22 c + tx ty tz d ] +We note that this matrix can be split into three matrices. +mat = rot * trans * corr, where rot is rotation part, trans is translation part, and corr is the correction due to +perspective (if any). rot = [ r00 r01 r02 0 r10 r11 r12 0 r20 r21 r22 0 0 0 0 1 ] trans = [ 1 0 0 0 0 1 0 0 + 0 0 1 0 + tx ty tz 1 ] +corr = [ 1 0 0 px + 0 1 0 py + 0 0 1 pz + 0 0 0 s ] +where the elements of corr are obtained from linear combinations of the elements of rot, trans, and mat. +So the inverse is mat' = (trans * corr)' * rot', where rot' must be computed the traditional way, which is easy since it +is only a 3x3 matrix. This problem is simplified if [px py pz s] = [0 0 0 1], which will happen if mat was composed only +of rotations, scales, and translations (which is common). In this case, we can ignore corr entirely which saves on a +lot of computations. +******************************************/ + +bool Matrix_implementation::invert_4x3(const Matrix_implementation& mat) +{ + if (&mat == this) + { + Matrix_implementation tm(mat); + return invert_4x3(tm); + } + + value_type r00 = mat._mat[0][0]; + value_type r01 = mat._mat[0][1]; + value_type r02 = mat._mat[0][2]; + value_type r10 = mat._mat[1][0]; + value_type r11 = mat._mat[1][1]; + value_type r12 = mat._mat[1][2]; + value_type r20 = mat._mat[2][0]; + value_type r21 = mat._mat[2][1]; + value_type r22 = mat._mat[2][2]; + + // Partially compute inverse of rot + _mat[0][0] = r11 * r22 - r12 * r21; + _mat[0][1] = r02 * r21 - r01 * r22; + _mat[0][2] = r01 * r12 - r02 * r11; + + // Compute determinant of rot from 3 elements just computed + value_type one_over_det = 1.0 / (r00 * _mat[0][0] + r10 * _mat[0][1] + r20 * _mat[0][2]); + r00 *= one_over_det; + r10 *= one_over_det; + r20 *= one_over_det; // Saves on later computations + + // Finish computing inverse of rot + _mat[0][0] *= one_over_det; + _mat[0][1] *= one_over_det; + _mat[0][2] *= one_over_det; + _mat[0][3] = 0.0; + _mat[1][0] = r12 * r20 - r10 * r22; // Have already been divided by det + _mat[1][1] = r00 * r22 - r02 * r20; // same + _mat[1][2] = r02 * r10 - r00 * r12; // same + _mat[1][3] = 0.0; + _mat[2][0] = r10 * r21 - r11 * r20; // Have already been divided by det + _mat[2][1] = r01 * r20 - r00 * r21; // same + _mat[2][2] = r00 * r11 - r01 * r10; // same + _mat[2][3] = 0.0; + _mat[3][3] = 1.0; + + // We no longer need the rxx or det variables anymore, so we can reuse them for whatever we want. But we will still + // rename them for the sake of clarity. + +#define d r22 + d = mat._mat[3][3]; + + if (osg::square(d - 1.0) > 1.0e-6) // Involves perspective, so we must + { // compute the full inverse + + Matrix_implementation TPinv; + _mat[3][0] = _mat[3][1] = _mat[3][2] = 0.0; + +#define px r00 +#define py r01 +#define pz r02 +#define one_over_s one_over_det +#define a r10 +#define b r11 +#define c r12 + + a = mat._mat[0][3]; + b = mat._mat[1][3]; + c = mat._mat[2][3]; + px = _mat[0][0] * a + _mat[0][1] * b + _mat[0][2] * c; + py = _mat[1][0] * a + _mat[1][1] * b + _mat[1][2] * c; + pz = _mat[2][0] * a + _mat[2][1] * b + _mat[2][2] * c; + +#undef a +#undef b +#undef c +#define tx r10 +#define ty r11 +#define tz r12 + + tx = mat._mat[3][0]; + ty = mat._mat[3][1]; + tz = mat._mat[3][2]; + one_over_s = 1.0 / (d - (tx * px + ty * py + tz * pz)); + + tx *= one_over_s; + ty *= one_over_s; + tz *= one_over_s; // Reduces number of calculations later on + + // Compute inverse of trans*corr + TPinv._mat[0][0] = tx * px + 1.0; + TPinv._mat[0][1] = ty * px; + TPinv._mat[0][2] = tz * px; + TPinv._mat[0][3] = -px * one_over_s; + TPinv._mat[1][0] = tx * py; + TPinv._mat[1][1] = ty * py + 1.0; + TPinv._mat[1][2] = tz * py; + TPinv._mat[1][3] = -py * one_over_s; + TPinv._mat[2][0] = tx * pz; + TPinv._mat[2][1] = ty * pz; + TPinv._mat[2][2] = tz * pz + 1.0; + TPinv._mat[2][3] = -pz * one_over_s; + TPinv._mat[3][0] = -tx; + TPinv._mat[3][1] = -ty; + TPinv._mat[3][2] = -tz; + TPinv._mat[3][3] = one_over_s; + + preMult(TPinv); // Finish computing full inverse of mat + +#undef px +#undef py +#undef pz +#undef one_over_s +#undef d + } + else // Rightmost column is [0; 0; 0; 1] so it can be ignored + { + tx = mat._mat[3][0]; + ty = mat._mat[3][1]; + tz = mat._mat[3][2]; + + // Compute translation components of mat' + _mat[3][0] = -(tx * _mat[0][0] + ty * _mat[1][0] + tz * _mat[2][0]); + _mat[3][1] = -(tx * _mat[0][1] + ty * _mat[1][1] + tz * _mat[2][1]); + _mat[3][2] = -(tx * _mat[0][2] + ty * _mat[1][2] + tz * _mat[2][2]); + +#undef tx +#undef ty +#undef tz + } + + return true; +} + +template +inline T SGL_ABS(T a) +{ + return (a >= 0 ? a : -a); +} + +#ifndef SGL_SWAP +#define SGL_SWAP(a, b, temp) ((temp) = (a), (a) = (b), (b) = (temp)) +#endif + +bool Matrix_implementation::transpose(const Matrix_implementation& mat) +{ + if (&mat == this) + { + Matrix_implementation tm(mat); + return transpose(tm); + } + _mat[0][0] = mat._mat[0][0]; + _mat[0][1] = mat._mat[1][0]; + _mat[0][2] = mat._mat[2][0]; + _mat[0][3] = mat._mat[3][0]; + _mat[1][0] = mat._mat[0][1]; + _mat[1][1] = mat._mat[1][1]; + _mat[1][2] = mat._mat[2][1]; + _mat[1][3] = mat._mat[3][1]; + _mat[2][0] = mat._mat[0][2]; + _mat[2][1] = mat._mat[1][2]; + _mat[2][2] = mat._mat[2][2]; + _mat[2][3] = mat._mat[3][2]; + _mat[3][0] = mat._mat[0][3]; + _mat[3][1] = mat._mat[1][3]; + _mat[3][2] = mat._mat[2][3]; + _mat[3][3] = mat._mat[3][3]; + return true; +} + +bool Matrix_implementation::transpose3x3(const Matrix_implementation& mat) +{ + if (&mat == this) + { + Matrix_implementation tm(mat); + return transpose3x3(tm); + } + _mat[0][0] = mat._mat[0][0]; + _mat[0][1] = mat._mat[1][0]; + _mat[0][2] = mat._mat[2][0]; + _mat[1][0] = mat._mat[0][1]; + _mat[1][1] = mat._mat[1][1]; + _mat[1][2] = mat._mat[2][1]; + _mat[2][0] = mat._mat[0][2]; + _mat[2][1] = mat._mat[1][2]; + _mat[2][2] = mat._mat[2][2]; + + return true; +} + +bool Matrix_implementation::invert_4x4(const Matrix_implementation& mat) +{ + if (&mat == this) + { + Matrix_implementation tm(mat); + return invert_4x4(tm); + } + + unsigned int indxc[4], indxr[4], ipiv[4]; + unsigned int i, j, k, l, ll; + unsigned int icol = 0; + unsigned int irow = 0; + double temp, pivinv, dum, big; + + // copy in place this may be unnecessary + *this = mat; + + for (j = 0; j < 4; j++) + ipiv[j] = 0; + + for (i = 0; i < 4; i++) + { + big = 0.0; + for (j = 0; j < 4; ++j) + if (ipiv[j] != 1) + for (k = 0; k < 4; k++) + { + if (ipiv[k] == 0) + { + if (SGL_ABS(operator()(j, k)) >= big) + { + big = SGL_ABS(operator()(j, k)); + irow = j; + icol = k; + } + } + else if (ipiv[k] > 1) + return false; + } + ++(ipiv[icol]); + if (irow != icol) + for (l = 0; l < 4; ++l) + SGL_SWAP(operator()(irow, l), operator()(icol, l), temp); + + indxr[i] = irow; + indxc[i] = icol; + if (operator()(icol, icol) == 0) + return false; + + pivinv = 1.0 / operator()(icol, icol); + operator()(icol, icol) = 1; + for (l = 0; l < 4; ++l) + operator()(icol, l) *= pivinv; + for (ll = 0; ll < 4; ++ll) + if (ll != icol) + { + dum = operator()(ll, icol); + operator()(ll, icol) = 0; + for (l = 0; l < 4; ++l) + operator()(ll, l) -= operator()(icol, l) * dum; + } + } + for (int lx = 4; lx > 0; --lx) + { + if (indxr[lx - 1] != indxc[lx - 1]) + for (k = 0; k < 4; k++) + SGL_SWAP(operator()(k, indxr[lx - 1]), operator()(k, indxc[lx - 1]), temp); + } + + return true; +} + +void Matrix_implementation::makeOrtho(double left, double right, double bottom, double top, double zNear, double zFar) +{ + // note transpose of Matrix_implementation wr.t OpenGL documentation, since the OSG use post multiplication rather + // than pre. + double tx = -(right + left) / (right - left); + double ty = -(top + bottom) / (top - bottom); + double tz = -(zFar + zNear) / (zFar - zNear); + SET_ROW(0, 2.0 / (right - left), 0.0, 0.0, 0.0) + SET_ROW(1, 0.0, 2.0 / (top - bottom), 0.0, 0.0) + SET_ROW(2, 0.0, 0.0, -2.0 / (zFar - zNear), 0.0) + SET_ROW(3, tx, ty, tz, 1.0) +} + +bool Matrix_implementation::getOrtho(Matrix_implementation::value_type& left, Matrix_implementation::value_type& right, + Matrix_implementation::value_type& bottom, Matrix_implementation::value_type& top, + Matrix_implementation::value_type& zNear, Matrix_implementation::value_type& zFar) const +{ + if (_mat[0][3] != 0.0 || _mat[1][3] != 0.0 || _mat[2][3] != 0.0 || _mat[3][3] != 1.0) + return false; + + zNear = (_mat[3][2] + 1.0) / _mat[2][2]; + zFar = (_mat[3][2] - 1.0) / _mat[2][2]; + + left = -(1.0 + _mat[3][0]) / _mat[0][0]; + right = (1.0 - _mat[3][0]) / _mat[0][0]; + + bottom = -(1.0 + _mat[3][1]) / _mat[1][1]; + top = (1.0 - _mat[3][1]) / _mat[1][1]; + + return true; +} + +bool Matrix_implementation::getOrtho(Matrix_implementation::other_value_type& left, + Matrix_implementation::other_value_type& right, Matrix_implementation::other_value_type& bottom, + Matrix_implementation::other_value_type& top, Matrix_implementation::other_value_type& zNear, + Matrix_implementation::other_value_type& zFar) const +{ + Matrix_implementation::value_type temp_left, temp_right, temp_bottom, temp_top, temp_zNear, temp_zFar; + if (getOrtho(temp_left, temp_right, temp_bottom, temp_top, temp_zNear, temp_zFar)) + { + left = temp_left; + right = temp_right; + bottom = temp_bottom; + top = temp_top; + zNear = temp_zNear; + zFar = temp_zFar; + return true; + } + else + { + return false; + } +} + +void Matrix_implementation::makeFrustum(double left, double right, double bottom, double top, double zNear, double zFar) +{ + // note transpose of Matrix_implementation wr.t OpenGL documentation, since the OSG use post multiplication rather + // than pre. + double A = (right + left) / (right - left); + double B = (top + bottom) / (top - bottom); + double C = (fabs(zFar) > DBL_MAX) ? -1. : -(zFar + zNear) / (zFar - zNear); + double D = (fabs(zFar) > DBL_MAX) ? -2. * zNear : -2.0 * zFar * zNear / (zFar - zNear); + SET_ROW(0, 2.0 * zNear / (right - left), 0.0, 0.0, 0.0) + SET_ROW(1, 0.0, 2.0 * zNear / (top - bottom), 0.0, 0.0) + SET_ROW(2, A, B, C, -1.0) + SET_ROW(3, 0.0, 0.0, D, 0.0) +} + +bool Matrix_implementation::getFrustum(Matrix_implementation::value_type& left, + Matrix_implementation::value_type& right, Matrix_implementation::value_type& bottom, + Matrix_implementation::value_type& top, Matrix_implementation::value_type& zNear, + Matrix_implementation::value_type& zFar) const +{ + if (_mat[0][3] != 0.0 || _mat[1][3] != 0.0 || _mat[2][3] != -1.0 || _mat[3][3] != 0.0) + return false; + + // note: near and far must be used inside this method instead of zNear and zFar + // because zNear and zFar are references and they may point to the same variable. + Matrix_implementation::value_type temp_near = _mat[3][2] / (_mat[2][2] - 1.0); + Matrix_implementation::value_type temp_far = _mat[3][2] / (1.0 + _mat[2][2]); + + left = temp_near * (_mat[2][0] - 1.0) / _mat[0][0]; + right = temp_near * (1.0 + _mat[2][0]) / _mat[0][0]; + + top = temp_near * (1.0 + _mat[2][1]) / _mat[1][1]; + bottom = temp_near * (_mat[2][1] - 1.0) / _mat[1][1]; + + zNear = temp_near; + zFar = temp_far; + + return true; +} + +bool Matrix_implementation::getFrustum(Matrix_implementation::other_value_type& left, + Matrix_implementation::other_value_type& right, Matrix_implementation::other_value_type& bottom, + Matrix_implementation::other_value_type& top, Matrix_implementation::other_value_type& zNear, + Matrix_implementation::other_value_type& zFar) const +{ + Matrix_implementation::value_type temp_left, temp_right, temp_bottom, temp_top, temp_zNear, temp_zFar; + if (getFrustum(temp_left, temp_right, temp_bottom, temp_top, temp_zNear, temp_zFar)) + { + left = temp_left; + right = temp_right; + bottom = temp_bottom; + top = temp_top; + zNear = temp_zNear; + zFar = temp_zFar; + return true; + } + else + { + return false; + } +} + +void Matrix_implementation::makePerspective(double fovy, double aspectRatio, double zNear, double zFar) +{ + // calculate the appropriate left, right etc. + double tan_fovy = tan(DegreesToRadians(fovy * 0.5)); + double right = tan_fovy * aspectRatio * zNear; + double left = -right; + double top = tan_fovy * zNear; + double bottom = -top; + makeFrustum(left, right, bottom, top, zNear, zFar); +} + +bool Matrix_implementation::getPerspective(Matrix_implementation::value_type& fovy, + Matrix_implementation::value_type& aspectRatio, Matrix_implementation::value_type& zNear, + Matrix_implementation::value_type& zFar) const +{ + Matrix_implementation::value_type right = 0.0; + Matrix_implementation::value_type left = 0.0; + Matrix_implementation::value_type top = 0.0; + Matrix_implementation::value_type bottom = 0.0; + + // note: near and far must be used inside this method instead of zNear and zFar + // because zNear and zFar are references and they may point to the same variable. + Matrix_implementation::value_type temp_near = 0.0; + Matrix_implementation::value_type temp_far = 0.0; + + // get frustum and compute results + bool r = getFrustum(left, right, bottom, top, temp_near, temp_far); + if (r) + { + fovy = RadiansToDegrees(atan(top / temp_near) - atan(bottom / temp_near)); + aspectRatio = (right - left) / (top - bottom); + } + zNear = temp_near; + zFar = temp_far; + return r; +} + +bool Matrix_implementation::getPerspective(Matrix_implementation::other_value_type& fovy, + Matrix_implementation::other_value_type& aspectRatio, Matrix_implementation::other_value_type& zNear, + Matrix_implementation::other_value_type& zFar) const +{ + Matrix_implementation::value_type temp_fovy, temp_aspectRatio, temp_zNear, temp_zFar; + if (getPerspective(temp_fovy, temp_aspectRatio, temp_zNear, temp_zFar)) + { + fovy = temp_fovy; + aspectRatio = temp_aspectRatio; + zNear = temp_zNear; + zFar = temp_zFar; + return true; + } + else + { + return false; + } +} + +void Matrix_implementation::makeLookAt(const Vec3d& eye, const Vec3d& center, const Vec3d& up) +{ + Vec3d f(center - eye); + f.normalize(); + Vec3d s(f ^ up); + s.normalize(); + Vec3d u(s ^ f); + u.normalize(); + + set(s[0], u[0], -f[0], 0.0, s[1], u[1], -f[1], 0.0, s[2], u[2], -f[2], 0.0, 0.0, 0.0, 0.0, 1.0); + + preMultTranslate(-eye); +} + +void Matrix_implementation::getLookAt(Vec3f& eye, Vec3f& center, Vec3f& up, value_type lookDistance) const +{ + Matrix_implementation inv; + inv.invert(*this); + + // note: e and c variables must be used inside this method instead of eye and center + // because eye and center are references and they may point to the same variable. + Vec3f e = osg::Vec3f(0.0, 0.0, 0.0) * inv; + up = transform3x3(*this, osg::Vec3f(0.0, 1.0, 0.0)); + Vec3f c = transform3x3(*this, osg::Vec3f(0.0, 0.0, -1)); + c.normalize(); + c = e + c * lookDistance; + + // assign the results + eye = e; + center = c; +} + +void Matrix_implementation::getLookAt(Vec3d& eye, Vec3d& center, Vec3d& up, value_type lookDistance) const +{ + Matrix_implementation inv; + inv.invert(*this); + + // note: e and c variables must be used inside this method instead of eye and center + // because eye and center are references and they may point to the same variable. + Vec3d e = osg::Vec3d(0.0, 0.0, 0.0) * inv; + up = transform3x3(*this, osg::Vec3d(0.0, 1.0, 0.0)); + Vec3d c = transform3x3(*this, osg::Vec3d(0.0, 0.0, -1)); + c.normalize(); + c = e + c * lookDistance; + + // assign the results + eye = e; + center = c; +} + +#undef SET_ROW diff --git a/components/vsgadapters/osg/Matrixd.cpp b/components/vsgadapters/osg/Matrixd.cpp new file mode 100644 index 00000000000..b19da1bef4e --- /dev/null +++ b/components/vsgadapters/osg/Matrixd.cpp @@ -0,0 +1,37 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. + */ + +#include +#include + +// specialise Matrix_implementaiton to be Matrixd +#define Matrix_implementation Matrixd + +osg::Matrixd::Matrixd(const osg::Matrixf& mat) +{ + set(mat.ptr()); +} + +osg::Matrixd& osg::Matrixd::operator=(const osg::Matrixf& rhs) +{ + set(rhs.ptr()); + return *this; +} + +void osg::Matrixd::set(const osg::Matrixf& rhs) +{ + set(rhs.ptr()); +} + +// now compile up Matrix via Matrix_implementation +#include "Matrix_implementation.cpp" diff --git a/components/vsgadapters/osg/Matrixf.cpp b/components/vsgadapters/osg/Matrixf.cpp new file mode 100644 index 00000000000..fc84a0d7207 --- /dev/null +++ b/components/vsgadapters/osg/Matrixf.cpp @@ -0,0 +1,37 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. + */ + +#include +#include + +// specialise Matrix_implementaiton to be Matrixf +#define Matrix_implementation Matrixf + +osg::Matrixf::Matrixf(const osg::Matrixd& mat) +{ + set(mat.ptr()); +} + +osg::Matrixf& osg::Matrixf::operator=(const osg::Matrixd& rhs) +{ + set(rhs.ptr()); + return *this; +} + +void osg::Matrixf::set(const osg::Matrixd& rhs) +{ + set(rhs.ptr()); +} + +// now compile up Matrix via Matrix_implementation +#include "Matrix_implementation.cpp" diff --git a/components/vsgadapters/osg/Quat.cpp b/components/vsgadapters/osg/Quat.cpp new file mode 100644 index 00000000000..24dc8fe5fa8 --- /dev/null +++ b/components/vsgadapters/osg/Quat.cpp @@ -0,0 +1,351 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. + */ +#include +#include +#include +#include +// #include + +#include + +/// Good introductions to Quaternions at: +/// http://www.gamasutra.com/features/programming/19980703/quaternions_01.htm +/// http://mathworld.wolfram.com/Quaternion.html + +using namespace osg; + +void Quat::set(const Matrixf& matrix) +{ + *this = matrix.getRotate(); +} + +void Quat::set(const Matrixd& matrix) +{ + *this = matrix.getRotate(); +} + +void Quat::get(Matrixf& matrix) const +{ + matrix.makeRotate(*this); +} + +void Quat::get(Matrixd& matrix) const +{ + matrix.makeRotate(*this); +} + +/// Set the elements of the Quat to represent a rotation of angle +/// (radians) around the axis (x,y,z) +void Quat::makeRotate(value_type angle, value_type x, value_type y, value_type z) +{ + const value_type epsilon = 0.0000001; + + value_type length = sqrt(x * x + y * y + z * z); + if (length < epsilon) + { + // ~zero length axis, so reset rotation to zero. + *this = Quat(); + return; + } + + value_type inversenorm = 1.0 / length; + value_type coshalfangle = cos(0.5 * angle); + value_type sinhalfangle = sin(0.5 * angle); + + _v[0] = x * sinhalfangle * inversenorm; + _v[1] = y * sinhalfangle * inversenorm; + _v[2] = z * sinhalfangle * inversenorm; + _v[3] = coshalfangle; +} + +void Quat::makeRotate(value_type angle, const Vec3f& vec) +{ + makeRotate(angle, vec[0], vec[1], vec[2]); +} +void Quat::makeRotate(value_type angle, const Vec3d& vec) +{ + makeRotate(angle, vec[0], vec[1], vec[2]); +} + +void Quat::makeRotate( + value_type angle1, const Vec3f& axis1, value_type angle2, const Vec3f& axis2, value_type angle3, const Vec3f& axis3) +{ + makeRotate(angle1, Vec3d(axis1), angle2, Vec3d(axis2), angle3, Vec3d(axis3)); +} + +void Quat::makeRotate( + value_type angle1, const Vec3d& axis1, value_type angle2, const Vec3d& axis2, value_type angle3, const Vec3d& axis3) +{ + Quat q1; + q1.makeRotate(angle1, axis1); + Quat q2; + q2.makeRotate(angle2, axis2); + Quat q3; + q3.makeRotate(angle3, axis3); + + *this = q1 * q2 * q3; +} + +void Quat::makeRotate(const Vec3f& from, const Vec3f& to) +{ + makeRotate(Vec3d(from), Vec3d(to)); +} + +/** Make a rotation Quat which will rotate vec1 to vec2 + +This routine uses only fast geometric transforms, without costly acos/sin computations. +It's exact, fast, and with less degenerate cases than the acos/sin method. + +For an explanation of the math used, you may see for example: +http://logiciels.cnes.fr/MARMOTTES/marmottes-mathematique.pdf + +@note This is the rotation with shortest angle, which is the one equivalent to the +acos/sin transform method. Other rotations exists, for example to additionally keep +a local horizontal attitude. + +@author Nicolas Brodu +*/ +void Quat::makeRotate(const Vec3d& from, const Vec3d& to) +{ + + // This routine takes any vector as argument but normalized + // vectors are necessary, if only for computing the dot product. + // Too bad the API is that generic, it leads to performance loss. + // Even in the case the 2 vectors are not normalized but same length, + // the sqrt could be shared, but we have no way to know beforehand + // at this point, while the caller may know. + // So, we have to test... in the hope of saving at least a sqrt + Vec3d sourceVector = from; + Vec3d targetVector = to; + + value_type fromLen2 = from.length2(); + value_type fromLen; + // normalize only when necessary, epsilon test + if ((fromLen2 < 1.0 - 1e-7) || (fromLen2 > 1.0 + 1e-7)) + { + fromLen = sqrt(fromLen2); + sourceVector /= fromLen; + } + else + fromLen = 1.0; + + value_type toLen2 = to.length2(); + // normalize only when necessary, epsilon test + if ((toLen2 < 1.0 - 1e-7) || (toLen2 > 1.0 + 1e-7)) + { + value_type toLen; + // re-use fromLen for case of mapping 2 vectors of the same length + if ((toLen2 > fromLen2 - 1e-7) && (toLen2 < fromLen2 + 1e-7)) + { + toLen = fromLen; + } + else + toLen = sqrt(toLen2); + targetVector /= toLen; + } + + // Now let's get into the real stuff + // Use "dot product plus one" as test as it can be re-used later on + double dotProdPlus1 = 1.0 + sourceVector * targetVector; + + // Check for degenerate case of full u-turn. Use epsilon for detection + if (dotProdPlus1 < 1e-7) + { + + // Get an orthogonal vector of the given vector + // in a plane with maximum vector coordinates. + // Then use it as quaternion axis with pi angle + // Trick is to realize one value at least is >0.6 for a normalized vector. + if (fabs(sourceVector.x()) < 0.6) + { + const double norm = sqrt(1.0 - sourceVector.x() * sourceVector.x()); + _v[0] = 0.0; + _v[1] = sourceVector.z() / norm; + _v[2] = -sourceVector.y() / norm; + _v[3] = 0.0; + } + else if (fabs(sourceVector.y()) < 0.6) + { + const double norm = sqrt(1.0 - sourceVector.y() * sourceVector.y()); + _v[0] = -sourceVector.z() / norm; + _v[1] = 0.0; + _v[2] = sourceVector.x() / norm; + _v[3] = 0.0; + } + else + { + const double norm = sqrt(1.0 - sourceVector.z() * sourceVector.z()); + _v[0] = sourceVector.y() / norm; + _v[1] = -sourceVector.x() / norm; + _v[2] = 0.0; + _v[3] = 0.0; + } + } + + else + { + // Find the shortest angle quaternion that transforms normalized vectors + // into one other. Formula is still valid when vectors are colinear + const double s = sqrt(0.5 * dotProdPlus1); + const Vec3d tmp = sourceVector ^ targetVector / (2.0 * s); + _v[0] = tmp.x(); + _v[1] = tmp.y(); + _v[2] = tmp.z(); + _v[3] = s; + } +} + +// Make a rotation Quat which will rotate vec1 to vec2 +// Generally take adot product to get the angle between these +// and then use a cross product to get the rotation axis +// Watch out for the two special cases of when the vectors +// are co-incident or opposite in direction. +void Quat::makeRotate_original(const Vec3d& from, const Vec3d& to) +{ + const value_type epsilon = 0.0000001; + + value_type length1 = from.length(); + value_type length2 = to.length(); + + // dot product vec1*vec2 + value_type cosangle = from * to / (length1 * length2); + + if (fabs(cosangle - 1) < epsilon) + { + // OSG_INFO<<"*** Quat::makeRotate(from,to) with near co-linear vectors, epsilon= + // "< epsilon) + { + omega = acos(cosomega); // 0 <= omega <= Pi (see man acos) + sinomega = sin(omega); // this sinomega should always be +ve so + // could try sinomega=sqrt(1-cosomega*cosomega) to avoid a sin()? + scale_from = sin((1.0 - t) * omega) / sinomega; + scale_to = sin(t * omega) / sinomega; + } + else + { + /* -------------------------------------------------- + The ends of the vectors are very close + we can use simple linear interpolation - no need + to worry about the "spherical" interpolation + -------------------------------------------------- */ + scale_from = 1.0 - t; + scale_to = t; + } + + *this = (from * scale_from) + (quatTo * scale_to); + + // so that we get a Vec4 +} diff --git a/components/vsgadapters/osgcompat.hpp b/components/vsgadapters/osgcompat.hpp new file mode 100644 index 00000000000..78db232993c --- /dev/null +++ b/components/vsgadapters/osgcompat.hpp @@ -0,0 +1,68 @@ +#ifndef VSGOPENMW_VSGADAPTERS_OSGCOMPAT_H +#define VSGOPENMW_VSGADAPTERS_OSGCOMPAT_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +// namespace +//{ +inline vsg::quat toVsg(const osg::Quat& quat) +{ + return vsg::quat(quat.x(), quat.y(), quat.z(), quat.w()); +} +inline vsg::vec2 toVsg(const osg::Vec2f& vec) +{ + return { vec.x(), vec.y() }; +} +inline vsg::vec3 toVsg(const osg::Vec3f& vec) +{ + return { vec.x(), vec.y(), vec.z() }; +} +inline vsg::vec4 toVsg(const osg::Vec4f& vec) +{ + return { vec.x(), vec.y(), vec.z(), vec.w() }; +} +inline vsg::dvec3 toVsg(const osg::Vec3d& vec) +{ + return { vec.x(), vec.y(), vec.z() }; +} +inline float toVsg(float val) +{ + return val; +} +template +vsg::ref_ptr copyArray(const Source& vec) +{ + vsg::ref_ptr array = Array::create(vec.size()); + std::memcpy(array->data(), &vec.front(), vec.size() * sizeof(typename Source::value_type)); + return array; +} + +inline osg::Quat toOsg(const vsg::quat& quat) +{ + return osg::Quat(quat.x, quat.y, quat.z, quat.w); +} +inline osg::Vec3f toOsg(const vsg::vec3& vec) +{ + return { vec.x, vec.y, vec.z }; +} +inline osg::Vec4f toOsg(const vsg::vec4& vec) +{ + return { vec.x, vec.y, vec.z, vec.w }; +} +inline osg::Matrixf toOsg(vsg::mat4 m) +{ + osg::Matrixf osg; + std::memcpy(&osg, &m, sizeof(m)); + return osg; +} +//} + +#endif diff --git a/components/vsgadapters/sdl/surface.cpp b/components/vsgadapters/sdl/surface.cpp new file mode 100644 index 00000000000..53fec942f33 --- /dev/null +++ b/components/vsgadapters/sdl/surface.cpp @@ -0,0 +1,77 @@ +#include "surface.hpp" + +#include +#include + +namespace vsgAdapters::sdl +{ + void fillSurface(SDL_Surface* surface, vsg::ref_ptr data) + { + int width = data->width(); + int height = data->height(); + if (surface->w != width || surface->h != height) + throw std::runtime_error("surface->w != width || surface->h != height"); + + auto srcFormat = data->properties.format; + auto pixelOrder = SDL_PIXELORDER(surface->format->format); + if (surface->format->BytesPerPixel == data->valueSize() && ( + ((pixelOrder == SDL_PACKEDORDER_ABGR || pixelOrder == SDL_PACKEDORDER_XBGR) && srcFormat == VK_FORMAT_R8G8B8A8_UNORM) + || ((pixelOrder == SDL_PACKEDORDER_ARGB || pixelOrder == SDL_PACKEDORDER_XRGB) && srcFormat == VK_FORMAT_B8G8R8A8_UNORM))) + { + auto* pDst = (Uint8*)surface->pixels; + auto* pSrc = (Uint8*)data->dataPointer(); + int srcPitch = data->valueSize() * width; + if (srcPitch == surface->pitch) + std::memcpy(pDst, pSrc, data->dataSize()); + else + { + for (int row = 0; row < height; ++row) + { + std::memcpy(pDst, pSrc, srcPitch); + pSrc += srcPitch; + pDst += surface->pitch; + } + } + } + else + { + for (int x = 0; x < width; ++x) + { + for (int y = 0; y < height; ++y) + { + std::array clr; + auto* pixel = static_cast(data->dataPointer(y * width + x)); + switch (srcFormat) + { + case VK_FORMAT_R8G8B8A8_UNORM: + { + clr = { *pixel++, *pixel++, *pixel++, *pixel }; + break; + } case VK_FORMAT_B8G8R8A8_UNORM: + { + clr = { pixel[2], pixel[1], pixel[0], pixel[3] }; + break; + } + default: + throw std::runtime_error("!SDL_MapRGBA(VkFormat=" + std::to_string(data->getLayout().format) + ")"); + } + int bpp = surface->format->BytesPerPixel; + auto* p = (Uint8*)surface->pixels + y * surface->pitch + x * bpp; + *(Uint32*)(p) = SDL_MapRGBA(surface->format, clr[0], clr[1], clr[2], clr[3]); + } + } + } + } + + SurfaceUniquePtr createSurface(vsg::ref_ptr data) + { + int width = data->width(); + int height = data->height(); + SDL_Surface* surface + = SDL_CreateRGBSurface(0, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); + if (!surface) + throw std::runtime_error(SDL_GetError()); + fillSurface(surface, data); + return SurfaceUniquePtr(surface, SDL_FreeSurface); + } +} diff --git a/components/vsgadapters/sdl/surface.hpp b/components/vsgadapters/sdl/surface.hpp new file mode 100644 index 00000000000..bea760866a3 --- /dev/null +++ b/components/vsgadapters/sdl/surface.hpp @@ -0,0 +1,19 @@ +#ifndef VSGOPENMW_VSGADAPTERS_SDL_SURFACE_H +#define VSGOPENMW_VSGADAPTERS_SDL_SURFACE_H + +#include + +#include + +#include + +namespace vsgAdapters::sdl +{ + typedef std::unique_ptr SurfaceUniquePtr; + + void fillSurface(SDL_Surface* surface, vsg::ref_ptr data); + + SurfaceUniquePtr createSurface(vsg::ref_ptr data); +} + +#endif diff --git a/components/vsgadapters/sdl/window.cpp b/components/vsgadapters/sdl/window.cpp new file mode 100644 index 00000000000..b02354f2ad2 --- /dev/null +++ b/components/vsgadapters/sdl/window.cpp @@ -0,0 +1,30 @@ +#include "window.hpp" + +#include + +namespace vsgAdapters::sdl +{ + Window::Window(SDL_Window* window, vsg::ref_ptr traits) + : vsg::WindowAdapter(vsg::ref_ptr(), traits) + , mWindow(window) + { + unsigned int extensionCount; + const char** extensionNames = 0; + SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr); + extensionNames = new const char*[extensionCount]; + SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames); + + for (unsigned int i = 0; i < extensionCount; ++i) + _traits->instanceExtensionNames.push_back(extensionNames[i]); + windowVisible = true; + windowValid = true; + } + + void Window::_initSurface() + { + VkSurfaceKHR surface; + if (!SDL_Vulkan_CreateSurface(mWindow, _instance->vk(), &surface)) + throw std::runtime_error("SDL_Vulkan_CreateSurface failed"); + _surface = vsg::Surface::create(surface, _instance); + } +} diff --git a/components/vsgadapters/sdl/window.hpp b/components/vsgadapters/sdl/window.hpp new file mode 100644 index 00000000000..7135927f6db --- /dev/null +++ b/components/vsgadapters/sdl/window.hpp @@ -0,0 +1,23 @@ +#ifndef VSGOPENMW_VSGADAPTERS_SDL_WINDOW_H +#define VSGOPENMW_VSGADAPTERS_SDL_WINDOW_H + +#include + +class SDL_Window; + +namespace vsgAdapters::sdl +{ + // Renders into an existing SDL_Window created with SDL_WINDOW_VULKAN capability. + class Window : public vsg::WindowAdapter + { + public: + Window(SDL_Window* window, vsg::ref_ptr traits); + const char* instanceExtensionSurfaceName() const override { return "VK_KHR_surface"; } + + protected: + void _initSurface() override; + SDL_Window* mWindow; + }; +} + +#endif diff --git a/components/vsgadapters/vfs.cpp b/components/vsgadapters/vfs.cpp new file mode 100644 index 00000000000..737864bece6 --- /dev/null +++ b/components/vsgadapters/vfs.cpp @@ -0,0 +1,31 @@ +#include "vfs.hpp" + +#include + +#include + +namespace vsgAdapters +{ + vsg::ref_ptr vfs::read(const vsg::Path& filename, vsg::ref_ptr options) const + { + auto readImpl = [this] (const vsg::Path& filename, vsg::ref_ptr options) -> vsg::ref_ptr + { + // ; + if (!mVfs.exists(filename.c_str())) + return {}; + + auto stream = mVfs./*search*/ get(filename.c_str()); + auto ext = vsg::lowerCaseFileExtension(filename); + + auto extOptions = vsg::Options::create(*options); + extOptions->extensionHint = ext; + extOptions->setValue("filename", std::string(filename)); + + return CompositeReaderWriter::read(*stream, extOptions); + }; + + if (options->findFileCallback) + return readImpl(options->findFileCallback(filename, options), options); + return readImpl(filename, options); + } +} diff --git a/components/vsgadapters/vfs.hpp b/components/vsgadapters/vfs.hpp new file mode 100644 index 00000000000..ceafca9e23b --- /dev/null +++ b/components/vsgadapters/vfs.hpp @@ -0,0 +1,31 @@ +#ifndef VSGOPENMW_VSGADAPTERS_VFS_H +#define VSGOPENMW_VSGADAPTERS_VFS_H + +#include + +namespace VFS +{ + class Manager; +} +namespace vsgAdapters +{ + /* + * Adapts read(Path) to read(istream) through VFS. + */ + class vfs : public vsg::CompositeReaderWriter + { + public: + vfs(const VFS::Manager& vfs, const vsg::ReaderWriters& children = {}) + : mVfs(vfs) + { + readerWriters = children; + } + vsg::ref_ptr read( + const vsg::Path& filename, vsg::ref_ptr options = {}) const override; + + private: + const VFS::Manager& mVfs; + }; +} + +#endif