diff options
Diffstat (limited to 'src/plugins')
54 files changed, 6432 insertions, 0 deletions
diff --git a/src/plugins/imageformats/imageformats.pro b/src/plugins/imageformats/imageformats.pro new file mode 100644 index 000000000..c0ea5d84c --- /dev/null +++ b/src/plugins/imageformats/imageformats.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = tga diff --git a/src/plugins/imageformats/tga/main.cpp b/src/plugins/imageformats/tga/main.cpp new file mode 100644 index 000000000..f3873ee82 --- /dev/null +++ b/src/plugins/imageformats/tga/main.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qimageiohandler.h> +#include <qdebug.h> + +#include "qtgahandler.h" + +QT_BEGIN_NAMESPACE + +class QTgaPlugin : public QImageIOPlugin +{ +public: + Capabilities capabilities(QIODevice * device, const QByteArray & format) const; + QImageIOHandler * create(QIODevice * device, const QByteArray & format = QByteArray()) const; + QStringList keys() const; +}; + +QImageIOPlugin::Capabilities QTgaPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == "tga" || format == "TGA") + return Capabilities(CanRead); + if (!format.isEmpty()) + return 0; + if (!device->isOpen()) + return 0; + + Capabilities cap; + if (device->isReadable() && QTgaHandler::canRead(device)) + cap |= CanRead; + return cap; +} + +QImageIOHandler* QTgaPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *tgaHandler = new QTgaHandler(); + tgaHandler->setDevice(device); + tgaHandler->setFormat(format); + return tgaHandler; +} + +QStringList QTgaPlugin::keys() const +{ + return QStringList() << QLatin1String("tga") << QLatin1String("TGA"); +} + +Q_EXPORT_STATIC_PLUGIN(QTgaPlugin) +Q_EXPORT_PLUGIN2(qtga, QTgaPlugin) + +QT_END_NAMESPACE + diff --git a/src/plugins/imageformats/tga/qtgafile.cpp b/src/plugins/imageformats/tga/qtgafile.cpp new file mode 100644 index 000000000..f5f45c71f --- /dev/null +++ b/src/plugins/imageformats/tga/qtgafile.cpp @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtgafile.h" + +#include <QtCore/qiodevice.h> +#include <QtCore/qdebug.h> +#include <QtCore/qdatetime.h> + +struct TgaReader +{ + virtual ~TgaReader() {} + virtual QRgb operator()(QIODevice *s) const = 0; +}; + +struct Tga16Reader : public TgaReader +{ + ~Tga16Reader() {} + QRgb operator()(QIODevice *s) const + { + char ch1, ch2; + if (s->getChar(&ch1) && s->getChar(&ch2)) { + quint16 d = (int(ch1) & 0xFF) | ((int(ch2) & 0xFF) << 8); + QRgb result = (d & 0x8000) ? 0xFF000000 : 0x00000000; + result |= (d & 0x7C00 << 6) | (d & 0x03E0 << 3) | (d & 0x001F); + return result; + } else { + return 0; + } + } +}; + +struct Tga24Reader : public TgaReader +{ + QRgb operator()(QIODevice *s) const + { + char r, g, b; + if (s->getChar(&b) && s->getChar(&g) && s->getChar(&r)) + return qRgb(uchar(r), uchar(g), uchar(b)); + else + return 0; + } +}; + +struct Tga32Reader : public TgaReader +{ + QRgb operator()(QIODevice *s) const + { + char r, g, b, a; + if (s->getChar(&b) && s->getChar(&g) && s->getChar(&r) && s->getChar(&a)) + return qRgba(uchar(r), uchar(g), uchar(b), uchar(a)); + else + return 0; + } +}; + +/*! + \class QTgaFile + \since 4.8 + \internal + + File data container for a TrueVision Graphics format file. + + Format is as described here: + http://local.wasp.uwa.edu.au/~pbourke/dataformats/tga/ + http://netghost.narod.ru/gff2/graphics/summary/tga.htm + + Usage is: + \code + QTgaFile tga(myFile); + QImage tgaImage; + if (tga.isValid()) + tgaImage = tga.readImage(); + \endcode + + The class is designed to handle sequential and non-sequential + sources, so during construction the mHeader is read. Then during + the readImage() call the rest of the data is read. + + After passing myFile to the constructor, if the QIODevice *myFile + is read, or has seek() called, the results are undefined - so don't + do that. +*/ + +/*! + Construct a new QTgaFile object getting data from \a device. + + The object does not take ownership of the \a device, but until the + object is destroyed do not do any non-const operations, eg seek or + read on the device. +*/ +QTgaFile::QTgaFile(QIODevice *device) + : mDevice(device) +{ + ::memset(mHeader, 0, HeaderSize); + if (!mDevice->isReadable()) + { + mErrorMessage = QObject::tr("Could not read image data"); + return; + } + if (mDevice->isSequential()) + { + mErrorMessage = QObject::tr("Sequential device (eg socket) for image read not supported"); + return; + } + if (!mDevice->seek(0)) + { + mErrorMessage = QObject::tr("Seek file/device for image read failed"); + return; + } + int bytes = device->read((char*)mHeader, HeaderSize); + if (bytes != HeaderSize) + { + mErrorMessage = QObject::tr("Image mHeader read failed"); + return; + } + if (mHeader[ImageType] != 2) + { + // TODO: should support other image types + mErrorMessage = QObject::tr("Image type not supported"); + return; + } +} + +/*! + \internal + Destroy the device, recovering any resources. +*/ +QTgaFile::~QTgaFile() +{ +} + +/*! + \internal + Reads an image file from the QTgaFile's device, and returns it. + + This method seeks to the absolute position of the image data in the file, + so no assumptions are made about where the devices read pointer is when this + method is called. For this reason only random access devices are supported. + + If the constructor completed successfully, such that isValid() returns true, + then this method is likely to succeed, unless the file is somehow corrupted. + + In the case that the read fails, the QImage returned will be null, such that + QImage::isNull() will be true. +*/ +QImage QTgaFile::readImage() +{ + if (!isValid()) + return QImage(); + + int offset = mHeader[IdLength]; // Mostly always zero + + // Even in TrueColor files a color pallette may be present + if (mHeader[ColorMapType] == 1) + offset += littleEndianInt(&mHeader[CMapLength]) * littleEndianInt(&mHeader[CMapDepth]); + + mDevice->seek(HeaderSize + offset); + + char dummy; + for (int i = 0; i < offset; ++i) + mDevice->getChar(&dummy); + + int bitsPerPixel = mHeader[PixelDepth]; + int imageWidth = width(); + int imageHeight = height(); + + unsigned char desc = mHeader[ImageDescriptor]; + //unsigned char xCorner = desc & 0x10; // 0 = left, 1 = right + unsigned char yCorner = desc & 0x20; // 0 = lower, 1 = upper + + QImage im(imageWidth, imageHeight, QImage::Format_ARGB32); + TgaReader *reader = 0; + if (bitsPerPixel == 16) + reader = new Tga16Reader(); + else if (bitsPerPixel == 24) + reader = new Tga24Reader(); + else if (bitsPerPixel == 32) + reader = new Tga32Reader(); + TgaReader &read = *reader; + + // For now only deal with yCorner, since no one uses xCorner == 1 + // Also this is upside down, since Qt has the origin flipped + if (yCorner) + { + for (int y = 0; y < imageHeight; ++y) + for (int x = 0; x < imageWidth; ++x) + im.setPixel(x, y, read(mDevice)); + } + else + { + for (int y = imageHeight - 1; y >= 0; --y) + for (int x = 0; x < imageWidth; ++x) + im.setPixel(x, y, read(mDevice)); + } + + // TODO: add processing of TGA extension information - ie TGA 2.0 files + return im; +} diff --git a/src/plugins/imageformats/tga/qtgafile.h b/src/plugins/imageformats/tga/qtgafile.h new file mode 100644 index 000000000..ec78f73c2 --- /dev/null +++ b/src/plugins/imageformats/tga/qtgafile.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTGAFILE_H +#define QTGAFILE_H + +#include <QtGui/qcolor.h> +#include <QtGui/qimage.h> + +class QIODevice; + +class QTgaFile +{ +public: + enum Compression { + NoCompression = 0, + RleCompression = 1 + }; + + enum HeaderOffset { + IdLength = 0, /* 00h Size of Image ID field */ + ColorMapType = 1, /* 01h Color map type */ + ImageType = 2, /* 02h Image type code */ + CMapStart = 3, /* 03h Color map origin */ + CMapLength = 5, /* 05h Color map length */ + CMapDepth = 7, /* 07h Depth of color map entries */ + XOffset = 8, /* 08h X origin of image */ + YOffset = 10, /* 0Ah Y origin of image */ + Width = 12, /* 0Ch Width of image */ + Height = 14, /* 0Eh Height of image */ + PixelDepth = 16, /* 10h Image pixel size */ + ImageDescriptor = 17, /* 11h Image descriptor byte */ + HeaderSize = 18 + }; + + QTgaFile(QIODevice *); + ~QTgaFile(); + + inline bool isValid() const; + inline QString errorMessage() const; + QImage readImage(); + inline int xOffset() const; + inline int yOffset() const; + inline int width() const; + inline int height() const; + inline QSize size() const; + inline Compression compression() const; + +private: + static inline quint16 littleEndianInt(const unsigned char *d); + + QString mErrorMessage; + unsigned char mHeader[HeaderSize]; + QIODevice *mDevice; +}; + +inline bool QTgaFile::isValid() const +{ + return mErrorMessage.isEmpty(); +} + +inline QString QTgaFile::errorMessage() const +{ + return mErrorMessage; +} + +/*! + \internal + Returns the integer encoded in the two little endian bytes at \a d. +*/ +inline quint16 QTgaFile::littleEndianInt(const unsigned char *d) +{ + return d[0] + d[1] * 256; +} + +inline int QTgaFile::xOffset() const +{ + return littleEndianInt(&mHeader[XOffset]); +} + +inline int QTgaFile::yOffset() const +{ + return littleEndianInt(&mHeader[YOffset]); +} + +inline int QTgaFile::width() const +{ + return littleEndianInt(&mHeader[Width]); +} + +inline int QTgaFile::height() const +{ + return littleEndianInt(&mHeader[Height]); +} + +inline QSize QTgaFile::size() const +{ + return QSize(width(), height()); +} + +inline QTgaFile::Compression QTgaFile::compression() const +{ + // TODO: for now, only handle type 2 files, with no color table + // and no compression + return NoCompression; +} + +#endif // QTGAFILE_H diff --git a/src/plugins/imageformats/tga/qtgahandler.cpp b/src/plugins/imageformats/tga/qtgahandler.cpp new file mode 100644 index 000000000..90dcd4321 --- /dev/null +++ b/src/plugins/imageformats/tga/qtgahandler.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtgahandler.h" +#include "qtgafile.h" + +#include <qvariant.h> +#include <qdebug.h> +#include <qimage.h> +#include <qglobal.h> + +QT_BEGIN_NAMESPACE + +QTgaHandler::QTgaHandler() + : QImageIOHandler() + , tga(0) +{ +} + +QTgaHandler::~QTgaHandler() +{ + delete tga; +} + +bool QTgaHandler::canRead() const +{ + if (!tga) + tga = new QTgaFile(device()); + if (tga->isValid()) + { + setFormat("tga"); + return true; + } + return false; +} + +bool QTgaHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning("QTgaHandler::canRead() called with no device"); + return false; + } + QTgaFile tga(device); + return tga.isValid(); +} + +bool QTgaHandler::read(QImage *image) +{ + if (!canRead()) + return false; + *image = tga->readImage(); + return !image->isNull(); +} + +QByteArray QTgaHandler::name() const +{ + return "tga"; +} + +QVariant QTgaHandler::option(ImageOption option) const +{ + if (option == Size && canRead()) { + return tga->size(); + } else if (option == CompressionRatio) { + return tga->compression(); + } else if (option == ImageFormat) { + return QImage::Format_ARGB32; + } + return QVariant(); +} + +void QTgaHandler::setOption(ImageOption option, const QVariant &value) +{ + Q_UNUSED(option); + Q_UNUSED(value); +} + +bool QTgaHandler::supportsOption(ImageOption option) const +{ + return option == CompressionRatio + || option == Size + || option == ImageFormat; +} + +QT_END_NAMESPACE diff --git a/src/plugins/imageformats/tga/qtgahandler.h b/src/plugins/imageformats/tga/qtgahandler.h new file mode 100644 index 000000000..d66dd17a0 --- /dev/null +++ b/src/plugins/imageformats/tga/qtgahandler.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTGAHANDLER_H +#define QTGAHANDLER_H + +#include <QtGui/qimageiohandler.h> + +QT_BEGIN_NAMESPACE + +class QTgaFile; + +class QTgaHandler : public QImageIOHandler +{ +public: + QTgaHandler(); + ~QTgaHandler(); + + bool canRead() const; + bool read(QImage *image); + + QByteArray name() const; + + static bool canRead(QIODevice *device); + + QVariant option(ImageOption option) const; + void setOption(ImageOption option, const QVariant &value); + bool supportsOption(ImageOption option) const; + +private: + mutable QTgaFile *tga; +}; + +QT_END_NAMESPACE + +#endif // QTGAHANDLER_H diff --git a/src/plugins/imageformats/tga/tga.pro b/src/plugins/imageformats/tga/tga.pro new file mode 100644 index 000000000..209756e23 --- /dev/null +++ b/src/plugins/imageformats/tga/tga.pro @@ -0,0 +1,10 @@ +TARGET = qtga +include(../../qpluginbase.pri) +HEADERS += qtgahandler.h \ + qtgafile.h +SOURCES += main.cpp \ + qtgahandler.cpp \ + qtgafile.cpp +QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/imageformats +target.path += $$[QT_INSTALL_PLUGINS]/imageformats +INSTALLS += target diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro new file mode 100644 index 000000000..cdf851ff3 --- /dev/null +++ b/src/plugins/plugins.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = sceneformats imageformats +scenegraph:SUBDIRS += scenegraph diff --git a/src/plugins/qpluginbase.pri b/src/plugins/qpluginbase.pri new file mode 100644 index 000000000..64e6e132b --- /dev/null +++ b/src/plugins/qpluginbase.pri @@ -0,0 +1,25 @@ +TEMPLATE = lib +isEmpty(QT_MAJOR_VERSION) { + VERSION=4.7.0 +} else { + VERSION=$${QT_MAJOR_VERSION}.$${QT_MINOR_VERSION}.$${QT_PATCH_VERSION} +} +CONFIG += qt plugin + +win32|mac:!wince*:!win32-msvc:!macx-xcode:CONFIG += debug_and_release +TARGET = $$qtLibraryTarget($$TARGET) +contains(QT_CONFIG, reduce_exports):CONFIG += hide_symbols + +include(qt_targets.pri) + +wince*:LIBS += $$QMAKE_LIBS_GUI + +symbian: { + TARGET.EPOCALLOWDLLDATA=1 + contains(QT_EDITION, OpenSource) { + TARGET.CAPABILITY = LocalServices NetworkServices ReadUserData UserEnvironment WriteUserData + } else { + TARGET.CAPABILITY = All -Tcb + } + TARGET = $${TARGET}$${QT_LIBINFIX} +} diff --git a/src/plugins/qt_targets.pri b/src/plugins/qt_targets.pri new file mode 100644 index 000000000..8c9a8dd0a --- /dev/null +++ b/src/plugins/qt_targets.pri @@ -0,0 +1,4 @@ +QMAKE_TARGET_COMPANY = Nokia Corporation and/or its subsidiary(-ies) +QMAKE_TARGET_PRODUCT = Qt4 +QMAKE_TARGET_DESCRIPTION = C++ application development framework. +QMAKE_TARGET_COPYRIGHT = Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) diff --git a/src/plugins/sceneformats/3ds/3ds.pro b/src/plugins/sceneformats/3ds/3ds.pro new file mode 100644 index 000000000..8cd4338dd --- /dev/null +++ b/src/plugins/sceneformats/3ds/3ds.pro @@ -0,0 +1,28 @@ +TARGET = qscene3ds +include(../../qpluginbase.pri) +HEADERS += qgl3dsloader.h \ + qgl3dsscene.h \ + qgl3dsscenehandler.h \ + qgl3dsmesh.h +SOURCES += main.cpp \ + qgl3dsloader.cpp \ + qgl3dsscene.cpp \ + qgl3dsscenehandler.cpp \ + qgl3dsmesh.cpp +CONFIG += qt3d +system_3ds { + !isEmpty(QMAKE_INCDIR_3DS):INCLUDEPATH += $$QMAKE_INCDIR_3DS + !isEmpty(QMAKE_LIBDIR_3DS):LIBS += -L$$QMAKE_LIBDIR_3DS + + !isEmpty(QMAKE_LIBS_3DS):LIBS += -l$$QMAKE_LIBS_3DS + else { + win32:LIBS += -llib3ds-1_3 + else:LIBS += -l3ds + } +} else { + include(../../../../3rdparty/lib3ds/lib3ds.pri) +} + +QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/sceneformats +target.path += $$[QT_INSTALL_PLUGINS]/sceneformats +INSTALLS += target diff --git a/src/plugins/sceneformats/3ds/README.txt b/src/plugins/sceneformats/3ds/README.txt new file mode 100644 index 000000000..1fa00084b --- /dev/null +++ b/src/plugins/sceneformats/3ds/README.txt @@ -0,0 +1,55 @@ +This implementation of 3D studio max file import relies on the library +lib3ds available from http://lib3ds.org/. A copy of this library is +included with Qt/3D, or you can download and build your own version +as described below. + +To enable building and use of the plugin, pass CONFIG+=system_3ds to qmake + +Currently version 1.3 is required. Do not use version 2.0 or later as it is incompatible. + +For Linux systems typically 1.3 can be obtained by installing a binary package, eg lib3ds-dev + +When compiling from source on Unix a simple ./configure, make, make install seems to work fine +on both linux and mac osx. + +On windows with Visual Studio the lib3ds library can be compiled as follows. + +1) Download the zipped source of 1.3 and expand it, cd into the directory created. + +2) Edit the file msvc8\lib3ds.rc + -#include "winres.h" + +#include "WinResrc.h" + +3) Locate the vcbuild.exe on the windows system: + + // this is needed for VS9 + C:\path\to\vcbuild.exe /upgrade msvc8\lib3ds.vcproj + C:\path\to\vcbuild.exe msvc8\lib3ds.vcproj + +4) Release and debug versions of the library files will be created. Install by: + + xcopy msvc8\release\* C:\path\to\bin + xcopy msvc8\debug\* C:\path\to\bin + +To avoid having to install the lib3ds.org library/dll/headers in a path where the compiler +will locate them, pass the following arguments to qmake: + QMAKE_LIBDIR_3DS=C:\build\lib3ds\msvc8\release + QMAKE_INCDIR_3DS=C:\build\lib3ds + +If the library is called something different to lib3ds-1_3 on windows or lib3ds.1.3.so on +Unix, for example to use the debug library on windows pass this argument to qmake: + QMAKE_LIBS_3DS=lib3ds-1_3d + +There is a handy 3dsplay utility in the lib3ds distribution - its not needed for Qt3D but is +convenient for debugging models. + +To build the 3dsplay utility - a command line 3ds viewer that uses GLUT and OpenGL - after +building and installing lib3ds as above, from the build directory use: +Linux: gcc -o 3dsplay -lGL -lglut -l3ds examples/3dsplay.c +MacOSX: gcc -FOpenGL -FGLUT -framework OpenGL -framework GLUT -l3ds -o 3dsplay examples/3dsplay.c + +To get image loading support to enable textures, this works on MacSOX: +First install SDL: http://www.libsdl.org/download-1.2.php +And SDL_image: http://www.libsdl.org/projects/SDL_image/ + gcc -c -DUSE_SDL -o examples/3dsplay.o -I/Library/Frameworks/SDL.framework/Headers -I/Library/Frameworks/SDL_image.framework/Headers examples/3dsplay.c + gcc -framework SDL_image -framework SDL -framework OpenGL -framework GLUT -l3ds -o 3dsplay examples/3dsplay.o diff --git a/src/plugins/sceneformats/3ds/main.cpp b/src/plugins/sceneformats/3ds/main.cpp new file mode 100644 index 000000000..6bd311270 --- /dev/null +++ b/src/plugins/sceneformats/3ds/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglsceneformatplugin.h" +#include "qgl3dsscenehandler.h" + +QT_BEGIN_NAMESPACE + +class QGL3dsScenePlugin : public QGLSceneFormatPlugin +{ +public: + QStringList keys() const; + virtual QGLSceneFormatHandler *create(QIODevice *device, const QUrl& url, const QString &format) const; +}; + +QStringList QGL3dsScenePlugin::keys() const +{ + return QStringList() << QLatin1String("3ds") << QLatin1String("application/x-3ds") << QLatin1String("image/x-3ds"); +} + +QGLSceneFormatHandler *QGL3dsScenePlugin::create(QIODevice *device, const QUrl& url, const QString &format) const +{ + Q_UNUSED(device); + Q_UNUSED(url); + Q_UNUSED(format); + return new QGL3dsSceneHandler; +} + +Q_EXPORT_STATIC_PLUGIN(QGL3dsScenePlugin) +Q_EXPORT_PLUGIN2(qscene3ds, QGL3dsScenePlugin) + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/3ds/qgl3dsloader.cpp b/src/plugins/sceneformats/3ds/qgl3dsloader.cpp new file mode 100644 index 000000000..34cefa336 --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsloader.cpp @@ -0,0 +1,333 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgl3dsloader.h" +#include "qglmaterial.h" +#include "qglmaterialcollection.h" +#include "qglpainter.h" +#include "qgltexture2d.h" +#include "qgl3dsscene.h" +#include "qglscenenode.h" +#include "qgl3dsmesh.h" + +#include <lib3ds/mesh.h> +#include <lib3ds/file.h> +#include <lib3ds/node.h> +#include <lib3ds/material.h> +#include <lib3ds/matrix.h> + +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qdir.h> +#include <QtCore/qobject.h> +#include <QtCore/qfileinfo.h> + +QGL3dsLoader::QGL3dsLoader(Lib3dsFile *file, QGL3dsSceneHandler* sh) + : m_file(file) + , m_rootNode(new QGLSceneNode()) + , m_scene(sh) + , m_hasTextures(false) +{ + m_rootNode->setPalette(new QGLMaterialCollection(m_rootNode)); + m_rootNode->setObjectName(QString::fromLocal8Bit(file->name)); + setUrl(sh->url()); +} + +QGL3dsLoader::~QGL3dsLoader() +{ + // nothing to do here - m_rootNode is taken ownership of by caller of + // rootNode() method +} + +/*! + Sets the location for resolving resource paths to \a url. + + Only URL's with a "file" scheme are supported. +*/ +void QGL3dsLoader::setUrl(const QUrl &url) +{ + // TODO - handling of network URLs + if (url.scheme() == QLatin1String("file")) + m_url = url; + else + qWarning("QGL3dsLoader::setUrl(%s): ignored, only file urls supported", + url.encodedPath().constData()); +} + +void QGL3dsLoader::loadMesh(Lib3dsMesh *mesh) +{ +#ifndef QT_NO_DEBUG_STREAM + if (mesh->points == 0 && (m_scene->options() & QGL::ShowWarnings)) + qDebug() << "Mesh" << mesh->name << "has zero vertex count"; + else if (mesh->faces == 0 && (m_scene->options() & QGL::ShowWarnings)) + qDebug() << "Mesh" << mesh->name << "has zero face count"; +#endif + QGL3dsMesh m(mesh, m_rootNode->palette()); + QString name = QString::fromLatin1(mesh->name); + QGL::ModelOptions o = m_scene->meshOptions(name); + if (o == 0) + o = m_scene->options(); + m.setOptions(o); + QGLSceneNode *node; + if (mesh->faces == 0 || mesh->points == 0) + { + node = new QGLSceneNode(m_rootNode); + node->setObjectName(name); + m_meshes.insert(name, node); + return; + } + m.initialize(); + if (!m_hasTextures) + m_hasTextures = m.hasTexture(); + if (!m_hasLitMaterials) + m_hasLitMaterials = !m.hasTexture(); + node = m.finalizedSceneNode(); + m_meshes.insert(name, node); + m_rootNode->addNode(node); +} + +#ifndef QT_NO_DEBUG_STREAM +static const char *node_type_names[] = { + "LIB3DS_UNKNOWN_NODE", + "LIB3DS_AMBIENT_NODE", + "LIB3DS_OBJECT_NODE", + "LIB3DS_CAMERA_NODE", + "LIB3DS_TARGET_NODE", + "LIB3DS_LIGHT_NODE", + "LIB3DS_SPOT_NODE" +}; +#endif + +inline static QMatrix4x4 getNodeMatrix(Lib3dsNode *node) +{ + QMatrix4x4 nodeMatrix; + for (int col = 0; col < 4; ++col) + for (int row = 0; row < 4; ++row) + nodeMatrix(row, col) = node->matrix[col][row]; + nodeMatrix.optimize(); + Lib3dsObjectData *d = &node->data.object; + if (!qFuzzyIsNull(d->pivot[0]) || !qFuzzyIsNull(d->pivot[1]) || !qFuzzyIsNull(d->pivot[2])) + nodeMatrix.translate(-d->pivot[0], -d->pivot[1], -d->pivot[2]); + return nodeMatrix; +} + +void QGL3dsLoader::loadNodes(Lib3dsNode *nodeList, QGLSceneNode *parentNode) +{ + Lib3dsNode *node; + for (node = nodeList; node != NULL; node = node->next) + { + if (node->type == LIB3DS_OBJECT_NODE) + { + Lib3dsObjectData *d = &node->data.object; + QString meshName = QString::fromLatin1(d->morph); + if (meshName.isEmpty()) + meshName = QString::fromLatin1(d->instance); + if (meshName.isEmpty()) + meshName = QString::fromLatin1(node->name); + if (!meshName.isEmpty() && m_meshes.contains(meshName)) + { + QGLSceneNode *mesh = m_meshes[meshName]; + QMatrix4x4 mat = getNodeMatrix(node); + mat = mat * mesh->localTransform(); + mesh->setLocalTransform(mat); + } + else + { + QGLSceneNode *sceneNode = new QGLSceneNode(parentNode); + sceneNode->setPalette(parentNode->palette()); + sceneNode->setLocalTransform(getNodeMatrix(node)); + //sceneNode->userTransform().setToIdentity(); //DP: set matrix to identity so it is initialised in a useful way at least. + QString nodeName(QString::fromLatin1(node->name)); + if (nodeName == QLatin1String("$$$DUMMY")) + { + nodeName = QString::fromLatin1(node->data.object.instance); + sceneNode->setObjectName(nodeName); + loadNodes(node->childs, sceneNode); + } + } + } +#ifndef QT_NO_DEBUG_STREAM + else + { + qDebug() << "Node" << node->name << "of type" << node_type_names[node->type] << "not currently supported"; + } +#endif + } +} + +/*! + \internal + Loads all the geometry, materials, and texture associations from the assigned + file, and returns the root node of the resulting scene graph. + + The caller must take ownership of the root node returned, and delete it + when its no longer required. +*/ +QGLSceneNode *QGL3dsLoader::loadMeshes() +{ + Q_CHECK_PTR(m_file); + Lib3dsMaterial *mat; + for (mat = m_file->materials; mat != NULL; mat = mat->next) + loadMaterial(mat); + Lib3dsMesh * mesh; + for (mesh = m_file->meshes; mesh != NULL; mesh = mesh->next) + loadMesh(mesh); + QGL::MeshOptionMap optList = m_scene->meshOptions(); + QStringList optionedMeshes = optList.keys(); + QStringList gotMeshes = m_meshes.keys(); + for (int i = 0; i < gotMeshes.size(); ++i) + optionedMeshes.removeAll(gotMeshes.at(i)); + for (int i = 0; i < optionedMeshes.size(); ++i) + qWarning("Option specified, but mesh %s not found", + qPrintable(optionedMeshes.at(i))); + m_rootNode->palette()->removeUnusedMaterials(); + loadNodes(m_file->nodes, m_rootNode); + m_rootNode->setEffect(m_hasTextures ? QGL::LitModulateTexture2D : QGL::LitMaterial); + return m_rootNode; +} + +/*! + \internal + Search for a resource based on the given \a path. + + If the URL for the currently loading mesh has a scheme other than + "file" then a URL with the path relative to that URL is returned. + + If the URL for the currently loading mesh has a "file" scheme, then + first a case-sensitive search is done of all of the current directory, + and the :/ resource directory, and the directory of the current mesh + file. + + If the file is not found in any of those locations then they are + searched again case-insensitively. If the file is found, then a + URL based on the absolute file path of the matching file is returned. + + Otherwise an empty string is returned. +*/ +QUrl QGL3dsLoader::ensureResource(const QString &path) +{ + QUrl res; + if (m_url.scheme() == QLatin1String("file")) + { + res = m_url.resolved(path); + if (QFile::exists(res.path())) // shortcut common case + return res; + QStringList paths; + paths << QLatin1String(".") << QLatin1String(":/"); // current directory and aliased/root resource file + if (!m_url.isEmpty()) + { + QFileInfo fi(m_url.path()); + paths.prepend(fi.absoluteDir().absolutePath()); + } + bool caseInsensitive = false; + do { + QStringList::const_iterator it(paths.begin()); + for ( ; it != paths.end(); ++it) + { + QDir resDir(*it); + QStringList fileList = resDir.entryList(QDir::Files); + if (caseInsensitive) + { + QStringList::const_iterator fit(fileList.begin()); + for ( ; fit != fileList.end(); ++fit) + { + if (fit->toLower() == path.toLower()) + { + res.setScheme(QLatin1String("file")); + res.setPath(resDir.absoluteFilePath(*fit)); + break; + } + } + } + else + { + if (fileList.contains(path)) + { + //return resDir.absoluteFilePath(path); + res.setScheme(QLatin1String("file")); + res.setPath(resDir.absoluteFilePath(path)); + break; + } + } + } + if (caseInsensitive) + break; + caseInsensitive = true; + } while (true); + } + else + { + // non-file url + res = m_url.resolved(path); + } + return res; +} + +/*! + Load a material +*/ +void QGL3dsLoader::loadMaterial(Lib3dsMaterial *mat3ds) +{ + QGLMaterialCollection *palette = m_rootNode->palette(); + QGLMaterial *mat = new QGLMaterial(); + Lib3dsRgba &amb = mat3ds->ambient; + Lib3dsRgba &dif = mat3ds->diffuse; + Lib3dsRgba &spc = mat3ds->specular; + mat->setAmbientColor(QColor::fromRgbF(amb[0], amb[1], amb[2], amb[3])); + mat->setDiffuseColor(QColor::fromRgbF(dif[0], dif[1], dif[2], dif[3])); + mat->setSpecularColor(QColor::fromRgbF(spc[0], spc[1], spc[2], spc[3])); + mat->setShininess(128 * mat3ds->shininess); + mat->setObjectName(QString::fromLatin1(mat3ds->name)); + palette->addMaterial(mat); + if (mat3ds->texture1_map.name[0]) + { + QString txName(QString::fromLatin1(mat3ds->texture1_map.name)); + QUrl url = ensureResource(txName); + if (url.isEmpty()) + { + if (m_scene->options() & QGL::ShowWarnings) + qWarning("Could not load texture: %s", mat3ds->texture1_map.name); + } + else + { + mat->setTextureUrl(url); + } + } +} diff --git a/src/plugins/sceneformats/3ds/qgl3dsloader.h b/src/plugins/sceneformats/3ds/qgl3dsloader.h new file mode 100644 index 000000000..edfe1b34b --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsloader.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSLOADER_H +#define QGL3DSLOADER_H + +#include <QtCore/qurl.h> +#include <QtCore/qstring.h> +#include <QtCore/qmap.h> + +class Lib3dsFile; +class Lib3dsMesh; +class Lib3dsMaterial; +class Lib3dsNode; +class QGL3dsMesh; +class QGLSceneNode; +class QGL3dsSceneHandler; + +class QGL3dsLoader +{ +public: + QGL3dsLoader(Lib3dsFile *file, QGL3dsSceneHandler* sh); + ~QGL3dsLoader(); + QGLSceneNode *loadMeshes(); + +private: + void loadMesh(Lib3dsMesh *); + void loadNodes(Lib3dsNode *, QGLSceneNode *); + void loadMaterial(Lib3dsMaterial *); + QUrl ensureResource(const QString &); + void setUrl(const QUrl &url); + + Lib3dsFile *m_file; + QGLSceneNode *m_rootNode; + QGL3dsSceneHandler *m_scene; + QList<QGLSceneNode *> m_nodes; + QMap<QString, QGLSceneNode *> m_meshes; + QUrl m_url; + bool m_hasTextures; + bool m_hasLitMaterials; +}; + +#endif // QGL3DSLOADER_H diff --git a/src/plugins/sceneformats/3ds/qgl3dsmesh.cpp b/src/plugins/sceneformats/3ds/qgl3dsmesh.cpp new file mode 100644 index 000000000..6cc5db244 --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsmesh.cpp @@ -0,0 +1,755 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgl3dsmesh.h" +#include "qglmaterialcollection.h" +#include "qglsection_p.h" +#include "qarray.h" +#include "qvector_utils_p.h" + +#include <lib3ds/mesh.h> +#include <lib3ds/material.h> +#include <lib3ds/vector.h> +#include <lib3ds/types.h> + +#include <QtGui/qmatrix4x4.h> +#include <QtCore/qmath.h> + +// Faceted meshes look terrible when they have more than a small number +// of faces. Usually if large meshes are faceted its some kind of error +// in the model, or in importing of the model by lib3ds. Force on +// smoothing when greater than this many faces are detected. +#define FACETED_THRESHOLD 1000 + +// Two faces that have an angle between their plane vectors with a cosine +// less than this are judged to form a sharp (acute) angle. +// -ve cosine (less than 0.0f) means 90 degrees or sharper like the sides +// of a rectangular prism so this is a good value. +#define ACUTE -0.0001f + +// Two vectors that have an angle between them with a cosine less than this +// value are judged to be approximately the inverse of each other, for the +// purposes of determining whether a normal has been inverted or not +#define INVERSE -0.20f + +// Only try to correct normals if this many neighbour faces contribute to +// the assessment. 2 or 3 are good values. 4 or greater is the same as +// defining DO_NORMAL_CORRECT false +#define AVG_CNT 2 + +// make a QVector3D from a Lib3dsVector - by a hard cast. Its required +// that QVector3D behave like a POD type - float[3] - for all of the +// OpenGL stuff +static inline QVector3D &l2v(Lib3dsVector &vec) +{ + return reinterpret_cast<QVector3D&>(vec); +} + +// is this a null 3ds vector - same as QVector3D::isNull() +static inline bool qIsNull(const Lib3dsVector &vec) +{ + return (qIsNull(vec[0]) && qIsNull(vec[1]) && qIsNull(vec[2])); +} + +static inline bool qFskCompare(const Lib3dsVector &a, const Lib3dsVector &b) +{ + return (qFskCompare(a[0], b[0]) && qFskCompare(a[1], b[1]) && qFskCompare(a[2], b[2])); +} + +// find the normalized plane vector, that is a unit vector perpendicular +// to the plane of the face. this is the same thing as the default normal +static inline void planeVec(Lib3dsFace *face, Lib3dsPoint *pointList, Lib3dsVector result) +{ + Lib3dsVector &l3a = pointList[face->points[0]].pos; + Lib3dsVector &l3b = pointList[face->points[1]].pos; + Lib3dsVector &l3c = pointList[face->points[2]].pos; + lib3ds_vector_normal(result, l3a, l3b, l3c); +} + +struct ModulateRecord +{ + bool disabled; + bool keyFresh; + int facesProcessed; + int numModulated; + Lib3dsDword altKey; + Lib3dsDword key; +}; + +QGL3dsMesh::QGL3dsMesh(Lib3dsMesh *mesh, QGLMaterialCollection *materials) + : QGLBuilder(materials) + , m_mesh(mesh) + , m_texFlip(false) + , m_hasZeroSmoothing(false) + , m_faceMap(0) +{ +} + +QGL3dsMesh::~QGL3dsMesh() +{ + delete[] m_faceMap; +} + +void QGL3dsMesh::processNodeForMaterial(int matIx, QGLSceneNode *node) +{ + QGLSceneNode *s = sceneNode(); + QString baseName = s->objectName(); + node->setMaterialIndex(matIx); + node->setObjectName(baseName + QLatin1String("::") + + ((matIx == -1) + ? QLatin1String("No_Material") + : s->palette()->materialName(matIx))); + checkTextures(matIx); + generateVertices(); + s->palette()->markMaterialAsUsed(matIx); +} + +void QGL3dsMesh::initAdjacencyMap() +{ + Lib3dsFace *face; + m_faceMap = new FacePtr[m_mesh->faces * 3]; + qMemSet(m_faceMap, 0, sizeof(m_faceMap)); + for (Lib3dsDword f = 0; f < m_mesh->faces; ++f) + { + face = &m_mesh->faceL[f]; + planeVec(face, m_mesh->pointL, face->normal); + m_mesh->faceL[f].user.p = &m_faceMap[f*3]; + } +} + +void QGL3dsMesh::initialize() +{ + QGLSceneNode *s = sceneNode(); + s->setObjectName(QString::fromLatin1(m_mesh->name)); + if ((m_options & QGL::ForceSmooth) && (m_options & QGL::ForceFaceted)) + { + if (m_options & QGL::ShowWarnings) + qWarning("Both smooth and faceted forced on for %s: forcing smooth\n", + m_mesh->name); + m_options &= ~QGL::ForceFaceted; + } + initAdjacencyMap(); + if (m_options & (QGL::CorrectNormals | QGL::CorrectAcute)) + modulateMesh(); + analyzeMesh(); + + if (m_smoothingGroups == 0) + { + bool forceSmooth = (m_options & QGL::ForceSmooth); + if (!forceSmooth && m_mesh->faces > FACETED_THRESHOLD) + { + if (m_options & QGL::ShowWarnings) + fprintf(stderr, "Mesh %s has %d faces (threshold is %d):" + "forcing smooth render", m_mesh->name, m_mesh->faces, + FACETED_THRESHOLD); + forceSmooth = true; + } + if (forceSmooth) + { + for (Lib3dsDword f = 0; f < m_mesh->faces; ++f) + m_mesh->faceL[f].smoothing = 1; + analyzeMesh(); + } + } + + bool mixedTexturedAndPlain = m_plainMaterials.count() > 0 && + m_textureMaterials.count() > 0; + + s->setLocalTransform(meshMatrix()); + + // start a new section and node + newSection(m_smoothingGroups ? QGL::Smooth : QGL::Faceted); + QGLSceneNode *node = currentNode(); + + // process all the plain materials first, then textured to avoid effect swapping + QList<int> matList = m_plainMaterials.toList(); + if (mixedTexturedAndPlain) + { + node->setEffect(QGL::LitMaterial); + node->setObjectName(s->objectName() + QLatin1String("::Materials")); + //qDebug() << ">>> mixed:" << node; + pushNode(); + //qDebug() << " pushed - current now:" << currentNode(); + } + else + { + s->setEffect(m_textureMaterials.count() > 0 ? QGL::LitModulateTexture2D : QGL::LitMaterial); + } + //qDebug() << "processing:" << matList.count() << "materials"; + while (matList.count() > 0) + { + int matIx = matList.takeFirst(); + processNodeForMaterial(matIx, node); + if (matList.count() > 0) + node = newNode(); + } + matList = m_textureMaterials.toList(); + if (mixedTexturedAndPlain) + { + //qDebug() << "<<< mixed:" << currentNode(); + popNode(); + //qDebug() << " popped - current now:" << currentNode(); + node = currentNode(); + node->setEffect(QGL::LitModulateTexture2D); + node->setObjectName(s->objectName() + QLatin1String("::Textures")); + } + while (matList.count() > 0) + { + int matIx = matList.takeFirst(); + processNodeForMaterial(matIx, node); + if (matList.count() > 0) + node = newNode(); + } +} + +// Build a linked list, in a QArray: the first N*2 entries correspond +// to the N vertices: for each n'th vertex, n*2 is the face number, +// n*2+1is the index of the next entry for that vertex, or -1 if there +// is no next entry. +// +// While on the job find the smoothing keys and store in allKeys. +// +// And zero out the user data pointer for each face, and calculate the +// smoothing group count. +QArray<int> QGL3dsMesh::mapFacesToVerts(Lib3dsDword *allKeys) +{ + Lib3dsFace *face; + QArray<int> vlist(m_mesh->points * 2, -1); + int nx = 2 * m_mesh->points; + m_smoothingGroupCount = 0; + for (Lib3dsDword f = 0; f < m_mesh->faces; ++f) + { + face = &m_mesh->faceL[f]; + if (face->smoothing) + { + if (!(face->smoothing & *allKeys)) + { + *allKeys |= face->smoothing; + m_smoothingGroupCount += 1; + } + } + for (int i = 0; i < 3; ++i) + { + int v = face->points[i]; + int ptr = v*2; + int prv = -1; + if (vlist[ptr] != -1) + { + prv = ptr; + while (vlist[ptr+1] != -1) + { + ptr = vlist[ptr+1]; + prv = ptr; + } + ptr = nx; + vlist.extend(2); + nx += 2; + } + vlist[ptr] = f; + vlist[ptr+1] = -1; + if (prv != -1) + vlist[prv+1] = ptr; + } + } + return vlist; +} + +void QGL3dsMesh::addToAdjacencyMap(Lib3dsFace *face, Lib3dsFace *neighbour) +{ + Q_ASSERT(face); + Q_ASSERT(face->user.p); + Q_ASSERT(neighbour); + FacePtr *h = static_cast<FacePtr*>(face->user.p); + while (*h != 0) + ++h; + *h = neighbour; +} + +// add to each face a linked list of its <= 3 neighbouring faces +// and also ensure each face knows is own normal. Rather than doing +// lots of small allocs, just assume every face has 3 neighbours +// and allocate the list nodes in one big lump - the m_faceMap. +// use the user data pointer in the lib3ds face struct to point +// to the first node in the list. +void QGL3dsMesh::buildAdjacencyMap(const QArray<int> &vlist) +{ + Lib3dsFace *face; + Lib3dsFace *nbr; + for (Lib3dsDword f = 0; f < m_mesh->faces; ++f) + { + // for each edge (where an edge is a pair of verts) find ones + // adjacent, ie both verts in edge have same face (which is + // not this face). + face = &m_mesh->faceL[f]; + for (int i = 0; i < 3; ++i) + { + Q_ASSERT(face->points[i] < m_mesh->points); + int vi = 2 * face->points[i]; + int neighbouri = -1; + while (true) + { + neighbouri = vlist[vi]; + if (neighbouri != (int)f) + { + Q_ASSERT(face->points[(i + 1) % 3] < m_mesh->points); + int vj = 2 * face->points[(i + 1) % 3]; + int neighbourj = -1; + while (true) + { + neighbourj = vlist[vj]; + if (neighbourj == neighbouri) + { + nbr = &m_mesh->faceL[neighbouri]; + // only add as neighbour if at least one smoothing group + // is shared - still have to test again below for each group + if (nbr->smoothing & face->smoothing) + addToAdjacencyMap(face, nbr); + break; + } + if (vlist[vj+1] == -1) + break; + vj = vlist[vj+1]; + } + } + if (vlist[vi+1] == -1) + break; + else + vi = vlist[vi+1]; + } + } + } +} + +static inline void incOrWarn(ModulateRecord *mod) +{ + if (mod->altKey != 0x80000000) + { + mod->altKey <<= 1; + } + else + { + qWarning("Overflowed smoothing keys - modulation disabled."); + mod->disabled = true; + } +} + +static inline void nextUnusedKey(ModulateRecord *mod, const Lib3dsDword &allKeys) +{ + if (!mod->disabled) + { + if (mod->altKey != 0) + incOrWarn(mod); + else + { + mod->altKey = 1; // initialize + } + while ((mod->altKey & allKeys) && !mod->disabled) + { + incOrWarn(mod); + } + mod->keyFresh = true; + } +} + +static int numCorrected = 0; +static int numWindingCorrected = 0; + +static inline void doNormalCorrect(Lib3dsFace *face) +{ + QGL3dsMesh::FacePtr *n = reinterpret_cast<QGL3dsMesh::FacePtr*>(face->user.p); + int acnt = 0; + Lib3dsVector avgn = { 0 }; + for (; *n; ++n) + { + if ((*n)->smoothing & face->smoothing) + { + lib3ds_vector_add(avgn, avgn, (*n)->normal); + ++acnt; + } + } + if (acnt >= AVG_CNT) + { + lib3ds_vector_normalize(avgn); + float na = lib3ds_vector_dot(face->normal, avgn); + if (na < INVERSE) + { + //fprintf(stderr, "corrected: %p - %d, %d, %d", face, + // face->points[0], face->points[1], face->points[2]); + //lib3ds_vector_dump(face->normal); + lib3ds_vector_neg(face->normal); + numWindingCorrected++; + qSwap(face->points[1], face->points[2]); + } + } +} + +static inline void modFace(Lib3dsFace *face, ModulateRecord *mod) +{ + ++mod->numModulated; + face->smoothing &= ~mod->key; // remove old key + face->smoothing |= mod->altKey; // add in new key + ::strncpy(face->material, "bright-red", 60); //debug +} + +static inline void doModulate(Lib3dsFace *face, ModulateRecord *mod) +{ + QGL3dsMesh::FacePtr *n = reinterpret_cast<QGL3dsMesh::FacePtr*>(face->user.p); + ++mod->facesProcessed; + for ( ; *n; ++n) + { + QGL3dsMesh::FacePtr neighbour = *n; + if (neighbour->smoothing & face->smoothing) + { + if (lib3ds_vector_dot(face->normal, neighbour->normal) < ACUTE) + { + fprintf(stderr, "Modulated due to ACUTE\n"); + modFace(neighbour, mod); + mod->keyFresh = false; + } + } + } +} + +bool operator<(const QVector3D &a, const QVector3D &b) +{ + if (qFskCompare(a.x(), b.x())) + { + if (qFskCompare(a.y(), b.y())) + { + if (qFskCompare(a.z(), b.z())) + { + return false; // they're equal a is not less than b + } + else + { + return a.z() < b.z(); + } + } + else + { + return a.y() < b.y(); + } + } + else + { + return a.x() < b.x(); + } +} + +// Go thru each smoothing group - we don't care about the zero group since +// they're already faceted. In each group process all connected faces +// starting the queue off with a seed face (the first face in the group). +// Set the alternate group to be the first unused smoothing group key. +// +// To process a face: add the face to the processed set; for each adjacent +// face if its in this same smoothing group, add it to the queue. +// +// Modulate smoothing mode: if an adjacent face is at an acute angle to this +// face, move it to the alternate group. Note that the alternate groups are +// not further iterated since they are not in "each smoothing group" +// (the allKeys variable). +// +// When the queue is empty, go to the next smoothing group in the mesh. + +// Normal repair mode: if a face normal is flipped in error, ie a mistake +// in model building (typically points specified in the wrong order) +// this can be detected and repaired. The normal is flipped if the inverse +// of the normal is at an acute angle to the average of neighbour normals. +// If detected the normals is corrected and the winding is also checked to +// see if it needs to be corrected also. +void QGL3dsMesh::modulateMesh() +{ + if (!(m_options & (QGL::CorrectNormals | QGL::CorrectAcute))) + return; + FacePtr face; + Lib3dsDword allKeys = 0; + QArray<int> vlist = mapFacesToVerts(&allKeys); + buildAdjacencyMap(vlist); + QSet<FacePtr> processed; + ModulateRecord mod; + qMemSet(&mod, 0, sizeof(struct ModulateRecord)); + mod.key = 1; + while ((mod.key < allKeys) && !mod.disabled && mod.key) + { + if (!(allKeys & mod.key)) + { + ++mod.key; + continue; + } + QArray<FacePtr> queue; + Lib3dsDword fptr = 0; + int head = 0; + while (true) + { + if (head >= queue.size()) // seed another island of faces + { + if (!mod.keyFresh && head > 0) // if not first time thru, did we use last key? + allKeys = allKeys | mod.altKey; + FacePtr qf = 0; + for ( ; fptr < m_mesh->faces; ++fptr) + { + qf = &m_mesh->faceL[fptr]; + if ((mod.key & qf->smoothing) && !processed.contains(qf)) + break; + } + if (fptr == m_mesh->faces) + break; + queue.append(qf); + if (!mod.keyFresh) + { + nextUnusedKey(&mod, allKeys); + if (mod.disabled) + break; + } + } + face = queue.at(head++); + processed.insert(face); + doNormalCorrect(face); + doModulate(face, &mod); + FacePtr *n = reinterpret_cast<FacePtr*>(face->user.p); + for ( ; *n; ++n) + if ((mod.key & (*n)->smoothing) && !processed.contains(*n)) + queue.append(*n); + } + } + if ((m_options & QGL::CorrectNormals) && (m_options & QGL::ShowWarnings)) + qDebug() << "CorrectNormals mode:" << numCorrected << "normals corrected."; + if ((m_options & QGL::CorrectAcute) && (m_options & QGL::ShowWarnings)) + qDebug() << "CorrectAcute mode:" << mod.numModulated << "normals corrected"; +} + +int QGL3dsMesh::cachedMaterialLookup(const char *material) +{ + static bool initialized = false; + static int lastLookup = -1; + static char lastName[512]; + if (!initialized) + { + ::memset(lastName, 0, 512); + initialized = true; + } + if (qstrncmp(lastName, material, 510) != 0) + { + lastLookup = sceneNode()->palette()->indexOf(QString::fromLatin1(material)); + qstrncpy(lastName, material, 510); + } + return lastLookup; +} + +/*! + \internal + Find material indexes and smoothing groups used in this mesh. If any + face has no material assigned then a -1 index will be listed. + Also figures out how complex the mesh is, by finding the count of + smoothing groups for the material with the greatest number of groups. + If the mesh is faceted (no smoothing) but has greater than FACETED_THRESHOLD + faces then smoothing is forced on and the mesh rescanned. + +*/ +void QGL3dsMesh::analyzeMesh() +{ + QGLMaterialCollection *pal = sceneNode()->palette(); + Lib3dsFace *face; + Lib3dsDword allKeys = 0; + m_smoothingGroupCount = 0; + m_hasZeroSmoothing = false; + m_plainMaterials.clear(); + m_textureMaterials.clear(); + for (Lib3dsDword f = 0; f < m_mesh->faces; ++f) + { + face = &m_mesh->faceL[f]; + int matIx = cachedMaterialLookup(face->material); +#ifndef QT_NO_DEBUG_STREAM + if (matIx == -1 && strlen(face->material) != 0 && (m_options & QGL::ShowWarnings)) + qDebug("Bad .3ds file: no material %s! (Referenced in mesh %s)\n", + face->material, m_mesh->name); +#endif + if (face->smoothing) + { + if ((face->smoothing & allKeys) != face->smoothing) + { + Lib3dsDword key = 1; + while (key) + { + if ((key & face->smoothing) && !(allKeys & key)) + { + allKeys = allKeys | key; + m_smoothingGroupCount += 1; + } + key <<= 1; + } + } + } + else + { + m_hasZeroSmoothing = true; + } + if (!m_plainMaterials.contains(matIx) && !m_textureMaterials.contains(matIx)) + { + QGLMaterial *mat = pal->material(matIx); + if (mat && mat->texture()) + m_textureMaterials.insert(matIx); + else + m_plainMaterials.insert(matIx); + } + } + m_smoothingGroups = allKeys; +} + +/*! + \internal + Check the meshes textures and update the record of whether this mesh + has textures or not. In debug mode issue a warning if the textures are + corrupt (number of texels and vertices not equal). +*/ +void QGL3dsMesh::checkTextures(int material) +{ + QGLMaterial *mat = sceneNode()->palette()->material(material); + QGLTexture2D *tex = (mat ? mat->texture() : 0); + m_hasTextures = false; + if (tex) + { + m_hasTextures = true; +#ifndef QT_NO_DEBUG_STREAM + if (m_mesh->points != m_mesh->texels) + qWarning("Mesh %s has unequal number of texels (%d) and vertices (%d)", + m_mesh->name, m_mesh->texels, m_mesh->points); +#endif + // all texture coordinates from 3ds have to be flipped because + // 3ds uses the correct coordinate system, whilst qt uses + // upside-down coordinates + m_texFlip = (tex->bindOptions() & QGLContext::InvertedYBindOption) == 0; + } +} + +/*! + \internal + Returns any local transformation matrix for the mesh. +*/ +QMatrix4x4 QGL3dsMesh::meshMatrix() const +{ + Lib3dsMatrix &m = m_mesh->matrix; // typedef for float[4][4] + QMatrix4x4 mat; + for (int col = 0; col < 4; ++col) + for (int row = 0; row < 4; ++row) { + float e = m[col][row]; + if (qFuzzyIsNull(e)) + mat(row, col) = 0.0f; + else + mat(row, col) = e; + } + mat.optimize(); // setup to use optimizations + if (mat.isIdentity()) + return mat; + // The reverse transform is what we apply to model-view in order + // to draw the underlying geometry + bool invertible = true; + mat = mat.inverted(&invertible); + if (invertible) + return mat; + if (m_options & QGL::ShowWarnings) + qWarning("Could not invert matrix for mesh %s", m_mesh->name); + return QMatrix4x4(); +} + +/*! + \internal + Generate the vertices for the faces based on their smoothing keys and + the current nodes material. +*/ +void QGL3dsMesh::generateVertices() +{ + int matIx = currentNode()->materialIndex(); + int keyCount = m_smoothingGroupCount; + if (m_hasZeroSmoothing) + ++keyCount; + QString baseName = currentNode()->objectName(); + Lib3dsDword key = 0; + while (key <= m_smoothingGroups) + { + if ((key & m_smoothingGroups) || ((key == 0) && m_hasZeroSmoothing)) + { + if (key == 0) + currentSection()->setSmoothing(QGL::Faceted); + keyCount -= 1; + currentNode()->setMaterialIndex(matIx); + currentNode()->setObjectName(baseName + QLatin1String("::") + QString::number(key)); + QGeometryData tri; + int cur = 0; + for (Lib3dsDword f = 0; f < m_mesh->faces; ++f) + { + Lib3dsFace *face = &m_mesh->faceL[f]; + int faceMat = cachedMaterialLookup(face->material); + if (faceMat == matIx && + ((key & face->smoothing) || (key == 0 && face->smoothing == 0))) + { + QVector3D norm = l2v(face->normal); + for (int i = 0; i < 3; ++i) + { + int a = face->points[i]; + Lib3dsVector &l3a = m_mesh->pointL[a].pos; + tri.appendVertex(l2v(l3a)); + tri.appendNormal(norm); + if (m_hasTextures) + { + Lib3dsTexel &t0 = m_mesh->texelL[a]; + tri.appendTexCoord(QVector2D(t0[0], m_texFlip ? 1.0f - t0[1] : t0[1])); + } + if (m_options & QGL::NativeIndices) + currentSection()->appendSmooth(tri.logicalVertexAt(cur++), a); + } + } + } + if (m_options & QGL::NativeIndices) + currentNode()->setCount(cur); + else + addTriangles(tri); + if (keyCount > 0) + newSection(QGL::Smooth); + else + break; + } + if (key == 0) + key = 1; + else + key <<= 1; + } +} diff --git a/src/plugins/sceneformats/3ds/qgl3dsmesh.h b/src/plugins/sceneformats/3ds/qgl3dsmesh.h new file mode 100644 index 000000000..6bbd47d1c --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsmesh.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSMESH_H +#define QGL3DSMESH_H + +#include "qglbuilder.h" +#include "qgl3dsscenehandler.h" + +#include <lib3ds/types.h> +#include <lib3ds/mesh.h> + +class QGLMaterialCollection; +class QGL3dsLoader; +class Lib3dsMesh; +class ModulateRecord; + +class QGL3dsMesh : public QGLBuilder +{ +public: + explicit QGL3dsMesh(Lib3dsMesh *mesh, QGLMaterialCollection *materials); + virtual ~QGL3dsMesh(); + void initialize(); + void setOptions(QGL::ModelOptions options) { m_options = options; } + bool hasTexture() { return m_hasTextures; } + + typedef Lib3dsFace *FacePtr; + +protected: + void analyzeMesh(); + void modulateMesh(); + void checkTextures(int); + QMatrix4x4 meshMatrix() const; + void generateVertices(); + +private: + void processNodeForMaterial(int matIx, QGLSceneNode *node); + QArray<int> mapFacesToVerts(Lib3dsDword *allKeys); + void addToAdjacencyMap(Lib3dsFace *face, Lib3dsFace *nbr); + void buildAdjacencyMap(const QArray<int> &vlist); + int cachedMaterialLookup(const char *material); + void findCommonNormal(ModulateRecord *mod) const; + void initAdjacencyMap(); + + Lib3dsMesh *m_mesh; + bool m_hasTextures; + Lib3dsDword m_smoothingGroups; + int m_smoothingGroupCount; + QSet<int> m_plainMaterials; + QSet<int> m_textureMaterials; + bool m_texFlip; + bool m_hasZeroSmoothing; + FacePtr *m_faceMap; + QGL::ModelOptions m_options; +}; + +#endif // QGL3DSMESH_H diff --git a/src/plugins/sceneformats/3ds/qgl3dsscene.cpp b/src/plugins/sceneformats/3ds/qgl3dsscene.cpp new file mode 100644 index 000000000..9e096b30d --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsscene.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgl3dsscene.h" + +#include "qglscenenode.h" +#include "qgl3dsmesh.h" +#include "qgl3dsloader.h" + +#include <lib3ds/file.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QGL3dsScene + \brief The QGL3dsScene class manages and contains a 3ds scene. + The scene consists of a QGLSceneNode object which is the top-level node + and references the geometry in a 3D Studio Max ".3ds" file. That top + level node references sub-nodes +*/ + +/*! + \internal + Construct a new QGL3dsScene object using the data in the \a file, + and setting the given \a parent. Resources are searched for at the + given \a url. + + The QGL3dsScene object takes ownership of the \a file. +*/ +QGL3dsScene::QGL3dsScene(Lib3dsFile *file, QGL3dsSceneHandler *h) + : QGLAbstractScene(0) + , mFile(file) +{ + Q_ASSERT(h); + Q_ASSERT(file); + QGL3dsLoader loader(file, h); + mRootNode = loader.loadMeshes(); +} + +/*! + \reimp + Destroy this QGL3dsScene, recovering all resources. + + This method destroys the Lib3dsFile object the scene is based + on by calling the appropriate lib3ds function. +*/ +QGL3dsScene::~QGL3dsScene() +{ + lib3ds_file_free(mFile); +} + +/*! + \internal + \reimp +*/ +QList<QObject *> QGL3dsScene::objects() const +{ + QList<QObject *> objs; + if (!mRootNode) + return objs; + objs.append(mRootNode); + QList<QGLSceneNode*> children = mRootNode->allChildren(); + QList<QGLSceneNode*>::const_iterator it = children.constBegin(); + for ( ; it != children.constEnd(); ++it) + objs.append(*it); + return objs; +} + +/*! + \internal + \reimp +*/ +QGLSceneNode *QGL3dsScene::mainNode() const +{ + return mRootNode; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/3ds/qgl3dsscene.h b/src/plugins/sceneformats/3ds/qgl3dsscene.h new file mode 100644 index 000000000..e47d3efcb --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsscene.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSSCENE_H +#define QGL3DSSCENE_H + +#include "qglabstractscene.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +#include <QtCore/qurl.h> + +class QGLSceneNode; +class QGLSceneNode; +class QGL3dsSceneHandler; +class Lib3dsFile; + +class QGL3dsScene : public QGLAbstractScene +{ + Q_OBJECT +public: + explicit QGL3dsScene(Lib3dsFile *file, QGL3dsSceneHandler *h); + virtual ~QGL3dsScene(); + + QList<QObject *> objects() const; + QGLSceneNode *mainNode() const; + + QUrl url() const { return mUrl; } + +private: + Lib3dsFile *mFile; + QUrl mUrl; + QGLSceneNode *mRootNode; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/plugins/sceneformats/3ds/qgl3dsscenehandler.cpp b/src/plugins/sceneformats/3ds/qgl3dsscenehandler.cpp new file mode 100644 index 000000000..bf0f9b518 --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsscenehandler.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgl3dsscenehandler.h" +#include "qgl3dsscene.h" + +#include <lib3ds/file.h> +#include <lib3ds/io.h> +#include <lib3ds/mesh.h> +#include <lib3ds/node.h> + +#include <QtCore/qdir.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +struct IODevice3ds +{ + QIODevice *dev; + bool errorState; + +}; + +extern "C" { + static Lib3dsBool qgl3ds_fileio_error_func(void *self) + { + IODevice3ds *io3d = (IODevice3ds*)self; + return io3d->errorState; + } + + static long qgl3ds_fileio_seek_func(void *self, long offset, Lib3dsIoSeek origin) + { + IODevice3ds *io3d = (IODevice3ds*)self; + // cannot deal with sockets right now + Q_ASSERT(!io3d->dev->isSequential()); + switch (origin) + { + case LIB3DS_SEEK_SET: + io3d->errorState = io3d->dev->seek(offset); + break; + case LIB3DS_SEEK_CUR: + io3d->errorState = io3d->dev->seek(io3d->dev->pos() + offset); + break; + case LIB3DS_SEEK_END: + io3d->errorState = io3d->dev->seek(io3d->dev->size() + offset); + break; + default: + Q_ASSERT(0); + return(0); + } + if (io3d->errorState) + return -1; + return io3d->dev->pos(); + } + + static long qgl3ds_fileio_tell_func(void *self) + { + IODevice3ds *io3d = (IODevice3ds*)self; + return io3d->dev->pos(); + } + + static size_t qgl3ds_fileio_read_func(void *self, void *buffer, size_t size) + { + IODevice3ds *io3d = (IODevice3ds*)self; + qint64 result = io3d->dev->read((char*)buffer, size); + io3d->errorState = (result == -1); + if (io3d->errorState) + fprintf(stderr, "3ds io error: %s\n", io3d->dev->errorString().toLocal8Bit().constData()); + return result; + } + + static size_t qgl3ds_fileio_write_func(void *self, const void *buffer, size_t size) + { + IODevice3ds *io3d = (IODevice3ds*)self; + qint64 result = io3d->dev->write((char*)buffer, size); + io3d->errorState = (result == -1); + if (io3d->errorState) + fprintf(stderr, "3ds io error: %s\n", io3d->dev->errorString().toLocal8Bit().constData()); + return result; + } +} + +static Lib3dsFile *qgl3ds_lib3ds_file_load(QIODevice *iod) +{ + Lib3dsFile *file; + Lib3dsIo *io; + Q_ASSERT(iod->isOpen() && iod->isReadable()); + file = lib3ds_file_new(); + if (!file) { + iod->close(); + return(0); + } + IODevice3ds io3d; + io3d.dev = iod; + io3d.errorState = false; + io = lib3ds_io_new( + &io3d, + qgl3ds_fileio_error_func, + qgl3ds_fileio_seek_func, + qgl3ds_fileio_tell_func, + qgl3ds_fileio_read_func, + qgl3ds_fileio_write_func + ); + if (!io) { + lib3ds_file_free(file); + iod->close(); + return(0); + } + if (!lib3ds_file_read(file, io)) { + lib3ds_file_free(file); + iod->close(); + return(0); + } + lib3ds_io_free(io); + iod->close(); + return(file); +} + +QGL3dsSceneHandler::QGL3dsSceneHandler() + : m_options(0) +{ +} + +QGL3dsSceneHandler::~QGL3dsSceneHandler() +{ + // nothing to do +} + +void QGL3dsSceneHandler::decodeOptions(const QString &options) +{ + static const char *validOptions[] = { + "NativeIndices", + "CorrectNormals", + "CorrectAcute", + "ForceSmooth", + "ForceFaceted", + "ShowWarnings" + }; + static int optionKeys[] = { + QGL::NativeIndices, + QGL::CorrectNormals, + QGL::CorrectAcute, + QGL::ForceSmooth, + QGL::ForceFaceted, + QGL::ShowWarnings, + -1 + }; + + // format: "mesh=option mesh=option option option" + // mesh: string name of a mesh in the file, with no spaces + // standalone option applies to the whole file - all meshes + // option: NativeIndices | CorrectNormals | CorrectAcute etc + QStringList opList = options.split(QLatin1Char(' '), QString::SkipEmptyParts); + for (int i = 0; i < opList.count(); ++i) + { + QString op = opList.at(i); + QString mdl; + if (op.contains(QLatin1Char('='))) + { + QStringList sl = op.split(QLatin1Char('='), QString::SkipEmptyParts); + mdl = sl[0]; + op = sl[1]; + } + int k = 0; + for ( ; optionKeys[k] != -1; ++k) + if (op == QLatin1String(validOptions[k])) + break; + if (optionKeys[k] != -1) // found + { + QGL::ModelOptions o = static_cast<QGL::ModelOptions>(optionKeys[k]); + if (!mdl.isEmpty()) + setMeshOptions(o, mdl); + else + setOptions(o); + } + else + { + qWarning("Bad option: \"%s\" in %s", qPrintable(op), + qPrintable(options)); + } + } +} + +QGLAbstractScene *QGL3dsSceneHandler::read() +{ + Lib3dsFile *file = qgl3ds_lib3ds_file_load(device()); + Q_CHECK_PTR(file); // wtf? + + if (!file->nodes) + { + Lib3dsMesh *mesh; + Lib3dsNode *node; + for (mesh = file->meshes; mesh; mesh = mesh->next) + { + if (mesh->faces && mesh->points) + { + node = lib3ds_node_new_object(); + qstrcpy(node->name, mesh->name); + node->parent_id = LIB3DS_NO_PARENT; + lib3ds_file_insert_node(file, node); + } + } + } + + lib3ds_file_eval(file, 0.0f); + + QGL3dsScene *scene = new QGL3dsScene(file, this); + return scene; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/3ds/qgl3dsscenehandler.h b/src/plugins/sceneformats/3ds/qgl3dsscenehandler.h new file mode 100644 index 000000000..92cf6ff4b --- /dev/null +++ b/src/plugins/sceneformats/3ds/qgl3dsscenehandler.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSSCENEHANDLER_H +#define QGL3DSSCENEHANDLER_H + +#include "qglsceneformatplugin.h" +#include <QtCore/qmap.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +namespace QGL { + enum ModelOption + { + NativeIndices = 0x01, + CorrectNormals = 0x02, + CorrectAcute = 0x04, + ForceSmooth = 0x08, + ForceFaceted = 0x10, + ShowWarnings = 0x20 + }; + Q_DECLARE_FLAGS(ModelOptions, ModelOption); + + typedef QMap<QString, QGL::ModelOptions> MeshOptionMap; +}; + +class QGL3dsSceneHandler : public QGLSceneFormatHandler +{ +public: + QGL3dsSceneHandler(); + ~QGL3dsSceneHandler(); + + QGLAbstractScene *read(); + + void setOptions(QGL::ModelOptions options) { m_options |= options; } + QGL::ModelOptions options() const { return m_options; } + + void setMeshOptions(QGL::ModelOptions options, const QString &meshName) + { + m_meshOptions[meshName] |= options; + } + QGL::ModelOptions meshOptions(const QString &meshName) const + { + if (m_meshOptions.contains(meshName)) + return m_meshOptions.value(meshName); + return 0; + } + QGL::MeshOptionMap meshOptions() const { return m_meshOptions; } + void decodeOptions(const QString &options); + +private: + QGL::ModelOptions m_options; + QGL::MeshOptionMap m_meshOptions; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGL::ModelOptions); + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/sceneformats/assimp/README.txt b/src/plugins/sceneformats/assimp/README.txt new file mode 100644 index 000000000..cabf1bdd8 --- /dev/null +++ b/src/plugins/sceneformats/assimp/README.txt @@ -0,0 +1,12 @@ +This importer uses the Asset Importer library available from: + +http://assimp.sourceforge.net/ + +Copyright (c) 2008-2010 ASSIMP Development Team +All rights reserved. + +For convenience a stripped down source tree is included in this distribution. See the license +included at $QT3D_SOURCE/3rdparty/assimp/LICENCE, or at http://assimp.sourceforge.net/main_license.html + +To use your own build of AssImp, modify the assimp.pro file and/or export the environment variables +specified there. diff --git a/src/plugins/sceneformats/assimp/ailoaderiostream.cpp b/src/plugins/sceneformats/assimp/ailoaderiostream.cpp new file mode 100644 index 000000000..0ee44863f --- /dev/null +++ b/src/plugins/sceneformats/assimp/ailoaderiostream.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "ailoaderiostream.h" + +#include <QtCore/qiodevice.h> + +#include <QtCore/qdebug.h> + +AiLoaderIOStream::AiLoaderIOStream(QIODevice *device) + : m_device(device) + , m_errorState(false) +{ + Q_ASSERT(device); +} + +AiLoaderIOStream::~AiLoaderIOStream() +{ + // Nothing to do here +} + +size_t AiLoaderIOStream::Read( void* pvBuffer, size_t pSize, size_t pCount) +{ + qint64 result = m_device->read((char*)pvBuffer, pSize * pCount); + size_t res = result; + m_errorState = (result == -1); + if (m_errorState) + fprintf(stderr, "AI read error: %s\n", qPrintable(m_device->errorString())); + if ((pSize * pCount) != res) // AI will treat as error + { + fprintf(stderr, "read mismatch requested size: %lu x count: %lu = %lu != %lu actuall read\n", + pSize, pCount, (pSize * pCount), res); + } + return res; +} + +size_t AiLoaderIOStream::Write( const void* pvBuffer, size_t pSize, size_t pCount) +{ + qint64 result = m_device->write((char*)pvBuffer, pSize * pCount); + m_errorState = (result == -1); + if (m_errorState) + fprintf(stderr, "AI write error: %s\n", qPrintable(m_device->errorString())); + return result; +} + +aiReturn AiLoaderIOStream::Seek(size_t pOffset, aiOrigin pOrigin) +{ + // cannot deal with sockets right now + Q_ASSERT(!m_device->isSequential()); + switch (pOrigin) + { + case aiOrigin_SET: + m_errorState = m_device->seek(pOffset); + break; + case aiOrigin_CUR: + m_errorState = m_device->seek(m_device->pos() + pOffset); + break; + case aiOrigin_END: + m_errorState = m_device->seek(m_device->size() + pOffset); + break; + default: + Q_ASSERT(0); + return(aiReturn_FAILURE); + } + if (m_errorState) + return aiReturn_FAILURE; + return aiReturn_SUCCESS; +} + +size_t AiLoaderIOStream::Tell() const +{ + return m_device->pos(); +} + +size_t AiLoaderIOStream::FileSize() const +{ + return m_device->size(); +} + +void AiLoaderIOStream::Flush() +{ + // do nothing +} diff --git a/src/plugins/sceneformats/assimp/ailoaderiostream.h b/src/plugins/sceneformats/assimp/ailoaderiostream.h new file mode 100644 index 000000000..be3172bb2 --- /dev/null +++ b/src/plugins/sceneformats/assimp/ailoaderiostream.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef AILOADERIOSTREAM_H +#define AILOADERIOSTREAM_H + +#include "IOStream.h" +#include "IOSystem.h" + +class QIODevice; + +class AiLoaderIOStream : public Assimp::IOStream +{ +public: + AiLoaderIOStream(QIODevice *device); + ~AiLoaderIOStream(); + size_t Read( void* pvBuffer, size_t pSize, size_t pCount); + size_t Write( const void* pvBuffer, size_t pSize, size_t pCount); + aiReturn Seek( size_t pOffset, aiOrigin pOrigin); + size_t Tell() const; + size_t FileSize() const; + void Flush(); + QIODevice *device() const { return m_device; } +private: + QIODevice *m_device; + bool m_errorState; +}; + +#endif // AILOADERIOSTREAM_H diff --git a/src/plugins/sceneformats/assimp/ailoaderiosystem.cpp b/src/plugins/sceneformats/assimp/ailoaderiosystem.cpp new file mode 100644 index 000000000..b31f275d0 --- /dev/null +++ b/src/plugins/sceneformats/assimp/ailoaderiosystem.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "ailoaderiosystem.h" +#include "ailoaderiostream.h" +#include "DefaultLogger.h" + +#include <QtCore/qfile.h> +#include <QtCore/qdir.h> + +AiLoaderIOSystem::AiLoaderIOSystem(QIODevice *device, QUrl url) + : m_device(device) + , m_url(url) +{ +} + +AiLoaderIOSystem::~AiLoaderIOSystem() +{ + // we don't own m_device + qDeleteAll(m_sub); +} + +bool AiLoaderIOSystem::Exists(const char* path) const +{ + return QFile::exists(QLatin1String(path)); +} + +char AiLoaderIOSystem::getOsSeparator() const +{ + return QDir::separator().toLatin1(); +} + +/*! + \internal + Open the pFile with the pMode, where mode is given by "man fopen" +*/ +Assimp::IOStream* AiLoaderIOSystem::Open(const char* pFile, const char* pMode) +{ + // This is just the file already opened on the device + if (m_url.toEncoded().endsWith(pFile)) + return new AiLoaderIOStream(m_device); + + // New relative file + QUrl rel; + rel.setScheme(m_url.scheme()); + rel.setPath(QLatin1String(pFile)); + QUrl url = m_url.resolved(rel); + + // TODO: handle network case + if (url.scheme() != QLatin1String("file")) + { + qWarning("Opening %s url not supported", qPrintable(url.scheme())); + return 0; + } + + char mode_str[4]; + qMemSet(mode_str, '\0', 4); + int i = 0; + for (const char *ptr = pMode; i < 4 && *ptr; ++ptr) + { + if (*ptr != 'b') // ignore the binary attribute + mode_str[i++] = *ptr; + } + QIODevice::OpenMode mode = QIODevice::NotOpen; + if (::strncmp("r", mode_str, 1) == 0) + { + mode = QIODevice::ReadOnly; + } + else if (::strncmp("r+", mode_str, 2) == 0) + { + mode = QIODevice::ReadWrite; + } + else if (::strncmp("w", mode_str, 1) == 0) + { + mode = QIODevice::WriteOnly | QIODevice::Truncate; + } + else if (::strncmp("w+", mode_str, 2) == 0) + { + mode = QIODevice::ReadWrite | QIODevice::Truncate; + } + else if (::strncmp("a", mode_str, 1) == 0) + { + mode = QIODevice::WriteOnly | QIODevice::Append; + } + else if (::strncmp("a+", mode_str, 2) == 0) + { + mode = QIODevice::ReadWrite | QIODevice::Append; + } + else + { + std::string err("Error: invalid mode flag:"); + err.append(mode_str).append(" when opening ").append(pFile); + Assimp::DefaultLogger::get()->warn(err); + return 0; + } + + QFile *f = new QFile(url.toLocalFile()); + bool res = f->open(mode); + if (!res) + { + std::string err("Error: could not open subsequent file:"); + err.append(pFile).append("--").append(f->errorString().toStdString()); + Assimp::DefaultLogger::get()->warn(err); + delete f; + return 0; + } + m_sub.append(f); + AiLoaderIOStream *s = new AiLoaderIOStream(f); + return s; +} + +void AiLoaderIOSystem::Close(Assimp::IOStream* stream) +{ + AiLoaderIOStream *s = static_cast<AiLoaderIOStream*>(stream); + Q_ASSERT(s); + s->device()->close(); + delete stream; +} diff --git a/src/plugins/sceneformats/assimp/ailoaderiosystem.h b/src/plugins/sceneformats/assimp/ailoaderiosystem.h new file mode 100644 index 000000000..8df0a3c75 --- /dev/null +++ b/src/plugins/sceneformats/assimp/ailoaderiosystem.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef AILOADERIOSYSTEM_H +#define AILOADERIOSYSTEM_H + +#include "IOSystem.h" + +#include <QtCore/qurl.h> + +class QIODevice; +class IOStream; + +class AiLoaderIOSystem : public Assimp::IOSystem +{ +public: + AiLoaderIOSystem(QIODevice *device, QUrl url); + ~AiLoaderIOSystem(); + virtual bool Exists( const char* path) const; + virtual char getOsSeparator() const; + virtual Assimp::IOStream* Open(const char* pFile, const char* pMode = "rb"); + virtual void Close(Assimp::IOStream* pFile); +private: + QIODevice *m_device; + QList<QIODevice*> m_sub; + QUrl m_url; +}; + +#endif // AILOADERIOSYSTEM_H diff --git a/src/plugins/sceneformats/assimp/assimp.pro b/src/plugins/sceneformats/assimp/assimp.pro new file mode 100644 index 000000000..43b74363e --- /dev/null +++ b/src/plugins/sceneformats/assimp/assimp.pro @@ -0,0 +1,32 @@ +TARGET = qsceneai +include(../../qpluginbase.pri) +HEADERS += qailoader.h \ + qaiscene.h \ + qaiscenehandler.h \ + qaimesh.h \ + ailoaderiostream.h \ + ailoaderiosystem.h +SOURCES += main.cpp \ + qailoader.cpp \ + qaiscene.cpp \ + qaiscenehandler.cpp \ + qaimesh.cpp \ + ailoaderiostream.cpp \ + ailoaderiosystem.cpp +CONFIG += qt3d +system_ai { + !isEmpty(QMAKE_INCDIR_AI):INCLUDEPATH += $$QMAKE_INCDIR_AI + !isEmpty(QMAKE_LIBDIR_AI):LIBS += -L$$QMAKE_LIBDIR_AI + + !isEmpty(QMAKE_LIBS_AI):LIBS += -l$$QMAKE_LIBS_AI + else { + win32:LIBS += -llibai-1_3 + else:LIBS += -lai + } +} else { + include(../../../../3rdparty/assimp/assimp.pri) +} + +QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/sceneformats +target.path += $$[QT_INSTALL_PLUGINS]/sceneformats +INSTALLS += target diff --git a/src/plugins/sceneformats/assimp/main.cpp b/src/plugins/sceneformats/assimp/main.cpp new file mode 100644 index 000000000..7251e4627 --- /dev/null +++ b/src/plugins/sceneformats/assimp/main.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglsceneformatplugin.h" +#include "qaiscenehandler.h" + +#include "assimp.hpp" + +#include <QtCore/qmap.h> +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +class QAiScenePlugin : public QGLSceneFormatPlugin +{ +public: + QStringList keys() const; + virtual QGLSceneFormatHandler *create(QIODevice *device, const QUrl& url, const QString &format) const; +}; + +QStringList QAiScenePlugin::keys() const +{ + static QMultiMap<QString, QString> mimetypes; + if (mimetypes.size() == 0) + { + mimetypes.insertMulti("3ds", "application/x-3ds"); + mimetypes.insertMulti("3ds", "image/x-3ds"); + mimetypes.insertMulti("dae", "model/x3d+binary"); + mimetypes.insertMulti("dxf", "application/x-dxf"); + } + QStringList result; + Assimp::Importer importer; + aiString extns; + importer.GetExtensionList(extns); + QString qextns = QString::fromUtf8(extns.data, extns.length); + QStringList extnList = qextns.split(';'); + for (int i = 0; i < extnList.size(); ++i) + { + QString xt = extnList.at(i); + xt = xt.simplified(); + if (xt.startsWith(QLatin1String("*."))) + xt = xt.mid(2); + result << xt; + QMap<QString, QString>::const_iterator it = mimetypes.constFind(xt); + for ( ; it != mimetypes.constEnd(); ++it) + result << it.value(); + } + return result; +} + +QGLSceneFormatHandler *QAiScenePlugin::create(QIODevice *device, const QUrl& url, const QString &format) const +{ + Q_UNUSED(device); + Q_UNUSED(url); + Q_UNUSED(format); + return new QAiSceneHandler; +} + +Q_EXPORT_STATIC_PLUGIN(QAiScenePlugin) +Q_EXPORT_PLUGIN2(qsceneai, QAiScenePlugin) + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/assimp/qailoader.cpp b/src/plugins/sceneformats/assimp/qailoader.cpp new file mode 100644 index 000000000..18ba1e172 --- /dev/null +++ b/src/plugins/sceneformats/assimp/qailoader.cpp @@ -0,0 +1,540 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qailoader.h" +#include "qaiscenehandler.h" +#include "qaiscene.h" +#include "qaimesh.h" + +#include "qgeometrydata.h" +#include "qgltwosidedmaterial.h" +#include "qglmaterial.h" +#include "qglmaterialcollection.h" +#include "qglpainter.h" +#include "qgltexture2d.h" +#include "qglscenenode.h" +#include "qlogicalvertex.h" + +#include "aiScene.h" +#include "aiMaterial.h" +#include "aiMesh.h" +#include "DefaultLogger.h" + +#include <QtCore/qdir.h> +#include <QtCore/qobject.h> +#include <QtCore/qfileinfo.h> + +QAiLoader::QAiLoader(const aiScene *scene, QAiSceneHandler* handler) + : m_scene(scene) + , m_root(0) + , m_handler(handler) + , m_hasTextures(false) + , m_hasLitMaterials(false) + , m_builder(new QGLMaterialCollection(m_root)) +{ +} + +QAiLoader::~QAiLoader() +{ + // nothing to do here - m_rootNode is taken ownership of by caller of + // rootNode() method +} + +static inline void assertOnePrimitiveType(aiMesh *mesh) +{ +#ifndef QT_NO_DEBUG + int k = 0; // count the number of bits set in the primitives + unsigned int msk = 0x01; + for (unsigned int p = mesh->mPrimitiveTypes; p; p >>= 1) + if (p & msk) + ++k; + Q_ASSERT(k == 1); // Assimp SortByPType promises this +#else + Q_UNUSED(mesh); +#endif +} + +static inline bool qHasTextures(const QGLSceneNode *node) +{ + QGLMaterial *mat = node->material(); + for (int i = 0; i < mat->textureLayerCount(); ++i) + if (node->material()->texture(i)) + return true; + return false; +} + +void QAiLoader::loadMesh(aiMesh *mesh) +{ + QString name = QString::fromUtf8(mesh->mName.data, mesh->mName.length); + // qDebug() << "loadMesh" << name << "with" << mesh->mNumVertices << "vertices" + // << "and" << mesh->mNumFaces << "faces"; + + assertOnePrimitiveType(mesh); + + if (mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) + { + m_builder.newSection(); + QGLSceneNode *node = m_builder.currentNode(); + node->setObjectName(name); + QAiMesh m(mesh); + m.build(m_builder, m_handler->showWarnings()); + m_meshes.append(node); + if (qHasTextures(node)) + m_hasTextures = true; + else + m_hasLitMaterials = true; + } + else + { + // TODO: Implement other types in qaimesh.cpp + if (m_handler->showWarnings()) + { + QString error = QLatin1String("Bad primitive type in mesh %1 : %2"); + error = error.arg(name).arg(mesh->mPrimitiveTypes); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + } + } + + if (m_handler->showWarnings()) + { + if (!(m_handler->removeComponentFlags() & aiComponent_COLORS)) + { + // TODO: Implement models with per vertex colors. Ok, the + // vertex colors were wanted but are not supported yet. + for (int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) + { + if (mesh->HasVertexColors(i)) + { + QString error = QLatin1String( + "Found color information in mesh %1, channel %2" + "- per vertex color not yet supported"); + error = error.arg(name).arg(i); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + break; + } + } + } + if (mesh->HasBones()) + { + // TODO: Implement skeletal animation + QString error = QLatin1String("Bones in mesh %1 not yet supported"); + error.arg(name); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + } + if (mesh->HasTangentsAndBitangents()) + { + // TODO: Implement normal maps - here and in the texture import + QString error = QLatin1String("Tangents for normal map in mesh %1 not yet supported"); + error.arg(name); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + } + } +} + +inline static QMatrix4x4 getNodeMatrix(aiNode *node) +{ + QMatrix4x4 nodeMatrix; + if (node->mTransformation.IsIdentity()) + return nodeMatrix; + aiQuaternion rotation; + aiVector3D position; + aiVector3D scale; + node->mTransformation.Decompose(scale, rotation, position); + QVector3D qscale(scale.x,scale.y, scale.z); + QVector3D qposition(position.x, position.y, position.z); + QQuaternion qrotation(rotation.w, rotation.x, rotation.y, rotation.z); + if (!qscale.isNull()) + nodeMatrix.scale(qscale); + if (!qposition.isNull()) + nodeMatrix.translate(qposition); + if (!qrotation.isNull()) + nodeMatrix.rotate(qrotation); + return nodeMatrix; +} + +void QAiLoader::loadNodes(aiNode *nodeList, QGLSceneNode *parentNode) +{ + QMap<aiNode *, QGLSceneNode *>::const_iterator it = m_nodeMap.constFind(nodeList); + QGLSceneNode *node = 0; + if (it == m_nodeMap.constEnd()) // not found + { + node = new QGLSceneNode(parentNode); + m_nodes.append(node); + QString name = QString::fromUtf8(nodeList->mName.data, nodeList->mName.length); + if (name.isEmpty()) + name = QString(QLatin1String("aiNode %1")).arg(m_nodes.size()); + node->setObjectName(name); + QMatrix4x4 mat = getNodeMatrix(nodeList); + if (!mat.isIdentity()) + node->setLocalTransform(mat); + for (unsigned int i = 0; i < nodeList->mNumChildren; ++i) + loadNodes(nodeList->mChildren[i], node); + for (unsigned int i = 0; i < nodeList->mNumMeshes; ++i) + { + int n = nodeList->mMeshes[i]; + if (n < m_meshes.size()) + node->addNode(m_meshes.at(n)); + } + } + else + { + node = it.value(); + parentNode->addNode(node); + } +} + +/*! + \internal + Loads all the geometry, materials, and texture associations from the assigned + file, and returns the root node of the resulting scene graph. + + The caller must take ownership of the root node returned, and delete it + when its no longer required. +*/ +QGLSceneNode *QAiLoader::loadMeshes() +{ + Q_ASSERT(m_scene); + for (unsigned int i = 0; i < m_scene->mNumMaterials; ++i) + loadMaterial(m_scene->mMaterials[i]); + + // builds a naive scene heierarchy with all meshes under the root node + for (unsigned int i = 0; i < m_scene->mNumMeshes; ++i) + loadMesh(m_scene->mMeshes[i]); + + // fetch the naive scene heierarchy from the builder + m_root = m_builder.finalizedSceneNode(); + + QString name = m_handler->url().path(); + int pos = name.lastIndexOf("/"); + if (pos == -1) + pos = name.lastIndexOf("\\"); + if (pos != -1) + name = name.mid(pos+1); + m_root->setObjectName(name); + + // if scene has a node heierarchy replace the naive heierarchy with that + if (m_scene->mRootNode->mNumChildren > 0 && m_scene->mRootNode->mChildren) + { + m_root->removeNodes(m_root->children()); + loadNodes(m_scene->mRootNode, m_root); + } + + if (m_hasTextures) // make textures the default + { + m_root->setEffect(QGL::LitModulateTexture2D); + if (m_hasLitMaterials) + { + for (int i = 0; i < m_meshes.size(); ++i) + if (!qHasTextures(m_meshes.at(i))) + m_meshes.at(i)->setEffect(QGL::LitMaterial); + } + } + else + { + m_root->setEffect(QGL::LitMaterial); + } + + if (m_handler->showWarnings()) + { + QString message = QLatin1String("AssetImporter loader %1 -- " + "Mesh count: %2 -- Node count: %3 -- " + "Material count: %4"); + QUrl url = m_handler->url(); + message = message.arg(url.toString()).arg(m_meshes.size()) + .arg(m_nodes.size()).arg(m_root->palette()->size()); + Assimp::DefaultLogger::get()->warn(message.toStdString()); + } + +//#define DEBUG_ME +#ifdef DEBUG_ME + qDumpScene(m_root); + + QList<QGLSceneNode*> c = m_root->allChildren(); + QSet<quint64> debugged; + for (int i = 0; i < c.size(); ++i) + { + if (c.at(i)->geometry().count() > 0) + { + QGeometryData g = c.at(i)->geometry(); + qDebug() << "geometry for:" << c.at(i) << "is:" << g.id(); + if (!debugged.contains(g.id())) + { + qDebug() << g; + debugged.insert(g.id()); + } + } + } +#endif + + return m_root; +} + +/*! + \internal + Search for a resource based on the given \a path. + + If the URL for the currently loading mesh has a scheme other than + "file" then a URL with the path relative to that URL is returned. + + If the URL for the currently loading mesh has a "file" scheme, then + first a case-sensitive search is done of all of the current directory, + and the :/ resource directory, and the directory of the current mesh + file. + + If the file is not found in any of those locations then they are + searched again case-insensitively. If the file is found, then a + URL based on the absolute file path of the matching file is returned. + + Otherwise an empty string is returned. +*/ +QUrl QAiLoader::ensureResource(const QString &path) +{ + QUrl res; + QUrl base = m_handler->url(); + if (base.scheme() == QLatin1String("file")) + { + res = base.resolved(path); + //qDebug() << "ensureResource - base:" << base + // << " -- path:" << path << "-- resolved:" << res; + if (QFile::exists(res.path())) // shortcut common case + return res; + QStringList paths; + paths << QLatin1String(".") << QLatin1String(":/"); // current directory and aliased/root resource file + if (!base.isEmpty()) + { + QFileInfo fi(base.path()); + paths.prepend(fi.absoluteDir().absolutePath()); + } + bool caseInsensitive = false; + do { + QStringList::const_iterator it(paths.begin()); + for ( ; it != paths.end(); ++it) + { + QDir resDir(*it); + QStringList fileList = resDir.entryList(QDir::Files); + if (caseInsensitive) + { + QStringList::const_iterator fit(fileList.begin()); + for ( ; fit != fileList.end(); ++fit) + { + if (fit->toLower() == path.toLower()) + { + res.setScheme(QLatin1String("file")); + res.setPath(resDir.absoluteFilePath(*fit)); + break; + } + } + } + else + { + if (fileList.contains(path)) + { + //return resDir.absoluteFilePath(path); + res.setScheme(QLatin1String("file")); + res.setPath(resDir.absoluteFilePath(path)); + break; + } + } + } + if (caseInsensitive) + break; + caseInsensitive = true; + } while (true); + } + else + { + // non-file url + res = base.resolved(path); + } + return res; +} + +void QAiLoader::loadTextures(aiMaterial *ma, QGLMaterial *mq) +{ + int texCount; + aiTextureType texType; + if (m_handler->showWarnings()) + { + // TODO: AssImp has a very rich set of texture related functionality + // but until things in this list get implemented, all are just going + // to get ignored, tho' they will generate a warning in verbose mode. + // So for now, its just inside this conditional - move it out once + // implementation of these items begins. + static const char * typeNames[] = { + "None", + "Diffuse", + "Specular", + "Ambient", + "Emissive", + "Height", + "Normals", + "Shininess", + "Opacity", + "Displacement", + "Lightmap", + "Reflection", + "Unknown", + 0 + }; + + for (unsigned int i = 0; i <= aiTextureType_UNKNOWN; ++i) + { + texType = static_cast<aiTextureType>(i); + texCount = ma->GetTextureCount(texType); + if (texCount && texType != aiTextureType_DIFFUSE) + { + QString error = QLatin1String("Unsupported texture type \"%1\" in material \"%2\"."); + error.arg(typeNames[i]).arg(mq->objectName()); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + } + } + } + // TODO: For now assume the only texture we care about is the diffuse one + // + texCount = ma->GetTextureCount(aiTextureType_DIFFUSE); + if (texCount > 0) + { + if (texCount > 1 && m_handler->showWarnings()) + { + QString error = QLatin1String("Multi-textures not supported: \"%1\" has %2"); + error.arg(mq->objectName()).arg(texCount); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + } + else + { + aiString path; + ma->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), path); + QString qpath = QString::fromUtf8(path.data, path.length); + QUrl url = ensureResource(qpath); + if (url.isEmpty()) + { + if (m_handler->showWarnings()) + { + QString error = QLatin1String("Could not load texture: %1 for material %2"); + error.arg(url.toString()).arg(mq->objectName()); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + } + } + else + { + mq->setTextureUrl(url); + } + } + } +} + +/*! + Load a material +*/ +void QAiLoader::loadMaterial(aiMaterial *ma) +{ + QGLMaterialCollection *palette = m_builder.palette(); + QGLMaterial *mq = new QGLMaterial; + mq->setObjectName("___DEFAULT_NAME___"); + + bool isTwoSided = false; + bool isWireframe = false; + struct aiColor4D clr; + float shininess; + float amplitude; + + aiReturn r; + + // In AssImp if the material is two sided then it means cull face + // should be turned off: http://assimp.sourceforge.net/lib_html/materials.html + r = ma->Get(AI_MATKEY_TWOSIDED, isTwoSided); + if (r == aiReturn_SUCCESS && isTwoSided) + mq->setProperty("isTwoSided", isTwoSided); + + aiString aName; + r = ma->Get(AI_MATKEY_NAME, aName); + if (r == aiReturn_SUCCESS && aName.length > 0) + mq->setObjectName(QLatin1String(aName.data)); + + r = ma->Get(AI_MATKEY_COLOR_AMBIENT, clr); + if (r == aiReturn_SUCCESS) + mq->setAmbientColor(QColor::fromRgbF(clr.r, clr.g, clr.b, clr.a)); + + r = ma->Get(AI_MATKEY_COLOR_DIFFUSE, clr); + if (r == aiReturn_SUCCESS) + mq->setDiffuseColor(QColor::fromRgbF(clr.r, clr.g, clr.b, clr.a)); + + r = ma->Get(AI_MATKEY_COLOR_SPECULAR, clr); + if (r == aiReturn_SUCCESS) + { + QColor spec = QColor::fromRgbF(clr.r, clr.g, clr.b, clr.a); + mq->setSpecularColor(spec); + + // By default the specular color is black - very dark. + // If the specular color is bright be careful with shininess - + // a shininess of 0 will blow everything out to full white. So in + // the case of bad materials with this problem, set shiness low. + mq->setShininess(64.0); + } + + r = ma->Get(AI_MATKEY_SHININESS, shininess); + if (r == aiReturn_SUCCESS) + mq->setShininess(shininess); + + r = ma->Get(AI_MATKEY_SHININESS_STRENGTH, amplitude); + if (r == aiReturn_SUCCESS) + mq->setShininess(shininess * amplitude); + + r = ma->Get(AI_MATKEY_ENABLE_WIREFRAME, isWireframe); + if (r == aiReturn_SUCCESS && isWireframe) + mq->setProperty("isWireFrame", isWireframe); + + loadTextures(ma, mq); + + // INVARIANT: since we create the palette newly in this class, and this + // function is the only place we add materials to this palette, the index + // values (the positions in the palette generated by this call to addMaterial) + // will exactly match the index values of the materials traversed in the + // for loop in loadMeshes() - so therefore AI's index values and the ones in + // the palette will be the same. + // + // executive summary: don't muck around with the palettte outside of this call + + int k = palette->addMaterial(mq); + + Q_UNUSED(k); + //qDebug() << "loaded material" << k << mq; +} diff --git a/src/plugins/sceneformats/assimp/qailoader.h b/src/plugins/sceneformats/assimp/qailoader.h new file mode 100644 index 000000000..cb646295a --- /dev/null +++ b/src/plugins/sceneformats/assimp/qailoader.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSLOADER_H +#define QGL3DSLOADER_H + +#include <QtCore/qurl.h> +#include <QtCore/qstring.h> +#include <QtCore/qmap.h> + +#include "aiScene.h" + +#include "qglbuilder.h" + +class QAiMesh; +class QGLSceneNode; +class QAiSceneHandler; +class QGLMaterial; +class aiMaterial; + +class QAiLoader +{ +public: + QAiLoader(const aiScene *scene, QAiSceneHandler* handler); + ~QAiLoader(); + QGLSceneNode *loadMeshes(); + +private: + void loadMesh(aiMesh *); + void loadNodes(aiNode *, QGLSceneNode *); + void loadMaterial(aiMaterial *); + void loadTextures(aiMaterial *, QGLMaterial *); + QUrl ensureResource(const QString &); + void optimizeData(); + void optimizeNodes(QGLSceneNode *node = 0, QGLSceneNode *parent = 0); + void countChildNodeReferences(); + + const aiScene *m_scene; + QGLSceneNode *m_root; + QAiSceneHandler *m_handler; + QList<QGLSceneNode *> m_nodes; + QList<QGLMaterial *> m_materials; + QList<QGLSceneNode *> m_meshes; + QMap<aiNode *, QGLSceneNode *> m_nodeMap; + QMap<QGLSceneNode *, int> m_refCounts; + bool m_hasTextures; + bool m_hasLitMaterials; + QGLBuilder m_builder; +}; + +#endif // QGL3DSLOADER_H diff --git a/src/plugins/sceneformats/assimp/qaimesh.cpp b/src/plugins/sceneformats/assimp/qaimesh.cpp new file mode 100644 index 000000000..e7d801dc3 --- /dev/null +++ b/src/plugins/sceneformats/assimp/qaimesh.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaimesh.h" +#include "qglscenenode.h" +#include "qglmaterialcollection.h" +#include "qglbuilder.h" + +#include <QtGui/qmatrix4x4.h> +#include <QtCore/qmath.h> + +#include "aiMesh.h" +#include "DefaultLogger.h" + +QAiMesh::QAiMesh(aiMesh *mesh) + : m_mesh(mesh) +{ +} + +QAiMesh::~QAiMesh() +{ +} + +static inline QVector2D qv2d(const aiVector3D &v) +{ + return QVector2D(v.x, v.y); +} + +static inline QVector2D qv2d_inv(const aiVector3D &v) +{ + // invert the v coord because Qt is upside-down + return QVector2D(v.x, (1.0 - v.y)); +} + +static inline QVector3D qv3d(const aiVector3D &v) +{ + return QVector3D(v.x, v.y, v.z); +} + +void QAiMesh::loadTriangles(QGLBuilder &builder) +{ + QGeometryData data; + for (unsigned int i = 0; i < m_mesh->mNumVertices; ++i) + data.appendVertex(qv3d(m_mesh->mVertices[i])); + if (m_mesh->HasNormals()) + for (unsigned int i = 0; i < m_mesh->mNumVertices; ++i) + data.appendNormal(qv3d(m_mesh->mNormals[i])); + int k = m_mesh->GetNumUVChannels(); + for (int t = 0; t < k; ++t) + { + if (m_mesh->mNumUVComponents[t] != 2) + Assimp::DefaultLogger::get()->warn("Tex co-ords only supports U & V"); + QGLMaterial *m = builder.currentNode()->material(); + if (m && m->textureUrl().path().endsWith(".dds", Qt::CaseInsensitive)) + { + for (unsigned int i = 0; i < m_mesh->mNumVertices; ++i) + data.appendTexCoord(qv2d_inv(m_mesh->mTextureCoords[t][i]), static_cast<QGL::VertexAttribute>(QGL::TextureCoord0 + t)); + } + else + { + for (unsigned int i = 0; i < m_mesh->mNumVertices; ++i) + data.appendTexCoord(qv2d(m_mesh->mTextureCoords[t][i]), static_cast<QGL::VertexAttribute>(QGL::TextureCoord0 + t)); + } + } + + for (unsigned int i = 0; i < m_mesh->mNumFaces; ++i) + { + aiFace *face = &m_mesh->mFaces[i]; + data.appendIndices(face->mIndices[0], face->mIndices[1], face->mIndices[2]); + } + + // raw triangle mode + builder.addTriangles(data); +} + +void QAiMesh::build(QGLBuilder &builder, bool showWarnings) +{ + QGLSceneNode *node = builder.currentNode(); + QString name = node->objectName(); + + if (!m_mesh->HasFaces() || !m_mesh->HasPositions()) + { + if (showWarnings) + { + QString error = QLatin1String("Mesh %1 has zero vertex/face count"); + error.arg(name.isEmpty() ? QString(QLatin1String("<unnamed mesh>")) : name); + Assimp::DefaultLogger::get()->warn(error.toStdString()); + } + return; + } + + node->setMaterialIndex(m_mesh->mMaterialIndex); + node->palette()->markMaterialAsUsed(m_mesh->mMaterialIndex); + + if (m_mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) + loadTriangles(builder); + else + return; // TODO - lines, points, quads, polygons + + QGLMaterial * mat = node->palette()->material(m_mesh->mMaterialIndex); + if (mat->property("isTwoSided").isValid() && mat->property("isTwoSided").toBool()) + node->setBackMaterialIndex(m_mesh->mMaterialIndex); + if (mat->property("isWireFrame").isValid() && mat->property("isWireFrame").toBool()) + node->setDrawingMode(QGL::Lines); +} diff --git a/src/plugins/sceneformats/assimp/qaimesh.h b/src/plugins/sceneformats/assimp/qaimesh.h new file mode 100644 index 000000000..2a1efc2ed --- /dev/null +++ b/src/plugins/sceneformats/assimp/qaimesh.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSMESH_H +#define QGL3DSMESH_H + +#include "qaiscenehandler.h" +#include "qgeometrydata.h" + +class QGLMaterialCollection; +class QGLSceneNode; +class QGLBuilder; +class QAiLoader; +class aiMesh; + +class QAiMesh +{ +public: + QAiMesh(aiMesh *mesh); + virtual ~QAiMesh(); + + void build(QGLBuilder &builder, bool showWarnings = false); +private: + void loadTriangles(QGLBuilder &builder); + + aiMesh *m_mesh; +}; + +#endif // QGL3DSMESH_H diff --git a/src/plugins/sceneformats/assimp/qaiscene.cpp b/src/plugins/sceneformats/assimp/qaiscene.cpp new file mode 100644 index 000000000..cb67fe99d --- /dev/null +++ b/src/plugins/sceneformats/assimp/qaiscene.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaiscene.h" +#include "qaimesh.h" +#include "qailoader.h" + +#include "qglscenenode.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QAiScene + \brief The QAiScene class manages and contains an asset importer scene. + The scene consists of a QGLSceneNode object which is the top-level node + and references the geometry imported from a file. +*/ + +/*! + \internal + Construct a new QAiScene object using the data in the \a scene, + and setting the given \a parent. + + The QAiScene object takes ownership of the \a file. +*/ +QAiScene::QAiScene(const aiScene *scene, QAiSceneHandler *handler) + : QGLAbstractScene(0) +{ + Q_ASSERT(handler); + Q_ASSERT(scene); + QAiLoader loader(scene, handler); + m_root = loader.loadMeshes(); +} + +/*! + \reimp + Destroy this QAiScene, recovering all resources. + + This method destroys the Lib3dsFile object the scene is based + on by calling the appropriate lib3ds function. +*/ +QAiScene::~QAiScene() +{ + // nothing to do here +} + +/*! + \internal + \reimp +*/ +QList<QObject *> QAiScene::objects() const +{ + QList<QObject *> objs; + if (!m_root) + return objs; + objs.append(m_root); + QList<QGLSceneNode*> children = m_root->allChildren(); + QList<QGLSceneNode*>::const_iterator it = children.constBegin(); + for ( ; it != children.constEnd(); ++it) + objs.append(*it); + return objs; +} + +/*! + \internal + \reimp +*/ +QGLSceneNode *QAiScene::mainNode() const +{ + return m_root; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/assimp/qaiscene.h b/src/plugins/sceneformats/assimp/qaiscene.h new file mode 100644 index 000000000..5b9aac58c --- /dev/null +++ b/src/plugins/sceneformats/assimp/qaiscene.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSSCENE_H +#define QGL3DSSCENE_H + +#include "qglabstractscene.h" + +#include "aiScene.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +#include <QtCore/qurl.h> + +class QGLSceneNode; +class QAiSceneHandler; + +class QAiScene : public QGLAbstractScene +{ + Q_OBJECT +public: + explicit QAiScene(const aiScene *scene, QAiSceneHandler *handler); + virtual ~QAiScene(); + + QList<QObject *> objects() const; + QGLSceneNode *mainNode() const; +private: + QGLSceneNode *m_root; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/plugins/sceneformats/assimp/qaiscenehandler.cpp b/src/plugins/sceneformats/assimp/qaiscenehandler.cpp new file mode 100644 index 000000000..259bf9021 --- /dev/null +++ b/src/plugins/sceneformats/assimp/qaiscenehandler.cpp @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qaiscenehandler.h" +#include "qaiscene.h" +#include "ailoaderiosystem.h" + +#include "aiScene.h" +#include "aiPostProcess.h" +#include "DefaultLogger.h" + +#include <QtCore/qdir.h> +#include <QtCore/qdebug.h> + + +#define qAiPostProcessPreset ( \ + aiProcess_CalcTangentSpace | \ + aiProcess_GenSmoothNormals | \ + aiProcess_JoinIdenticalVertices | \ + aiProcess_ImproveCacheLocality | \ + aiProcess_LimitBoneWeights | \ + aiProcess_RemoveRedundantMaterials | \ + aiProcess_SplitLargeMeshes | \ + aiProcess_Triangulate | \ + aiProcess_GenUVCoords | \ + aiProcess_SortByPType | \ + aiProcess_FindDegenerates | \ + aiProcess_FindInvalidData ) + +QT_BEGIN_NAMESPACE + +QAiSceneHandler::QAiSceneHandler() + : m_options(qAiPostProcessPreset) + , m_showWarnings(false) + , m_mayHaveLinesPoints(false) + , m_meshSplitVertexLimit(2000) + , m_meshSplitTriangleLimit(2000) + , m_removeComponentFlags(0) + , m_removeSortFlags(0) +{ + // by default remove per vertex colors from the data - no-one uses that in + // models - if they need it it can be turned on with UseVertexColors + m_removeComponentFlags |= aiComponent_COLORS; + + // by default remove points and lines from the model, since these are usually + // degenerate structures from bad modelling or bad import/export. if they + // are needed it can be turned on with IncludeLinesPoints + m_removeSortFlags |= aiPrimitiveType_POINT | aiPrimitiveType_LINE; +} + +QAiSceneHandler::~QAiSceneHandler() +{ + // nothing to do +} + +void QAiSceneHandler::decodeOptions(const QString &options) +{ + if (options.isEmpty()) + return; + + // See aiPostProcess.h for aiProcessPreset_TargetRealtime_Quality + // - a useful default set of values - its exactly what we want but + // wont compile with flags, so redefined with the above macro. + // Also, allow the user to override some settings + m_options = qAiPostProcessPreset; + + // Has to match the enum + static const char *validOptions[] = { + "NoOptions", + "ShowWarnings", + "CalculateNormals", + "ForceFaceted", + "ForceSmooth", + "IncludeAllMaterials", + "IncludeLinesPoints", + "FixNormals", + "DeDupMeshes", + "Optimize", + "FlipUVs", + "FlipWinding", + "UseVertexColors", + "VertexSplitLimitx2", + "TriangleSplitLimitx2", + 0 + }; + + QStringList opList = options.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); + + for (int i = 0; i < opList.count(); ++i) + { + QString op = opList.at(i); + op = op.trimmed(); + int k = 0; + for ( ; validOptions[k]; ++k) + if (op == QString::fromLatin1(validOptions[k])) + break; + if (validOptions[k]) // found + { + Options o = static_cast<Options>(k); + switch (o) + { + case NoOptions: + break; + case ShowWarnings: + if (m_showWarnings) + { + m_importer.SetExtraVerbose(true); + } + else + { + m_showWarnings = true; + m_options |= aiProcess_ValidateDataStructure; + } + break; + case CalculateNormals: + m_removeComponentFlags |= aiComponent_NORMALS; + m_options |= aiProcess_GenSmoothNormals; + m_options &= ~aiProcess_GenNormals; + break; + case ForceFaceted: + m_removeComponentFlags |= aiComponent_NORMALS; + m_options |= aiProcess_GenNormals; + m_options &= ~aiProcess_GenSmoothNormals; + m_options &= ~aiProcess_JoinIdenticalVertices; + break; + case ForceSmooth: + Assimp::DefaultLogger::get()->warn("ForceSmooth is deprecated - ignoring (meshes now smooth by default)"); + break; + case IncludeAllMaterials: + m_options &= ~aiProcess_RemoveRedundantMaterials; + break; + case IncludeLinesPoints: + m_removeSortFlags &= ~(aiPrimitiveType_LINE | aiPrimitiveType_POINT); + m_mayHaveLinesPoints = true; + // leave it with the FindDegenerates option turned on - we want zero + // area triangles to display as proper GL lines or points + break; + case FixNormals: + m_options |= aiProcess_FixInfacingNormals; + break; + case DeDupMeshes: + m_options |= aiProcess_FindInstances; + break; + case Optimize: + m_options |= aiProcess_OptimizeGraph | aiProcess_OptimizeMeshes; + break; + case FlipUVs: + m_options |= aiProcess_FlipUVs; + break; + case FlipWinding: + m_options |= aiProcess_FlipWindingOrder; + break; + case UseVertexColors: + m_removeComponentFlags &= ~aiComponent_COLORS; + break; + case VertexSplitLimitx2: + m_meshSplitVertexLimit <<= 1; + // repeating this in the option string more than once works... + break; + case TriangleSplitLimitx2: + // ....and we're OK with that, just don't overdo it + m_meshSplitTriangleLimit <<= 1; + break; + } + } + else + { + qWarning("Bad option: \"%s\" in %s", qPrintable(op), + qPrintable(options)); + } + } +} + +QGLAbstractScene *QAiSceneHandler::read() +{ + AiLoaderIOSystem *ios = new AiLoaderIOSystem(device(), url()); + m_importer.SetIOHandler(ios); + + Assimp::Logger *log = 0; + Assimp::Logger::LogSeverity severity = Assimp::Logger::NORMAL; + if (m_showWarnings) + { + severity = Assimp::Logger::VERBOSE; + int streams = aiDefaultLogStream_FILE | +#ifdef Q_CC_MSVC + aiDefaultLogStream_DEBUGGER +#else + aiDefaultLogStream_STDERR +#endif + ; + log = Assimp::DefaultLogger::create("AssimpLog.txt", severity, streams); + } + + QString path; + QUrl u = url(); + if (u.scheme() != QLatin1String("file")) + { + qWarning("Non-file URL's not yet supported"); + return 0; + } + path = u.toLocalFile(); + + if (m_removeComponentFlags) + m_options |= aiProcess_RemoveComponent; + else + m_options &= ~aiProcess_RemoveComponent; + + m_importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, m_removeComponentFlags); + m_importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, m_removeSortFlags); + m_importer.SetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT, m_meshSplitVertexLimit); + m_importer.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, m_meshSplitTriangleLimit); + + // force this on, and provide no way to turn it off. Its set by the + // aiProcessPreset_TargetRealtime_Quality option in the constructor. + // Guarantees that all meshes only have one primitive type + Q_ASSERT(m_options & aiProcess_SortByPType); + + // the importer owns the scene, so when the it goes out of scope on exiting + // this function the scene will get destroyed + const aiScene* scene = m_importer.ReadFile(path.toStdString(), m_options); + + if (!scene) + { + // Notes on import success flags - according to assimp doco if validation + // is requested the flags AI_SCENE_FLAGS_VALIDATION_WARNING will be set + // if there's a warning, and AI_SCENE_FLAGS_VALIDATED is set on success. + // This does not happen. Also AI_SCENE_FLAGS_INCOMPLETE can be set on a + // valid model, so checking for that is no use either. Best way to proceed + // is that if ShowWarnings is turned on above, then any pertinent warnings + // will be shown; and if a NULL result is returned here, then its a fatal + // error and a message is shown here. If a non-NULL result is returned + // just go ahead and try to load it. + QString c = QDir::current().absolutePath(); + qWarning("Asset importer error: %s\n", m_importer.GetErrorString()); + if (log) + qWarning("For details check log: %s/AssimpLog.txt\n", qPrintable(c)); + return 0; + } + + + QAiScene *qscene = new QAiScene(scene, this); + + Assimp::DefaultLogger::kill(); + + return qscene; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/assimp/qaiscenehandler.h b/src/plugins/sceneformats/assimp/qaiscenehandler.h new file mode 100644 index 000000000..95b800025 --- /dev/null +++ b/src/plugins/sceneformats/assimp/qaiscenehandler.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGL3DSSCENEHANDLER_H +#define QGL3DSSCENEHANDLER_H + +#include "qglsceneformatplugin.h" + +#include "aiPostProcess.h" +#include "assimp.hpp" + +#include <QtCore/qurl.h> + +Q_DECLARE_FLAGS(aiPostProcessFlags, aiPostProcessSteps); +Q_DECLARE_OPERATORS_FOR_FLAGS(aiPostProcessFlags); + + +QT_BEGIN_NAMESPACE + +class QIODevice; + +class QAiSceneHandler : public QGLSceneFormatHandler +{ +public: + enum Options { + NoOptions, + ShowWarnings, // show any warnings while loading the file + CalculateNormals, // replace normals from the file with smooth generated ones + ForceFaceted, // generate non-smooth normals (implies CalculateNormals) + ForceSmooth, // deprecated - retained only for backward compatibility + IncludeAllMaterials, // include even redundant (unused) materials + IncludeLinesPoints, // include even collapsed triangles (lines or points) + FixNormals, // try to fix incorrect (in facing) normals + DeDupMeshes, // replace copied meshes with refs to a single instance + Optimize, // collapse meshes, nodes & scene heierarchies + FlipUVs, // flips UV's on the y-axis (for upside-down textures) + FlipWinding, // makes faces CW instead of CCW + UseVertexColors, // use vertex colors that are in a model + VertexSplitLimitx2, // double the vertex count which will split a large mesh + TriangleSplitLimitx2 // double the triangle count which will split a large mesh + }; + + QAiSceneHandler(); + ~QAiSceneHandler(); + + QGLAbstractScene *read(); + + void decodeOptions(const QString &options); + + bool showWarnings() const { return m_showWarnings; } + bool mayHaveLinesPoints() const { return m_mayHaveLinesPoints; } + + aiPostProcessFlags options() const { return m_options; } + quint32 removeComponentFlags() const { return m_removeComponentFlags; } + quint32 removeSortFlags() const { return m_removeSortFlags; } + +private: + aiPostProcessFlags m_options; + bool m_showWarnings; + bool m_mayHaveLinesPoints; + int m_meshSplitVertexLimit; + int m_meshSplitTriangleLimit; + Assimp::Importer m_importer; + quint32 m_removeComponentFlags; + quint32 m_removeSortFlags; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/sceneformats/bezier/bezier.pro b/src/plugins/sceneformats/bezier/bezier.pro new file mode 100644 index 000000000..bed100239 --- /dev/null +++ b/src/plugins/sceneformats/bezier/bezier.pro @@ -0,0 +1,12 @@ +TARGET = qscenebezier +include(../../qpluginbase.pri) + +HEADERS += qglbezierscene.h \ + qglbezierscenehandler.h +SOURCES += main.cpp \ + qglbezierscene.cpp \ + qglbezierscenehandler.cpp +CONFIG += qt3d +QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/sceneformats +target.path += $$[QT_INSTALL_PLUGINS]/sceneformats +INSTALLS += target diff --git a/src/plugins/sceneformats/bezier/main.cpp b/src/plugins/sceneformats/bezier/main.cpp new file mode 100644 index 000000000..592da2e69 --- /dev/null +++ b/src/plugins/sceneformats/bezier/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglsceneformatplugin.h" +#include "qglbezierscenehandler.h" + +QT_BEGIN_NAMESPACE + +class QGLBezierScenePlugin : public QGLSceneFormatPlugin +{ +public: + QStringList keys() const; + virtual QGLSceneFormatHandler *create(QIODevice *device, const QUrl& url, const QString &format) const; +}; + +QStringList QGLBezierScenePlugin::keys() const +{ + return QStringList() << QLatin1String("bezier") << QLatin1String("bez"); +} + +QGLSceneFormatHandler *QGLBezierScenePlugin::create(QIODevice *device, const QUrl& url, const QString &format) const +{ + Q_UNUSED(device); + Q_UNUSED(url); + Q_UNUSED(format); + return new QGLBezierSceneHandler; +} + +Q_EXPORT_STATIC_PLUGIN(QGLBezierScenePlugin) +Q_EXPORT_PLUGIN2(qscenebezier, QGLBezierScenePlugin) + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/bezier/qglbezierscene.cpp b/src/plugins/sceneformats/bezier/qglbezierscene.cpp new file mode 100644 index 000000000..d0428c876 --- /dev/null +++ b/src/plugins/sceneformats/bezier/qglbezierscene.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglbezierscene.h" +#include "qglscenenode.h" + +QT_BEGIN_NAMESPACE + +QGLBezierScene::QGLBezierScene(QGLSceneNode *geometry, QObject *parent) + : QGLAbstractScene(parent) +{ + mainObject = geometry; + mainObject->setObjectName(QLatin1String("mesh")); // No tr + mainObject->setParent(this); +} + +QGLBezierScene::~QGLBezierScene() +{ +} + +QList<QObject *> QGLBezierScene::objects() const +{ + QList<QObject *> objs; + objs.append(mainObject); + return objs; +} + +QGLSceneNode *QGLBezierScene::mainNode() const +{ + return mainObject; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/bezier/qglbezierscene.h b/src/plugins/sceneformats/bezier/qglbezierscene.h new file mode 100644 index 000000000..2c34c9e5b --- /dev/null +++ b/src/plugins/sceneformats/bezier/qglbezierscene.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLBEZIERSCENE_H +#define QGLBEZIERSCENE_H + +#include "qglabstractscene.h" +#include "qglbuilder.h" + +QT_BEGIN_NAMESPACE + +class QGLBezierScene : public QGLAbstractScene +{ + Q_OBJECT +public: + explicit QGLBezierScene(QGLSceneNode *geometry, QObject *parent = 0); + virtual ~QGLBezierScene(); + + QList<QObject *> objects() const; + QGLSceneNode *mainNode() const; + +private: + QGLSceneNode *mainObject; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/sceneformats/bezier/qglbezierscenehandler.cpp b/src/plugins/sceneformats/bezier/qglbezierscenehandler.cpp new file mode 100644 index 000000000..dfe579cd8 --- /dev/null +++ b/src/plugins/sceneformats/bezier/qglbezierscenehandler.cpp @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglbezierscenehandler.h" +#include "qglbezierscene.h" +#include "qglbezierpatches.h" +#include "qvector3darray.h" +#include <QtCore/qtextstream.h> + +QT_BEGIN_NAMESPACE + +QGLAbstractScene *QGLBezierSceneHandler::read() +{ + QTextStream stream(device()); + QGLBezierPatches patches; + + // Read the number of patches from the first line. + int patchCount; + stream >> patchCount; + stream.skipWhiteSpace(); + if (patchCount <= 0) + return 0; + + // Read the patch indices. + int depth = 0; + QArray<int> indices; + for (int patch = 0; patch < patchCount; ++patch) { + bool eoln = false; + for (int index = 0; index < 16; ++index) { + int value; + stream >> value; + indices.append(value - 1); + + QChar sep; + stream >> sep; + if (sep == QLatin1Char('\n') || sep == QLatin1Char('\r')) { + eoln = true; + break; + } + if (sep != QLatin1Char(',')) + return 0; + } + if (!eoln) { + // Optional sub-division depth on the end. + int value; + stream >> value; + if (value > depth) + depth = value; + } + stream.skipWhiteSpace(); + } + + // Read the number of vertices. + int vertexCount; + stream >> vertexCount; + stream.skipWhiteSpace(); + if (vertexCount <= 0) + return 0; + + // Read the vertex values. + QVector3DArray vertices; + for (int vertex = 0; vertex < vertexCount; ++vertex) { + bool eoln = false; + qreal coords[3] = {0.0f, 0.0f, 0.0f}; + for (int index = 0; index < 3; ++index) { + stream >> coords[index]; + + QChar sep; + stream >> sep; + if (sep == QLatin1Char('\n') || sep == QLatin1Char('\r')) { + eoln = true; + break; + } + if (sep != QLatin1Char(',')) + return 0; + } + vertices.append(coords[0], coords[1], coords[2]); + if (!eoln) { + // Optional normal on the end: read it but discard. + for (int index = 0; index < 3; ++index) { + stream >> coords[index]; + + QChar sep; + stream >> sep; + if (sep == QLatin1Char('\n') || sep == QLatin1Char('\r')) + break; + if (sep != QLatin1Char(',')) + return 0; + } + } + stream.skipWhiteSpace(); + } + + // Check for options at the end of the stream. + if (!stream.atEnd()) { + QChar ch; + stream >> ch; + if (ch == QLatin1Char('#')) { + QString options = stream.readLine(); + if (options.contains(QLatin1String("teapot-adjust"))) { + // Perform the "teapot adjustment" to convert the raw + // teapot data into something more suitable for applications. + // i.e., do the equivalent of the following transformation: + // matrix.rotate(270.0f, 1.0f, 0.0f, 0.0f); + // matrix.scale(0.5f, 0.5f, 0.5f); + // matrix.translate(0.0f, 0.0f, -1.5f); + for (int vertex = 0; vertex < vertexCount; ++vertex) { + QVector3D vec = vertices[vertex]; + qreal x = vec.x(); + qreal y = vec.y(); + qreal z = vec.z(); + z -= 1.5f; + x *= 0.5f; + y *= 0.5f; + z *= 0.5f; + qreal y2 = z; + qreal z2 = -y; + y = y2; + z = z2; + vertices[vertex] = QVector3D(x, y, z); + } + } + if (options.contains(QLatin1String("reverse-patches"))) { + // Reverse the patch order to convert clockwise + // patches into standard anti-clockwise patches. + QArray<int> newIndices; + for (int patch = 0; patch < patchCount; ++patch) { + int temp[16]; + for (int index = 0; index < 16; ++index) + temp[index] = indices[patch * 16 + index]; + for (int i = 0; i < 16; ++i) + newIndices.append(temp[(i & 0x0C) + (3 - (i % 4))]); + } + indices = newIndices; + } + } + } + + // Create the geometry node from the Bezier patch data. + if (depth != 0) + patches.setSubdivisionDepth(depth); + QVector3DArray positions; + for (int pindex = 0; pindex < indices.size(); ++pindex) + positions += vertices[indices[pindex]]; + patches.setPositions(positions); + QGLBuilder geometry; + geometry << patches; + + // Create a scene with a single object containing the geometry. + return new QGLBezierScene(geometry.finalizedSceneNode()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/bezier/qglbezierscenehandler.h b/src/plugins/sceneformats/bezier/qglbezierscenehandler.h new file mode 100644 index 000000000..ce43c28e9 --- /dev/null +++ b/src/plugins/sceneformats/bezier/qglbezierscenehandler.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLBEZIERSCENEHANDLER_H +#define QGLBEZIERSCENEHANDLER_H + +#include "qglsceneformatplugin.h" + +QT_BEGIN_NAMESPACE + +class QGLBezierSceneHandler : public QGLSceneFormatHandler +{ +public: + QGLAbstractScene *read(); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/sceneformats/obj/main.cpp b/src/plugins/sceneformats/obj/main.cpp new file mode 100644 index 000000000..1da55b4e7 --- /dev/null +++ b/src/plugins/sceneformats/obj/main.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglsceneformatplugin.h" +#include "qglobjscenehandler.h" + +QT_BEGIN_NAMESPACE + +//! [1] +class QGLObjScenePlugin : public QGLSceneFormatPlugin +{ +public: + QStringList keys() const; + virtual QGLSceneFormatHandler *create(QIODevice *device, const QUrl& url, const QString &format) const; +}; +//! [1] + +//! [2] +QStringList QGLObjScenePlugin::keys() const +{ + return QStringList() << QLatin1String("obj") << QLatin1String("model/obj"); +} +//! [2] + +//! [3] +QGLSceneFormatHandler *QGLObjScenePlugin::create(QIODevice *device, const QUrl& url, const QString &format) const +{ + Q_UNUSED(device); + Q_UNUSED(url); + Q_UNUSED(format); + return new QGLObjSceneHandler; +} +//! [3] + +//! [4] +Q_EXPORT_STATIC_PLUGIN(QGLObjScenePlugin) +Q_EXPORT_PLUGIN2(qsceneobj, QGLObjScenePlugin) +//! [4] + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/obj/obj.pro b/src/plugins/sceneformats/obj/obj.pro new file mode 100644 index 000000000..0eaa76e09 --- /dev/null +++ b/src/plugins/sceneformats/obj/obj.pro @@ -0,0 +1,12 @@ +TARGET = qsceneobj +include(../../qpluginbase.pri) + +HEADERS += qglobjscene.h \ + qglobjscenehandler.h +SOURCES += main.cpp \ + qglobjscene.cpp \ + qglobjscenehandler.cpp +CONFIG += qt3d +QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/sceneformats +target.path += $$[QT_INSTALL_PLUGINS]/sceneformats +INSTALLS += target diff --git a/src/plugins/sceneformats/obj/qglobjscene.cpp b/src/plugins/sceneformats/obj/qglobjscene.cpp new file mode 100644 index 000000000..c34c5bfc9 --- /dev/null +++ b/src/plugins/sceneformats/obj/qglobjscene.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobjscene.h" +#include "qglbuilder.h" +#include "qglscenenode.h" + +QT_BEGIN_NAMESPACE + +QGLObjScene::QGLObjScene(QGLSceneNode *defaultNode, QObject *parent) + : QGLAbstractScene(parent) + , mainObject(defaultNode) +{ + defaultNode->setParent(this); +} + +QGLObjScene::~QGLObjScene() +{ +} + +QList<QObject *> QGLObjScene::objects() const +{ + QList<QObject *> objs; + objs.append(mainObject); + QList<QGLSceneNode *> children = mainObject->allChildren(); + for (int index = 0; index < children.count(); ++index) + objs.append(children.at(index)); + return objs; +} + +QGLSceneNode *QGLObjScene::mainNode() const +{ + return mainObject; +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/obj/qglobjscene.h b/src/plugins/sceneformats/obj/qglobjscene.h new file mode 100644 index 000000000..4f4875413 --- /dev/null +++ b/src/plugins/sceneformats/obj/qglobjscene.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLOBJSCENE_H +#define QGLOBJSCENE_H + +#include "qglabstractscene.h" + +QT_BEGIN_NAMESPACE + +class QGLBuilder; +class QGLSceneNode; + +//! [1] +class QGLObjScene : public QGLAbstractScene +{ + Q_OBJECT +public: +//! [1] + explicit QGLObjScene(QGLSceneNode *defaultNode, QObject *parent = 0); + virtual ~QGLObjScene(); + +//! [2] + QList<QObject *> objects() const; + QGLSceneNode *mainNode() const; +//! [2] + +private: + QGLSceneNode *mainObject; +//! [3] +}; +//! [3] + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/sceneformats/obj/qglobjscenehandler.cpp b/src/plugins/sceneformats/obj/qglobjscenehandler.cpp new file mode 100644 index 000000000..bad2f3d42 --- /dev/null +++ b/src/plugins/sceneformats/obj/qglobjscenehandler.cpp @@ -0,0 +1,463 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglobjscenehandler.h" +#include "qglobjscene.h" +#include "qvector2darray.h" +#include "qvector3darray.h" +#include "qglbuilder.h" + +#include <QtCore/qiodevice.h> +#include <QtCore/qfile.h> +#include <QtGui/qimage.h> + +QT_BEGIN_NAMESPACE + +QGLObjSceneHandler::QGLObjSceneHandler() + : QGLSceneFormatHandler() + , palette(0) + , smoothing(QGL::Faceted) + , smoothingForced(false) +{ +} + +// Documentation for OBJ and MTL files from: +// http://www.fileformat.info/format/wavefrontobj/egff.htm +// http://www.fileformat.info/format/material/ + +static int objSkipWS(const QByteArray& line, int posn) +{ + while (posn < line.size() && (line[posn] == ' ' || line[posn] == '\t')) + ++posn; + return posn; +} + +static int objSkipNonWS(const QByteArray& line, int posn, int stopch) +{ + while (posn < line.size() && + line[posn] != ' ' && line[posn] != '\t' && line[posn] != stopch) + ++posn; + return posn; +} + +static qreal objReadFloat(const QByteArray& line, int *posn) +{ + *posn = objSkipWS(line, *posn); + int end = objSkipNonWS(line, *posn, 0); + qreal value; + if (end > *posn) + value = qreal(line.mid(*posn, end - *posn).toDouble()); + else + value = 0.0f; + *posn = end; + return value; +} + +static int objReadInteger(const QByteArray& line, int *posn) +{ + *posn = objSkipWS(line, *posn); + int end = objSkipNonWS(line, *posn, '/'); + int value; + if (end > *posn) + value = line.mid(*posn, end - *posn).toInt(); + else + value = 0; + //*posn = objSkipNonWS(line, end, 0); + *posn = end; + return value; +} + +static int objReadSlashInteger(const QByteArray& line, int *posn) +{ + if (*posn >= line.size() || line[*posn] != '/') + return 0; + ++(*posn); + int end = objSkipNonWS(line, *posn, '/'); + int value; + if (end > *posn) + value = line.mid(*posn, end - *posn).toInt(); + else + value = 0; + //*posn = objSkipNonWS(line, end, 0); + *posn = end; + return value; +} + +static QColor objReadColor(const QByteArray& line, int posn) +{ + qreal red = objReadFloat(line, &posn); + qreal green = objReadFloat(line, &posn); + qreal blue = objReadFloat(line, &posn); + qreal alpha = 1.0f; + posn = objSkipWS(line, posn); + if (posn < line.size()) + alpha = objReadFloat(line, &posn); + return QColor::fromRgbF(red, green, blue, alpha); +} + +void QGLObjSceneHandler::decodeOptions(const QString &options) +{ + if (options.contains(QLatin1String("ForceSmooth"))) + { + smoothingForced = true; + smoothing = QGL::Smooth; + } + else + { + smoothingForced = true; + smoothing = QGL::Faceted; + } +} + +QGLAbstractScene *QGLObjSceneHandler::read() +{ + QByteArray line; + QByteArray keyword; + int posn, index, count; + int tindex, nindex; + QVector3DArray positions; + QVector2DArray texCoords; + QVector3DArray normals; + qreal x, y, z; + quint32 fields = 0; + QGLMaterial *material = 0; + QGLSceneNode *defaultNode; + + // Create the geometry builder and start an initial Faceted section. + QGLBuilder builder; + builder.newSection(smoothing); + QGLSceneNode *root = builder.sceneNode(); + palette = root->palette(); + defaultNode = root; + defaultNode->setObjectName(QLatin1String("__main")); + builder.pushNode(); + + QGeometryData op; + while (!device()->atEnd()) { + // Read the next line, including any backslash continuations. + line = device()->readLine().trimmed(); + while (line.endsWith('\\')) { + line.truncate(line.size() - 1); + if (device()->atEnd()) + break; + line += device()->readLine().trimmed(); + } + if (line.startsWith('#') || line.isEmpty()) + continue; // Skip comments and blank lines. + + // Extract the keyword at the start of the line. + posn = 0; + while (posn < line.size() && + line[posn] != ' ' && line[posn] != '\t') + ++posn; + keyword = line.left(posn); + + // Determine how to process this line from the keyword. + if (keyword == "v") { + x = objReadFloat(line, &posn); + y = objReadFloat(line, &posn); + z = objReadFloat(line, &posn); + positions.append(x, y, z); + } else if (keyword == "vt") { + x = objReadFloat(line, &posn); + y = objReadFloat(line, &posn); + texCoords.append(x, y); + } else if (keyword == "vn") { + x = objReadFloat(line, &posn); + y = objReadFloat(line, &posn); + z = objReadFloat(line, &posn); + normals.append(x, y, z); + } else if (keyword == "f") { + posn = objSkipWS(line, posn); + count = 0; + //QGeometryData op; //(dlist, QGL::TRIANGLE_FAN); + op = QGeometryData(); // clear leaves field definitions + while (posn < line.size()) { + // Note: we currently only read the initial vertex + // index and also use it for texture co-ordinates + // and normals. e.g. "2/2", "3/3", etc. This will + // need to be fixed to handle "2/1", "3/7", etc. + index = objReadInteger(line, &posn); + tindex = objReadSlashInteger(line, &posn); + nindex = objReadSlashInteger(line, &posn); + if (index < 0) + index = positions.count() + index; + else if (index > 0) + --index; // Indices in obj are 1-based. + if (index >= 0 && index < positions.count()) + op.appendVertex(positions[index]); + if (tindex < 0) + tindex = texCoords.count() + tindex; + else if (tindex > 0) + --tindex; // Indices in obj are 1-based. + else + tindex = -1; + if (tindex >= 0 && tindex < texCoords.count()) + op.appendTexCoord(texCoords[tindex]); + if (nindex < 0) + nindex = normals.count() + nindex; + else if (nindex > 0) + --nindex; // Indices in obj are 1-based. + else + nindex = -1; + if (nindex >= 0 && nindex < normals.count()) + op.appendNormal(normals[nindex]); + ++count; + posn = objSkipNonWS(line, posn, 0); + posn = objSkipWS(line, posn); + } + // if geometry has already been added with a different combination + // of fields start a new section + // the primitive doesn't get posted to the section until op.end() + if (op.fields() != fields) + { + if (fields && builder.currentNode()->count() > 0) + builder.newSection(smoothing); + fields = op.fields(); + } + builder.addTriangleFan(op); + } else if (keyword == "usemtl") { + // Specify a material for the faces that follow. + posn = objSkipWS(line, posn); + QByteArray rest = line.mid(posn); + QString materialName = QString::fromLocal8Bit(rest.constData(), rest.size()); + if (!materialName.isEmpty() && + materialName != QLatin1String("(null)")) { + index = palette->indexOf(materialName); + if (index != -1) { + QGLSceneNode *node = builder.newNode(); + node->setMaterialIndex(index); + QGLMaterial *material = palette->material(index); + if (material->texture()) + node->setEffect(QGL::LitDecalTexture2D); + else + node->setEffect(QGL::LitMaterial); + } else { + qWarning() << "obj material" << materialName << "not found"; + material = 0; + } + } + } else if (keyword == "mtllib") { + // Load a material library. + posn = objSkipWS(line, posn); + QByteArray filename = line.mid(posn); + loadMaterialLibrary(QString::fromLocal8Bit(filename.constData(), filename.size())); + } else if (keyword == "s") { + if (!smoothingForced) + { + // Set smoothing on or off. + posn = objSkipWS(line, posn); + index = objSkipNonWS(line, posn, 0); + QByteArray arg = line.mid(posn, index - posn); + QGL::Smoothing smooth; + if (arg == "on" || arg == "1") + smooth = QGL::Smooth; + else + smooth = QGL::Faceted; + if (smoothing != smooth) { + smoothing = smooth; + builder.newSection(smooth); + } + } + } else if (keyword == "g" || keyword == "o") { + // Label the faces that follow as part of a named group or object. + posn = objSkipWS(line, posn); + QByteArray rest = line.mid(posn); + QString objectName = QString::fromLocal8Bit(rest.constData(), rest.size()); + QGLSceneNode *node = builder.currentNode(); + // if content has already been added to a current group, then + // create a new node in the scene graph for the group, otherwise + // just label the existing group with this name + QGLSceneNode *p = qobject_cast<QGLSceneNode*>(node->parent()); + if (node->count() > 0 && p && p->objectName().isEmpty()) + { + node = p; + } + else + { + builder.popNode(); + node = builder.currentNode(); + builder.pushNode(); + } + node->setObjectName(objectName); + } else { + qWarning() << "unsupported obj command: " << keyword.constData(); + } + } + + // Create a scene from the geometry + return new QGLObjScene(builder.finalizedSceneNode()); +} + +void QGLObjSceneHandler::loadMaterialLibrary(const QString& name) +{ + QUrl materialUrl = url().resolved(name); + if (materialUrl.scheme() == QLatin1String("file")) { + QFile file(materialUrl.toLocalFile()); + if (!file.open(QIODevice::ReadOnly)) + qWarning() << "QGLObjSceneHandler::loadMaterialLibrary: could not open:" << materialUrl.toLocalFile(); + else + loadMaterials(&file); + } else { + // TODO + qWarning("QGLObjSceneHandler::loadMaterialLibrary: non-file urls not supported"); + } +} + +void QGLObjSceneHandler::loadMaterials(QIODevice *device) +{ + QByteArray line; + QByteArray keyword; + int posn, index; + QGLMaterial *material = 0; + QString materialName; + QString textureName; + + while (!device->atEnd()) { + // Read the next line, including any backslash continuations. + line = device->readLine().trimmed(); + while (line.endsWith('\\')) { + line.truncate(line.size() - 1); + if (device->atEnd()) + break; + line += device->readLine().trimmed(); + } + if (line.startsWith('#') || line.isEmpty()) + continue; // Skip comments and blank lines. + + // Extract the keyword at the start of the line. + posn = 0; + while (posn < line.size() && + line[posn] != ' ' && line[posn] != '\t') + ++posn; + keyword = line.left(posn); + + // Determine how to process this line from the keyword. + if (keyword == "newmtl") { + // Start a new material definition. + posn = objSkipWS(line, posn); + QByteArray rest = line.mid(posn); + materialName = QString::fromLocal8Bit(rest.constData(), rest.size()); + index = palette->indexOf(materialName); + if (index != -1) { + qWarning() << "redefining obj material:" << materialName; + material = palette->material(index); + } else { + material = new QGLMaterial(); + material->setObjectName(materialName); + palette->addMaterial(material); + } + } else if (keyword == "Ka") { + // Ambient color of the material. + if (material) + material->setAmbientColor(objReadColor(line, posn)); + } else if (keyword == "Kd") { + // Diffuse color of the material. + if (material) + material->setDiffuseColor(objReadColor(line, posn)); + } else if (keyword == "Ks") { + // Specular color of the material. + if (material) + material->setSpecularColor(objReadColor(line, posn)); + } else if (keyword == "map_Kd") { + // Texture associated with the material. + posn = objSkipWS(line, posn); + QByteArray rest = line.mid(posn); + textureName = QString::fromLocal8Bit(rest.constData(), rest.size()); + QGLTexture2D *texture = loadTexture(textureName); + if (texture) { + index = palette->indexOf(materialName); + if (index >= 0) { + QGLMaterial *material = palette->material(index); + texture->setParent(material); + material->setTexture(texture); + } else { + delete texture; + } + } + } else if (keyword == "d") { + // "Dissolve factor" of the material, which is its opacity. + if (material) { + qreal alpha = objReadFloat(line, &posn); + QColor ambient = material->ambientColor(); + QColor diffuse = material->diffuseColor(); + ambient.setAlphaF(alpha); + diffuse.setAlphaF(alpha); + material->setAmbientColor(ambient); + material->setDiffuseColor(diffuse); + } + } else if (keyword == "Ns") { + // Specular exponent of the material. + if (material) + material->setShininess(qRound(objReadFloat(line, &posn))); + } else if (keyword == "illum") { + // Illumination model - ignored at present. + } else if (keyword == "Ni") { + // Optical density - ignored at present. + } else { + qWarning() << "unsupported obj material command: " << keyword.constData(); + } + } +} + +QGLTexture2D *QGLObjSceneHandler::loadTexture(const QString& name) +{ + QUrl textureUrl = url().resolved(name); + if (textureUrl.scheme() == QLatin1String("file")) { + QFile file(textureUrl.toLocalFile()); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "QGLObjSceneHandler::loadTexture: could not open:" << textureUrl.toLocalFile(); + return 0; + } else { + file.close(); + QImage image(textureUrl.toLocalFile()); + QGLTexture2D *tex = new QGLTexture2D(); + tex->setImage(image); + return tex; + } + } else { + // TODO + qWarning("QGLObjSceneHandler::loadTexture: non-file urls not supported"); + return 0; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/sceneformats/obj/qglobjscenehandler.h b/src/plugins/sceneformats/obj/qglobjscenehandler.h new file mode 100644 index 000000000..c46caf529 --- /dev/null +++ b/src/plugins/sceneformats/obj/qglobjscenehandler.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLOBJSCENEHANDLER_H +#define QGLOBJSCENEHANDLER_H + +#include "qglsceneformatplugin.h" +#include "qglmaterialcollection.h" +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +QT_BEGIN_NAMESPACE + +//! [1] +class QGLObjSceneHandler : public QGLSceneFormatHandler +{ +public: + QGLObjSceneHandler(); + QGLAbstractScene *read(); +//! [1] + void decodeOptions(const QString &options); + +private: + void loadMaterialLibrary(const QString& name); + void loadMaterials(QIODevice *device); + QGLTexture2D *loadTexture(const QString& name); + + QGLMaterialCollection *palette; + QGL::Smoothing smoothing; + bool smoothingForced; +//! [2] +}; +//! [2] + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/sceneformats/sceneformats.pro b/src/plugins/sceneformats/sceneformats.pro new file mode 100644 index 000000000..97ec09a6f --- /dev/null +++ b/src/plugins/sceneformats/sceneformats.pro @@ -0,0 +1,7 @@ +TEMPLATE = subdirs + +old_importer { + SUBDIRS = bezier 3ds obj +} else { + SUBDIRS = bezier assimp +} diff --git a/src/plugins/scenegraph/scenegraph.pro b/src/plugins/scenegraph/scenegraph.pro new file mode 100644 index 000000000..e34fa2537 --- /dev/null +++ b/src/plugins/scenegraph/scenegraph.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += stereo diff --git a/src/plugins/scenegraph/stereo/main.cpp b/src/plugins/scenegraph/stereo/main.cpp new file mode 100644 index 000000000..b4a6d946c --- /dev/null +++ b/src/plugins/scenegraph/stereo/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtDeclarative/qsgcontextplugin.h> +#include "qsgstereocontext.h" + +QT_BEGIN_NAMESPACE + +class QSGStereoContextPlugin : public QSGContextPlugin +{ +public: + QStringList keys() const; + QSGContext *create(const QString &key) const; +}; + +QStringList QSGStereoContextPlugin::keys() const +{ + QStringList keys; + keys += QLatin1String("stereo"); + keys += QLatin1String("stereo-test"); // Red-cyan test mode + return keys; +} + +QSGContext *QSGStereoContextPlugin::create(const QString &key) const +{ + return new QSGStereoContext(key); +} + +Q_EXPORT_STATIC_PLUGIN(QSGStereoContextPlugin) +Q_EXPORT_PLUGIN2(qstereoscenegraph, QSGStereoContextPlugin) + +QT_END_NAMESPACE diff --git a/src/plugins/scenegraph/stereo/qsgstereocontext.cpp b/src/plugins/scenegraph/stereo/qsgstereocontext.cpp new file mode 100644 index 000000000..b5f2168fc --- /dev/null +++ b/src/plugins/scenegraph/stereo/qsgstereocontext.cpp @@ -0,0 +1,358 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsgstereocontext.h" +#include "qsgpretransformnode_p.h" +#include "qglnamespace.h" +#include <QtDeclarative/private/nodeupdater_p.h> +#include <QtDeclarative/renderer.h> +#include <QtOpenGL/qglframebufferobject.h> + +QT_BEGIN_NAMESPACE + +// Determine how to switch drawing buffers. On desktop OpenGL we +// use glDrawBuffer(). On OpenGL/ES we attempt to resolve either +// glDrawBufferOES() or glDrawBuffersOES(), under the assumption +// that the OpenGL/ES implementation has a buffer-switching extension +// that mirrors how desktop OpenGL works. For other kinds of buffer +// switching, modify QSGStereoContext::renderNextFrame(). +#if defined(GL_BACK_LEFT) && defined(GL_BACK_RIGHT) && !defined(QT_OPENGL_ES) + #define DESKTOP_GL_DRAW_BUFFERS 1 +#elif defined(QT_OPENGL_ES) + #define OPENGL_ES_DRAW_BUFFERS 1 +#endif +#ifndef GL_BACK_LEFT +#define GL_BACK_LEFT 0x0402 +#endif +#ifndef GL_BACK_RIGHT +#define GL_BACK_RIGHT 0x0403 +#endif + +#ifdef Q_WS_WIN +# define QSG_GLF_APIENTRY APIENTRY +#endif +#ifndef Q_WS_MAC +# ifndef QSG_GLF_APIENTRYP +# ifdef QSG_GLF_APIENTRY +# define QSG_GLF_APIENTRYP QSG_GLF_APIENTRY * +# else +# define QSG_GLF_APIENTRY +# define QSG_GLF_APIENTRYP * +# endif +# endif +#else +# define QSG_GLF_APIENTRY +# define QSG_GLF_APIENTRYP * +#endif + +typedef void (QSG_GLF_APIENTRYP q_PFNGLDRAWBUFFERPROC)(GLenum mode); +typedef void (QSG_GLF_APIENTRYP q_PFNGLDRAWBUFFERSPROC)(GLsizei n, const GLenum *bufs); + +class QSGStereoContextPrivate +{ +public: + enum StereoMode + { + Autodetect, + HardwareStereo, + RedCyanStereo + }; + + QSGStereoContextPrivate() + : eye(QGL::NoEye) + , stereoMode(QSGStereoContextPrivate::Autodetect) +#if defined(OPENGL_ES_DRAW_BUFFERS) + , functionsResolved(false) + , drawBuffer(0) + , drawBuffers(0) +#endif + { + } + + QGL::Eye eye; + StereoMode stereoMode; + +#if defined(OPENGL_ES_DRAW_BUFFERS) + bool functionsResolved; + q_PFNGLDRAWBUFFERPROC drawBuffer; + q_PFNGLDRAWBUFFERSPROC drawBuffers; +#endif + + void setEye(QGL::Eye value) { eye = value; } + void setDrawBuffer(GLenum buffer); +}; + +void QSGStereoContextPrivate::setDrawBuffer(GLenum buffer) +{ +#if defined(DESKTOP_GL_DRAW_BUFFERS) + glDrawBuffer(buffer); +#elif defined(OPENGL_ES_DRAW_BUFFERS) + if (!functionsResolved) { + QGLContext *context = const_cast<QGLContext *>(QGLContext::currentContext()); + drawBuffer = (q_PFNGLDRAWBUFFERPROC) + context->getProcAddress(QLatin1String("glDrawBuffer")); + if (!drawBuffer) { + drawBuffer = (q_PFNGLDRAWBUFFERPROC) + context->getProcAddress(QLatin1String("glDrawBufferOES")); + } + if (!drawBuffer) { + drawBuffer = (q_PFNGLDRAWBUFFERPROC) + context->getProcAddress(QLatin1String("glDrawBufferARB")); + } + drawBuffers = (q_PFNGLDRAWBUFFERSPROC) + context->getProcAddress(QLatin1String("glDrawBuffers")); + if (!drawBuffers) { + drawBuffers = (q_PFNGLDRAWBUFFERSPROC) + context->getProcAddress(QLatin1String("glDrawBuffersOES")); + } + if (!drawBuffers) { + drawBuffers = (q_PFNGLDRAWBUFFERSPROC) + context->getProcAddress(QLatin1String("glDrawBuffersARB")); + } + if (!drawBuffer && !drawBuffers) { + qWarning("QSGContext: QGLContext is stereo-capable but " + "glDrawBuffersOES() is not available"); + } + functionsResolved = true; + } + if (drawBuffer) + drawBuffer(buffer); + else if (drawBuffers) + drawBuffers(1, &buffer); +#else + Q_UNUSED(buffer); +#endif +} + +class CurrentContextBindable : public Bindable +{ +public: + CurrentContextBindable() + : m_ctx(const_cast<QGLContext *>(QGLContext::currentContext())) + { + } + + void bind() const; + +private: + QGLContext *m_ctx; +}; + +class DrawBufferBindable : public CurrentContextBindable +{ +public: + DrawBufferBindable(QSGStereoContextPrivate *context, GLenum buffer) + : m_context(context), m_buffer(buffer) {} + + void bind() const; + +private: + QSGStereoContextPrivate *m_context; + GLenum m_buffer; +}; + +class RedStereoBindable : public CurrentContextBindable +{ +public: + void clear() const; + void reactivate() const; +}; + +class CyanStereoBindable : public CurrentContextBindable +{ +public: + void clear() const; + void reactivate() const; +}; + +void CurrentContextBindable::bind() const +{ + m_ctx->makeCurrent(); + QGLFramebufferObject::bindDefault(); +} + +void DrawBufferBindable::bind() const +{ + CurrentContextBindable::bind(); + m_context->setDrawBuffer(m_buffer); +} + +void RedStereoBindable::clear() const +{ + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE); +} + +void RedStereoBindable::reactivate() const +{ + glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE); +} + +void CyanStereoBindable::clear() const +{ + // Only need to clear the depth buffer on the second pass. + glClear(GL_DEPTH_BUFFER_BIT); + glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE); +} + +void CyanStereoBindable::reactivate() const +{ + glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE); +} + +class QSGStereoNodeUpdater : public NodeUpdater +{ +protected: + void enterPreTransformNode(QSGPreTransformNode *); + void leavePreTransformNode(QSGPreTransformNode *); + + void visitNode(Node *n); +}; + +void QSGStereoNodeUpdater::enterPreTransformNode(QSGPreTransformNode *t) +{ + if (t->dirtyFlags() & Node::DirtyMatrix) + ++m_force_update; + + if (!t->preMatrix().isIdentity() || !t->matrix().isIdentity()) { + m_combined_matrix_stack.push(&t->combinedMatrix()); + + m_matrix_stack.push(); + m_matrix_stack = t->preMatrix() * m_matrix_stack.top() * t->matrix(); + } + + t->setCombinedMatrix(m_matrix_stack.top()); +} + +void QSGStereoNodeUpdater::leavePreTransformNode(QSGPreTransformNode *t) +{ + if (t->dirtyFlags() & Node::DirtyMatrix) + --m_force_update; + + if (!t->preMatrix().isIdentity() || !t->matrix().isIdentity()) { + m_matrix_stack.pop(); + m_combined_matrix_stack.pop(); + } +} + +void QSGStereoNodeUpdater::visitNode(Node *n) +{ + if (n->type() == QSGPreTransformNode::PreTransformNodeType) { + QSGPreTransformNode *t = static_cast<QSGPreTransformNode *>(n); + enterPreTransformNode(t); + visitChildren(t); + leavePreTransformNode(t); + } else { + NodeUpdater::visitNode(n); + } +} + +QSGStereoContext::QSGStereoContext(const QString &key, QObject *parent) + : QSGContext(parent) + , d_ptr(new QSGStereoContextPrivate) +{ + Q_D(QSGStereoContext); + if (key == QLatin1String("stereo-test")) + d->stereoMode = QSGStereoContextPrivate::RedCyanStereo; +} + +QSGStereoContext::~QSGStereoContext() +{ +} + +int QSGStereoContext::eye() const +{ + Q_D(const QSGStereoContext); + return int(d->eye); +} + +Renderer *QSGStereoContext::createRenderer() +{ + Renderer *renderer = QSGContext::createRenderer(); + renderer->setNodeUpdater(new QSGStereoNodeUpdater); + return renderer; +} + +void QSGStereoContext::renderNextFrame() +{ + Q_D(QSGStereoContext); + + emit aboutToRenderNextFrame(); + + // Detect the type of stereo we should use the first time we render. + if (d->stereoMode == QSGStereoContextPrivate::Autodetect) { + if (glContext()->format().stereo()) + d->stereoMode = QSGStereoContextPrivate::HardwareStereo; + else + d->stereoMode = QSGStereoContextPrivate::RedCyanStereo; + } + + // Render the scene according to the stereo mode. + Renderer *renderer = this->renderer(); + switch (d->stereoMode) { + case QSGStereoContextPrivate::HardwareStereo: { + DrawBufferBindable left(d, GL_BACK_LEFT); + DrawBufferBindable right(d, GL_BACK_RIGHT); + d->setEye(QGL::LeftEye); + renderer->renderScene(left); + d->setEye(QGL::RightEye); + renderer->renderScene(right); + d->setEye(QGL::NoEye); + break; } + + case QSGStereoContextPrivate::RedCyanStereo: { + RedStereoBindable left; + CyanStereoBindable right; + d->setEye(QGL::LeftEye); + renderer->renderScene(left); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + d->setEye(QGL::RightEye); + renderer->renderScene(right); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + d->setEye(QGL::NoEye); + break; } + + default: break; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/scenegraph/stereo/qsgstereocontext.h b/src/plugins/scenegraph/stereo/qsgstereocontext.h new file mode 100644 index 000000000..d8cc1f30e --- /dev/null +++ b/src/plugins/scenegraph/stereo/qsgstereocontext.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QSGSTEREOCONTEXT_H +#define QSGSTEREOCONTEXT_H + +#include <QtDeclarative/qsgcontext.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QSGStereoContextPrivate; + +class QSGStereoContext : public QSGContext +{ + Q_OBJECT + // These properties exist for the QSGStereoInfo class to + // fetch information about the stereo context without having + // to directly link against QSGStereoContext. + Q_PROPERTY(bool hasStereo READ returnTrue) + Q_PROPERTY(bool hasPreTransform READ returnTrue) + Q_PROPERTY(int eye READ eye) +public: + explicit QSGStereoContext(const QString &key, QObject *parent = 0); + ~QSGStereoContext(); + + Renderer *createRenderer(); + void renderNextFrame(); + +private: + bool returnTrue() const { return true; } + int eye() const; + + QScopedPointer<QSGStereoContextPrivate> d_ptr; + + Q_DECLARE_PRIVATE(QSGStereoContext) + Q_DISABLE_COPY(QSGStereoContext) +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/plugins/scenegraph/stereo/stereo.pro b/src/plugins/scenegraph/stereo/stereo.pro new file mode 100644 index 000000000..9f30306ff --- /dev/null +++ b/src/plugins/scenegraph/stereo/stereo.pro @@ -0,0 +1,15 @@ +TARGET = qstereoscenegraph +include(../../qpluginbase.pri) + +CONFIG += quick3d + +SOURCES += \ + main.cpp \ + qsgstereocontext.cpp + +HEADERS += \ + qsgstereocontext.h + +QTDIR_build:DESTDIR = $$QT_BUILD_TREE/plugins/scenegraph +target.path += $$[QT_INSTALL_PLUGINS]/scenegraph +INSTALLS += target |