diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..0aff9456 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,30 @@ +build_and_store_wheels: &BUILD_AND_STORE_WHEELS + install_cibuildwheel_script: + - python -m pip install cibuildwheel==2.16.2 + run_cibuildwheel_script: + - cibuildwheel + wheels_artifacts: + path: "wheelhouse/*" + + # Upload only for tagged commit + only_if: $CIRRUS_TAG != '' + publish_script: + - python -m pip install twine + - python -m twine upload --repository-url https://upload.pypi.org/legacy/ --username __token__ wheelhouse/*.whl + + +linux_aarch64_task: + name: Build Linux aarch64 wheels. + compute_engine_instance: + image_project: cirrus-images + image: family/docker-builder-arm64 + architecture: arm64 + platform: linux + cpu: 4 + memory: 4G + environment: + TWINE_PASSWORD: ENCRYPTED[ade2037764e68fea251152f7585f3f77cdd748af06dc0f06942c45a8a8770fff19032c985f8dc193229c8adb2c0fecb9] + + install_pre_requirements_script: + - apt install -y python3-venv python-is-python3 + <<: *BUILD_AND_STORE_WHEELS diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..74210b03 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +--- +BasedOnStyle: Google +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +BreakBeforeBraces: Attach +Standard: Cpp03 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 00000000..971557fd --- /dev/null +++ b/.drone.yml @@ -0,0 +1,10 @@ +image: syoyo/ubu-dev +script: + - curl -L -o premake4 https://github.com/syoyo/orebuildenv/blob/master/build/linux/bin/premake4?raw=true + - chmod +x ./premake4 + - ./premake4 gmake + - make +notify: + email: + recipients: + - syoyo@lighttransport.com diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0fd988d9 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: syoyo # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3ba13e0c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md new file mode 100644 index 00000000..1361a329 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue-report.md @@ -0,0 +1,28 @@ +--- +name: Issue report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the issue** +A clear and concise description of what the issue is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Compile TinyObjLoader with '...' +2. Load .obj file '...' +3. See error + +Please attach minimal and reproducible files(source codes, .obj/.mtl files, etc) + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Environment** + - TinyObjLoader version + - OS: [e.g. Linux] + - Compiler [e.g. gcc 7.3] + - Other environment [e.g. Python version if you use python binding] diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml new file mode 100644 index 00000000..b37104a2 --- /dev/null +++ b/.github/workflows/wheels.yml @@ -0,0 +1,115 @@ +name: Build and upload to PyPI + +# Build on every branch push, tag push, and pull request change: +on: [push, pull_request] + +jobs: + + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true # Optional, use if you use setuptools_scm + + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.5 + # to supply options, put them in 'env', like: + # env: + # CIBW_SOME_OPTION: value + # Disable building PyPy wheels on all platforms + env: + CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" + CIBW_ARCHS_WINDOWS: "AMD64 x86" + # disable aarm64 build since its too slow to build(docker + qemu) + CIBW_ARCHS_LINUX: "x86_64 i686" + # it looks cibuildwheel fails to add version string to wheel file for python 3.6, so skip it + CIBW_SKIP: pp* + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + # It looks cibuildwheels did not clean build folder(CMake), and it results to Windows arm64 build failure(trying to reuse x86 build of .obj) + # So supply separated build job for Windows ARM64 build + # TODO: clean build folder using CIBW_BEFORE_ALL? + build_win_arm64_wheels: + name: Build ARM64 wheels on Windows. + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true # Optional, use if you use setuptools_scm + + - name: Build wheels + uses: pypa/cibuildwheel@v2.16.5 + # to supply options, put them in 'env', like: + # env: + # CIBW_SOME_OPTION: value + # Disable building PyPy wheels on all platforms + env: + CIBW_ARCHS_WINDOWS: "ARM64" + CIBW_SKIP: pp* + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + make_sdist: + name: Make SDist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Optional, use if you use setuptools_scm + fetch-tags: true # Optional, use if you use setuptools_scm + + - name: Build SDist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + upload_all: + needs: [build_wheels, build_wheels, make_sdist] + runs-on: ubuntu-latest + environment: release + permissions: + # IMPORTANT: this permission is mandatory for trusted publishing + id-token: write + # upload to PyPI on every tag starting with 'v' + # NOTE: Without github.event_name & githug.ref check, `upload_all` task is still triggered on 'main' branch push. + # (then get 'Branch "main" is not allowed to deploy to release due to environment protection rules.' error) + # So still do event_name and github.ref check. + # TODO: Make it work only using Github `environment` feature. + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') + # alternatively, to publish when a GitHub Release is created, use the following rule: + # if: github.event_name == 'push' && github.event.action == 'published' + steps: + - uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + # Use Trusted Publisher feature: + # https://docs.pypi.org/trusted-publishers/ + # so no use of PYPI_API_TOKEN + #password: ${{ secrets.PYPI_API_TOKEN }} + # + # Avoid race condition when using multiple CIs + skip-existing: true + verbose: true diff --git a/.gitignore b/.gitignore index 493e8883..f9b4d691 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ #Common folder for building objects build/ +/python/dist/ +/python/*.egg-info +/python/.eggs +/python/tiny_obj_loader.h +/tests/tester +/tests/tester.dSYM diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..12b67f28 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,56 @@ +language: cpp +sudo: required +matrix: + include: + - addons: &1 + apt: + sources: + - george-edison55-precise-backports + - ubuntu-toolchain-r-test + - llvm-toolchain-precise-3.7 + packages: + - cmake + - cmake-data + - ninja-build + - g++-4.9 + - clang-3.7 + compiler: clang + env: DEPLOY_BUILD=1 COMPILER_VERSION=3.7 BUILD_TYPE=Debug + - addons: *1 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Release + - addons: &2 + apt: + sources: + - george-edison55-precise-backports + - ubuntu-toolchain-r-test + packages: + - cmake + - cmake-data + - ninja-build + - g++-4.9 + compiler: gcc + env: COMPILER_VERSION=4.9 BUILD_TYPE=Debug + - addons: *2 + compiler: gcc + env: COMPILER_VERSION=4.9 BUILD_TYPE=Release + - addons: *1 + compiler: clang + env: COMPILER_VERSION=3.7 BUILD_TYPE=Debug +before_install: +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade; fi +- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get update python; fi +- if [ -n "$REPORT_COVERAGE" ]; then sudo apt-get install python-dev libffi-dev libssl-dev; fi +- if [ -n "$REPORT_COVERAGE" ]; then sudo pip install --upgrade pip; fi +- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user requests[security]; fi +- if [ -n "$REPORT_COVERAGE" ]; then CXX=g++ pip install --user cpp-coveralls; fi +script: +- cd tests +- make check +- if [ -n "$REPORT_COVERAGE" ]; then coveralls -b . -r .. -e examples -e tools -e + jni -e python -e images -E ".*CompilerId.*" -E ".*feature_tests.*" ; fi +- cd .. +- rm -rf dist +- mkdir dist +- cp tiny_obj_loader.h dist/ + diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 00000000..dda60c76 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,9 @@ +cc_library( + name = "tinyobjloader", + hdrs = ["tiny_obj_loader.h"], + copts = select({ + "@platforms//os:windows": [], + "//conditions:default": ["-Wno-maybe-uninitialized"], + }), + visibility = ["//visibility:public"], +) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94ec3302..9aea91fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,50 +1,221 @@ #Tiny Object Loader Cmake configuration file. #This configures the Cmake system with multiple properties, depending #on the platform and configuration it is set to build in. -project(tinyobjloader) -cmake_minimum_required(VERSION 2.8.6) +cmake_minimum_required(VERSION 3.16) +project(tinyobjloader CXX) +set(TINYOBJLOADER_SOVERSION 2) +set(TINYOBJLOADER_VERSION 2.0.0-rc.13) +set(PY_TARGET "pytinyobjloader") + +#optional double precision support +option(TINYOBJLOADER_USE_DOUBLE "Build library with double precision instead of single (float)" OFF) +option(TINYOBJLOADER_WITH_PYTHON "Build Python module(for developer). Use pyproject.toml/setup.py to build Python module for end-users" OFF) +option(TINYOBJLOADER_PREFER_LOCAL_PYTHON_INSTALLATION + "Prefer locally-installed Python interpreter than system or conda/brew installed Python. Please specify your Python interpreter with `Python3_EXECUTABLE` cmake option if you enable this option." + OFF) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/sanitizers) +find_package(Sanitizers) # Address sanitizer (-DSANITIZE_ADDRESS=ON) + + +if(TINYOBJLOADER_USE_DOUBLE) + set(LIBRARY_NAME ${PROJECT_NAME}_double) +else() + set(LIBRARY_NAME ${PROJECT_NAME}) +endif() #Folder Shortcuts set(TINYOBJLOADEREXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples) set(tinyobjloader-Source - ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.h - ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.cc - ) + ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.h + ${CMAKE_CURRENT_SOURCE_DIR}/tiny_obj_loader.cc + ) -set(tinyobjloader-Test-Source - ${CMAKE_CURRENT_SOURCE_DIR}/test.cc - ) +set(tinyobjloader-Example-Source + ${CMAKE_CURRENT_SOURCE_DIR}/loader_example.cc + ) set(tinyobjloader-examples-objsticher - ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.h - ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.cc - ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc - ) + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.h + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_writer.cc + ${TINYOBJLOADEREXAMPLES_DIR}/obj_sticher/obj_sticher.cc + ) + +#Install destinations +include(GNUInstallDirs) + +set(TINYOBJLOADER_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake) +set(TINYOBJLOADER_DOC_DIR ${CMAKE_INSTALL_DOCDIR}) +set(TINYOBJLOADER_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}) +set(TINYOBJLOADER_LIBRARY_DIR ${CMAKE_INSTALL_LIBDIR}) +set(TINYOBJLOADER_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +set(TINYOBJLOADER_RUNTIME_DIR ${CMAKE_INSTALL_BINDIR}) + +option(TINYOBJLOADER_BUILD_TEST_LOADER "Build Example Loader Application" OFF) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Build standalone .so for Python binding(for developer) +if (TINYOBJLOADER_WITH_PYTHON) -add_library(tinyobjloader - ${tinyobjloader-Source} - ) + if(TINYOBJLOADER_PREFER_LOCAL_PYTHON_INSTALLATION) + #message(STATUS "Local Python") + set(Python3_FIND_FRAMEWORK NEVER) # Do not search framework python + set(Python3_FIND_STRATEGY LOCATION) + set(Python3_FIND_REGISTRY NEVER) # Windows only + else() + set(Python3_FIND_FRAMEWORK LAST + )# Prefer Brew/Conda to Apple framework python + endif() -add_executable(test ${tinyobjloader-Test-Source}) -target_link_libraries(test tinyobjloader) + find_package( + Python3 + COMPONENTS Interpreter Development + REQUIRED) + + find_package(pybind11 CONFIG REQUIRED) + +endif() + + + +add_library(${LIBRARY_NAME} ${tinyobjloader-Source}) +add_sanitizers(${LIBRARY_NAME}) + +if(BUILD_SHARED_LIBS) + set_target_properties(${LIBRARY_NAME} PROPERTIES + SOVERSION ${TINYOBJLOADER_SOVERSION} + WINDOWS_EXPORT_ALL_SYMBOLS ON + ) +endif() + +if(TINYOBJLOADER_USE_DOUBLE) + target_compile_definitions(${LIBRARY_NAME} PUBLIC TINYOBJLOADER_USE_DOUBLE) +endif() + +set_target_properties(${LIBRARY_NAME} PROPERTIES VERSION ${TINYOBJLOADER_VERSION}) + +target_include_directories(${LIBRARY_NAME} INTERFACE + $ + $ + ) -add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) -target_link_libraries(obj_sticher tinyobjloader) +export(TARGETS ${LIBRARY_NAME} FILE ${PROJECT_NAME}-targets.cmake) + +if(TINYOBJLOADER_BUILD_TEST_LOADER) + add_executable(test_loader ${tinyobjloader-Example-Source}) + target_link_libraries(test_loader ${LIBRARY_NAME}) +endif() + +option(TINYOBJLOADER_BUILD_OBJ_STICHER "Build OBJ Sticher Application" OFF) +if(TINYOBJLOADER_BUILD_OBJ_STICHER) + add_executable(obj_sticher ${tinyobjloader-examples-objsticher}) + target_link_libraries(obj_sticher ${LIBRARY_NAME}) + + install(TARGETS + obj_sticher + DESTINATION + ${TINYOBJLOADER_RUNTIME_DIR} + ) +endif() + +if (TINYOBJLOADER_WITH_PYTHON) + # pybind11 method: + pybind11_add_module(${PY_TARGET} ${CMAKE_SOURCE_DIR}/python/bindings.cc ${CMAKE_SOURCE_DIR}/python/tiny_obj_loader.cc) + + add_sanitizers(${PY_TARGET}) + set_target_properties(${PY_TARGET} PROPERTIES OUTPUT_NAME "tinyobjloader") + + # copy .so to jdepp/ + add_custom_command( + TARGET ${PY_TARGET} + POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy "$" + "${CMAKE_SOURCE_DIR}/python/$" + COMMENT "copying tinyobjloader native python module file to python/" + VERBATIM) + +endif() + +#Write CMake package config files +include(CMakePackageConfigHelpers) + +configure_package_config_file( + ${PROJECT_NAME}-config.cmake.in + ${PROJECT_NAME}-config.cmake + INSTALL_DESTINATION + ${TINYOBJLOADER_CMAKE_DIR} + PATH_VARS + TINYOBJLOADER_INCLUDE_DIR + TINYOBJLOADER_LIBRARY_DIR + NO_CHECK_REQUIRED_COMPONENTS_MACRO + ) + +write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake + VERSION + ${TINYOBJLOADER_VERSION} + COMPATIBILITY + SameMajorVersion + ) + +#pkg-config file +configure_file(${PROJECT_NAME}.pc.in ${LIBRARY_NAME}.pc @ONLY) + +if(DEFINED ENV{LIB_FUZZING_ENGINE}) + add_executable(fuzz_ParseFromString fuzzer/fuzz_ParseFromString.cc) + target_link_libraries(fuzz_ParseFromString ${LIBRARY_NAME} $ENV{LIB_FUZZING_ENGINE}) +endif() #Installation -install ( TARGETS - obj_sticher +install(TARGETS + ${LIBRARY_NAME} + EXPORT ${PROJECT_NAME}-targets DESTINATION - bin + ${TINYOBJLOADER_LIBRARY_DIR} + PUBLIC_HEADER DESTINATION + ${TINYOBJLOADER_INCLUDE_DIR} + RUNTIME DESTINATION + ${TINYOBJLOADER_RUNTIME_DIR} ) -install ( TARGETS - tinyobjloader +install(EXPORT + ${PROJECT_NAME}-targets + NAMESPACE + tinyobjloader:: DESTINATION - lib + ${TINYOBJLOADER_CMAKE_DIR} ) -install ( FILES +install(FILES tiny_obj_loader.h DESTINATION - include + ${TINYOBJLOADER_INCLUDE_DIR} ) +install(FILES + LICENSE + DESTINATION + ${TINYOBJLOADER_DOC_DIR} + ) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" + DESTINATION + ${TINYOBJLOADER_CMAKE_DIR} + ) +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}.pc" + DESTINATION + ${TINYOBJLOADER_PKGCONFIG_DIR} + ) + +if(NOT TARGET uninstall) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake IMMEDIATE @ONLY) + + add_custom_target( + uninstall COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..e9fbe447 --- /dev/null +++ b/LICENSE @@ -0,0 +1,42 @@ +The MIT License (MIT) + +Copyright (c) 2012-2019 Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------- + +mapbox/earcut.hpp + +ISC License + +Copyright (c) 2015, Mapbox + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..d2632da5 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,8 @@ +include pyproject.toml +include setup.py +include README.md +include LICENSE +include python/sample.py +include python/bindings.cc +include python/tiny_obj_loader.cc +include tiny_obj_loader.h diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 00000000..f8859286 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,9 @@ +module( + name = "tinyobjloader", + compatibility_level = 1, +) + +bazel_dep( + name = "platforms", + version = "0.0.8", +) diff --git a/README.md b/README.md index bb5418ff..8569a352 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,442 @@ -tinyobjloader -============= +# tinyobjloader -[![wercker status](https://app.wercker.com/status/495a3bac400212cdacdeb4dd9397bf4f/m "wercker status")](https://app.wercker.com/project/bykey/495a3bac400212cdacdeb4dd9397bf4f) +[![PyPI version](https://badge.fury.io/py/tinyobjloader.svg)](https://badge.fury.io/py/tinyobjloader) -http://syoyo.github.io/tinyobjloader/ +[![AZ Build Status](https://dev.azure.com/tinyobjloader/tinyobjloader/_apis/build/status/tinyobjloader.tinyobjloader?branchName=master)](https://dev.azure.com/tinyobjloader/tinyobjloader/_build/latest?definitionId=1&branchName=master) -Tiny but poweful single file wavefront obj loader written in C++. No dependency except for C++ STL. It can parse 10M over polygons with moderate memory and time. +[![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/m6wfkvket7gth8wn/branch/master?svg=true)](https://ci.appveyor.com/project/syoyo/tinyobjloader-6e4qf/branch/master) -Good for embedding .obj loader to your (global illumination) renderer ;-) +[![Coverage Status](https://coveralls.io/repos/github/syoyo/tinyobjloader/badge.svg?branch=master)](https://coveralls.io/github/syoyo/tinyobjloader?branch=master) -What's new ----------- +[![AUR version](https://img.shields.io/aur/version/tinyobjloader?logo=arch-linux)](https://aur.archlinux.org/packages/tinyobjloader) -* Mar 17, 2014 : Fixed trim newline bugs. Thanks ardneran! -* Apr 29, 2014 : Add API to read .obj from std::istream. Good for reading compressed .obj or connecting to procedural primitive generator. Thanks burnse! -* Apr 21, 2014 : Define default material if no material definition exists in .obj. Thanks YarmUI! -* Apr 10, 2014 : Add support for parsing 'illum' and 'd'/'Tr' statements. Thanks mmp! -* Jan 27, 2014 : Added CMake project. Thanks bradc6! -* Nov 26, 2013 : Performance optimization by NeuralSandwich. 9% improvement in his project, thanks! -* Sep 12, 2013 : Added multiple .obj sticher example. +Tiny but powerful single file wavefront obj loader written in C++03. No dependency except for C++ STL. It can parse over 10M polygons with moderate memory and time. -Example -------- +`tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-) -![Rungholt](https://github.com/syoyo/tinyobjloader/blob/master/images/rungholt.jpg?raw=true) +If you are looking for C99 version, please see https://github.com/syoyo/tinyobjloader-c . + +Version notice +-------------- + +We recommend to use `master`(`main`) branch. Its v2.0 release candidate. Most features are now nearly robust and stable(Remaining task for release v2.0 is polishing C++ and Python API, and fix built-in triangulation code). + +We have released new version v1.0.0 on 20 Aug, 2016. +Old version is available as `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x + +## What's new + +* 29 Jul, 2021 : Added Mapbox's earcut for robust triangulation. Also fixes triangulation bug(still there is some issue in built-in triangulation algorithm: https://github.com/tinyobjloader/tinyobjloader/issues/319). +* 19 Feb, 2020 : The repository has been moved to https://github.com/tinyobjloader/tinyobjloader ! +* 18 May, 2019 : Python binding!(See `python` folder. Also see https://pypi.org/project/tinyobjloader/) +* 14 Apr, 2019 : Bump version v2.0.0 rc0. New C++ API and python bindings!(1.x API still exists for backward compatibility) +* 20 Aug, 2016 : Bump version v1.0.0. New data structure and API! + +## Requirements + +* C++03 compiler + +### Old version + +Previous old version is available in `v0.9.x` branch. + +## Example + +![Rungholt](images/rungholt.jpg) tinyobjloader can successfully load 6M triangles Rungholt scene. -http://graphics.cs.williams.edu/data/meshes.xml +http://casual-effects.com/data/index.html + +![](images/sanmugel.png) + +* [examples/viewer/](examples/viewer) OpenGL .obj viewer +* [examples/callback_api/](examples/callback_api/) Callback API example +* [examples/voxelize/](examples/voxelize/) Voxelizer example -Use case --------- +## Use case TinyObjLoader is successfully used in ... +### New version(v1.0.x) + +* Double precision support through `TINYOBJLOADER_USE_DOUBLE` thanks to noma +* Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models +* .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master +* Vulkan Cookbook https://github.com/PacktPublishing/Vulkan-Cookbook +* cudabox: CUDA Solid Voxelizer Engine https://github.com/gaspardzoss/cudavox +* Drake: A planning, control, and analysis toolbox for nonlinear dynamical systems https://github.com/RobotLocomotion/drake +* VFPR - a Vulkan Forward Plus Renderer : https://github.com/WindyDarian/Vulkan-Forward-Plus-Renderer +* glslViewer: https://github.com/patriciogonzalezvivo/glslViewer +* Lighthouse2: https://github.com/jbikker/lighthouse2 +* rayrender(an open source R package for raytracing scenes in created in R): https://github.com/tylermorganwall/rayrender +* liblava - A modern C++ and easy-to-use framework for the Vulkan API. [MIT]: https://github.com/liblava/liblava +* rtxON - Simple Vulkan raytracing tutorials https://github.com/iOrange/rtxON +* metal-ray-tracer - Writing ray-tracer using Metal Performance Shaders https://github.com/sergeyreznik/metal-ray-tracer https://sergeyreznik.github.io/metal-ray-tracer/index.html +* Supernova Engine - 2D and 3D projects with Lua or C++ in data oriented design: https://github.com/supernovaengine/supernova +* AGE (Arc Game Engine) - An open-source engine for building 2D & 3D real-time rendering and interactive contents: https://github.com/MohitSethi99/ArcGameEngine +* [Wicked Engine](https://github.com/turanszkij/WickedEngine) - 3D engine with modern graphics +* Your project here! (Letting us know via github issue is welcome!) + +### Old version(v0.9.x) + * bullet3 https://github.com/erwincoumans/bullet3 -* pbrt-v2 https://https://github.com/mmp/pbrt-v2 +* pbrt-v2 https://github.com/mmp/pbrt-v2 * OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01 +* mallie https://lighttransport.github.io/mallie +* IBLBaker (Image Based Lighting Baker). http://www.derkreature.com/iblbaker/ +* Stanford CS148 http://web.stanford.edu/class/cs148/assignments/assignment3.pdf +* Awesome Bump http://awesomebump.besaba.com/about/ +* sdlgl3-wavefront OpenGL .obj viewer https://github.com/chrisliebert/sdlgl3-wavefront +* pbrt-v3 https://github.com/mmp/pbrt-v3 +* cocos2d-x https://github.com/cocos2d/cocos2d-x/ +* Android Vulkan demo https://github.com/SaschaWillems/Vulkan +* voxelizer https://github.com/karimnaaji/voxelizer +* Probulator https://github.com/kayru/Probulator +* OptiX Prime baking https://github.com/nvpro-samples/optix_prime_baking +* FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK +* parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg +* Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl +* Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/ +* Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf +* Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf +* GeeXLab http://www.geeks3d.com/hacklab/20160531/geexlab-0-12-0-0-released-for-windows/ + -Features --------- +## Features * Group(parse multiple group name) * Vertex + * Vertex color(as an extension: https://blender.stackexchange.com/questions/31997/how-can-i-get-vertex-painted-obj-files-to-import-into-blender) * Texcoord * Normal -* Material - * Unknown material attributes are treated as key-value. +* Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification) +* Callback API for custom loading. +* Double precision support(for HPC application). +* Smoothing group +* Python binding : See `python` folder. + * Precompiled binary(manylinux1-x86_64 only) is hosted at pypi https://pypi.org/project/tinyobjloader/) -Notes ------ +### Primitives -Polygon is converted into triangle. +* [x] face(`f`) +* [x] lines(`l`) +* [ ] points(`p`) +* [ ] curve +* [ ] 2D curve +* [ ] surface. +* [ ] Free form curve/surfaces -TODO ----- +### Material -* Support quad polygon and some tags for OpenSubdiv http://graphics.pixar.com/opensubdiv/ +* PBR material extension for .MTL. Please see [pbr-mtl.md](pbr-mtl.md) for details. +* Texture options +* Unknown material attributes are returned as key-value(value is string) map. -License -------- +## TODO -Licensed under 2 clause BSD. +* [ ] Fix obj_sticker example. +* [ ] More unit test codes. -Usage ------ +## License - std::string inputfile = "cornell_box.obj"; - std::vector shapes; - - std::string err = tinyobj::LoadObj(shapes, inputfile.c_str()); - - if (!err.empty()) { - std::cerr << err << std::endl; - exit(1); - } - - std::cout << "# of shapes : " << shapes.size() << std::endl; - - for (size_t i = 0; i < shapes.size(); i++) { - printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); - printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); - assert((shapes[i].mesh.indices.size() % 3) == 0); - for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { - printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); +TinyObjLoader is licensed under MIT license. + +### Third party licenses. + +* pybind11 : BSD-style license. +* mapbox earcut.hpp: ISC License. + +## Usage + +### Installation + +One option is to simply copy the header file into your project and to make sure that `TINYOBJLOADER_IMPLEMENTATION` is defined exactly once. + +### Building tinyobjloader - Using vcpkg(not recommended though) + +Although it is not a recommended way, you can download and install tinyobjloader using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: + + git clone https://github.com/Microsoft/vcpkg.git + cd vcpkg + ./bootstrap-vcpkg.sh + ./vcpkg integrate install + ./vcpkg install tinyobjloader + +The tinyobjloader port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +### Data format + +`attrib_t` contains single and linear array of vertex data(position, normal and texcoord). + +``` +attrib_t::vertices => 3 floats per vertex + + v[0] v[1] v[2] v[3] v[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z | + +-----------+-----------+-----------+-----------+ +-----------+ + +attrib_t::normals => 3 floats per vertex + + n[0] n[1] n[2] n[3] n[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z | + +-----------+-----------+-----------+-----------+ +-----------+ + +attrib_t::texcoords => 2 floats per vertex + + t[0] t[1] t[2] t[3] t[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | u | v | u | v | u | v | u | v | .... | u | v | + +-----------+-----------+-----------+-----------+ +-----------+ + +attrib_t::colors => 3 floats per vertex(vertex color. optional) + + c[0] c[1] c[2] c[3] c[n-1] + +-----------+-----------+-----------+-----------+ +-----------+ + | x | y | z | x | y | z | x | y | z | x | y | z | .... | x | y | z | + +-----------+-----------+-----------+-----------+ +-----------+ + +``` + +Each `shape_t::mesh_t` does not contain vertex data but contains array index to `attrib_t`. +See `loader_example.cc` for more details. + + +``` + +mesh_t::indices => array of vertex indices. + + +----+----+----+----+----+----+----+----+----+----+ +--------+ + | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-1) | + +----+----+----+----+----+----+----+----+----+----+ +--------+ + +Each index has an array index to attrib_t::vertices, attrib_t::normals and attrib_t::texcoords. + +mesh_t::num_face_vertices => array of the number of vertices per face(e.g. 3 = triangle, 4 = quad , 5 or more = N-gons). + + + +---+---+---+ +---+ + | 3 | 4 | 3 | ...... | 3 | + +---+---+---+ +---+ + | | | | + | | | +-----------------------------------------+ + | | | | + | | +------------------------------+ | + | | | | + | +------------------+ | | + | | | | + |/ |/ |/ |/ + + mesh_t::indices + + | face[0] | face[1] | face[2] | | face[n-1] | + +----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+ + | i0 | i1 | i2 | i3 | i4 | i5 | i6 | i7 | i8 | i9 | ... | i(n-3) | i(n-2) | i(n-1) | + +----+----+----+----+----+----+----+----+----+----+ +--------+--------+--------+ + +``` + +Note that when `triangulate` flag is true in `tinyobj::LoadObj()` argument, `num_face_vertices` are all filled with 3(triangle). + +### float data type + +TinyObjLoader now use `real_t` for floating point data type. +Default is `float(32bit)`. +You can enable `double(64bit)` precision by using `TINYOBJLOADER_USE_DOUBLE` define. + +### Robust triangulation + +When you enable `triangulation`(default is enabled), +TinyObjLoader triangulate polygons(faces with 4 or more vertices). + +Built-in triangulation code may not work well in some polygon shape. + +You can define `TINYOBJLOADER_USE_MAPBOX_EARCUT` for robust triangulation using `mapbox/earcut.hpp`. +This requires C++11 compiler though. And you need to copy `mapbox/earcut.hpp` to your project. +If you have your own `mapbox/earcut.hpp` file incuded in your project, you can define `TINYOBJLOADER_DONOT_INCLUDE_MAPBOX_EARCUT` so that `mapbox/earcut.hpp` is not included inside of `tiny_obj_loader.h`. + +#### Example code (Deprecated API) + +```c++ +#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc +// Optional. define TINYOBJLOADER_USE_MAPBOX_EARCUT gives robust triangulation. Requires C++11 +//#define TINYOBJLOADER_USE_MAPBOX_EARCUT +#include "tiny_obj_loader.h" + +std::string inputfile = "cornell_box.obj"; +tinyobj::attrib_t attrib; +std::vector shapes; +std::vector materials; + +std::string warn; +std::string err; + +bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, inputfile.c_str()); + +if (!warn.empty()) { + std::cout << warn << std::endl; +} + +if (!err.empty()) { + std::cerr << err << std::endl; +} + +if (!ret) { + exit(1); +} + +// Loop over shapes +for (size_t s = 0; s < shapes.size(); s++) { + // Loop over faces(polygon) + size_t index_offset = 0; + for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { + size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]); + + // Loop over vertices in the face. + for (size_t v = 0; v < fv; v++) { + // access to vertex + tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v]; + + tinyobj::real_t vx = attrib.vertices[3*size_t(idx.vertex_index)+0]; + tinyobj::real_t vy = attrib.vertices[3*size_t(idx.vertex_index)+1]; + tinyobj::real_t vz = attrib.vertices[3*size_t(idx.vertex_index)+2]; + + // Check if `normal_index` is zero or positive. negative = no normal data + if (idx.normal_index >= 0) { + tinyobj::real_t nx = attrib.normals[3*size_t(idx.normal_index)+0]; + tinyobj::real_t ny = attrib.normals[3*size_t(idx.normal_index)+1]; + tinyobj::real_t nz = attrib.normals[3*size_t(idx.normal_index)+2]; } - - printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); - assert((shapes[i].mesh.positions.size() % 3) == 0); - for (size_t v = 0; v < shapes[i].mesh.positions.size() / 3; v++) { - printf(" v[%ld] = (%f, %f, %f)\n", v, - shapes[i].mesh.positions[3*v+0], - shapes[i].mesh.positions[3*v+1], - shapes[i].mesh.positions[3*v+2]); + + // Check if `texcoord_index` is zero or positive. negative = no texcoord data + if (idx.texcoord_index >= 0) { + tinyobj::real_t tx = attrib.texcoords[2*size_t(idx.texcoord_index)+0]; + tinyobj::real_t ty = attrib.texcoords[2*size_t(idx.texcoord_index)+1]; + } + // Optional: vertex colors + // tinyobj::real_t red = attrib.colors[3*size_t(idx.vertex_index)+0]; + // tinyobj::real_t green = attrib.colors[3*size_t(idx.vertex_index)+1]; + // tinyobj::real_t blue = attrib.colors[3*size_t(idx.vertex_index)+2]; + } + index_offset += fv; + + // per-face material + shapes[s].mesh.material_ids[f]; + } +} + +``` + +#### Example code (New Object Oriented API) + +```c++ +#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc +// Optional. define TINYOBJLOADER_USE_MAPBOX_EARCUT gives robust triangulation. Requires C++11 +//#define TINYOBJLOADER_USE_MAPBOX_EARCUT +#include "tiny_obj_loader.h" + + +std::string inputfile = "cornell_box.obj"; +tinyobj::ObjReaderConfig reader_config; +reader_config.mtl_search_path = "./"; // Path to material files + +tinyobj::ObjReader reader; + +if (!reader.ParseFromFile(inputfile, reader_config)) { + if (!reader.Error().empty()) { + std::cerr << "TinyObjReader: " << reader.Error(); + } + exit(1); +} + +if (!reader.Warning().empty()) { + std::cout << "TinyObjReader: " << reader.Warning(); +} + +auto& attrib = reader.GetAttrib(); +auto& shapes = reader.GetShapes(); +auto& materials = reader.GetMaterials(); + +// Loop over shapes +for (size_t s = 0; s < shapes.size(); s++) { + // Loop over faces(polygon) + size_t index_offset = 0; + for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { + size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]); + + // Loop over vertices in the face. + for (size_t v = 0; v < fv; v++) { + // access to vertex + tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v]; + tinyobj::real_t vx = attrib.vertices[3*size_t(idx.vertex_index)+0]; + tinyobj::real_t vy = attrib.vertices[3*size_t(idx.vertex_index)+1]; + tinyobj::real_t vz = attrib.vertices[3*size_t(idx.vertex_index)+2]; + + // Check if `normal_index` is zero or positive. negative = no normal data + if (idx.normal_index >= 0) { + tinyobj::real_t nx = attrib.normals[3*size_t(idx.normal_index)+0]; + tinyobj::real_t ny = attrib.normals[3*size_t(idx.normal_index)+1]; + tinyobj::real_t nz = attrib.normals[3*size_t(idx.normal_index)+2]; } - - printf("shape[%ld].material.name = %s\n", i, shapes[i].material.name.c_str()); - printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); - printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); - printf(" material.Ks = (%f, %f ,%f)\n", shapes[i].material.specular[0], shapes[i].material.specular[1], shapes[i].material.specular[2]); - printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); - printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); - printf(" material.Ns = %f\n", shapes[i].material.shininess); - printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); - printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); - printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); - printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); - std::map::iterator it(shapes[i].material.unknown_parameter.begin()); - std::map::iterator itEnd(shapes[i].material.unknown_parameter.end()); - for (; it != itEnd; it++) { - printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + + // Check if `texcoord_index` is zero or positive. negative = no texcoord data + if (idx.texcoord_index >= 0) { + tinyobj::real_t tx = attrib.texcoords[2*size_t(idx.texcoord_index)+0]; + tinyobj::real_t ty = attrib.texcoords[2*size_t(idx.texcoord_index)+1]; } - printf("\n"); + + // Optional: vertex colors + // tinyobj::real_t red = attrib.colors[3*size_t(idx.vertex_index)+0]; + // tinyobj::real_t green = attrib.colors[3*size_t(idx.vertex_index)+1]; + // tinyobj::real_t blue = attrib.colors[3*size_t(idx.vertex_index)+2]; } + index_offset += fv; + + // per-face material + shapes[s].mesh.material_ids[f]; + } +} + +``` + + + +## Optimized loader + +Optimized multi-threaded .obj loader is available at `experimental/` directory. +If you want absolute performance to load .obj data, this optimized loader will fit your purpose. +Note that the optimized loader uses C++11 thread and it does less error checks but may work most .obj data. + +Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core m5 1.2GHz). + +* Rungholt scene(6M triangles) + * old version(v0.9.x): 15500 msecs. + * baseline(v1.0.x): 6800 msecs(2.3x faster than old version) + * optimised: 1500 msecs(10x faster than old version, 4.5x faster than baseline) + +## Python binding + +``` +$ python -m pip install tinyobjloader +``` + +See [python/sample.py](python/sample.py) for example use of Python binding of tinyobjloader. + +### CI + PyPI upload + +cibuildwheels + twine upload for each git tagging event is handled in Github Actions and Cirrus CI(arm builds). + +#### How to bump version(For developer) + +* Apply `black` to python files(`python/sample.py`) +* Bump version in CMakeLists.txt +* Commit and push `release`. Confirm C.I. build is OK. +* Create tag starting with `v`(e.g. `v2.1.0`) +* `git push --tags` + * version settings is automatically handled in python binding through setuptools_scm. + * cibuildwheels + pypi upload(through twine) will be automatically triggered in Github Actions + Cirrus CI. + +## Tests + +Unit tests are provided in `tests` directory. See `tests/README.md` for details. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..93f691ce --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,22 @@ +version: 1.0.{build} + +platform: x64 + +install: + ####################################################################################### + # All external dependencies are installed in C:\projects\deps + ####################################################################################### + - mkdir C:\projects\deps + + ####################################################################################### + # Install Ninja + ####################################################################################### + - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.10.0/ninja-win.zip" + - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip + - 7z x ninja.zip -oC:\projects\deps\ninja > nul + - set PATH=C:\projects\deps\ninja;%PATH% + - ninja --version + +build_script: + - cd tests + - vcbuild.bat diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..2580f172 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,170 @@ +# +# Python wheels build is now done in Github Actions + Cirrus CI(for arm build) +# so python build is disabled in Azure pipelines. +# + +variables: + # https://cibuildwheel.readthedocs.io/en/stable/cpp_standards/ + # cibuildwheel now supports python 3.6+(as of 2022 Oct) + #CIBW_SKIP: "pp*" + CIBW_BEFORE_BUILD: "pip install pybind11" + CIBW_ARCHS_LINUXBEFORE_BUILD: "pip install pybind11" + # disable aarch64 build for a while since it(pulling docker aarch64 image) exceeds Azure's 60 min limit + # NOTE: aarch64 linux support in Azure pipeline is not yet officially supported(as of 2022 Oct) https://github.com/microsoft/azure-pipelines-agent/issues/3935 + #CIBW_ARCHS_LINUX: auto aarch64 + CIBW_ARCHS_MACOS: x86_64 universal2 arm64 + #CIBW_BEFORE_BUILD_MACOS: "pip install -U pip setuptools" + #CIBW_BEFORE_BUILD_LINUX: "pip install -U pip setuptools" + #CIBW_TEST_COMMAND: TODO "python -c \"import tinyobjloader; tinyobjloader.test()\"" + CIBW_BUILD_VERBOSITY: "2" + #CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 + #CIBW_MANYLINUX_I686_IMAGE: manylinux2014 + +jobs: + - job: unit_linux + pool: { vmImage: "ubuntu-latest" } + steps: + - script: | + cd tests + make && ./tester + displayName: Run unit tests + + - job: python_format + pool: { vmImage: "ubuntu-latest" } + steps: + - task: UsePythonVersion@0 + - script: | + # 19.10b0 triggers 'cannot import name '_unicodefun' from 'click'' error. + # https://stackoverflow.com/questions/71673404/importerror-cannot-import-name-unicodefun-from-click + #pip install black==19.10b0 + #pip install black==22.3.0 + pip install black==22.10.0 + + black --check python/ + displayName: Check Python code format + + # Disabled: python build + ## + ## Ubuntu16.04 seems now deprecated(as of 2021/12/01), + ## so use `ubuntu-latest` + #- job: linux + # pool: {vmImage: "ubuntu-latest"} + # steps: + # - task: UsePythonVersion@0 + # - bash: | + # python3 -m pip install --upgrade pip + # pip3 install cibuildwheel twine + + # # Use pipx to build source dist + # pip3 install pipx + + # # Source dist + # pipx run build --sdist + # ls -la dist/* + + # # build binary wheels + # cibuildwheel --platform linux --output-dir wheelhouse . + + # - task: CopyFiles@2 + # inputs: + # contents: 'wheelhouse/**' + # targetFolder: $(Build.ArtifactStagingDirectory) + + # - task: CopyFiles@2 + # inputs: + # contents: 'dist/**' + # targetFolder: $(Build.ArtifactStagingDirectory) + + # - task: PublishBuildArtifacts@1 + # inputs: + # path: $(Build.ArtifactStagingDirectory) + # artifactName: tinyobjDeployLinux + + #- job: macos + # pool: {vmImage: 'macOS-latest'} + # variables: + # # Support C++11: https://github.com/joerick/cibuildwheel/pull/156 + # MACOSX_DEPLOYMENT_TARGET: 10.9 + # steps: + # - task: UsePythonVersion@0 + # - bash: | + # python3 -m pip install --upgrade pip + # pip3 install cibuildwheel + # cibuildwheel --platform macos --output-dir wheelhouse . + # - task: CopyFiles@2 + # inputs: + # contents: 'wheelhouse/*.whl' + # targetFolder: $(Build.ArtifactStagingDirectory) + # - task: PublishBuildArtifacts@1 + # inputs: + # path: $(Build.ArtifactStagingDirectory) + # artifactName: tinyobjDeployMacOS + + #- job: windows + # pool: {vmImage: 'windows-latest'} + # steps: + # - task: UsePythonVersion@0 + # - bash: | + # python -m pip install --upgrade pip + # pip install cibuildwheel + # cibuildwheel --platform windows --output-dir wheelhouse . + # - task: CopyFiles@2 + # inputs: + # contents: 'wheelhouse/*.whl' + # targetFolder: $(Build.ArtifactStagingDirectory) + # - task: PublishBuildArtifacts@1 + # inputs: + # path: $(Build.ArtifactStagingDirectory) + # artifactName: tinyobjDeployWindows + + #- job: deployPyPI + # # Based on vispy: https://github.com/vispy/vispy/blob/master/azure-pipelines.yml + # pool: {vmImage: 'ubuntu-latest'} + # condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) + # dependsOn: + # - linux + # - macos + # - windows + # steps: + # - task: UsePythonVersion@0 + + # # TODO(syoyo): Use buildType: specific to download multiple artifacts at once? + # - task: DownloadBuildArtifacts@0 + # inputs: + # artifactName: 'tinyobjDeployLinux' + # downloadPath: $(Pipeline.Workspace) + + # - task: DownloadBuildArtifacts@0 + # inputs: + # artifactName: 'tinyobjDeployMacOS' + # downloadPath: $(Pipeline.Workspace) + + # - task: DownloadBuildArtifacts@0 + # inputs: + # artifactName: 'tinyobjDeployWindows' + # downloadPath: $(Pipeline.Workspace) + + # # Publish to PyPI through twine + # - bash: | + # cd $(Pipeline.Workspace) + # find . + # python -m pip install --upgrade pip + # pip install twine + # echo tinyobjDeployLinux/dist/* + # echo tinyobjDeployLinux/wheelhouse/* tinyobjDeployMacOS/wheelhouse/* tinyobjDeployWindows/wheelhouse/* + # twine upload -u "__token__" --skip-existing tinyobjDeployLinux/dist/* tinyobjDeployLinux/wheelhouse/* tinyobjDeployMacOS/wheelhouse/* tinyobjDeployWindows/wheelhouse/* + # env: + # TWINE_PASSWORD: $(pypiToken2) + +trigger: + branches: + include: + - '*' + tags: + include: + - 'v*' + +pr: + branches: + include: + - "*" diff --git a/bootstrap-cmake-linux-with-pyhthon.sh b/bootstrap-cmake-linux-with-pyhthon.sh new file mode 100755 index 00000000..96cf4bf6 --- /dev/null +++ b/bootstrap-cmake-linux-with-pyhthon.sh @@ -0,0 +1,20 @@ +curdir=`pwd` + +builddir=${curdir}/build_python_module + +rm -rf ${builddir} +mkdir ${builddir} + +# set path to pybind11 +# If you install pybind11 through pip, its usually installed to /pybind11. +pybind11_path=`python -c "import site; print (site.getsitepackages()[0])"` +echo ${pybind11_path} + +CC=clang CXX=clang++ \ + pybind11_DIR=${pybind11_path}/pybind11 \ + cmake \ + -B${builddir} \ + -DCMAKE_VERBOSE_MAKEFILE=1 \ + -DTINYOBJLOADER_WITH_PYTHON=1 + +cd ${curdir} diff --git a/build.ninja b/build.ninja new file mode 100644 index 00000000..77801bb5 --- /dev/null +++ b/build.ninja @@ -0,0 +1,53 @@ +ninja_required_version = 1.4 + +gnubuilddir = build +gnudefines = +gnuincludes = -I. +gnucflags = -O2 -g +gnucxxflags = -O2 -g -pedantic -Wall -Wextra -Wcast-align -Wcast-qual $ + -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self $ + -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast $ + -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion $ + -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror $ + -Wno-unused -fsanitize=address +gnuldflags = -fsanitize=address + +pool link_pool + depth = 1 + +rule gnucxx + command = $gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags $ + -c $in -o $out + description = CXX $out + depfile = $out.d + deps = gcc +rule gnucc + command = $gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $ + $in -o $out + description = CC $out + depfile = $out.d + deps = gcc +rule gnulink + command = $gnuld -o $out $in $libs $gnuldflags + description = LINK $out + pool = link_pool +rule gnuar + command = $gnuar rsc $out $in + description = AR $out + pool = link_pool +rule gnustamp + command = touch $out + description = STAMP $out + +gnucxx = g++ +gnucc = gcc +gnuld = $gnucxx +gnuar = ar + +build loader_example.o: gnucxx loader_example.cc + + +build loader_example: gnulink loader_example.o +build all: phony loader_example + +default all diff --git a/cmake/ClangClCMakeCompileRules.cmake b/cmake/ClangClCMakeCompileRules.cmake new file mode 100644 index 00000000..a3bcf1c2 --- /dev/null +++ b/cmake/ClangClCMakeCompileRules.cmake @@ -0,0 +1,9 @@ +# macOS paths usually start with /Users/*. Unfortunately, clang-cl interprets +# paths starting with /U as macro undefines, so we need to put a -- before the +# input file path to force it to be treated as a path. CMake's compilation rules +# should be tweaked accordingly, but until that's done, and to support older +# CMake versions, overriding compilation rules works well enough. This file will +# be included by cmake after the default compilation rules have already been set +# up, so we can just modify them instead of duplicating them entirely. +string(REPLACE "-c " "-c -- " CMAKE_C_COMPILE_OBJECT "${CMAKE_C_COMPILE_OBJECT}") +string(REPLACE "-c " "-c -- " CMAKE_CXX_COMPILE_OBJECT "${CMAKE_CXX_COMPILE_OBJECT}") diff --git a/cmake/aarch64-linux-gnu.toolchain b/cmake/aarch64-linux-gnu.toolchain new file mode 100644 index 00000000..cdcdaf25 --- /dev/null +++ b/cmake/aarch64-linux-gnu.toolchain @@ -0,0 +1,14 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) +set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu) + +set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu/) + +# Sync with GitHub Actions config +set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/cmake/clang-cl-msvc-windows.cmake b/cmake/clang-cl-msvc-windows.cmake new file mode 100644 index 00000000..e2eac142 --- /dev/null +++ b/cmake/clang-cl-msvc-windows.cmake @@ -0,0 +1,327 @@ +# From llvm/cmake/platforms/WinMsvc.cmake +# Modified to use clang-cl on native Windows. + +# Cross toolchain configuration for using clang-cl on non-Windows hosts to +# target MSVC. +# +# Usage: +# cmake -G Ninja +# -DCMAKE_TOOLCHAIN_FILE=/path/to/this/file +# -DHOST_ARCH=[aarch64|arm64|armv7|arm|i686|x86|x86_64|x64] +# -DLLVM_NATIVE_TOOLCHAIN=/path/to/llvm/installation +# -DMSVC_BASE=/path/to/MSVC/system/libraries/and/includes +# -DWINSDK_BASE=/path/to/windows-sdk +# -DWINSDK_VER=windows sdk version folder name +# +# HOST_ARCH: +# The architecture to build for. +# +# LLVM_NATIVE_TOOLCHAIN: +# *Absolute path* to a folder containing the toolchain which will be used to +# build. At a minimum, this folder should have a bin directory with a +# copy of clang-cl, clang, clang++, and lld-link, as well as a lib directory +# containing clang's system resource directory. +# +# MSVC_BASE: +# *Absolute path* to the folder containing MSVC headers and system libraries. +# The layout of the folder matches that which is intalled by MSVC 2017 on +# Windows, and should look like this: +# +# ${MSVC_BASE} +# include +# vector +# stdint.h +# etc... +# lib +# x64 +# libcmt.lib +# msvcrt.lib +# etc... +# x86 +# libcmt.lib +# msvcrt.lib +# etc... +# +# For versions of MSVC < 2017, or where you have a hermetic toolchain in a +# custom format, you must use symlinks or restructure it to look like the above. +# +# WINSDK_BASE: +# Together with WINSDK_VER, determines the location of Windows SDK headers +# and libraries. +# +# WINSDK_VER: +# Together with WINSDK_BASE, determines the locations of Windows SDK headers +# and libraries. +# +# WINSDK_BASE and WINSDK_VER work together to define a folder layout that matches +# that of the Windows SDK installation on a standard Windows machine. It should +# match the layout described below. +# +# Note that if you install Windows SDK to a windows machine and simply copy the +# files, it will already be in the correct layout. +# +# ${WINSDK_BASE} +# Include +# ${WINSDK_VER} +# shared +# ucrt +# um +# windows.h +# etc... +# Lib +# ${WINSDK_VER} +# ucrt +# x64 +# x86 +# ucrt.lib +# etc... +# um +# x64 +# x86 +# kernel32.lib +# etc +# +# IMPORTANT: In order for this to work, you will need a valid copy of the Windows +# SDK and C++ STL headers and libraries on your host. Additionally, since the +# Windows libraries and headers are not case-correct, this toolchain file sets +# up a VFS overlay for the SDK headers and case-correcting symlinks for the +# libraries when running on a case-sensitive filesystem. + + +# When configuring CMake with a toolchain file against a top-level CMakeLists.txt, +# it will actually run CMake many times, once for each small test program used to +# determine what features a compiler supports. Unfortunately, none of these +# invocations share a CMakeCache.txt with the top-level invocation, meaning they +# won't see the value of any arguments the user passed via -D. Since these are +# necessary to properly configure MSVC in both the top-level configuration as well as +# all feature-test invocations, we set environment variables with the values so that +# these environments get inherited by child invocations. We can switch to +# CMAKE_TRY_COMPILE_PLATFORM_VARIABLES once our minimum supported CMake version +# is 3.6 or greater. +function(init_user_prop prop) + if(${prop}) + set(ENV{_${prop}} "${${prop}}") + else() + set(${prop} "$ENV{_${prop}}" PARENT_SCOPE) + endif() +endfunction() + +function(generate_winsdk_vfs_overlay winsdk_include_dir output_path) + set(include_dirs) + file(GLOB_RECURSE entries LIST_DIRECTORIES true "${winsdk_include_dir}/*") + foreach(entry ${entries}) + if(IS_DIRECTORY "${entry}") + list(APPEND include_dirs "${entry}") + endif() + endforeach() + + file(WRITE "${output_path}" "version: 0\n") + file(APPEND "${output_path}" "case-sensitive: false\n") + file(APPEND "${output_path}" "roots:\n") + + foreach(dir ${include_dirs}) + file(GLOB headers RELATIVE "${dir}" "${dir}/*.h") + if(NOT headers) + continue() + endif() + + file(APPEND "${output_path}" " - name: \"${dir}\"\n") + file(APPEND "${output_path}" " type: directory\n") + file(APPEND "${output_path}" " contents:\n") + + foreach(header ${headers}) + file(APPEND "${output_path}" " - name: \"${header}\"\n") + file(APPEND "${output_path}" " type: file\n") + file(APPEND "${output_path}" " external-contents: \"${dir}/${header}\"\n") + endforeach() + endforeach() +endfunction() + +function(generate_winsdk_lib_symlinks winsdk_um_lib_dir output_dir) + execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}") + file(GLOB libraries RELATIVE "${winsdk_um_lib_dir}" "${winsdk_um_lib_dir}/*") + foreach(library ${libraries}) + string(TOLOWER "${library}" all_lowercase_symlink_name) + if(NOT library STREQUAL all_lowercase_symlink_name) + execute_process(COMMAND "${CMAKE_COMMAND}" + -E create_symlink + "${winsdk_um_lib_dir}/${library}" + "${output_dir}/${all_lowercase_symlink_name}") + endif() + + get_filename_component(name_we "${library}" NAME_WE) + get_filename_component(ext "${library}" EXT) + string(TOLOWER "${ext}" lowercase_ext) + set(lowercase_ext_symlink_name "${name_we}${lowercase_ext}") + if(NOT library STREQUAL lowercase_ext_symlink_name AND + NOT all_lowercase_symlink_name STREQUAL lowercase_ext_symlink_name) + execute_process(COMMAND "${CMAKE_COMMAND}" + -E create_symlink + "${winsdk_um_lib_dir}/${library}" + "${output_dir}/${lowercase_ext_symlink_name}") + endif() + endforeach() +endfunction() + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_VERSION 10.0) +set(CMAKE_SYSTEM_PROCESSOR AMD64) + +init_user_prop(HOST_ARCH) +init_user_prop(LLVM_NATIVE_TOOLCHAIN) +init_user_prop(MSVC_BASE) +init_user_prop(WINSDK_BASE) +init_user_prop(WINSDK_VER) + +if(NOT HOST_ARCH) + set(HOST_ARCH x86_64) +endif() +if(HOST_ARCH STREQUAL "aarch64" OR HOST_ARCH STREQUAL "arm64") + set(TRIPLE_ARCH "aarch64") + set(WINSDK_ARCH "arm64") +elseif(HOST_ARCH STREQUAL "armv7" OR HOST_ARCH STREQUAL "arm") + set(TRIPLE_ARCH "armv7") + set(WINSDK_ARCH "arm") +elseif(HOST_ARCH STREQUAL "i686" OR HOST_ARCH STREQUAL "x86") + set(TRIPLE_ARCH "i686") + set(WINSDK_ARCH "x86") +elseif(HOST_ARCH STREQUAL "x86_64" OR HOST_ARCH STREQUAL "x64") + set(TRIPLE_ARCH "x86_64") + set(WINSDK_ARCH "x64") +else() + message(SEND_ERROR "Unknown host architecture ${HOST_ARCH}. Must be aarch64 (or arm64), armv7 (or arm), i686 (or x86), or x86_64 (or x64).") +endif() + +set(MSVC_INCLUDE "${MSVC_BASE}/include") +set(ATLMFC_INCLUDE "${MSVC_BASE}/atlmfc/include") +set(MSVC_LIB "${MSVC_BASE}/lib") +set(ATLMFC_LIB "${MSVC_BASE}/atlmfc/lib") +set(WINSDK_INCLUDE "${WINSDK_BASE}/Include/${WINSDK_VER}") +set(WINSDK_LIB "${WINSDK_BASE}/Lib/${WINSDK_VER}") + +# Do some sanity checking to make sure we can find a native toolchain and +# that the Windows SDK / MSVC STL directories look kosher. +if(NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl.exe" OR + NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link.exe") + message(SEND_ERROR + "LLVM_NATIVE_TOOLCHAIN folder '${LLVM_NATIVE_TOOLCHAIN}' does not " + "point to a valid directory containing bin/clang-cl.exe and bin/lld-link.exe " + "binaries") +endif() + +if(NOT EXISTS "${MSVC_BASE}" OR + NOT EXISTS "${MSVC_INCLUDE}" OR + NOT EXISTS "${MSVC_LIB}") + message(SEND_ERROR + "CMake variable MSVC_BASE must point to a folder containing MSVC " + "system headers and libraries") +endif() + +if(NOT EXISTS "${WINSDK_BASE}" OR + NOT EXISTS "${WINSDK_INCLUDE}" OR + NOT EXISTS "${WINSDK_LIB}") + message(SEND_ERROR + "CMake variable WINSDK_BASE and WINSDK_VER must resolve to a valid " + "Windows SDK installation") +endif() + +if(NOT EXISTS "${WINSDK_INCLUDE}/um/Windows.h") + message(SEND_ERROR "Cannot find Windows.h") +endif() +if(NOT EXISTS "${WINSDK_INCLUDE}/um/WINDOWS.H") + set(case_sensitive_filesystem TRUE) +endif() + +set(CMAKE_C_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl.exe" CACHE FILEPATH "") +set(CMAKE_CXX_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl.exe" CACHE FILEPATH "") +set(CMAKE_LINKER "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link.exe" CACHE FILEPATH "") + +# Even though we're cross-compiling, we need some native tools (e.g. llvm-tblgen), and those +# native tools have to be built before we can start doing the cross-build. LLVM supports +# a CROSS_TOOLCHAIN_FLAGS_NATIVE argument which consists of a list of flags to pass to CMake +# when configuring the NATIVE portion of the cross-build. By default we construct this so +# that it points to the tools in the same location as the native clang-cl that we're using. +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_ASM_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_C_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_CXX_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang++") + +set(CROSS_TOOLCHAIN_FLAGS_NATIVE "${_CTF_NATIVE_DEFAULT}" CACHE STRING "") + +set(COMPILE_FLAGS + -D_CRT_SECURE_NO_WARNINGS + --target=${TRIPLE_ARCH}-windows-msvc + -fms-compatibility-version=19.11 + -imsvc "\"${ATLMFC_INCLUDE}\"" + -imsvc "\"${MSVC_INCLUDE}\"" + -imsvc "\"${WINSDK_INCLUDE}/ucrt\"" + -imsvc "\"${WINSDK_INCLUDE}/shared\"" + -imsvc "\"${WINSDK_INCLUDE}/um\"" + -imsvc "\"${WINSDK_INCLUDE}/winrt\"") + +if(case_sensitive_filesystem) + # Ensure all sub-configures use the top-level VFS overlay instead of generating their own. + init_user_prop(winsdk_vfs_overlay_path) + if(NOT winsdk_vfs_overlay_path) + set(winsdk_vfs_overlay_path "${CMAKE_BINARY_DIR}/winsdk_vfs_overlay.yaml") + generate_winsdk_vfs_overlay("${WINSDK_BASE}/Include/${WINSDK_VER}" "${winsdk_vfs_overlay_path}") + init_user_prop(winsdk_vfs_overlay_path) + endif() + list(APPEND COMPILE_FLAGS + -Xclang -ivfsoverlay -Xclang "${winsdk_vfs_overlay_path}") +endif() + +string(REPLACE ";" " " COMPILE_FLAGS "${COMPILE_FLAGS}") + +# We need to preserve any flags that were passed in by the user. However, we +# can't append to CMAKE_C_FLAGS and friends directly, because toolchain files +# will be re-invoked on each reconfigure and therefore need to be idempotent. +# The assignments to the _INITIAL cache variables don't use FORCE, so they'll +# only be populated on the initial configure, and their values won't change +# afterward. +set(_CMAKE_C_FLAGS_INITIAL "${CMAKE_C_FLAGS}" CACHE STRING "") +set(CMAKE_C_FLAGS "${_CMAKE_C_FLAGS_INITIAL} ${COMPILE_FLAGS}" CACHE STRING "" FORCE) + +set(_CMAKE_CXX_FLAGS_INITIAL "${CMAKE_CXX_FLAGS}" CACHE STRING "") +set(CMAKE_CXX_FLAGS "${_CMAKE_CXX_FLAGS_INITIAL} ${COMPILE_FLAGS}" CACHE STRING "" FORCE) + +set(LINK_FLAGS + # Prevent CMake from attempting to invoke mt.exe. It only recognizes the slashed form and not the dashed form. + /manifest:no + + -libpath:"${ATLMFC_LIB}/${WINSDK_ARCH}" + -libpath:"${MSVC_LIB}/${WINSDK_ARCH}" + -libpath:"${WINSDK_LIB}/ucrt/${WINSDK_ARCH}" + -libpath:"${WINSDK_LIB}/um/${WINSDK_ARCH}") + +if(case_sensitive_filesystem) + # Ensure all sub-configures use the top-level symlinks dir instead of generating their own. + init_user_prop(winsdk_lib_symlinks_dir) + if(NOT winsdk_lib_symlinks_dir) + set(winsdk_lib_symlinks_dir "${CMAKE_BINARY_DIR}/winsdk_lib_symlinks") + generate_winsdk_lib_symlinks("${WINSDK_BASE}/Lib/${WINSDK_VER}/um/${WINSDK_ARCH}" "${winsdk_lib_symlinks_dir}") + init_user_prop(winsdk_lib_symlinks_dir) + endif() + list(APPEND LINK_FLAGS + -libpath:"${winsdk_lib_symlinks_dir}") +endif() + +string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}") + +# See explanation for compiler flags above for the _INITIAL variables. +set(_CMAKE_EXE_LINKER_FLAGS_INITIAL "${CMAKE_EXE_LINKER_FLAGS}" CACHE STRING "") +set(CMAKE_EXE_LINKER_FLAGS "${_CMAKE_EXE_LINKER_FLAGS_INITIAL} ${LINK_FLAGS}" CACHE STRING "" FORCE) + +set(_CMAKE_MODULE_LINKER_FLAGS_INITIAL "${CMAKE_MODULE_LINKER_FLAGS}" CACHE STRING "") +set(CMAKE_MODULE_LINKER_FLAGS "${_CMAKE_MODULE_LINKER_FLAGS_INITIAL} ${LINK_FLAGS}" CACHE STRING "" FORCE) + +set(_CMAKE_SHARED_LINKER_FLAGS_INITIAL "${CMAKE_SHARED_LINKER_FLAGS}" CACHE STRING "") +set(CMAKE_SHARED_LINKER_FLAGS "${_CMAKE_SHARED_LINKER_FLAGS_INITIAL} ${LINK_FLAGS}" CACHE STRING "" FORCE) + +# CMake populates these with a bunch of unnecessary libraries, which requires +# extra case-correcting symlinks and what not. Instead, let projects explicitly +# control which libraries they require. +set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) +set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) + +# Allow clang-cl to work with macOS paths. +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/ClangClCMakeCompileRules.cmake") diff --git a/cmake/clang-cl-msvc-wsl.cmake b/cmake/clang-cl-msvc-wsl.cmake new file mode 100644 index 00000000..ffe21314 --- /dev/null +++ b/cmake/clang-cl-msvc-wsl.cmake @@ -0,0 +1,327 @@ +# From llvm/cmake/platforms/WinMsvc.cmake +# Modified to use clang-cl on native Windows. + +# Cross toolchain configuration for using clang-cl on non-Windows hosts to +# target MSVC. +# +# Usage: +# cmake -G Ninja +# -DCMAKE_TOOLCHAIN_FILE=/path/to/this/file +# -DHOST_ARCH=[aarch64|arm64|armv7|arm|i686|x86|x86_64|x64] +# -DLLVM_NATIVE_TOOLCHAIN=/path/to/llvm/installation +# -DMSVC_BASE=/path/to/MSVC/system/libraries/and/includes +# -DWINSDK_BASE=/path/to/windows-sdk +# -DWINSDK_VER=windows sdk version folder name +# +# HOST_ARCH: +# The architecture to build for. +# +# LLVM_NATIVE_TOOLCHAIN: +# *Absolute path* to a folder containing the toolchain which will be used to +# build. At a minimum, this folder should have a bin directory with a +# copy of clang-cl, clang, clang++, and lld-link, as well as a lib directory +# containing clang's system resource directory. +# +# MSVC_BASE: +# *Absolute path* to the folder containing MSVC headers and system libraries. +# The layout of the folder matches that which is intalled by MSVC 2017 on +# Windows, and should look like this: +# +# ${MSVC_BASE} +# include +# vector +# stdint.h +# etc... +# lib +# x64 +# libcmt.lib +# msvcrt.lib +# etc... +# x86 +# libcmt.lib +# msvcrt.lib +# etc... +# +# For versions of MSVC < 2017, or where you have a hermetic toolchain in a +# custom format, you must use symlinks or restructure it to look like the above. +# +# WINSDK_BASE: +# Together with WINSDK_VER, determines the location of Windows SDK headers +# and libraries. +# +# WINSDK_VER: +# Together with WINSDK_BASE, determines the locations of Windows SDK headers +# and libraries. +# +# WINSDK_BASE and WINSDK_VER work together to define a folder layout that matches +# that of the Windows SDK installation on a standard Windows machine. It should +# match the layout described below. +# +# Note that if you install Windows SDK to a windows machine and simply copy the +# files, it will already be in the correct layout. +# +# ${WINSDK_BASE} +# Include +# ${WINSDK_VER} +# shared +# ucrt +# um +# windows.h +# etc... +# Lib +# ${WINSDK_VER} +# ucrt +# x64 +# x86 +# ucrt.lib +# etc... +# um +# x64 +# x86 +# kernel32.lib +# etc +# +# IMPORTANT: In order for this to work, you will need a valid copy of the Windows +# SDK and C++ STL headers and libraries on your host. Additionally, since the +# Windows libraries and headers are not case-correct, this toolchain file sets +# up a VFS overlay for the SDK headers and case-correcting symlinks for the +# libraries when running on a case-sensitive filesystem. + + +# When configuring CMake with a toolchain file against a top-level CMakeLists.txt, +# it will actually run CMake many times, once for each small test program used to +# determine what features a compiler supports. Unfortunately, none of these +# invocations share a CMakeCache.txt with the top-level invocation, meaning they +# won't see the value of any arguments the user passed via -D. Since these are +# necessary to properly configure MSVC in both the top-level configuration as well as +# all feature-test invocations, we set environment variables with the values so that +# these environments get inherited by child invocations. We can switch to +# CMAKE_TRY_COMPILE_PLATFORM_VARIABLES once our minimum supported CMake version +# is 3.6 or greater. +function(init_user_prop prop) + if(${prop}) + set(ENV{_${prop}} "${${prop}}") + else() + set(${prop} "$ENV{_${prop}}" PARENT_SCOPE) + endif() +endfunction() + +function(generate_winsdk_vfs_overlay winsdk_include_dir output_path) + set(include_dirs) + file(GLOB_RECURSE entries LIST_DIRECTORIES true "${winsdk_include_dir}/*") + foreach(entry ${entries}) + if(IS_DIRECTORY "${entry}") + list(APPEND include_dirs "${entry}") + endif() + endforeach() + + file(WRITE "${output_path}" "version: 0\n") + file(APPEND "${output_path}" "case-sensitive: false\n") + file(APPEND "${output_path}" "roots:\n") + + foreach(dir ${include_dirs}) + file(GLOB headers RELATIVE "${dir}" "${dir}/*.h") + if(NOT headers) + continue() + endif() + + file(APPEND "${output_path}" " - name: \"${dir}\"\n") + file(APPEND "${output_path}" " type: directory\n") + file(APPEND "${output_path}" " contents:\n") + + foreach(header ${headers}) + file(APPEND "${output_path}" " - name: \"${header}\"\n") + file(APPEND "${output_path}" " type: file\n") + file(APPEND "${output_path}" " external-contents: \"${dir}/${header}\"\n") + endforeach() + endforeach() +endfunction() + +function(generate_winsdk_lib_symlinks winsdk_um_lib_dir output_dir) + execute_process(COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}") + file(GLOB libraries RELATIVE "${winsdk_um_lib_dir}" "${winsdk_um_lib_dir}/*") + foreach(library ${libraries}) + string(TOLOWER "${library}" all_lowercase_symlink_name) + if(NOT library STREQUAL all_lowercase_symlink_name) + execute_process(COMMAND "${CMAKE_COMMAND}" + -E create_symlink + "${winsdk_um_lib_dir}/${library}" + "${output_dir}/${all_lowercase_symlink_name}") + endif() + + get_filename_component(name_we "${library}" NAME_WE) + get_filename_component(ext "${library}" EXT) + string(TOLOWER "${ext}" lowercase_ext) + set(lowercase_ext_symlink_name "${name_we}${lowercase_ext}") + if(NOT library STREQUAL lowercase_ext_symlink_name AND + NOT all_lowercase_symlink_name STREQUAL lowercase_ext_symlink_name) + execute_process(COMMAND "${CMAKE_COMMAND}" + -E create_symlink + "${winsdk_um_lib_dir}/${library}" + "${output_dir}/${lowercase_ext_symlink_name}") + endif() + endforeach() +endfunction() + +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_VERSION 10.0) +set(CMAKE_SYSTEM_PROCESSOR AMD64) + +init_user_prop(HOST_ARCH) +init_user_prop(LLVM_NATIVE_TOOLCHAIN) +init_user_prop(MSVC_BASE) +init_user_prop(WINSDK_BASE) +init_user_prop(WINSDK_VER) + +if(NOT HOST_ARCH) + set(HOST_ARCH x86_64) +endif() +if(HOST_ARCH STREQUAL "aarch64" OR HOST_ARCH STREQUAL "arm64") + set(TRIPLE_ARCH "aarch64") + set(WINSDK_ARCH "arm64") +elseif(HOST_ARCH STREQUAL "armv7" OR HOST_ARCH STREQUAL "arm") + set(TRIPLE_ARCH "armv7") + set(WINSDK_ARCH "arm") +elseif(HOST_ARCH STREQUAL "i686" OR HOST_ARCH STREQUAL "x86") + set(TRIPLE_ARCH "i686") + set(WINSDK_ARCH "x86") +elseif(HOST_ARCH STREQUAL "x86_64" OR HOST_ARCH STREQUAL "x64") + set(TRIPLE_ARCH "x86_64") + set(WINSDK_ARCH "x64") +else() + message(SEND_ERROR "Unknown host architecture ${HOST_ARCH}. Must be aarch64 (or arm64), armv7 (or arm), i686 (or x86), or x86_64 (or x64).") +endif() + +set(MSVC_INCLUDE "${MSVC_BASE}/include") +set(ATLMFC_INCLUDE "${MSVC_BASE}/atlmfc/include") +set(MSVC_LIB "${MSVC_BASE}/lib") +set(ATLMFC_LIB "${MSVC_BASE}/atlmfc/lib") +set(WINSDK_INCLUDE "${WINSDK_BASE}/Include/${WINSDK_VER}") +set(WINSDK_LIB "${WINSDK_BASE}/Lib/${WINSDK_VER}") + +# Do some sanity checking to make sure we can find a native toolchain and +# that the Windows SDK / MSVC STL directories look kosher. +if(NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" OR + NOT EXISTS "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link") + message(SEND_ERROR + "LLVM_NATIVE_TOOLCHAIN folder '${LLVM_NATIVE_TOOLCHAIN}' does not " + "point to a valid directory containing bin/clang-cl and bin/lld-link " + "binaries") +endif() + +if(NOT EXISTS "${MSVC_BASE}" OR + NOT EXISTS "${MSVC_INCLUDE}" OR + NOT EXISTS "${MSVC_LIB}") + message(SEND_ERROR + "CMake variable MSVC_BASE must point to a folder containing MSVC " + "system headers and libraries") +endif() + +if(NOT EXISTS "${WINSDK_BASE}" OR + NOT EXISTS "${WINSDK_INCLUDE}" OR + NOT EXISTS "${WINSDK_LIB}") + message(SEND_ERROR + "CMake variable WINSDK_BASE and WINSDK_VER must resolve to a valid " + "Windows SDK installation") +endif() + +if(NOT EXISTS "${WINSDK_INCLUDE}/um/Windows.h") + message(SEND_ERROR "Cannot find Windows.h") +endif() +if(NOT EXISTS "${WINSDK_INCLUDE}/um/WINDOWS.H") + set(case_sensitive_filesystem TRUE) +endif() + +set(CMAKE_C_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" CACHE FILEPATH "") +set(CMAKE_CXX_COMPILER "${LLVM_NATIVE_TOOLCHAIN}/bin/clang-cl" CACHE FILEPATH "") +set(CMAKE_LINKER "${LLVM_NATIVE_TOOLCHAIN}/bin/lld-link" CACHE FILEPATH "") + +# Even though we're cross-compiling, we need some native tools (e.g. llvm-tblgen), and those +# native tools have to be built before we can start doing the cross-build. LLVM supports +# a CROSS_TOOLCHAIN_FLAGS_NATIVE argument which consists of a list of flags to pass to CMake +# when configuring the NATIVE portion of the cross-build. By default we construct this so +# that it points to the tools in the same location as the native clang-cl that we're using. +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_ASM_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_C_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang") +list(APPEND _CTF_NATIVE_DEFAULT "-DCMAKE_CXX_COMPILER=${LLVM_NATIVE_TOOLCHAIN}/bin/clang++") + +set(CROSS_TOOLCHAIN_FLAGS_NATIVE "${_CTF_NATIVE_DEFAULT}" CACHE STRING "") + +set(COMPILE_FLAGS + -D_CRT_SECURE_NO_WARNINGS + --target=${TRIPLE_ARCH}-windows-msvc + -fms-compatibility-version=19.11 + -imsvc "\"${ATLMFC_INCLUDE}\"" + -imsvc "\"${MSVC_INCLUDE}\"" + -imsvc "\"${WINSDK_INCLUDE}/ucrt\"" + -imsvc "\"${WINSDK_INCLUDE}/shared\"" + -imsvc "\"${WINSDK_INCLUDE}/um\"" + -imsvc "\"${WINSDK_INCLUDE}/winrt\"") + +if(case_sensitive_filesystem) + # Ensure all sub-configures use the top-level VFS overlay instead of generating their own. + init_user_prop(winsdk_vfs_overlay_path) + if(NOT winsdk_vfs_overlay_path) + set(winsdk_vfs_overlay_path "${CMAKE_BINARY_DIR}/winsdk_vfs_overlay.yaml") + generate_winsdk_vfs_overlay("${WINSDK_BASE}/Include/${WINSDK_VER}" "${winsdk_vfs_overlay_path}") + init_user_prop(winsdk_vfs_overlay_path) + endif() + list(APPEND COMPILE_FLAGS + -Xclang -ivfsoverlay -Xclang "${winsdk_vfs_overlay_path}") +endif() + +string(REPLACE ";" " " COMPILE_FLAGS "${COMPILE_FLAGS}") + +# We need to preserve any flags that were passed in by the user. However, we +# can't append to CMAKE_C_FLAGS and friends directly, because toolchain files +# will be re-invoked on each reconfigure and therefore need to be idempotent. +# The assignments to the _INITIAL cache variables don't use FORCE, so they'll +# only be populated on the initial configure, and their values won't change +# afterward. +set(_CMAKE_C_FLAGS_INITIAL "${CMAKE_C_FLAGS}" CACHE STRING "") +set(CMAKE_C_FLAGS "${_CMAKE_C_FLAGS_INITIAL} ${COMPILE_FLAGS}" CACHE STRING "" FORCE) + +set(_CMAKE_CXX_FLAGS_INITIAL "${CMAKE_CXX_FLAGS}" CACHE STRING "") +set(CMAKE_CXX_FLAGS "${_CMAKE_CXX_FLAGS_INITIAL} ${COMPILE_FLAGS}" CACHE STRING "" FORCE) + +set(LINK_FLAGS + # Prevent CMake from attempting to invoke mt.exe. It only recognizes the slashed form and not the dashed form. + /manifest:no + + -libpath:"${ATLMFC_LIB}/${WINSDK_ARCH}" + -libpath:"${MSVC_LIB}/${WINSDK_ARCH}" + -libpath:"${WINSDK_LIB}/ucrt/${WINSDK_ARCH}" + -libpath:"${WINSDK_LIB}/um/${WINSDK_ARCH}") + +if(case_sensitive_filesystem) + # Ensure all sub-configures use the top-level symlinks dir instead of generating their own. + init_user_prop(winsdk_lib_symlinks_dir) + if(NOT winsdk_lib_symlinks_dir) + set(winsdk_lib_symlinks_dir "${CMAKE_BINARY_DIR}/winsdk_lib_symlinks") + generate_winsdk_lib_symlinks("${WINSDK_BASE}/Lib/${WINSDK_VER}/um/${WINSDK_ARCH}" "${winsdk_lib_symlinks_dir}") + init_user_prop(winsdk_lib_symlinks_dir) + endif() + list(APPEND LINK_FLAGS + -libpath:"${winsdk_lib_symlinks_dir}") +endif() + +string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}") + +# See explanation for compiler flags above for the _INITIAL variables. +set(_CMAKE_EXE_LINKER_FLAGS_INITIAL "${CMAKE_EXE_LINKER_FLAGS}" CACHE STRING "") +set(CMAKE_EXE_LINKER_FLAGS "${_CMAKE_EXE_LINKER_FLAGS_INITIAL} ${LINK_FLAGS}" CACHE STRING "" FORCE) + +set(_CMAKE_MODULE_LINKER_FLAGS_INITIAL "${CMAKE_MODULE_LINKER_FLAGS}" CACHE STRING "") +set(CMAKE_MODULE_LINKER_FLAGS "${_CMAKE_MODULE_LINKER_FLAGS_INITIAL} ${LINK_FLAGS}" CACHE STRING "" FORCE) + +set(_CMAKE_SHARED_LINKER_FLAGS_INITIAL "${CMAKE_SHARED_LINKER_FLAGS}" CACHE STRING "") +set(CMAKE_SHARED_LINKER_FLAGS "${_CMAKE_SHARED_LINKER_FLAGS_INITIAL} ${LINK_FLAGS}" CACHE STRING "" FORCE) + +# CMake populates these with a bunch of unnecessary libraries, which requires +# extra case-correcting symlinks and what not. Instead, let projects explicitly +# control which libraries they require. +set(CMAKE_C_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) +set(CMAKE_CXX_STANDARD_LIBRARIES "" CACHE STRING "" FORCE) + +# Allow clang-cl to work with macOS paths. +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_LIST_DIR}/ClangClCMakeCompileRules.cmake") diff --git a/cmake/linux_i386.toolchain.cmake b/cmake/linux_i386.toolchain.cmake new file mode 100644 index 00000000..9c4a5094 --- /dev/null +++ b/cmake/linux_i386.toolchain.cmake @@ -0,0 +1,17 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR "i386") +set(CMAKE_C_COMPILER_TARGET i386-linux-gnu) + +# Assume debian/ubuntu +#set(CMAKE_FIND_ROOT_PATH /usr/lib/i386-linux-gnu/) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +# https://stackoverflow.com/questions/41557927/using-usr-lib-i386-linux-gnu-instead-of-usr-lib-x86-64-linux-gnu-to-find-libra +set(FIND_LIBRARY_USE_LIB64_PATHS OFF) diff --git a/cmake/llvm-mingw-cross.cmake b/cmake/llvm-mingw-cross.cmake new file mode 100644 index 00000000..f7e1759c --- /dev/null +++ b/cmake/llvm-mingw-cross.cmake @@ -0,0 +1,24 @@ +SET(CMAKE_SYSTEM_NAME Windows) + +IF (DEFINED ENV{LLVM_MINGW_DIR}) + SET(LLVM_MINGW_ROOT "$ENV{LLVM_MINGW_DIR}") +ELSE () + SET(LLVM_MINGW_ROOT "/mnt/data/local/llvm-mingw-20200325-ubuntu-18.04") +ENDIF() + + +SET(CMAKE_C_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-clang) +SET(CMAKE_CXX_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-clang++) +SET(CMAKE_RC_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-windres) + +#SET(CMAKE_C_LINK_EXECUTABLE x86_64-w64-mingw32-gcc) +#SET(CMAKE_CXX_LINK_EXECUTABLE x86_64-w64-mingw32-g++) + +SET(CMAKE_FIND_ROOT_PATH ${LLVM_MINGW_ROOT}/x86_64-w64-mingw32) + +# We may need some advanced thread APIs to compile, so enable 0x601(Win7) if required. +# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x601") + +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/llvm-mingw-win64.cmake b/cmake/llvm-mingw-win64.cmake new file mode 100644 index 00000000..439b5240 --- /dev/null +++ b/cmake/llvm-mingw-win64.cmake @@ -0,0 +1,20 @@ +SET(CMAKE_SYSTEM_NAME Windows) + +IF (DEFINED ENV{LLVM_MINGW_DIR}) + SET(LLVM_MINGW_ROOT "$ENV{LLVM_MINGW_DIR}") +ELSE () + SET(LLVM_MINGW_ROOT "C:/ProgramData/llvm-mingw") +ENDIF() + +SET(CMAKE_C_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-clang.exe) +SET(CMAKE_CXX_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-clang++.exe) +SET(CMAKE_RC_COMPILER ${LLVM_MINGW_ROOT}/bin/x86_64-w64-mingw32-windres.exe) + +SET(CMAKE_FIND_ROOT_PATH ${LLVM_MINGW_ROOT}/x86_64-w64-mingw32) + +# We may need some advanced thread APIs to compile tinyusz. use 0x601(Win7) if required +# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x601") + +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/mingw64-cross.cmake b/cmake/mingw64-cross.cmake new file mode 100644 index 00000000..479cdd4d --- /dev/null +++ b/cmake/mingw64-cross.cmake @@ -0,0 +1,20 @@ +SET(CMAKE_SYSTEM_NAME Windows) + +IF (DEFINED ENV{MINGW_GCC_DIR}) + SET(MINGW_GCC_ROOT "$ENV{MINGW_GCC_DIR}") +ELSE () + # Assume mingw cross compiler is installed in your system + SET(MINGW_GCC_ROOT "/usr") +ENDIF() + +# win32 may fail to compile with C++11 threads. + +SET(CMAKE_C_COMPILER ${MINGW_GCC_ROOT}/bin/x86_64-w64-mingw32-gcc-posix) +SET(CMAKE_CXX_COMPILER ${MINGW_GCC_ROOT}/bin/x86_64-w64-mingw32-g++-posix) +SET(CMAKE_RC_COMPILER ${MINGW_GCC_ROOT}/bin/x86_64-w64-mingw32-windres) + +SET(CMAKE_FIND_ROOT_PATH ${MINGW_GCC_ROOT}/x86_64-w64-mingw32) + +SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/cmake/sanitizers/FindASan.cmake b/cmake/sanitizers/FindASan.cmake new file mode 100644 index 00000000..98ea7cb3 --- /dev/null +++ b/cmake/sanitizers/FindASan.cmake @@ -0,0 +1,59 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + # Clang 3.2+ use this version. The no-omit-frame-pointer option is optional. + "-g -fsanitize=address -fno-omit-frame-pointer" + "-g -fsanitize=address" + + # Older deprecated flag for ASan + "-g -faddress-sanitizer" +) + + +if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY)) + message(FATAL_ERROR "AddressSanitizer is not compatible with " + "ThreadSanitizer or MemorySanitizer.") +endif () + + +include(sanitize-helpers) + +if (SANITIZE_ADDRESS) + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer" + "ASan") + + find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH}) + mark_as_advanced(ASan_WRAPPER) +endif () + +function (add_sanitize_address TARGET) + if (NOT SANITIZE_ADDRESS) + return() + endif () + + sanitizer_add_flags(${TARGET} "AddressSanitizer" "ASan") +endfunction () diff --git a/cmake/sanitizers/FindMSan.cmake b/cmake/sanitizers/FindMSan.cmake new file mode 100644 index 00000000..22d0050e --- /dev/null +++ b/cmake/sanitizers/FindMSan.cmake @@ -0,0 +1,57 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + "-g -fsanitize=memory" +) + + +include(sanitize-helpers) + +if (SANITIZE_MEMORY) + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + message(WARNING "MemorySanitizer disabled for target ${TARGET} because " + "MemorySanitizer is supported for Linux systems only.") + set(SANITIZE_MEMORY Off CACHE BOOL + "Enable MemorySanitizer for sanitized targets." FORCE) + elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) + message(WARNING "MemorySanitizer disabled for target ${TARGET} because " + "MemorySanitizer is supported for 64bit systems only.") + set(SANITIZE_MEMORY Off CACHE BOOL + "Enable MemorySanitizer for sanitized targets." FORCE) + else () + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer" + "MSan") + endif () +endif () + +function (add_sanitize_memory TARGET) + if (NOT SANITIZE_MEMORY) + return() + endif () + + sanitizer_add_flags(${TARGET} "MemorySanitizer" "MSan") +endfunction () diff --git a/cmake/sanitizers/FindSanitizers.cmake b/cmake/sanitizers/FindSanitizers.cmake new file mode 100755 index 00000000..101bab84 --- /dev/null +++ b/cmake/sanitizers/FindSanitizers.cmake @@ -0,0 +1,94 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# If any of the used compiler is a GNU compiler, add a second option to static +# link against the sanitizers. +option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off) + + + + +set(FIND_QUIETLY_FLAG "") +if (DEFINED Sanitizers_FIND_QUIETLY) + set(FIND_QUIETLY_FLAG "QUIET") +endif () + +find_package(ASan ${FIND_QUIETLY_FLAG}) +find_package(TSan ${FIND_QUIETLY_FLAG}) +find_package(MSan ${FIND_QUIETLY_FLAG}) +find_package(UBSan ${FIND_QUIETLY_FLAG}) + + + + +function(sanitizer_add_blacklist_file FILE) + if(NOT IS_ABSOLUTE ${FILE}) + set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}") + endif() + get_filename_component(FILE "${FILE}" REALPATH) + + sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}" + "SanitizerBlacklist" "SanBlist") +endfunction() + +function(add_sanitizers ...) + # If no sanitizer is enabled, return immediately. + if (NOT (SANITIZE_ADDRESS OR SANITIZE_MEMORY OR SANITIZE_THREAD OR + SANITIZE_UNDEFINED)) + return() + endif () + + foreach (TARGET ${ARGV}) + # Check if this target will be compiled by exactly one compiler. Other- + # wise sanitizers can't be used and a warning should be printed once. + get_target_property(TARGET_TYPE ${TARGET} TYPE) + if (TARGET_TYPE STREQUAL "INTERFACE_LIBRARY") + message(WARNING "Can't use any sanitizers for target ${TARGET}, " + "because it is an interface library and cannot be " + "compiled directly.") + return() + endif () + sanitizer_target_compilers(${TARGET} TARGET_COMPILER) + list(LENGTH TARGET_COMPILER NUM_COMPILERS) + if (NUM_COMPILERS GREATER 1) + message(WARNING "Can't use any sanitizers for target ${TARGET}, " + "because it will be compiled by incompatible compilers. " + "Target will be compiled without sanitizers.") + return() + + # If the target is compiled by no or no known compiler, give a warning. + elseif (NUM_COMPILERS EQUAL 0) + message(WARNING "Sanitizers for target ${TARGET} may not be" + " usable, because it uses no or an unknown compiler. " + "This is a false warning for targets using only " + "object lib(s) as input.") + endif () + + # Add sanitizers for target. + add_sanitize_address(${TARGET}) + add_sanitize_thread(${TARGET}) + add_sanitize_memory(${TARGET}) + add_sanitize_undefined(${TARGET}) + endforeach () +endfunction(add_sanitizers) diff --git a/cmake/sanitizers/FindTSan.cmake b/cmake/sanitizers/FindTSan.cmake new file mode 100644 index 00000000..3cba3c03 --- /dev/null +++ b/cmake/sanitizers/FindTSan.cmake @@ -0,0 +1,65 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + "-g -fsanitize=thread" +) + + +# ThreadSanitizer is not compatible with MemorySanitizer. +if (SANITIZE_THREAD AND SANITIZE_MEMORY) + message(FATAL_ERROR "ThreadSanitizer is not compatible with " + "MemorySanitizer.") +endif () + + +include(sanitize-helpers) + +if (SANITIZE_THREAD) + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND + NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " + "ThreadSanitizer is supported for Linux systems and macOS only.") + set(SANITIZE_THREAD Off CACHE BOOL + "Enable ThreadSanitizer for sanitized targets." FORCE) + elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) + message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " + "ThreadSanitizer is supported for 64bit systems only.") + set(SANITIZE_THREAD Off CACHE BOOL + "Enable ThreadSanitizer for sanitized targets." FORCE) + else () + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer" + "TSan") + endif () +endif () + +function (add_sanitize_thread TARGET) + if (NOT SANITIZE_THREAD) + return() + endif () + + sanitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan") +endfunction () diff --git a/cmake/sanitizers/FindUBSan.cmake b/cmake/sanitizers/FindUBSan.cmake new file mode 100644 index 00000000..ae103f71 --- /dev/null +++ b/cmake/sanitizers/FindUBSan.cmake @@ -0,0 +1,46 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +option(SANITIZE_UNDEFINED + "Enable UndefinedBehaviorSanitizer for sanitized targets." Off) + +set(FLAG_CANDIDATES + "-g -fsanitize=undefined" +) + + +include(sanitize-helpers) + +if (SANITIZE_UNDEFINED) + sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" + "UndefinedBehaviorSanitizer" "UBSan") +endif () + +function (add_sanitize_undefined TARGET) + if (NOT SANITIZE_UNDEFINED) + return() + endif () + + sanitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan") +endfunction () diff --git a/cmake/sanitizers/asan-wrapper b/cmake/sanitizers/asan-wrapper new file mode 100755 index 00000000..5d541033 --- /dev/null +++ b/cmake/sanitizers/asan-wrapper @@ -0,0 +1,55 @@ +#!/bin/sh + +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# This script is a wrapper for AddressSanitizer. In some special cases you need +# to preload AddressSanitizer to avoid error messages - e.g. if you're +# preloading another library to your application. At the moment this script will +# only do something, if we're running on a Linux platform. OSX might not be +# affected. + + +# Exit immediately, if platform is not Linux. +if [ "$(uname)" != "Linux" ] +then + exec $@ +fi + + +# Get the used libasan of the application ($1). If a libasan was found, it will +# be prepended to LD_PRELOAD. +libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1) +if [ -n "$libasan" ] +then + if [ -n "$LD_PRELOAD" ] + then + export LD_PRELOAD="$libasan:$LD_PRELOAD" + else + export LD_PRELOAD="$libasan" + fi +fi + +# Execute the application. +exec $@ diff --git a/cmake/sanitizers/sanitize-helpers.cmake b/cmake/sanitizers/sanitize-helpers.cmake new file mode 100755 index 00000000..3649b074 --- /dev/null +++ b/cmake/sanitizers/sanitize-helpers.cmake @@ -0,0 +1,177 @@ +# The MIT License (MIT) +# +# Copyright (c) +# 2013 Matthew Arsenault +# 2015-2016 RWTH Aachen University, Federal Republic of Germany +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Helper function to get the language of a source file. +function (sanitizer_lang_of_source FILE RETURN_VAR) + get_filename_component(LONGEST_EXT "${FILE}" EXT) + # If extension is empty return. This can happen for extensionless headers + if("${LONGEST_EXT}" STREQUAL "") + set(${RETURN_VAR} "" PARENT_SCOPE) + return() + endif() + # Get shortest extension as some files can have dot in their names + string(REGEX REPLACE "^.*(\\.[^.]+)$" "\\1" FILE_EXT ${LONGEST_EXT}) + string(TOLOWER "${FILE_EXT}" FILE_EXT) + string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) + + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + foreach (LANG ${ENABLED_LANGUAGES}) + list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) + if (NOT ${TEMP} EQUAL -1) + set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) + return() + endif () + endforeach() + + set(${RETURN_VAR} "" PARENT_SCOPE) +endfunction () + + +# Helper function to get compilers used by a target. +function (sanitizer_target_compilers TARGET RETURN_VAR) + # Check if all sources for target use the same compiler. If a target uses + # e.g. C and Fortran mixed and uses different compilers (e.g. clang and + # gfortran) this can trigger huge problems, because different compilers may + # use different implementations for sanitizers. + set(BUFFER "") + get_target_property(TSOURCES ${TARGET} SOURCES) + foreach (FILE ${TSOURCES}) + # If expression was found, FILE is a generator-expression for an object + # library. Object libraries will be ignored. + string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) + if ("${_file}" STREQUAL "") + sanitizer_lang_of_source(${FILE} LANG) + if (LANG) + list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID}) + endif () + endif () + endforeach () + + list(REMOVE_DUPLICATES BUFFER) + set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE) +endfunction () + + +# Helper function to check compiler flags for language compiler. +function (sanitizer_check_compiler_flag FLAG LANG VARIABLE) + if (${LANG} STREQUAL "C") + include(CheckCCompilerFlag) + check_c_compiler_flag("${FLAG}" ${VARIABLE}) + + elseif (${LANG} STREQUAL "CXX") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag("${FLAG}" ${VARIABLE}) + + elseif (${LANG} STREQUAL "Fortran") + # CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible + # with older Cmake versions, we will check if this module is present + # before we use it. Otherwise we will define Fortran coverage support as + # not available. + include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED) + if (INCLUDED) + check_fortran_compiler_flag("${FLAG}" ${VARIABLE}) + elseif (NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Performing Test ${VARIABLE}") + message(STATUS "Performing Test ${VARIABLE}" + " - Failed (Check not supported)") + endif () + endif() +endfunction () + + +# Helper function to test compiler flags. +function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX) + set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY}) + + get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) + foreach (LANG ${ENABLED_LANGUAGES}) + # Sanitizer flags are not dependend on language, but the used compiler. + # So instead of searching flags foreach language, search flags foreach + # compiler used. + set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) + if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS) + foreach (FLAG ${FLAG_CANDIDATES}) + if(NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]") + endif() + + set(CMAKE_REQUIRED_FLAGS "${FLAG}") + unset(${PREFIX}_FLAG_DETECTED CACHE) + sanitizer_check_compiler_flag("${FLAG}" ${LANG} + ${PREFIX}_FLAG_DETECTED) + + if (${PREFIX}_FLAG_DETECTED) + # If compiler is a GNU compiler, search for static flag, if + # SANITIZE_LINK_STATIC is enabled. + if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU")) + string(TOLOWER ${PREFIX} PREFIX_lower) + sanitizer_check_compiler_flag( + "-static-lib${PREFIX_lower}" ${LANG} + ${PREFIX}_STATIC_FLAG_DETECTED) + + if (${PREFIX}_STATIC_FLAG_DETECTED) + set(FLAG "-static-lib${PREFIX_lower} ${FLAG}") + endif () + endif () + + set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING + "${NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) + break() + endif () + endforeach () + + if (NOT ${PREFIX}_FLAG_DETECTED) + set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING + "${NAME} flags for ${COMPILER} compiler.") + mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) + + message(WARNING "${NAME} is not available for ${COMPILER} " + "compiler. Targets using this compiler will be " + "compiled without ${NAME}.") + endif () + endif () + endforeach () +endfunction () + + +# Helper to assign sanitizer flags for TARGET. +function (sanitizer_add_flags TARGET NAME PREFIX) + # Get list of compilers used by target and check, if sanitizer is available + # for this target. Other compiler checks like check for conflicting + # compilers will be done in add_sanitizers function. + sanitizer_target_compilers(${TARGET} TARGET_COMPILER) + list(LENGTH TARGET_COMPILER NUM_COMPILERS) + if ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "") + return() + endif() + + # Set compile- and link-flags for target. + set_property(TARGET ${TARGET} APPEND_STRING + PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") + set_property(TARGET ${TARGET} APPEND_STRING + PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}") + set_property(TARGET ${TARGET} APPEND_STRING + PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") +endfunction () diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 00000000..2037e365 --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif(NOT "${rm_retval}" STREQUAL 0) + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/deps/cpplint.py b/deps/cpplint.py new file mode 100755 index 00000000..ccc25d4c --- /dev/null +++ b/deps/cpplint.py @@ -0,0 +1,6323 @@ +#!/usr/bin/env python +# +# Copyright (c) 2009 Google Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Does google-lint on c++ files. + +The goal of this script is to identify places in the code that *may* +be in non-compliance with google style. It does not attempt to fix +up these problems -- the point is to educate. It does also not +attempt to find all problems, or to ensure that everything it does +find is legitimately a problem. + +In particular, we can get very confused by /* and // inside strings! +We do a small hack, which is to ignore //'s with "'s after them on the +same line, but it is far from perfect (in either direction). +""" + +import codecs +import copy +import getopt +import math # for log +import os +import re +import sre_compile +import string +import sys +import unicodedata + + +_USAGE = """ +Syntax: cpplint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] + [--counting=total|toplevel|detailed] [--root=subdir] + [--linelength=digits] + [file] ... + + The style guidelines this tries to follow are those in + http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml + + Every problem is given a confidence score from 1-5, with 5 meaning we are + certain of the problem, and 1 meaning it could be a legitimate construct. + This will miss some errors, and is not a substitute for a code review. + + To suppress false-positive errors of a certain category, add a + 'NOLINT(category)' comment to the line. NOLINT or NOLINT(*) + suppresses errors of all categories on that line. + + The files passed in will be linted; at least one file must be provided. + Default linted extensions are .cc, .cpp, .cu, .cuh and .h. Change the + extensions with the --extensions flag. + + Flags: + + output=vs7 + By default, the output is formatted to ease emacs parsing. Visual Studio + compatible output (vs7) may also be used. Other formats are unsupported. + + verbose=# + Specify a number 0-5 to restrict errors to certain verbosity levels. + + filter=-x,+y,... + Specify a comma-separated list of category-filters to apply: only + error messages whose category names pass the filters will be printed. + (Category names are printed with the message and look like + "[whitespace/indent]".) Filters are evaluated left to right. + "-FOO" and "FOO" means "do not print categories that start with FOO". + "+FOO" means "do print categories that start with FOO". + + Examples: --filter=-whitespace,+whitespace/braces + --filter=whitespace,runtime/printf,+runtime/printf_format + --filter=-,+build/include_what_you_use + + To see a list of all the categories used in cpplint, pass no arg: + --filter= + + counting=total|toplevel|detailed + The total number of errors found is always printed. If + 'toplevel' is provided, then the count of errors in each of + the top-level categories like 'build' and 'whitespace' will + also be printed. If 'detailed' is provided, then a count + is provided for each category like 'build/class'. + + root=subdir + The root directory used for deriving header guard CPP variable. + By default, the header guard CPP variable is calculated as the relative + path to the directory that contains .git, .hg, or .svn. When this flag + is specified, the relative path is calculated from the specified + directory. If the specified directory does not exist, this flag is + ignored. + + Examples: + Assuming that src/.git exists, the header guard CPP variables for + src/chrome/browser/ui/browser.h are: + + No flag => CHROME_BROWSER_UI_BROWSER_H_ + --root=chrome => BROWSER_UI_BROWSER_H_ + --root=chrome/browser => UI_BROWSER_H_ + + linelength=digits + This is the allowed line length for the project. The default value is + 80 characters. + + Examples: + --linelength=120 + + extensions=extension,extension,... + The allowed file extensions that cpplint will check + + Examples: + --extensions=hpp,cpp + + cpplint.py supports per-directory configurations specified in CPPLINT.cfg + files. CPPLINT.cfg file can contain a number of key=value pairs. + Currently the following options are supported: + + set noparent + filter=+filter1,-filter2,... + exclude_files=regex + linelength=80 + + "set noparent" option prevents cpplint from traversing directory tree + upwards looking for more .cfg files in parent directories. This option + is usually placed in the top-level project directory. + + The "filter" option is similar in function to --filter flag. It specifies + message filters in addition to the |_DEFAULT_FILTERS| and those specified + through --filter command-line flag. + + "exclude_files" allows to specify a regular expression to be matched against + a file name. If the expression matches, the file is skipped and not run + through liner. + + "linelength" allows to specify the allowed line length for the project. + + CPPLINT.cfg has an effect on files in the same directory and all + sub-directories, unless overridden by a nested configuration file. + + Example file: + filter=-build/include_order,+build/include_alpha + exclude_files=.*\.cc + + The above example disables build/include_order warning and enables + build/include_alpha as well as excludes all .cc from being + processed by linter, in the current directory (where the .cfg + file is located) and all sub-directories. +""" + +# We categorize each error message we print. Here are the categories. +# We want an explicit list so we can list them all in cpplint --filter=. +# If you add a new error message with a new category, add it to the list +# here! cpplint_unittest.py should tell you if you forget to do this. +_ERROR_CATEGORIES = [ + 'build/class', + 'build/c++11', + 'build/deprecated', + 'build/endif_comment', + 'build/explicit_make_pair', + 'build/forward_decl', + 'build/header_guard', + 'build/include', + 'build/include_alpha', + 'build/include_order', + 'build/include_what_you_use', + 'build/namespaces', + 'build/printf_format', + 'build/storage_class', + 'legal/copyright', + 'readability/alt_tokens', + 'readability/braces', + 'readability/casting', + 'readability/check', + 'readability/constructors', + 'readability/fn_size', + 'readability/function', + 'readability/inheritance', + 'readability/multiline_comment', + 'readability/multiline_string', + 'readability/namespace', + 'readability/nolint', + 'readability/nul', + 'readability/strings', + 'readability/todo', + 'readability/utf8', + 'runtime/arrays', + 'runtime/casting', + 'runtime/explicit', + 'runtime/int', + 'runtime/init', + 'runtime/invalid_increment', + 'runtime/member_string_references', + 'runtime/memset', + 'runtime/indentation_namespace', + 'runtime/operator', + 'runtime/printf', + 'runtime/printf_format', + 'runtime/references', + 'runtime/string', + 'runtime/threadsafe_fn', + 'runtime/vlog', + 'whitespace/blank_line', + 'whitespace/braces', + 'whitespace/comma', + 'whitespace/comments', + 'whitespace/empty_conditional_body', + 'whitespace/empty_loop_body', + 'whitespace/end_of_line', + 'whitespace/ending_newline', + 'whitespace/forcolon', + 'whitespace/indent', + 'whitespace/line_length', + 'whitespace/newline', + 'whitespace/operators', + 'whitespace/parens', + 'whitespace/semicolon', + 'whitespace/tab', + 'whitespace/todo', + ] + +# These error categories are no longer enforced by cpplint, but for backwards- +# compatibility they may still appear in NOLINT comments. +_LEGACY_ERROR_CATEGORIES = [ + 'readability/streams', + ] + +# The default state of the category filter. This is overridden by the --filter= +# flag. By default all errors are on, so only add here categories that should be +# off by default (i.e., categories that must be enabled by the --filter= flags). +# All entries here should start with a '-' or '+', as in the --filter= flag. +_DEFAULT_FILTERS = ['-build/include_alpha'] + +# We used to check for high-bit characters, but after much discussion we +# decided those were OK, as long as they were in UTF-8 and didn't represent +# hard-coded international strings, which belong in a separate i18n file. + +# C++ headers +_CPP_HEADERS = frozenset([ + # Legacy + 'algobase.h', + 'algo.h', + 'alloc.h', + 'builtinbuf.h', + 'bvector.h', + 'complex.h', + 'defalloc.h', + 'deque.h', + 'editbuf.h', + 'fstream.h', + 'function.h', + 'hash_map', + 'hash_map.h', + 'hash_set', + 'hash_set.h', + 'hashtable.h', + 'heap.h', + 'indstream.h', + 'iomanip.h', + 'iostream.h', + 'istream.h', + 'iterator.h', + 'list.h', + 'map.h', + 'multimap.h', + 'multiset.h', + 'ostream.h', + 'pair.h', + 'parsestream.h', + 'pfstream.h', + 'procbuf.h', + 'pthread_alloc', + 'pthread_alloc.h', + 'rope', + 'rope.h', + 'ropeimpl.h', + 'set.h', + 'slist', + 'slist.h', + 'stack.h', + 'stdiostream.h', + 'stl_alloc.h', + 'stl_relops.h', + 'streambuf.h', + 'stream.h', + 'strfile.h', + 'strstream.h', + 'tempbuf.h', + 'tree.h', + 'type_traits.h', + 'vector.h', + # 17.6.1.2 C++ library headers + 'algorithm', + 'array', + 'atomic', + 'bitset', + 'chrono', + 'codecvt', + 'complex', + 'condition_variable', + 'deque', + 'exception', + 'forward_list', + 'fstream', + 'functional', + 'future', + 'initializer_list', + 'iomanip', + 'ios', + 'iosfwd', + 'iostream', + 'istream', + 'iterator', + 'limits', + 'list', + 'locale', + 'map', + 'memory', + 'mutex', + 'new', + 'numeric', + 'ostream', + 'queue', + 'random', + 'ratio', + 'regex', + 'set', + 'sstream', + 'stack', + 'stdexcept', + 'streambuf', + 'string', + 'strstream', + 'system_error', + 'thread', + 'tuple', + 'typeindex', + 'typeinfo', + 'type_traits', + 'unordered_map', + 'unordered_set', + 'utility', + 'valarray', + 'vector', + # 17.6.1.2 C++ headers for C library facilities + 'cassert', + 'ccomplex', + 'cctype', + 'cerrno', + 'cfenv', + 'cfloat', + 'cinttypes', + 'ciso646', + 'climits', + 'clocale', + 'cmath', + 'csetjmp', + 'csignal', + 'cstdalign', + 'cstdarg', + 'cstdbool', + 'cstddef', + 'cstdint', + 'cstdio', + 'cstdlib', + 'cstring', + 'ctgmath', + 'ctime', + 'cuchar', + 'cwchar', + 'cwctype', + ]) + + +# These headers are excluded from [build/include] and [build/include_order] +# checks: +# - Anything not following google file name conventions (containing an +# uppercase character, such as Python.h or nsStringAPI.h, for example). +# - Lua headers. +_THIRD_PARTY_HEADERS_PATTERN = re.compile( + r'^(?:[^/]*[A-Z][^/]*\.h|lua\.h|lauxlib\.h|lualib\.h)$') + + +# Assertion macros. These are defined in base/logging.h and +# testing/base/gunit.h. Note that the _M versions need to come first +# for substring matching to work. +_CHECK_MACROS = [ + 'DCHECK', 'CHECK', + 'EXPECT_TRUE_M', 'EXPECT_TRUE', + 'ASSERT_TRUE_M', 'ASSERT_TRUE', + 'EXPECT_FALSE_M', 'EXPECT_FALSE', + 'ASSERT_FALSE_M', 'ASSERT_FALSE', + ] + +# Replacement macros for CHECK/DCHECK/EXPECT_TRUE/EXPECT_FALSE +_CHECK_REPLACEMENT = dict([(m, {}) for m in _CHECK_MACROS]) + +for op, replacement in [('==', 'EQ'), ('!=', 'NE'), + ('>=', 'GE'), ('>', 'GT'), + ('<=', 'LE'), ('<', 'LT')]: + _CHECK_REPLACEMENT['DCHECK'][op] = 'DCHECK_%s' % replacement + _CHECK_REPLACEMENT['CHECK'][op] = 'CHECK_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE'][op] = 'EXPECT_%s' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE'][op] = 'ASSERT_%s' % replacement + _CHECK_REPLACEMENT['EXPECT_TRUE_M'][op] = 'EXPECT_%s_M' % replacement + _CHECK_REPLACEMENT['ASSERT_TRUE_M'][op] = 'ASSERT_%s_M' % replacement + +for op, inv_replacement in [('==', 'NE'), ('!=', 'EQ'), + ('>=', 'LT'), ('>', 'LE'), + ('<=', 'GT'), ('<', 'GE')]: + _CHECK_REPLACEMENT['EXPECT_FALSE'][op] = 'EXPECT_%s' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE'][op] = 'ASSERT_%s' % inv_replacement + _CHECK_REPLACEMENT['EXPECT_FALSE_M'][op] = 'EXPECT_%s_M' % inv_replacement + _CHECK_REPLACEMENT['ASSERT_FALSE_M'][op] = 'ASSERT_%s_M' % inv_replacement + +# Alternative tokens and their replacements. For full list, see section 2.5 +# Alternative tokens [lex.digraph] in the C++ standard. +# +# Digraphs (such as '%:') are not included here since it's a mess to +# match those on a word boundary. +_ALT_TOKEN_REPLACEMENT = { + 'and': '&&', + 'bitor': '|', + 'or': '||', + 'xor': '^', + 'compl': '~', + 'bitand': '&', + 'and_eq': '&=', + 'or_eq': '|=', + 'xor_eq': '^=', + 'not': '!', + 'not_eq': '!=' + } + +# Compile regular expression that matches all the above keywords. The "[ =()]" +# bit is meant to avoid matching these keywords outside of boolean expressions. +# +# False positives include C-style multi-line comments and multi-line strings +# but those have always been troublesome for cpplint. +_ALT_TOKEN_REPLACEMENT_PATTERN = re.compile( + r'[ =()](' + ('|'.join(_ALT_TOKEN_REPLACEMENT.keys())) + r')(?=[ (]|$)') + + +# These constants define types of headers for use with +# _IncludeState.CheckNextIncludeOrder(). +_C_SYS_HEADER = 1 +_CPP_SYS_HEADER = 2 +_LIKELY_MY_HEADER = 3 +_POSSIBLE_MY_HEADER = 4 +_OTHER_HEADER = 5 + +# These constants define the current inline assembly state +_NO_ASM = 0 # Outside of inline assembly block +_INSIDE_ASM = 1 # Inside inline assembly block +_END_ASM = 2 # Last line of inline assembly block +_BLOCK_ASM = 3 # The whole block is an inline assembly block + +# Match start of assembly blocks +_MATCH_ASM = re.compile(r'^\s*(?:asm|_asm|__asm|__asm__)' + r'(?:\s+(volatile|__volatile__))?' + r'\s*[{(]') + + +_regexp_compile_cache = {} + +# {str, set(int)}: a map from error categories to sets of linenumbers +# on which those errors are expected and should be suppressed. +_error_suppressions = {} + +# The root directory used for deriving header guard CPP variable. +# This is set by --root flag. +_root = None + +# The allowed line length of files. +# This is set by --linelength flag. +_line_length = 80 + +# The allowed extensions for file names +# This is set by --extensions flag. +_valid_extensions = set(['cc', 'h', 'cpp', 'cu', 'cuh']) + +def ParseNolintSuppressions(filename, raw_line, linenum, error): + """Updates the global list of error-suppressions. + + Parses any NOLINT comments on the current line, updating the global + error_suppressions store. Reports an error if the NOLINT comment + was malformed. + + Args: + filename: str, the name of the input file. + raw_line: str, the line of input text, with comments. + linenum: int, the number of the current line. + error: function, an error handler. + """ + matched = Search(r'\bNOLINT(NEXTLINE)?\b(\([^)]+\))?', raw_line) + if matched: + if matched.group(1): + suppressed_line = linenum + 1 + else: + suppressed_line = linenum + category = matched.group(2) + if category in (None, '(*)'): # => "suppress all" + _error_suppressions.setdefault(None, set()).add(suppressed_line) + else: + if category.startswith('(') and category.endswith(')'): + category = category[1:-1] + if category in _ERROR_CATEGORIES: + _error_suppressions.setdefault(category, set()).add(suppressed_line) + elif category not in _LEGACY_ERROR_CATEGORIES: + error(filename, linenum, 'readability/nolint', 5, + 'Unknown NOLINT error category: %s' % category) + + +def ResetNolintSuppressions(): + """Resets the set of NOLINT suppressions to empty.""" + _error_suppressions.clear() + + +def IsErrorSuppressedByNolint(category, linenum): + """Returns true if the specified error category is suppressed on this line. + + Consults the global error_suppressions map populated by + ParseNolintSuppressions/ResetNolintSuppressions. + + Args: + category: str, the category of the error. + linenum: int, the current line number. + Returns: + bool, True iff the error should be suppressed due to a NOLINT comment. + """ + return (linenum in _error_suppressions.get(category, set()) or + linenum in _error_suppressions.get(None, set())) + + +def Match(pattern, s): + """Matches the string with the pattern, caching the compiled regexp.""" + # The regexp compilation caching is inlined in both Match and Search for + # performance reasons; factoring it out into a separate function turns out + # to be noticeably expensive. + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].match(s) + + +def ReplaceAll(pattern, rep, s): + """Replaces instances of pattern in a string with a replacement. + + The compiled regex is kept in a cache shared by Match and Search. + + Args: + pattern: regex pattern + rep: replacement text + s: search string + + Returns: + string with replacements made (or original string if no replacements) + """ + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].sub(rep, s) + + +def Search(pattern, s): + """Searches the string for the pattern, caching the compiled regexp.""" + if pattern not in _regexp_compile_cache: + _regexp_compile_cache[pattern] = sre_compile.compile(pattern) + return _regexp_compile_cache[pattern].search(s) + + +class _IncludeState(object): + """Tracks line numbers for includes, and the order in which includes appear. + + include_list contains list of lists of (header, line number) pairs. + It's a lists of lists rather than just one flat list to make it + easier to update across preprocessor boundaries. + + Call CheckNextIncludeOrder() once for each header in the file, passing + in the type constants defined above. Calls in an illegal order will + raise an _IncludeError with an appropriate error message. + + """ + # self._section will move monotonically through this set. If it ever + # needs to move backwards, CheckNextIncludeOrder will raise an error. + _INITIAL_SECTION = 0 + _MY_H_SECTION = 1 + _C_SECTION = 2 + _CPP_SECTION = 3 + _OTHER_H_SECTION = 4 + + _TYPE_NAMES = { + _C_SYS_HEADER: 'C system header', + _CPP_SYS_HEADER: 'C++ system header', + _LIKELY_MY_HEADER: 'header this file implements', + _POSSIBLE_MY_HEADER: 'header this file may implement', + _OTHER_HEADER: 'other header', + } + _SECTION_NAMES = { + _INITIAL_SECTION: "... nothing. (This can't be an error.)", + _MY_H_SECTION: 'a header this file implements', + _C_SECTION: 'C system header', + _CPP_SECTION: 'C++ system header', + _OTHER_H_SECTION: 'other header', + } + + def __init__(self): + self.include_list = [[]] + self.ResetSection('') + + def FindHeader(self, header): + """Check if a header has already been included. + + Args: + header: header to check. + Returns: + Line number of previous occurrence, or -1 if the header has not + been seen before. + """ + for section_list in self.include_list: + for f in section_list: + if f[0] == header: + return f[1] + return -1 + + def ResetSection(self, directive): + """Reset section checking for preprocessor directive. + + Args: + directive: preprocessor directive (e.g. "if", "else"). + """ + # The name of the current section. + self._section = self._INITIAL_SECTION + # The path of last found header. + self._last_header = '' + + # Update list of includes. Note that we never pop from the + # include list. + if directive in ('if', 'ifdef', 'ifndef'): + self.include_list.append([]) + elif directive in ('else', 'elif'): + self.include_list[-1] = [] + + def SetLastHeader(self, header_path): + self._last_header = header_path + + def CanonicalizeAlphabeticalOrder(self, header_path): + """Returns a path canonicalized for alphabetical comparison. + + - replaces "-" with "_" so they both cmp the same. + - removes '-inl' since we don't require them to be after the main header. + - lowercase everything, just in case. + + Args: + header_path: Path to be canonicalized. + + Returns: + Canonicalized path. + """ + return header_path.replace('-inl.h', '.h').replace('-', '_').lower() + + def IsInAlphabeticalOrder(self, clean_lines, linenum, header_path): + """Check if a header is in alphabetical order with the previous header. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + header_path: Canonicalized header to be checked. + + Returns: + Returns true if the header is in alphabetical order. + """ + # If previous section is different from current section, _last_header will + # be reset to empty string, so it's always less than current header. + # + # If previous line was a blank line, assume that the headers are + # intentionally sorted the way they are. + if (self._last_header > header_path and + Match(r'^\s*#\s*include\b', clean_lines.elided[linenum - 1])): + return False + return True + + def CheckNextIncludeOrder(self, header_type): + """Returns a non-empty error message if the next header is out of order. + + This function also updates the internal state to be ready to check + the next include. + + Args: + header_type: One of the _XXX_HEADER constants defined above. + + Returns: + The empty string if the header is in the right order, or an + error message describing what's wrong. + + """ + error_message = ('Found %s after %s' % + (self._TYPE_NAMES[header_type], + self._SECTION_NAMES[self._section])) + + last_section = self._section + + if header_type == _C_SYS_HEADER: + if self._section <= self._C_SECTION: + self._section = self._C_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _CPP_SYS_HEADER: + if self._section <= self._CPP_SECTION: + self._section = self._CPP_SECTION + else: + self._last_header = '' + return error_message + elif header_type == _LIKELY_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + self._section = self._OTHER_H_SECTION + elif header_type == _POSSIBLE_MY_HEADER: + if self._section <= self._MY_H_SECTION: + self._section = self._MY_H_SECTION + else: + # This will always be the fallback because we're not sure + # enough that the header is associated with this file. + self._section = self._OTHER_H_SECTION + else: + assert header_type == _OTHER_HEADER + self._section = self._OTHER_H_SECTION + + if last_section != self._section: + self._last_header = '' + + return '' + + +class _CppLintState(object): + """Maintains module-wide state..""" + + def __init__(self): + self.verbose_level = 1 # global setting. + self.error_count = 0 # global count of reported errors + # filters to apply when emitting error messages + self.filters = _DEFAULT_FILTERS[:] + # backup of filter list. Used to restore the state after each file. + self._filters_backup = self.filters[:] + self.counting = 'total' # In what way are we counting errors? + self.errors_by_category = {} # string to int dict storing error counts + + # output format: + # "emacs" - format that emacs can parse (default) + # "vs7" - format that Microsoft Visual Studio 7 can parse + self.output_format = 'emacs' + + def SetOutputFormat(self, output_format): + """Sets the output format for errors.""" + self.output_format = output_format + + def SetVerboseLevel(self, level): + """Sets the module's verbosity, and returns the previous setting.""" + last_verbose_level = self.verbose_level + self.verbose_level = level + return last_verbose_level + + def SetCountingStyle(self, counting_style): + """Sets the module's counting options.""" + self.counting = counting_style + + def SetFilters(self, filters): + """Sets the error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "+whitespace/indent"). + Each filter should start with + or -; else we die. + + Raises: + ValueError: The comma-separated filters did not all start with '+' or '-'. + E.g. "-,+whitespace,-whitespace/indent,whitespace/badfilter" + """ + # Default filters always have less priority than the flag ones. + self.filters = _DEFAULT_FILTERS[:] + self.AddFilters(filters) + + def AddFilters(self, filters): + """ Adds more filters to the existing list of error-message filters. """ + for filt in filters.split(','): + clean_filt = filt.strip() + if clean_filt: + self.filters.append(clean_filt) + for filt in self.filters: + if not (filt.startswith('+') or filt.startswith('-')): + raise ValueError('Every filter in --filters must start with + or -' + ' (%s does not)' % filt) + + def BackupFilters(self): + """ Saves the current filter list to backup storage.""" + self._filters_backup = self.filters[:] + + def RestoreFilters(self): + """ Restores filters previously backed up.""" + self.filters = self._filters_backup[:] + + def ResetErrorCounts(self): + """Sets the module's error statistic back to zero.""" + self.error_count = 0 + self.errors_by_category = {} + + def IncrementErrorCount(self, category): + """Bumps the module's error statistic.""" + self.error_count += 1 + if self.counting in ('toplevel', 'detailed'): + if self.counting != 'detailed': + category = category.split('/')[0] + if category not in self.errors_by_category: + self.errors_by_category[category] = 0 + self.errors_by_category[category] += 1 + + def PrintErrorCounts(self): + """Print a summary of errors by category, and the total.""" + for category, count in self.errors_by_category.iteritems(): + sys.stderr.write('Category \'%s\' errors found: %d\n' % + (category, count)) + sys.stderr.write('Total errors found: %d\n' % self.error_count) + +_cpplint_state = _CppLintState() + + +def _OutputFormat(): + """Gets the module's output format.""" + return _cpplint_state.output_format + + +def _SetOutputFormat(output_format): + """Sets the module's output format.""" + _cpplint_state.SetOutputFormat(output_format) + + +def _VerboseLevel(): + """Returns the module's verbosity setting.""" + return _cpplint_state.verbose_level + + +def _SetVerboseLevel(level): + """Sets the module's verbosity, and returns the previous setting.""" + return _cpplint_state.SetVerboseLevel(level) + + +def _SetCountingStyle(level): + """Sets the module's counting options.""" + _cpplint_state.SetCountingStyle(level) + + +def _Filters(): + """Returns the module's list of output filters, as a list.""" + return _cpplint_state.filters + + +def _SetFilters(filters): + """Sets the module's error-message filters. + + These filters are applied when deciding whether to emit a given + error message. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.SetFilters(filters) + +def _AddFilters(filters): + """Adds more filter overrides. + + Unlike _SetFilters, this function does not reset the current list of filters + available. + + Args: + filters: A string of comma-separated filters (eg "whitespace/indent"). + Each filter should start with + or -; else we die. + """ + _cpplint_state.AddFilters(filters) + +def _BackupFilters(): + """ Saves the current filter list to backup storage.""" + _cpplint_state.BackupFilters() + +def _RestoreFilters(): + """ Restores filters previously backed up.""" + _cpplint_state.RestoreFilters() + +class _FunctionState(object): + """Tracks current function name and the number of lines in its body.""" + + _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc. + _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER. + + def __init__(self): + self.in_a_function = False + self.lines_in_function = 0 + self.current_function = '' + + def Begin(self, function_name): + """Start analyzing function body. + + Args: + function_name: The name of the function being tracked. + """ + self.in_a_function = True + self.lines_in_function = 0 + self.current_function = function_name + + def Count(self): + """Count line in current function body.""" + if self.in_a_function: + self.lines_in_function += 1 + + def Check(self, error, filename, linenum): + """Report if too many lines in function body. + + Args: + error: The function to call with any errors found. + filename: The name of the current file. + linenum: The number of the line to check. + """ + if Match(r'T(EST|est)', self.current_function): + base_trigger = self._TEST_TRIGGER + else: + base_trigger = self._NORMAL_TRIGGER + trigger = base_trigger * 2**_VerboseLevel() + + if self.lines_in_function > trigger: + error_level = int(math.log(self.lines_in_function / base_trigger, 2)) + # 50 => 0, 100 => 1, 200 => 2, 400 => 3, 800 => 4, 1600 => 5, ... + if error_level > 5: + error_level = 5 + error(filename, linenum, 'readability/fn_size', error_level, + 'Small and focused functions are preferred:' + ' %s has %d non-comment lines' + ' (error triggered by exceeding %d lines).' % ( + self.current_function, self.lines_in_function, trigger)) + + def End(self): + """Stop analyzing function body.""" + self.in_a_function = False + + +class _IncludeError(Exception): + """Indicates a problem with the include order in a file.""" + pass + + +class FileInfo(object): + """Provides utility functions for filenames. + + FileInfo provides easy access to the components of a file's path + relative to the project root. + """ + + def __init__(self, filename): + self._filename = filename + + def FullName(self): + """Make Windows paths like Unix.""" + return os.path.abspath(self._filename).replace('\\', '/') + + def RepositoryName(self): + """FullName after removing the local path to the repository. + + If we have a real absolute path name here we can try to do something smart: + detecting the root of the checkout and truncating /path/to/checkout from + the name so that we get header guards that don't include things like + "C:\Documents and Settings\..." or "/home/username/..." in them and thus + people on different computers who have checked the source out to different + locations won't see bogus errors. + """ + fullname = self.FullName() + + if os.path.exists(fullname): + project_dir = os.path.dirname(fullname) + + if os.path.exists(os.path.join(project_dir, ".svn")): + # If there's a .svn file in the current directory, we recursively look + # up the directory tree for the top of the SVN checkout + root_dir = project_dir + one_up_dir = os.path.dirname(root_dir) + while os.path.exists(os.path.join(one_up_dir, ".svn")): + root_dir = os.path.dirname(root_dir) + one_up_dir = os.path.dirname(one_up_dir) + + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Not SVN <= 1.6? Try to find a git, hg, or svn top level directory by + # searching up from the current path. + root_dir = os.path.dirname(fullname) + while (root_dir != os.path.dirname(root_dir) and + not os.path.exists(os.path.join(root_dir, ".git")) and + not os.path.exists(os.path.join(root_dir, ".hg")) and + not os.path.exists(os.path.join(root_dir, ".svn"))): + root_dir = os.path.dirname(root_dir) + + if (os.path.exists(os.path.join(root_dir, ".git")) or + os.path.exists(os.path.join(root_dir, ".hg")) or + os.path.exists(os.path.join(root_dir, ".svn"))): + prefix = os.path.commonprefix([root_dir, project_dir]) + return fullname[len(prefix) + 1:] + + # Don't know what to do; header guard warnings may be wrong... + return fullname + + def Split(self): + """Splits the file into the directory, basename, and extension. + + For 'chrome/browser/browser.cc', Split() would + return ('chrome/browser', 'browser', '.cc') + + Returns: + A tuple of (directory, basename, extension). + """ + + googlename = self.RepositoryName() + project, rest = os.path.split(googlename) + return (project,) + os.path.splitext(rest) + + def BaseName(self): + """File base name - text after the final slash, before the final period.""" + return self.Split()[1] + + def Extension(self): + """File extension - text following the final period.""" + return self.Split()[2] + + def NoExtension(self): + """File has no source file extension.""" + return '/'.join(self.Split()[0:2]) + + def IsSource(self): + """File has a source file extension.""" + return self.Extension()[1:] in ('c', 'cc', 'cpp', 'cxx') + + +def _ShouldPrintError(category, confidence, linenum): + """If confidence >= verbose, category passes filter and is not suppressed.""" + + # There are three ways we might decide not to print an error message: + # a "NOLINT(category)" comment appears in the source, + # the verbosity level isn't high enough, or the filters filter it out. + if IsErrorSuppressedByNolint(category, linenum): + return False + + if confidence < _cpplint_state.verbose_level: + return False + + is_filtered = False + for one_filter in _Filters(): + if one_filter.startswith('-'): + if category.startswith(one_filter[1:]): + is_filtered = True + elif one_filter.startswith('+'): + if category.startswith(one_filter[1:]): + is_filtered = False + else: + assert False # should have been checked for in SetFilter. + if is_filtered: + return False + + return True + + +def Error(filename, linenum, category, confidence, message): + """Logs the fact we've found a lint error. + + We log where the error was found, and also our confidence in the error, + that is, how certain we are this is a legitimate style regression, and + not a misidentification or a use that's sometimes justified. + + False positives can be suppressed by the use of + "cpplint(category)" comments on the offending line. These are + parsed into _error_suppressions. + + Args: + filename: The name of the file containing the error. + linenum: The number of the line containing the error. + category: A string used to describe the "category" this bug + falls under: "whitespace", say, or "runtime". Categories + may have a hierarchy separated by slashes: "whitespace/indent". + confidence: A number from 1-5 representing a confidence score for + the error, with 5 meaning that we are certain of the problem, + and 1 meaning that it could be a legitimate construct. + message: The error message. + """ + if _ShouldPrintError(category, confidence, linenum): + _cpplint_state.IncrementErrorCount(category) + if _cpplint_state.output_format == 'vs7': + sys.stderr.write('%s(%s): %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'eclipse': + sys.stderr.write('%s:%s: warning: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + else: + sys.stderr.write('%s:%s: %s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) + + +# Matches standard C++ escape sequences per 2.13.2.3 of the C++ standard. +_RE_PATTERN_CLEANSE_LINE_ESCAPES = re.compile( + r'\\([abfnrtv?"\\\']|\d+|x[0-9a-fA-F]+)') +# Match a single C style comment on the same line. +_RE_PATTERN_C_COMMENTS = r'/\*(?:[^*]|\*(?!/))*\*/' +# Matches multi-line C style comments. +# This RE is a little bit more complicated than one might expect, because we +# have to take care of space removals tools so we can handle comments inside +# statements better. +# The current rule is: We only clear spaces from both sides when we're at the +# end of the line. Otherwise, we try to remove spaces from the right side, +# if this doesn't work we try on left side but only if there's a non-character +# on the right. +_RE_PATTERN_CLEANSE_LINE_C_COMMENTS = re.compile( + r'(\s*' + _RE_PATTERN_C_COMMENTS + r'\s*$|' + + _RE_PATTERN_C_COMMENTS + r'\s+|' + + r'\s+' + _RE_PATTERN_C_COMMENTS + r'(?=\W)|' + + _RE_PATTERN_C_COMMENTS + r')') + + +def IsCppString(line): + """Does line terminate so, that the next symbol is in string constant. + + This function does not consider single-line nor multi-line comments. + + Args: + line: is a partial line of code starting from the 0..n. + + Returns: + True, if next character appended to 'line' is inside a + string constant. + """ + + line = line.replace(r'\\', 'XX') # after this, \\" does not match to \" + return ((line.count('"') - line.count(r'\"') - line.count("'\"'")) & 1) == 1 + + +def CleanseRawStrings(raw_lines): + """Removes C++11 raw strings from lines. + + Before: + static const char kData[] = R"( + multi-line string + )"; + + After: + static const char kData[] = "" + (replaced by blank line) + ""; + + Args: + raw_lines: list of raw lines. + + Returns: + list of lines with C++11 raw strings replaced by empty strings. + """ + + delimiter = None + lines_without_raw_strings = [] + for line in raw_lines: + if delimiter: + # Inside a raw string, look for the end + end = line.find(delimiter) + if end >= 0: + # Found the end of the string, match leading space for this + # line and resume copying the original lines, and also insert + # a "" on the last line. + leading_space = Match(r'^(\s*)\S', line) + line = leading_space.group(1) + '""' + line[end + len(delimiter):] + delimiter = None + else: + # Haven't found the end yet, append a blank line. + line = '""' + + # Look for beginning of a raw string, and replace them with + # empty strings. This is done in a loop to handle multiple raw + # strings on the same line. + while delimiter is None: + # Look for beginning of a raw string. + # See 2.14.15 [lex.string] for syntax. + matched = Match(r'^(.*)\b(?:R|u8R|uR|UR|LR)"([^\s\\()]*)\((.*)$', line) + if matched: + delimiter = ')' + matched.group(2) + '"' + + end = matched.group(3).find(delimiter) + if end >= 0: + # Raw string ended on same line + line = (matched.group(1) + '""' + + matched.group(3)[end + len(delimiter):]) + delimiter = None + else: + # Start of a multi-line raw string + line = matched.group(1) + '""' + else: + break + + lines_without_raw_strings.append(line) + + # TODO(unknown): if delimiter is not None here, we might want to + # emit a warning for unterminated string. + return lines_without_raw_strings + + +def FindNextMultiLineCommentStart(lines, lineix): + """Find the beginning marker for a multiline comment.""" + while lineix < len(lines): + if lines[lineix].strip().startswith('/*'): + # Only return this marker if the comment goes beyond this line + if lines[lineix].strip().find('*/', 2) < 0: + return lineix + lineix += 1 + return len(lines) + + +def FindNextMultiLineCommentEnd(lines, lineix): + """We are inside a comment, find the end marker.""" + while lineix < len(lines): + if lines[lineix].strip().endswith('*/'): + return lineix + lineix += 1 + return len(lines) + + +def RemoveMultiLineCommentsFromRange(lines, begin, end): + """Clears a range of lines for multi-line comments.""" + # Having // dummy comments makes the lines non-empty, so we will not get + # unnecessary blank line warnings later in the code. + for i in range(begin, end): + lines[i] = '/**/' + + +def RemoveMultiLineComments(filename, lines, error): + """Removes multiline (c-style) comments from lines.""" + lineix = 0 + while lineix < len(lines): + lineix_begin = FindNextMultiLineCommentStart(lines, lineix) + if lineix_begin >= len(lines): + return + lineix_end = FindNextMultiLineCommentEnd(lines, lineix_begin) + if lineix_end >= len(lines): + error(filename, lineix_begin + 1, 'readability/multiline_comment', 5, + 'Could not find end of multi-line comment') + return + RemoveMultiLineCommentsFromRange(lines, lineix_begin, lineix_end + 1) + lineix = lineix_end + 1 + + +def CleanseComments(line): + """Removes //-comments and single-line C-style /* */ comments. + + Args: + line: A line of C++ source. + + Returns: + The line with single-line comments removed. + """ + commentpos = line.find('//') + if commentpos != -1 and not IsCppString(line[:commentpos]): + line = line[:commentpos].rstrip() + # get rid of /* ... */ + return _RE_PATTERN_CLEANSE_LINE_C_COMMENTS.sub('', line) + + +class CleansedLines(object): + """Holds 4 copies of all lines with different preprocessing applied to them. + + 1) elided member contains lines without strings and comments. + 2) lines member contains lines without comments. + 3) raw_lines member contains all the lines without processing. + 4) lines_without_raw_strings member is same as raw_lines, but with C++11 raw + strings removed. + All these members are of , and of the same length. + """ + + def __init__(self, lines): + self.elided = [] + self.lines = [] + self.raw_lines = lines + self.num_lines = len(lines) + self.lines_without_raw_strings = CleanseRawStrings(lines) + for linenum in range(len(self.lines_without_raw_strings)): + self.lines.append(CleanseComments( + self.lines_without_raw_strings[linenum])) + elided = self._CollapseStrings(self.lines_without_raw_strings[linenum]) + self.elided.append(CleanseComments(elided)) + + def NumLines(self): + """Returns the number of lines represented.""" + return self.num_lines + + @staticmethod + def _CollapseStrings(elided): + """Collapses strings and chars on a line to simple "" or '' blocks. + + We nix strings first so we're not fooled by text like '"http://"' + + Args: + elided: The line being processed. + + Returns: + The line with collapsed strings. + """ + if _RE_PATTERN_INCLUDE.match(elided): + return elided + + # Remove escaped characters first to make quote/single quote collapsing + # basic. Things that look like escaped characters shouldn't occur + # outside of strings and chars. + elided = _RE_PATTERN_CLEANSE_LINE_ESCAPES.sub('', elided) + + # Replace quoted strings and digit separators. Both single quotes + # and double quotes are processed in the same loop, otherwise + # nested quotes wouldn't work. + collapsed = '' + while True: + # Find the first quote character + match = Match(r'^([^\'"]*)([\'"])(.*)$', elided) + if not match: + collapsed += elided + break + head, quote, tail = match.groups() + + if quote == '"': + # Collapse double quoted strings + second_quote = tail.find('"') + if second_quote >= 0: + collapsed += head + '""' + elided = tail[second_quote + 1:] + else: + # Unmatched double quote, don't bother processing the rest + # of the line since this is probably a multiline string. + collapsed += elided + break + else: + # Found single quote, check nearby text to eliminate digit separators. + # + # There is no special handling for floating point here, because + # the integer/fractional/exponent parts would all be parsed + # correctly as long as there are digits on both sides of the + # separator. So we are fine as long as we don't see something + # like "0.'3" (gcc 4.9.0 will not allow this literal). + if Search(r'\b(?:0[bBxX]?|[1-9])[0-9a-fA-F]*$', head): + match_literal = Match(r'^((?:\'?[0-9a-zA-Z_])*)(.*)$', "'" + tail) + collapsed += head + match_literal.group(1).replace("'", '') + elided = match_literal.group(2) + else: + second_quote = tail.find('\'') + if second_quote >= 0: + collapsed += head + "''" + elided = tail[second_quote + 1:] + else: + # Unmatched single quote + collapsed += elided + break + + return collapsed + + +def FindEndOfExpressionInLine(line, startpos, stack): + """Find the position just after the end of current parenthesized expression. + + Args: + line: a CleansedLines line. + startpos: start searching at this position. + stack: nesting stack at startpos. + + Returns: + On finding matching end: (index just after matching end, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at end of this line) + """ + for i in xrange(startpos, len(line)): + char = line[i] + if char in '([{': + # Found start of parenthesized expression, push to expression stack + stack.append(char) + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + if stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + elif i > 0 and Search(r'\boperator\s*$', line[0:i]): + # operator<, don't add to stack + continue + else: + # Tentative start of template argument list + stack.append('<') + elif char in ')]}': + # Found end of parenthesized expression. + # + # If we are currently expecting a matching '>', the pending '<' + # must have been an operator. Remove them from expression stack. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + if ((stack[-1] == '(' and char == ')') or + (stack[-1] == '[' and char == ']') or + (stack[-1] == '{' and char == '}')): + stack.pop() + if not stack: + return (i + 1, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == '>': + # Found potential end of template argument list. + + # Ignore "->" and operator functions + if (i > 0 and + (line[i - 1] == '-' or Search(r'\boperator\s*$', line[0:i - 1]))): + continue + + # Pop the stack if there is a matching '<'. Otherwise, ignore + # this '>' since it must be an operator. + if stack: + if stack[-1] == '<': + stack.pop() + if not stack: + return (i + 1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '>', the matching '<' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '<': + stack.pop() + if not stack: + return (-1, None) + + # Did not find end of expression or unbalanced parentheses on this line + return (-1, stack) + + +def CloseExpression(clean_lines, linenum, pos): + """If input points to ( or { or [ or <, finds the position that closes it. + + If lines[linenum][pos] points to a '(' or '{' or '[' or '<', finds the + linenum/pos that correspond to the closing of the expression. + + TODO(unknown): cpplint spends a fair bit of time matching parentheses. + Ideally we would want to index all opening and closing parentheses once + and have CloseExpression be just a simple lookup, but due to preprocessor + tricks, this is not so easy. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *past* the closing brace, or + (line, len(lines), -1) if we never find a close. Note we ignore + strings and comments when matching; and the line we return is the + 'cleansed' line at linenum. + """ + + line = clean_lines.elided[linenum] + if (line[pos] not in '({[<') or Match(r'<[<=]', line[pos:]): + return (line, clean_lines.NumLines(), -1) + + # Check first line + (end_pos, stack) = FindEndOfExpressionInLine(line, pos, []) + if end_pos > -1: + return (line, linenum, end_pos) + + # Continue scanning forward + while stack and linenum < clean_lines.NumLines() - 1: + linenum += 1 + line = clean_lines.elided[linenum] + (end_pos, stack) = FindEndOfExpressionInLine(line, 0, stack) + if end_pos > -1: + return (line, linenum, end_pos) + + # Did not find end of expression before end of file, give up + return (line, clean_lines.NumLines(), -1) + + +def FindStartOfExpressionInLine(line, endpos, stack): + """Find position at the matching start of current expression. + + This is almost the reverse of FindEndOfExpressionInLine, but note + that the input position and returned position differs by 1. + + Args: + line: a CleansedLines line. + endpos: start searching at this position. + stack: nesting stack at endpos. + + Returns: + On finding matching start: (index at matching start, None) + On finding an unclosed expression: (-1, None) + Otherwise: (-1, new stack at beginning of this line) + """ + i = endpos + while i >= 0: + char = line[i] + if char in ')]}': + # Found end of expression, push to expression stack + stack.append(char) + elif char == '>': + # Found potential end of template argument list. + # + # Ignore it if it's a "->" or ">=" or "operator>" + if (i > 0 and + (line[i - 1] == '-' or + Match(r'\s>=\s', line[i - 1:]) or + Search(r'\boperator\s*$', line[0:i]))): + i -= 1 + else: + stack.append('>') + elif char == '<': + # Found potential start of template argument list + if i > 0 and line[i - 1] == '<': + # Left shift operator + i -= 1 + else: + # If there is a matching '>', we can pop the expression stack. + # Otherwise, ignore this '<' since it must be an operator. + if stack and stack[-1] == '>': + stack.pop() + if not stack: + return (i, None) + elif char in '([{': + # Found start of expression. + # + # If there are any unmatched '>' on the stack, they must be + # operators. Remove those. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + if ((char == '(' and stack[-1] == ')') or + (char == '[' and stack[-1] == ']') or + (char == '{' and stack[-1] == '}')): + stack.pop() + if not stack: + return (i, None) + else: + # Mismatched parentheses + return (-1, None) + elif char == ';': + # Found something that look like end of statements. If we are currently + # expecting a '<', the matching '>' must have been an operator, since + # template argument list should not contain statements. + while stack and stack[-1] == '>': + stack.pop() + if not stack: + return (-1, None) + + i -= 1 + + return (-1, stack) + + +def ReverseCloseExpression(clean_lines, linenum, pos): + """If input points to ) or } or ] or >, finds the position that opens it. + + If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the + linenum/pos that correspond to the opening of the expression. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: A position on the line. + + Returns: + A tuple (line, linenum, pos) pointer *at* the opening brace, or + (line, 0, -1) if we never find the matching opening brace. Note + we ignore strings and comments when matching; and the line we + return is the 'cleansed' line at linenum. + """ + line = clean_lines.elided[linenum] + if line[pos] not in ')}]>': + return (line, 0, -1) + + # Check last line + (start_pos, stack) = FindStartOfExpressionInLine(line, pos, []) + if start_pos > -1: + return (line, linenum, start_pos) + + # Continue scanning backward + while stack and linenum > 0: + linenum -= 1 + line = clean_lines.elided[linenum] + (start_pos, stack) = FindStartOfExpressionInLine(line, len(line) - 1, stack) + if start_pos > -1: + return (line, linenum, start_pos) + + # Did not find start of expression before beginning of file, give up + return (line, 0, -1) + + +def CheckForCopyright(filename, lines, error): + """Logs an error if no Copyright message appears at the top of the file.""" + + # We'll say it should occur by line 10. Don't forget there's a + # dummy line at the front. + for line in xrange(1, min(len(lines), 11)): + if re.search(r'Copyright', lines[line], re.I): break + else: # means no copyright line was found + error(filename, 0, 'legal/copyright', 5, + 'No copyright message found. ' + 'You should have a line: "Copyright [year] "') + + +def GetIndentLevel(line): + """Return the number of leading spaces in line. + + Args: + line: A string to check. + + Returns: + An integer count of leading spaces, possibly zero. + """ + indent = Match(r'^( *)\S', line) + if indent: + return len(indent.group(1)) + else: + return 0 + + +def GetHeaderGuardCPPVariable(filename): + """Returns the CPP variable that should be used as a header guard. + + Args: + filename: The name of a C++ header file. + + Returns: + The CPP variable that should be used as a header guard in the + named file. + + """ + + # Restores original filename in case that cpplint is invoked from Emacs's + # flymake. + filename = re.sub(r'_flymake\.h$', '.h', filename) + filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename) + # Replace 'c++' with 'cpp'. + filename = filename.replace('C++', 'cpp').replace('c++', 'cpp') + + fileinfo = FileInfo(filename) + file_path_from_root = fileinfo.RepositoryName() + if _root: + file_path_from_root = re.sub('^' + _root + os.sep, '', file_path_from_root) + return re.sub(r'[^a-zA-Z0-9]', '_', file_path_from_root).upper() + '_' + + +def CheckForHeaderGuard(filename, clean_lines, error): + """Checks that the file contains a header guard. + + Logs an error if no #ifndef header guard is present. For other + headers, checks that the full pathname is used. + + Args: + filename: The name of the C++ header file. + clean_lines: A CleansedLines instance containing the file. + error: The function to call with any errors found. + """ + + # Don't check for header guards if there are error suppression + # comments somewhere in this file. + # + # Because this is silencing a warning for a nonexistent line, we + # only support the very specific NOLINT(build/header_guard) syntax, + # and not the general NOLINT or NOLINT(*) syntax. + raw_lines = clean_lines.lines_without_raw_strings + for i in raw_lines: + if Search(r'//\s*NOLINT\(build/header_guard\)', i): + return + + cppvar = GetHeaderGuardCPPVariable(filename) + + ifndef = '' + ifndef_linenum = 0 + define = '' + endif = '' + endif_linenum = 0 + for linenum, line in enumerate(raw_lines): + linesplit = line.split() + if len(linesplit) >= 2: + # find the first occurrence of #ifndef and #define, save arg + if not ifndef and linesplit[0] == '#ifndef': + # set ifndef to the header guard presented on the #ifndef line. + ifndef = linesplit[1] + ifndef_linenum = linenum + if not define and linesplit[0] == '#define': + define = linesplit[1] + # find the last occurrence of #endif, save entire line + if line.startswith('#endif'): + endif = line + endif_linenum = linenum + + if not ifndef or not define or ifndef != define: + error(filename, 0, 'build/header_guard', 5, + 'No #ifndef header guard found, suggested CPP variable is: %s' % + cppvar) + return + + # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__ + # for backward compatibility. + if ifndef != cppvar: + error_level = 0 + if ifndef != cppvar + '_': + error_level = 5 + + ParseNolintSuppressions(filename, raw_lines[ifndef_linenum], ifndef_linenum, + error) + error(filename, ifndef_linenum, 'build/header_guard', error_level, + '#ifndef header guard has wrong style, please use: %s' % cppvar) + + # Check for "//" comments on endif line. + ParseNolintSuppressions(filename, raw_lines[endif_linenum], endif_linenum, + error) + match = Match(r'#endif\s*//\s*' + cppvar + r'(_)?\b', endif) + if match: + if match.group(1) == '_': + # Issue low severity warning for deprecated double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif // %s"' % cppvar) + return + + # Didn't find the corresponding "//" comment. If this file does not + # contain any "//" comments at all, it could be that the compiler + # only wants "/**/" comments, look for those instead. + no_single_line_comments = True + for i in xrange(1, len(raw_lines) - 1): + line = raw_lines[i] + if Match(r'^(?:(?:\'(?:\.|[^\'])*\')|(?:"(?:\.|[^"])*")|[^\'"])*//', line): + no_single_line_comments = False + break + + if no_single_line_comments: + match = Match(r'#endif\s*/\*\s*' + cppvar + r'(_)?\s*\*/', endif) + if match: + if match.group(1) == '_': + # Low severity warning for double trailing underscore + error(filename, endif_linenum, 'build/header_guard', 0, + '#endif line should be "#endif /* %s */"' % cppvar) + return + + # Didn't find anything + error(filename, endif_linenum, 'build/header_guard', 5, + '#endif line should be "#endif // %s"' % cppvar) + + +def CheckHeaderFileIncluded(filename, include_state, error): + """Logs an error if a .cc file does not include its header.""" + + # Do not check test files + if filename.endswith('_test.cc') or filename.endswith('_unittest.cc'): + return + + fileinfo = FileInfo(filename) + headerfile = filename[0:len(filename) - 2] + 'h' + if not os.path.exists(headerfile): + return + headername = FileInfo(headerfile).RepositoryName() + first_include = 0 + for section_list in include_state.include_list: + for f in section_list: + if headername in f[0] or f[0] in headername: + return + if not first_include: + first_include = f[1] + + error(filename, first_include, 'build/include', 5, + '%s should include its header file %s' % (fileinfo.RepositoryName(), + headername)) + + +def CheckForBadCharacters(filename, lines, error): + """Logs an error for each line containing bad characters. + + Two kinds of bad characters: + + 1. Unicode replacement characters: These indicate that either the file + contained invalid UTF-8 (likely) or Unicode replacement characters (which + it shouldn't). Note that it's possible for this to throw off line + numbering if the invalid UTF-8 occurred adjacent to a newline. + + 2. NUL bytes. These are problematic for some tools. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + for linenum, line in enumerate(lines): + if u'\ufffd' in line: + error(filename, linenum, 'readability/utf8', 5, + 'Line contains invalid UTF-8 (or Unicode replacement character).') + if '\0' in line: + error(filename, linenum, 'readability/nul', 5, 'Line contains NUL byte.') + + +def CheckForNewlineAtEOF(filename, lines, error): + """Logs an error if there is no newline char at the end of the file. + + Args: + filename: The name of the current file. + lines: An array of strings, each representing a line of the file. + error: The function to call with any errors found. + """ + + # The array lines() was created by adding two newlines to the + # original file (go figure), then splitting on \n. + # To verify that the file ends in \n, we just have to make sure the + # last-but-two element of lines() exists and is empty. + if len(lines) < 3 or lines[-2]: + error(filename, len(lines) - 2, 'whitespace/ending_newline', 5, + 'Could not find a newline character at the end of the file.') + + +def CheckForMultilineCommentsAndStrings(filename, clean_lines, linenum, error): + """Logs an error if we see /* ... */ or "..." that extend past one line. + + /* ... */ comments are legit inside macros, for one line. + Otherwise, we prefer // comments, so it's ok to warn about the + other. Likewise, it's ok for strings to extend across multiple + lines, as long as a line continuation character (backslash) + terminates each line. Although not currently prohibited by the C++ + style guide, it's ugly and unnecessary. We don't do well with either + in this lint program, so we warn about both. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remove all \\ (escaped backslashes) from the line. They are OK, and the + # second (escaped) slash may trigger later \" detection erroneously. + line = line.replace('\\\\', '') + + if line.count('/*') > line.count('*/'): + error(filename, linenum, 'readability/multiline_comment', 5, + 'Complex multi-line /*...*/-style comment found. ' + 'Lint may give bogus warnings. ' + 'Consider replacing these with //-style comments, ' + 'with #if 0...#endif, ' + 'or with more clearly structured multi-line comments.') + + if (line.count('"') - line.count('\\"')) % 2: + error(filename, linenum, 'readability/multiline_string', 5, + 'Multi-line string ("...") found. This lint script doesn\'t ' + 'do well with such strings, and may give bogus warnings. ' + 'Use C++11 raw strings or concatenation instead.') + + +# (non-threadsafe name, thread-safe alternative, validation pattern) +# +# The validation pattern is used to eliminate false positives such as: +# _rand(); // false positive due to substring match. +# ->rand(); // some member function rand(). +# ACMRandom rand(seed); // some variable named rand. +# ISAACRandom rand(); // another variable named rand. +# +# Basically we require the return value of these functions to be used +# in some expression context on the same line by matching on some +# operator before the function name. This eliminates constructors and +# member function calls. +_UNSAFE_FUNC_PREFIX = r'(?:[-+*/=%^&|(<]\s*|>\s+)' +_THREADING_LIST = ( + ('asctime(', 'asctime_r(', _UNSAFE_FUNC_PREFIX + r'asctime\([^)]+\)'), + ('ctime(', 'ctime_r(', _UNSAFE_FUNC_PREFIX + r'ctime\([^)]+\)'), + ('getgrgid(', 'getgrgid_r(', _UNSAFE_FUNC_PREFIX + r'getgrgid\([^)]+\)'), + ('getgrnam(', 'getgrnam_r(', _UNSAFE_FUNC_PREFIX + r'getgrnam\([^)]+\)'), + ('getlogin(', 'getlogin_r(', _UNSAFE_FUNC_PREFIX + r'getlogin\(\)'), + ('getpwnam(', 'getpwnam_r(', _UNSAFE_FUNC_PREFIX + r'getpwnam\([^)]+\)'), + ('getpwuid(', 'getpwuid_r(', _UNSAFE_FUNC_PREFIX + r'getpwuid\([^)]+\)'), + ('gmtime(', 'gmtime_r(', _UNSAFE_FUNC_PREFIX + r'gmtime\([^)]+\)'), + ('localtime(', 'localtime_r(', _UNSAFE_FUNC_PREFIX + r'localtime\([^)]+\)'), + ('rand(', 'rand_r(', _UNSAFE_FUNC_PREFIX + r'rand\(\)'), + ('strtok(', 'strtok_r(', + _UNSAFE_FUNC_PREFIX + r'strtok\([^)]+\)'), + ('ttyname(', 'ttyname_r(', _UNSAFE_FUNC_PREFIX + r'ttyname\([^)]+\)'), + ) + + +def CheckPosixThreading(filename, clean_lines, linenum, error): + """Checks for calls to thread-unsafe functions. + + Much code has been originally written without consideration of + multi-threading. Also, engineers are relying on their old experience; + they have learned posix before threading extensions were added. These + tests guide the engineers to use thread-safe functions (when using + posix directly). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for single_thread_func, multithread_safe_func, pattern in _THREADING_LIST: + # Additional pattern matching check to confirm that this is the + # function we are looking for + if Search(pattern, line): + error(filename, linenum, 'runtime/threadsafe_fn', 2, + 'Consider using ' + multithread_safe_func + + '...) instead of ' + single_thread_func + + '...) for improved thread safety.') + + +def CheckVlogArguments(filename, clean_lines, linenum, error): + """Checks that VLOG() is only used for defining a logging level. + + For example, VLOG(2) is correct. VLOG(INFO), VLOG(WARNING), VLOG(ERROR), and + VLOG(FATAL) are not. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if Search(r'\bVLOG\((INFO|ERROR|WARNING|DFATAL|FATAL)\)', line): + error(filename, linenum, 'runtime/vlog', 5, + 'VLOG() should be used with numeric verbosity level. ' + 'Use LOG() if you want symbolic severity levels.') + +# Matches invalid increment: *count++, which moves pointer instead of +# incrementing a value. +_RE_PATTERN_INVALID_INCREMENT = re.compile( + r'^\s*\*\w+(\+\+|--);') + + +def CheckInvalidIncrement(filename, clean_lines, linenum, error): + """Checks for invalid increment *count++. + + For example following function: + void increment_counter(int* count) { + *count++; + } + is invalid, because it effectively does count++, moving pointer, and should + be replaced with ++*count, (*count)++ or *count += 1. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + if _RE_PATTERN_INVALID_INCREMENT.match(line): + error(filename, linenum, 'runtime/invalid_increment', 5, + 'Changing pointer instead of value (or unused value of operator*).') + + +def IsMacroDefinition(clean_lines, linenum): + if Search(r'^#define', clean_lines[linenum]): + return True + + if linenum > 0 and Search(r'\\$', clean_lines[linenum - 1]): + return True + + return False + + +def IsForwardClassDeclaration(clean_lines, linenum): + return Match(r'^\s*(\btemplate\b)*.*class\s+\w+;\s*$', clean_lines[linenum]) + + +class _BlockInfo(object): + """Stores information about a generic block of code.""" + + def __init__(self, seen_open_brace): + self.seen_open_brace = seen_open_brace + self.open_parentheses = 0 + self.inline_asm = _NO_ASM + self.check_namespace_indentation = False + + def CheckBegin(self, filename, clean_lines, linenum, error): + """Run checks that applies to text up to the opening brace. + + This is mostly for checking the text after the class identifier + and the "{", usually where the base class is specified. For other + blocks, there isn't much to check, so we always pass. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Run checks that applies to text after the closing brace. + + This is mostly used for checking end of namespace comments. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + pass + + def IsBlockInfo(self): + """Returns true if this block is a _BlockInfo. + + This is convenient for verifying that an object is an instance of + a _BlockInfo, but not an instance of any of the derived classes. + + Returns: + True for this class, False for derived classes. + """ + return self.__class__ == _BlockInfo + + +class _ExternCInfo(_BlockInfo): + """Stores information about an 'extern "C"' block.""" + + def __init__(self): + _BlockInfo.__init__(self, True) + + +class _ClassInfo(_BlockInfo): + """Stores information about a class.""" + + def __init__(self, name, class_or_struct, clean_lines, linenum): + _BlockInfo.__init__(self, False) + self.name = name + self.starting_linenum = linenum + self.is_derived = False + self.check_namespace_indentation = True + if class_or_struct == 'struct': + self.access = 'public' + self.is_struct = True + else: + self.access = 'private' + self.is_struct = False + + # Remember initial indentation level for this class. Using raw_lines here + # instead of elided to account for leading comments. + self.class_indent = GetIndentLevel(clean_lines.raw_lines[linenum]) + + # Try to find the end of the class. This will be confused by things like: + # class A { + # } *x = { ... + # + # But it's still good enough for CheckSectionSpacing. + self.last_line = 0 + depth = 0 + for i in range(linenum, clean_lines.NumLines()): + line = clean_lines.elided[i] + depth += line.count('{') - line.count('}') + if not depth: + self.last_line = i + break + + def CheckBegin(self, filename, clean_lines, linenum, error): + # Look for a bare ':' + if Search('(^|[^:]):($|[^:])', clean_lines.elided[linenum]): + self.is_derived = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + # If there is a DISALLOW macro, it should appear near the end of + # the class. + seen_last_thing_in_class = False + for i in xrange(linenum - 1, self.starting_linenum, -1): + match = Search( + r'\b(DISALLOW_COPY_AND_ASSIGN|DISALLOW_IMPLICIT_CONSTRUCTORS)\(' + + self.name + r'\)', + clean_lines.elided[i]) + if match: + if seen_last_thing_in_class: + error(filename, i, 'readability/constructors', 3, + match.group(1) + ' should be the last thing in the class') + break + + if not Match(r'^\s*$', clean_lines.elided[i]): + seen_last_thing_in_class = True + + # Check that closing brace is aligned with beginning of the class. + # Only do this if the closing brace is indented by only whitespaces. + # This means we will not check single-line class definitions. + indent = Match(r'^( *)\}', clean_lines.elided[linenum]) + if indent and len(indent.group(1)) != self.class_indent: + if self.is_struct: + parent = 'struct ' + self.name + else: + parent = 'class ' + self.name + error(filename, linenum, 'whitespace/indent', 3, + 'Closing brace should be aligned with beginning of %s' % parent) + + +class _NamespaceInfo(_BlockInfo): + """Stores information about a namespace.""" + + def __init__(self, name, linenum): + _BlockInfo.__init__(self, False) + self.name = name or '' + self.starting_linenum = linenum + self.check_namespace_indentation = True + + def CheckEnd(self, filename, clean_lines, linenum, error): + """Check end of namespace comments.""" + line = clean_lines.raw_lines[linenum] + + # Check how many lines is enclosed in this namespace. Don't issue + # warning for missing namespace comments if there aren't enough + # lines. However, do apply checks if there is already an end of + # namespace comment and it's incorrect. + # + # TODO(unknown): We always want to check end of namespace comments + # if a namespace is large, but sometimes we also want to apply the + # check if a short namespace contained nontrivial things (something + # other than forward declarations). There is currently no logic on + # deciding what these nontrivial things are, so this check is + # triggered by namespace size only, which works most of the time. + if (linenum - self.starting_linenum < 10 + and not Match(r'};*\s*(//|/\*).*\bnamespace\b', line)): + return + + # Look for matching comment at end of namespace. + # + # Note that we accept C style "/* */" comments for terminating + # namespaces, so that code that terminate namespaces inside + # preprocessor macros can be cpplint clean. + # + # We also accept stuff like "// end of namespace ." with the + # period at the end. + # + # Besides these, we don't accept anything else, otherwise we might + # get false negatives when existing comment is a substring of the + # expected namespace. + if self.name: + # Named namespace + if not Match((r'};*\s*(//|/\*).*\bnamespace\s+' + re.escape(self.name) + + r'[\*/\.\\\s]*$'), + line): + error(filename, linenum, 'readability/namespace', 5, + 'Namespace should be terminated with "// namespace %s"' % + self.name) + else: + # Anonymous namespace + if not Match(r'};*\s*(//|/\*).*\bnamespace[\*/\.\\\s]*$', line): + # If "// namespace anonymous" or "// anonymous namespace (more text)", + # mention "// anonymous namespace" as an acceptable form + if Match(r'}.*\b(namespace anonymous|anonymous namespace)\b', line): + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"' + ' or "// anonymous namespace"') + else: + error(filename, linenum, 'readability/namespace', 5, + 'Anonymous namespace should be terminated with "// namespace"') + + +class _PreprocessorInfo(object): + """Stores checkpoints of nesting stacks when #if/#else is seen.""" + + def __init__(self, stack_before_if): + # The entire nesting stack before #if + self.stack_before_if = stack_before_if + + # The entire nesting stack up to #else + self.stack_before_else = [] + + # Whether we have already seen #else or #elif + self.seen_else = False + + +class NestingState(object): + """Holds states related to parsing braces.""" + + def __init__(self): + # Stack for tracking all braces. An object is pushed whenever we + # see a "{", and popped when we see a "}". Only 3 types of + # objects are possible: + # - _ClassInfo: a class or struct. + # - _NamespaceInfo: a namespace. + # - _BlockInfo: some other type of block. + self.stack = [] + + # Top of the previous stack before each Update(). + # + # Because the nesting_stack is updated at the end of each line, we + # had to do some convoluted checks to find out what is the current + # scope at the beginning of the line. This check is simplified by + # saving the previous top of nesting stack. + # + # We could save the full stack, but we only need the top. Copying + # the full nesting stack would slow down cpplint by ~10%. + self.previous_stack_top = [] + + # Stack of _PreprocessorInfo objects. + self.pp_stack = [] + + def SeenOpenBrace(self): + """Check if we have seen the opening brace for the innermost block. + + Returns: + True if we have seen the opening brace, False if the innermost + block is still expecting an opening brace. + """ + return (not self.stack) or self.stack[-1].seen_open_brace + + def InNamespaceBody(self): + """Check if we are currently one level inside a namespace body. + + Returns: + True if top of the stack is a namespace block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _NamespaceInfo) + + def InExternC(self): + """Check if we are currently one level inside an 'extern "C"' block. + + Returns: + True if top of the stack is an extern block, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ExternCInfo) + + def InClassDeclaration(self): + """Check if we are currently one level inside a class or struct declaration. + + Returns: + True if top of the stack is a class/struct, False otherwise. + """ + return self.stack and isinstance(self.stack[-1], _ClassInfo) + + def InAsmBlock(self): + """Check if we are currently one level inside an inline ASM block. + + Returns: + True if the top of the stack is a block containing inline ASM. + """ + return self.stack and self.stack[-1].inline_asm != _NO_ASM + + def InTemplateArgumentList(self, clean_lines, linenum, pos): + """Check if current position is inside template argument list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + pos: position just after the suspected template argument. + Returns: + True if (linenum, pos) is inside template arguments. + """ + while linenum < clean_lines.NumLines(): + # Find the earliest character that might indicate a template argument + line = clean_lines.elided[linenum] + match = Match(r'^[^{};=\[\]\.<>]*(.)', line[pos:]) + if not match: + linenum += 1 + pos = 0 + continue + token = match.group(1) + pos += len(match.group(0)) + + # These things do not look like template argument list: + # class Suspect { + # class Suspect x; } + if token in ('{', '}', ';'): return False + + # These things look like template argument list: + # template + # template + # template + # template + if token in ('>', '=', '[', ']', '.'): return True + + # Check if token is an unmatched '<'. + # If not, move on to the next character. + if token != '<': + pos += 1 + if pos >= len(line): + linenum += 1 + pos = 0 + continue + + # We can't be sure if we just find a single '<', and need to + # find the matching '>'. + (_, end_line, end_pos) = CloseExpression(clean_lines, linenum, pos - 1) + if end_pos < 0: + # Not sure if template argument list or syntax error in file + return False + linenum = end_line + pos = end_pos + return False + + def UpdatePreprocessor(self, line): + """Update preprocessor stack. + + We need to handle preprocessors due to classes like this: + #ifdef SWIG + struct ResultDetailsPageElementExtensionPoint { + #else + struct ResultDetailsPageElementExtensionPoint : public Extension { + #endif + + We make the following assumptions (good enough for most files): + - Preprocessor condition evaluates to true from #if up to first + #else/#elif/#endif. + + - Preprocessor condition evaluates to false from #else/#elif up + to #endif. We still perform lint checks on these lines, but + these do not affect nesting stack. + + Args: + line: current line to check. + """ + if Match(r'^\s*#\s*(if|ifdef|ifndef)\b', line): + # Beginning of #if block, save the nesting stack here. The saved + # stack will allow us to restore the parsing state in the #else case. + self.pp_stack.append(_PreprocessorInfo(copy.deepcopy(self.stack))) + elif Match(r'^\s*#\s*(else|elif)\b', line): + # Beginning of #else block + if self.pp_stack: + if not self.pp_stack[-1].seen_else: + # This is the first #else or #elif block. Remember the + # whole nesting stack up to this point. This is what we + # keep after the #endif. + self.pp_stack[-1].seen_else = True + self.pp_stack[-1].stack_before_else = copy.deepcopy(self.stack) + + # Restore the stack to how it was before the #if + self.stack = copy.deepcopy(self.pp_stack[-1].stack_before_if) + else: + # TODO(unknown): unexpected #else, issue warning? + pass + elif Match(r'^\s*#\s*endif\b', line): + # End of #if or #else blocks. + if self.pp_stack: + # If we saw an #else, we will need to restore the nesting + # stack to its former state before the #else, otherwise we + # will just continue from where we left off. + if self.pp_stack[-1].seen_else: + # Here we can just use a shallow copy since we are the last + # reference to it. + self.stack = self.pp_stack[-1].stack_before_else + # Drop the corresponding #if + self.pp_stack.pop() + else: + # TODO(unknown): unexpected #endif, issue warning? + pass + + # TODO(unknown): Update() is too long, but we will refactor later. + def Update(self, filename, clean_lines, linenum, error): + """Update nesting state with current line. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Remember top of the previous nesting stack. + # + # The stack is always pushed/popped and not modified in place, so + # we can just do a shallow copy instead of copy.deepcopy. Using + # deepcopy would slow down cpplint by ~28%. + if self.stack: + self.previous_stack_top = self.stack[-1] + else: + self.previous_stack_top = None + + # Update pp_stack + self.UpdatePreprocessor(line) + + # Count parentheses. This is to avoid adding struct arguments to + # the nesting stack. + if self.stack: + inner_block = self.stack[-1] + depth_change = line.count('(') - line.count(')') + inner_block.open_parentheses += depth_change + + # Also check if we are starting or ending an inline assembly block. + if inner_block.inline_asm in (_NO_ASM, _END_ASM): + if (depth_change != 0 and + inner_block.open_parentheses == 1 and + _MATCH_ASM.match(line)): + # Enter assembly block + inner_block.inline_asm = _INSIDE_ASM + else: + # Not entering assembly block. If previous line was _END_ASM, + # we will now shift to _NO_ASM state. + inner_block.inline_asm = _NO_ASM + elif (inner_block.inline_asm == _INSIDE_ASM and + inner_block.open_parentheses == 0): + # Exit assembly block + inner_block.inline_asm = _END_ASM + + # Consume namespace declaration at the beginning of the line. Do + # this in a loop so that we catch same line declarations like this: + # namespace proto2 { namespace bridge { class MessageSet; } } + while True: + # Match start of namespace. The "\b\s*" below catches namespace + # declarations even if it weren't followed by a whitespace, this + # is so that we don't confuse our namespace checker. The + # missing spaces will be flagged by CheckSpacing. + namespace_decl_match = Match(r'^\s*namespace\b\s*([:\w]+)?(.*)$', line) + if not namespace_decl_match: + break + + new_namespace = _NamespaceInfo(namespace_decl_match.group(1), linenum) + self.stack.append(new_namespace) + + line = namespace_decl_match.group(2) + if line.find('{') != -1: + new_namespace.seen_open_brace = True + line = line[line.find('{') + 1:] + + # Look for a class declaration in whatever is left of the line + # after parsing namespaces. The regexp accounts for decorated classes + # such as in: + # class LOCKABLE API Object { + # }; + class_decl_match = Match( + r'^(\s*(?:template\s*<[\w\s<>,:]*>\s*)?' + r'(class|struct)\s+(?:[A-Z_]+\s+)*(\w+(?:::\w+)*))' + r'(.*)$', line) + if (class_decl_match and + (not self.stack or self.stack[-1].open_parentheses == 0)): + # We do not want to accept classes that are actually template arguments: + # template , + # template class Ignore3> + # void Function() {}; + # + # To avoid template argument cases, we scan forward and look for + # an unmatched '>'. If we see one, assume we are inside a + # template argument list. + end_declaration = len(class_decl_match.group(1)) + if not self.InTemplateArgumentList(clean_lines, linenum, end_declaration): + self.stack.append(_ClassInfo( + class_decl_match.group(3), class_decl_match.group(2), + clean_lines, linenum)) + line = class_decl_match.group(4) + + # If we have not yet seen the opening brace for the innermost block, + # run checks here. + if not self.SeenOpenBrace(): + self.stack[-1].CheckBegin(filename, clean_lines, linenum, error) + + # Update access control if we are inside a class/struct + if self.stack and isinstance(self.stack[-1], _ClassInfo): + classinfo = self.stack[-1] + access_match = Match( + r'^(.*)\b(public|private|protected|signals)(\s+(?:slots\s*)?)?' + r':(?:[^:]|$)', + line) + if access_match: + classinfo.access = access_match.group(2) + + # Check that access keywords are indented +1 space. Skip this + # check if the keywords are not preceded by whitespaces. + indent = access_match.group(1) + if (len(indent) != classinfo.class_indent + 1 and + Match(r'^\s*$', indent)): + if classinfo.is_struct: + parent = 'struct ' + classinfo.name + else: + parent = 'class ' + classinfo.name + slots = '' + if access_match.group(3): + slots = access_match.group(3) + error(filename, linenum, 'whitespace/indent', 3, + '%s%s: should be indented +1 space inside %s' % ( + access_match.group(2), slots, parent)) + + # Consume braces or semicolons from what's left of the line + while True: + # Match first brace, semicolon, or closed parenthesis. + matched = Match(r'^[^{;)}]*([{;)}])(.*)$', line) + if not matched: + break + + token = matched.group(1) + if token == '{': + # If namespace or class hasn't seen a opening brace yet, mark + # namespace/class head as complete. Push a new block onto the + # stack otherwise. + if not self.SeenOpenBrace(): + self.stack[-1].seen_open_brace = True + elif Match(r'^extern\s*"[^"]*"\s*\{', line): + self.stack.append(_ExternCInfo()) + else: + self.stack.append(_BlockInfo(True)) + if _MATCH_ASM.match(line): + self.stack[-1].inline_asm = _BLOCK_ASM + + elif token == ';' or token == ')': + # If we haven't seen an opening brace yet, but we already saw + # a semicolon, this is probably a forward declaration. Pop + # the stack for these. + # + # Similarly, if we haven't seen an opening brace yet, but we + # already saw a closing parenthesis, then these are probably + # function arguments with extra "class" or "struct" keywords. + # Also pop these stack for these. + if not self.SeenOpenBrace(): + self.stack.pop() + else: # token == '}' + # Perform end of block checks and pop the stack. + if self.stack: + self.stack[-1].CheckEnd(filename, clean_lines, linenum, error) + self.stack.pop() + line = matched.group(2) + + def InnermostClass(self): + """Get class info on the top of the stack. + + Returns: + A _ClassInfo object if we are inside a class, or None otherwise. + """ + for i in range(len(self.stack), 0, -1): + classinfo = self.stack[i - 1] + if isinstance(classinfo, _ClassInfo): + return classinfo + return None + + def CheckCompletedBlocks(self, filename, error): + """Checks that all classes and namespaces have been completely parsed. + + Call this when all lines in a file have been processed. + Args: + filename: The name of the current file. + error: The function to call with any errors found. + """ + # Note: This test can result in false positives if #ifdef constructs + # get in the way of brace matching. See the testBuildClass test in + # cpplint_unittest.py for an example of this. + for obj in self.stack: + if isinstance(obj, _ClassInfo): + error(filename, obj.starting_linenum, 'build/class', 5, + 'Failed to find complete declaration of class %s' % + obj.name) + elif isinstance(obj, _NamespaceInfo): + error(filename, obj.starting_linenum, 'build/namespaces', 5, + 'Failed to find complete declaration of namespace %s' % + obj.name) + + +def CheckForNonStandardConstructs(filename, clean_lines, linenum, + nesting_state, error): + r"""Logs an error if we see certain non-ANSI constructs ignored by gcc-2. + + Complain about several constructs which gcc-2 accepts, but which are + not standard C++. Warning about these in lint is one way to ease the + transition to new compilers. + - put storage class first (e.g. "static const" instead of "const static"). + - "%lld" instead of %qd" in printf-type functions. + - "%1$d" is non-standard in printf-type functions. + - "\%" is an undefined character escape sequence. + - text after #endif is not allowed. + - invalid inner-style forward declaration. + - >? and ?= and )\?=?\s*(\w+|[+-]?\d+)(\.\d*)?', + line): + error(filename, linenum, 'build/deprecated', 3, + '>? and ))?' + # r'\s*const\s*' + type_name + '\s*&\s*\w+\s*;' + error(filename, linenum, 'runtime/member_string_references', 2, + 'const string& members are dangerous. It is much better to use ' + 'alternatives, such as pointers or simple constants.') + + # Everything else in this function operates on class declarations. + # Return early if the top of the nesting stack is not a class, or if + # the class head is not completed yet. + classinfo = nesting_state.InnermostClass() + if not classinfo or not classinfo.seen_open_brace: + return + + # The class may have been declared with namespace or classname qualifiers. + # The constructor and destructor will not have those qualifiers. + base_classname = classinfo.name.split('::')[-1] + + # Look for single-argument constructors that aren't marked explicit. + # Technically a valid construct, but against style. Also look for + # non-single-argument constructors which are also technically valid, but + # strongly suggest something is wrong. + explicit_constructor_match = Match( + r'\s+(?:inline\s+)?(explicit\s+)?(?:inline\s+)?%s\s*' + r'\(((?:[^()]|\([^()]*\))*)\)' + % re.escape(base_classname), + line) + + if explicit_constructor_match: + is_marked_explicit = explicit_constructor_match.group(1) + + if not explicit_constructor_match.group(2): + constructor_args = [] + else: + constructor_args = explicit_constructor_match.group(2).split(',') + + # collapse arguments so that commas in template parameter lists and function + # argument parameter lists don't split arguments in two + i = 0 + while i < len(constructor_args): + constructor_arg = constructor_args[i] + while (constructor_arg.count('<') > constructor_arg.count('>') or + constructor_arg.count('(') > constructor_arg.count(')')): + constructor_arg += ',' + constructor_args[i + 1] + del constructor_args[i + 1] + constructor_args[i] = constructor_arg + i += 1 + + defaulted_args = [arg for arg in constructor_args if '=' in arg] + noarg_constructor = (not constructor_args or # empty arg list + # 'void' arg specifier + (len(constructor_args) == 1 and + constructor_args[0].strip() == 'void')) + onearg_constructor = ((len(constructor_args) == 1 and # exactly one arg + not noarg_constructor) or + # all but at most one arg defaulted + (len(constructor_args) >= 1 and + not noarg_constructor and + len(defaulted_args) >= len(constructor_args) - 1)) + initializer_list_constructor = bool( + onearg_constructor and + Search(r'\bstd\s*::\s*initializer_list\b', constructor_args[0])) + copy_constructor = bool( + onearg_constructor and + Match(r'(const\s+)?%s(\s*<[^>]*>)?(\s+const)?\s*(?:<\w+>\s*)?&' + % re.escape(base_classname), constructor_args[0].strip())) + + if (not is_marked_explicit and + onearg_constructor and + not initializer_list_constructor and + not copy_constructor): + if defaulted_args: + error(filename, linenum, 'runtime/explicit', 5, + 'Constructors callable with one argument ' + 'should be marked explicit.') + else: + error(filename, linenum, 'runtime/explicit', 5, + 'Single-parameter constructors should be marked explicit.') + elif is_marked_explicit and not onearg_constructor: + if noarg_constructor: + error(filename, linenum, 'runtime/explicit', 5, + 'Zero-parameter constructors should not be marked explicit.') + else: + error(filename, linenum, 'runtime/explicit', 0, + 'Constructors that require multiple arguments ' + 'should not be marked explicit.') + + +def CheckSpacingForFunctionCall(filename, clean_lines, linenum, error): + """Checks for the correctness of various spacing around function calls. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Since function calls often occur inside if/for/while/switch + # expressions - which have their own, more liberal conventions - we + # first see if we should be looking inside such an expression for a + # function call, to which we can apply more strict standards. + fncall = line # if there's no control flow construct, look at whole line + for pattern in (r'\bif\s*\((.*)\)\s*{', + r'\bfor\s*\((.*)\)\s*{', + r'\bwhile\s*\((.*)\)\s*[{;]', + r'\bswitch\s*\((.*)\)\s*{'): + match = Search(pattern, line) + if match: + fncall = match.group(1) # look inside the parens for function calls + break + + # Except in if/for/while/switch, there should never be space + # immediately inside parens (eg "f( 3, 4 )"). We make an exception + # for nested parens ( (a+b) + c ). Likewise, there should never be + # a space before a ( when it's a function argument. I assume it's a + # function argument when the char before the whitespace is legal in + # a function name (alnum + _) and we're not starting a macro. Also ignore + # pointers and references to arrays and functions coz they're too tricky: + # we use a very simple way to recognize these: + # " (something)(maybe-something)" or + # " (something)(maybe-something," or + # " (something)[something]" + # Note that we assume the contents of [] to be short enough that + # they'll never need to wrap. + if ( # Ignore control structures. + not Search(r'\b(if|for|while|switch|return|new|delete|catch|sizeof)\b', + fncall) and + # Ignore pointers/references to functions. + not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and + # Ignore pointers/references to arrays. + not Search(r' \([^)]+\)\[[^\]]+\]', fncall)): + if Search(r'\w\s*\(\s(?!\s*\\$)', fncall): # a ( used for a fn call + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space after ( in function call') + elif Search(r'\(\s+(?!(\s*\\)|\()', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space after (') + if (Search(r'\w\s+\(', fncall) and + not Search(r'#\s*define|typedef|using\s+\w+\s*=', fncall) and + not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall) and + not Search(r'\bcase\s+\(', fncall)): + # TODO(unknown): Space after an operator function seem to be a common + # error, silence those for now by restricting them to highest verbosity. + if Search(r'\boperator_*\b', line): + error(filename, linenum, 'whitespace/parens', 0, + 'Extra space before ( in function call') + else: + error(filename, linenum, 'whitespace/parens', 4, + 'Extra space before ( in function call') + # If the ) is followed only by a newline or a { + newline, assume it's + # part of a control statement (if/while/etc), and don't complain + if Search(r'[^)]\s+\)\s*[^{\s]', fncall): + # If the closing parenthesis is preceded by only whitespaces, + # try to give a more descriptive error message. + if Search(r'^\s+\)', fncall): + error(filename, linenum, 'whitespace/parens', 2, + 'Closing ) should be moved to the previous line') + else: + error(filename, linenum, 'whitespace/parens', 2, + 'Extra space before )') + + +def IsBlankLine(line): + """Returns true if the given line is blank. + + We consider a line to be blank if the line is empty or consists of + only white spaces. + + Args: + line: A line of a string. + + Returns: + True, if the given line is blank. + """ + return not line or line.isspace() + + +def CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error): + is_namespace_indent_item = ( + len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.previous_stack_top, _NamespaceInfo) and + nesting_state.previous_stack_top == nesting_state.stack[-2]) + + if ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + clean_lines.elided, line): + CheckItemIndentationInNamespace(filename, clean_lines.elided, + line, error) + + +def CheckForFunctionLengths(filename, clean_lines, linenum, + function_state, error): + """Reports for long function bodies. + + For an overview why this is done, see: + http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions + + Uses a simplistic algorithm assuming other style guidelines + (especially spacing) are followed. + Only checks unindented functions, so class members are unchecked. + Trivial bodies are unchecked, so constructors with huge initializer lists + may be missed. + Blank/comment lines are not counted so as to avoid encouraging the removal + of vertical space and comments just to get through a lint check. + NOLINT *on the last line of a function* disables this check. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + function_state: Current function name and lines in body so far. + error: The function to call with any errors found. + """ + lines = clean_lines.lines + line = lines[linenum] + joined_line = '' + + starting_func = False + regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ... + match_result = Match(regexp, line) + if match_result: + # If the name is all caps and underscores, figure it's a macro and + # ignore it, unless it's TEST or TEST_F. + function_name = match_result.group(1).split()[-1] + if function_name == 'TEST' or function_name == 'TEST_F' or ( + not Match(r'[A-Z_]+$', function_name)): + starting_func = True + + if starting_func: + body_found = False + for start_linenum in xrange(linenum, clean_lines.NumLines()): + start_line = lines[start_linenum] + joined_line += ' ' + start_line.lstrip() + if Search(r'(;|})', start_line): # Declarations and trivial functions + body_found = True + break # ... ignore + elif Search(r'{', start_line): + body_found = True + function = Search(r'((\w|:)*)\(', line).group(1) + if Match(r'TEST', function): # Handle TEST... macros + parameter_regexp = Search(r'(\(.*\))', joined_line) + if parameter_regexp: # Ignore bad syntax + function += parameter_regexp.group(1) + else: + function += '()' + function_state.Begin(function) + break + if not body_found: + # No body for the function (or evidence of a non-function) was found. + error(filename, linenum, 'readability/fn_size', 5, + 'Lint failed to find start of function body.') + elif Match(r'^\}\s*$', line): # function end + function_state.Check(error, filename, linenum) + function_state.End() + elif not Match(r'^\s*$', line): + function_state.Count() # Count non-blank/non-comment lines. + + +_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?:?(\s|$)?') + + +def CheckComment(line, filename, linenum, next_line_start, error): + """Checks for common mistakes in comments. + + Args: + line: The line in question. + filename: The name of the current file. + linenum: The number of the line to check. + next_line_start: The first non-whitespace column of the next line. + error: The function to call with any errors found. + """ + commentpos = line.find('//') + if commentpos != -1: + # Check if the // may be in quotes. If so, ignore it + # Comparisons made explicit for clarity -- pylint: disable=g-explicit-bool-comparison + if (line.count('"', 0, commentpos) - + line.count('\\"', 0, commentpos)) % 2 == 0: # not in quotes + # Allow one space for new scopes, two spaces otherwise: + if (not (Match(r'^.*{ *//', line) and next_line_start == commentpos) and + ((commentpos >= 1 and + line[commentpos-1] not in string.whitespace) or + (commentpos >= 2 and + line[commentpos-2] not in string.whitespace))): + error(filename, linenum, 'whitespace/comments', 2, + 'At least two spaces is best between code and comments') + + # Checks for common mistakes in TODO comments. + comment = line[commentpos:] + match = _RE_PATTERN_TODO.match(comment) + if match: + # One whitespace is correct; zero whitespace is handled elsewhere. + leading_whitespace = match.group(1) + if len(leading_whitespace) > 1: + error(filename, linenum, 'whitespace/todo', 2, + 'Too many spaces before TODO') + + username = match.group(2) + if not username: + error(filename, linenum, 'readability/todo', 2, + 'Missing username in TODO; it should look like ' + '"// TODO(my_username): Stuff."') + + middle_whitespace = match.group(3) + # Comparisons made explicit for correctness -- pylint: disable=g-explicit-bool-comparison + if middle_whitespace != ' ' and middle_whitespace != '': + error(filename, linenum, 'whitespace/todo', 2, + 'TODO(my_username) should be followed by a space') + + # If the comment contains an alphanumeric character, there + # should be a space somewhere between it and the // unless + # it's a /// or //! Doxygen comment. + if (Match(r'//[^ ]*\w', comment) and + not Match(r'(///|//\!)(\s+|$)', comment)): + error(filename, linenum, 'whitespace/comments', 4, + 'Should have a space between // and comment') + + +def CheckAccess(filename, clean_lines, linenum, nesting_state, error): + """Checks for improper use of DISALLOW* macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] # get rid of comments and strings + + matched = Match((r'\s*(DISALLOW_COPY_AND_ASSIGN|' + r'DISALLOW_IMPLICIT_CONSTRUCTORS)'), line) + if not matched: + return + if nesting_state.stack and isinstance(nesting_state.stack[-1], _ClassInfo): + if nesting_state.stack[-1].access != 'private': + error(filename, linenum, 'readability/constructors', 3, + '%s must be in the private: section' % matched.group(1)) + + else: + # Found DISALLOW* macro outside a class declaration, or perhaps it + # was used inside a function when it should have been part of the + # class declaration. We could issue a warning here, but it + # probably resulted in a compiler error already. + pass + + +def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): + """Checks for the correctness of various spacing issues in the code. + + Things we check for: spaces around operators, spaces after + if/for/while/switch, no spaces around parens in function calls, two + spaces between code and comment, don't start a block with a blank + line, don't end a function with a blank line, don't add a blank line + after public/protected/private, don't have too many blank lines in a row. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw = clean_lines.lines_without_raw_strings + line = raw[linenum] + + # Before nixing comments, check if the line is blank for no good + # reason. This includes the first line after a block is opened, and + # blank lines at the end of a function (ie, right before a line like '}' + # + # Skip all the blank line checks if we are immediately inside a + # namespace body. In other words, don't issue blank line warnings + # for this block: + # namespace { + # + # } + # + # A warning about missing end of namespace comments will be issued instead. + # + # Also skip blank line checks for 'extern "C"' blocks, which are formatted + # like namespaces. + if (IsBlankLine(line) and + not nesting_state.InNamespaceBody() and + not nesting_state.InExternC()): + elided = clean_lines.elided + prev_line = elided[linenum - 1] + prevbrace = prev_line.rfind('{') + # TODO(unknown): Don't complain if line before blank line, and line after, + # both start with alnums and are indented the same amount. + # This ignores whitespace at the start of a namespace block + # because those are not usually indented. + if prevbrace != -1 and prev_line[prevbrace:].find('}') == -1: + # OK, we have a blank line at the start of a code block. Before we + # complain, we check if it is an exception to the rule: The previous + # non-empty line has the parameters of a function header that are indented + # 4 spaces (because they did not fit in a 80 column line when placed on + # the same line as the function name). We also check for the case where + # the previous line is indented 6 spaces, which may happen when the + # initializers of a constructor do not fit into a 80 column line. + exception = False + if Match(r' {6}\w', prev_line): # Initializer list? + # We are looking for the opening column of initializer list, which + # should be indented 4 spaces to cause 6 space indentation afterwards. + search_position = linenum-2 + while (search_position >= 0 + and Match(r' {6}\w', elided[search_position])): + search_position -= 1 + exception = (search_position >= 0 + and elided[search_position][:5] == ' :') + else: + # Search for the function arguments or an initializer list. We use a + # simple heuristic here: If the line is indented 4 spaces; and we have a + # closing paren, without the opening paren, followed by an opening brace + # or colon (for initializer lists) we assume that it is the last line of + # a function header. If we have a colon indented 4 spaces, it is an + # initializer list. + exception = (Match(r' {4}\w[^\(]*\)\s*(const\s*)?(\{\s*$|:)', + prev_line) + or Match(r' {4}:', prev_line)) + + if not exception: + error(filename, linenum, 'whitespace/blank_line', 2, + 'Redundant blank line at the start of a code block ' + 'should be deleted.') + # Ignore blank lines at the end of a block in a long if-else + # chain, like this: + # if (condition1) { + # // Something followed by a blank line + # + # } else if (condition2) { + # // Something else + # } + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + if (next_line + and Match(r'\s*}', next_line) + and next_line.find('} else ') == -1): + error(filename, linenum, 'whitespace/blank_line', 3, + 'Redundant blank line at the end of a code block ' + 'should be deleted.') + + matched = Match(r'\s*(public|protected|private):', prev_line) + if matched: + error(filename, linenum, 'whitespace/blank_line', 3, + 'Do not leave a blank line after "%s:"' % matched.group(1)) + + # Next, check comments + next_line_start = 0 + if linenum + 1 < clean_lines.NumLines(): + next_line = raw[linenum + 1] + next_line_start = len(next_line) - len(next_line.lstrip()) + CheckComment(line, filename, linenum, next_line_start, error) + + # get rid of comments and strings + line = clean_lines.elided[linenum] + + # You shouldn't have spaces before your brackets, except maybe after + # 'delete []' or 'return []() {};' + if Search(r'\w\s+\[', line) and not Search(r'(?:delete|return)\s+\[', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Extra space before [') + + # In range-based for, we wanted spaces before and after the colon, but + # not around "::" tokens that might appear. + if (Search(r'for *\(.*[^:]:[^: ]', line) or + Search(r'for *\(.*[^: ]:[^:]', line)): + error(filename, linenum, 'whitespace/forcolon', 2, + 'Missing space around colon in range-based for loop') + + +def CheckOperatorSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around operators. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Don't try to do spacing checks for operator methods. Do this by + # replacing the troublesome characters with something else, + # preserving column position for all other characters. + # + # The replacement is done repeatedly to avoid false positives from + # operators that call operators. + while True: + match = Match(r'^(.*\boperator\b)(\S+)(\s*\(.*)$', line) + if match: + line = match.group(1) + ('_' * len(match.group(2))) + match.group(3) + else: + break + + # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". + # Otherwise not. Note we only check for non-spaces on *both* sides; + # sometimes people put non-spaces on one side when aligning ='s among + # many lines (not that this is behavior that I approve of...) + if ((Search(r'[\w.]=', line) or + Search(r'=[\w.]', line)) + and not Search(r'\b(if|while|for) ', line) + # Operators taken from [lex.operators] in C++11 standard. + and not Search(r'(>=|<=|==|!=|&=|\^=|\|=|\+=|\*=|\/=|\%=)', line) + and not Search(r'operator=', line)): + error(filename, linenum, 'whitespace/operators', 4, + 'Missing spaces around =') + + # It's ok not to have spaces around binary operators like + - * /, but if + # there's too little whitespace, we get concerned. It's hard to tell, + # though, so we punt on this one for now. TODO. + + # You should always have whitespace around binary operators. + # + # Check <= and >= first to avoid false positives with < and >, then + # check non-include lines for spacing around < and >. + # + # If the operator is followed by a comma, assume it's be used in a + # macro context and don't do any checks. This avoids false + # positives. + # + # Note that && is not included here. Those are checked separately + # in CheckRValueReference + match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around %s' % match.group(1)) + elif not Match(r'#.*include', line): + # Look for < that is not surrounded by spaces. This is only + # triggered if both sides are missing spaces, even though + # technically should should flag if at least one side is missing a + # space. This is done to avoid some false positives with shifts. + match = Match(r'^(.*[^\s<])<[^\s=<,]', line) + if match: + (_, _, end_pos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if end_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <') + + # Look for > that is not surrounded by spaces. Similar to the + # above, we only trigger if both sides are missing spaces to avoid + # false positives with shifts. + match = Match(r'^(.*[^-\s>])>[^\s=>,]', line) + if match: + (_, _, start_pos) = ReverseCloseExpression( + clean_lines, linenum, len(match.group(1))) + if start_pos <= -1: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >') + + # We allow no-spaces around << when used like this: 10<<20, but + # not otherwise (particularly, not when used as streams) + # + # We also allow operators following an opening parenthesis, since + # those tend to be macros that deal with operators. + match = Search(r'(operator|[^\s(<])(?:L|UL|ULL|l|ul|ull)?<<([^\s,=<])', line) + if (match and not (match.group(1).isdigit() and match.group(2).isdigit()) and + not (match.group(1) == 'operator' and match.group(2) == ';')): + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around <<') + + # We allow no-spaces around >> for almost anything. This is because + # C++11 allows ">>" to close nested templates, which accounts for + # most cases when ">>" is not followed by a space. + # + # We still warn on ">>" followed by alpha character, because that is + # likely due to ">>" being used for right shifts, e.g.: + # value >> alpha + # + # When ">>" is used to close templates, the alphanumeric letter that + # follows would be part of an identifier, and there should still be + # a space separating the template type and the identifier. + # type> alpha + match = Search(r'>>[a-zA-Z_]', line) + if match: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around >>') + + # There shouldn't be space around unary operators + match = Search(r'(!\s|~\s|[\s]--[\s;]|[\s]\+\+[\s;])', line) + if match: + error(filename, linenum, 'whitespace/operators', 4, + 'Extra space for operator %s' % match.group(1)) + + +def CheckParenthesisSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing around parentheses. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # No spaces after an if, while, switch, or for + match = Search(r' (if\(|for\(|while\(|switch\()', line) + if match: + error(filename, linenum, 'whitespace/parens', 5, + 'Missing space before ( in %s' % match.group(1)) + + # For if/for/while/switch, the left and right parens should be + # consistent about how many spaces are inside the parens, and + # there should either be zero or one spaces inside the parens. + # We don't want: "if ( foo)" or "if ( foo )". + # Exception: "for ( ; foo; bar)" and "for (foo; bar; )" are allowed. + match = Search(r'\b(if|for|while|switch)\s*' + r'\(([ ]*)(.).*[^ ]+([ ]*)\)\s*{\s*$', + line) + if match: + if len(match.group(2)) != len(match.group(4)): + if not (match.group(3) == ';' and + len(match.group(2)) == 1 + len(match.group(4)) or + not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)): + error(filename, linenum, 'whitespace/parens', 5, + 'Mismatching spaces inside () in %s' % match.group(1)) + if len(match.group(2)) not in [0, 1]: + error(filename, linenum, 'whitespace/parens', 5, + 'Should have zero or one spaces inside ( and ) in %s' % + match.group(1)) + + +def CheckCommaSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing near commas and semicolons. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + raw = clean_lines.lines_without_raw_strings + line = clean_lines.elided[linenum] + + # You should always have a space after a comma (either as fn arg or operator) + # + # This does not apply when the non-space character following the + # comma is another comma, since the only time when that happens is + # for empty macro arguments. + # + # We run this check in two passes: first pass on elided lines to + # verify that lines contain missing whitespaces, second pass on raw + # lines to confirm that those missing whitespaces are not due to + # elided comments. + if (Search(r',[^,\s]', ReplaceAll(r'\boperator\s*,\s*\(', 'F(', line)) and + Search(r',[^,\s]', raw[linenum])): + error(filename, linenum, 'whitespace/comma', 3, + 'Missing space after ,') + + # You should always have a space after a semicolon + # except for few corner cases + # TODO(unknown): clarify if 'if (1) { return 1;}' is requires one more + # space after ; + if Search(r';[^\s};\\)/]', line): + error(filename, linenum, 'whitespace/semicolon', 3, + 'Missing space after ;') + + +def CheckBracesSpacing(filename, clean_lines, linenum, error): + """Checks for horizontal spacing near commas. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Except after an opening paren, or after another opening brace (in case of + # an initializer list, for instance), you should have spaces before your + # braces. And since you should never have braces at the beginning of a line, + # this is an easy test. + match = Match(r'^(.*[^ ({>]){', line) + if match: + # Try a bit harder to check for brace initialization. This + # happens in one of the following forms: + # Constructor() : initializer_list_{} { ... } + # Constructor{}.MemberFunction() + # Type variable{}; + # FunctionCall(type{}, ...); + # LastArgument(..., type{}); + # LOG(INFO) << type{} << " ..."; + # map_of_type[{...}] = ...; + # ternary = expr ? new type{} : nullptr; + # OuterTemplate{}> + # + # We check for the character following the closing brace, and + # silence the warning if it's one of those listed above, i.e. + # "{.;,)<>]:". + # + # To account for nested initializer list, we allow any number of + # closing braces up to "{;,)<". We can't simply silence the + # warning on first sight of closing brace, because that would + # cause false negatives for things that are not initializer lists. + # Silence this: But not this: + # Outer{ if (...) { + # Inner{...} if (...){ // Missing space before { + # }; } + # + # There is a false negative with this approach if people inserted + # spurious semicolons, e.g. "if (cond){};", but we will catch the + # spurious semicolon with a separate check. + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + trailing_text = '' + if endpos > -1: + trailing_text = endline[endpos:] + for offset in xrange(endlinenum + 1, + min(endlinenum + 3, clean_lines.NumLines() - 1)): + trailing_text += clean_lines.elided[offset] + if not Match(r'^[\s}]*[{.;,)<>\]:]', trailing_text): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before {') + + # Make sure '} else {' has spaces. + if Search(r'}else', line): + error(filename, linenum, 'whitespace/braces', 5, + 'Missing space before else') + + # You shouldn't have a space before a semicolon at the end of the line. + # There's a special case for "for" since the style guide allows space before + # the semicolon there. + if Search(r':\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Semicolon defining empty statement. Use {} instead.') + elif Search(r'^\s*;\s*$', line): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Line contains only semicolon. If this should be an empty statement, ' + 'use {} instead.') + elif (Search(r'\s+;\s*$', line) and + not Search(r'\bfor\b', line)): + error(filename, linenum, 'whitespace/semicolon', 5, + 'Extra space before last semicolon. If this should be an empty ' + 'statement, use {} instead.') + + +def IsDecltype(clean_lines, linenum, column): + """Check if the token ending on (linenum, column) is decltype(). + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is decltype() expression, False otherwise. + """ + (text, _, start_col) = ReverseCloseExpression(clean_lines, linenum, column) + if start_col < 0: + return False + if Search(r'\bdecltype\s*$', text[0:start_col]): + return True + return False + + +def IsTemplateParameterList(clean_lines, linenum, column): + """Check if the token ending on (linenum, column) is the end of template<>. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is end of a template parameter list, False otherwise. + """ + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, column) + if (startpos > -1 and + Search(r'\btemplate\s*$', clean_lines.elided[startline][0:startpos])): + return True + return False + + +def IsRValueType(typenames, clean_lines, nesting_state, linenum, column): + """Check if the token ending on (linenum, column) is a type. + + Assumes that text to the right of the column is "&&" or a function + name. + + Args: + typenames: set of type names from template-argument-list. + clean_lines: A CleansedLines instance containing the file. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + linenum: the number of the line to check. + column: end column of the token to check. + Returns: + True if this token is a type, False if we are not sure. + """ + prefix = clean_lines.elided[linenum][0:column] + + # Get one word to the left. If we failed to do so, this is most + # likely not a type, since it's unlikely that the type name and "&&" + # would be split across multiple lines. + match = Match(r'^(.*)(\b\w+|[>*)&])\s*$', prefix) + if not match: + return False + + # Check text following the token. If it's "&&>" or "&&," or "&&...", it's + # most likely a rvalue reference used inside a template. + suffix = clean_lines.elided[linenum][column:] + if Match(r'&&\s*(?:[>,]|\.\.\.)', suffix): + return True + + # Check for known types and end of templates: + # int&& variable + # vector&& variable + # + # Because this function is called recursively, we also need to + # recognize pointer and reference types: + # int* Function() + # int& Function() + if (match.group(2) in typenames or + match.group(2) in ['char', 'char16_t', 'char32_t', 'wchar_t', 'bool', + 'short', 'int', 'long', 'signed', 'unsigned', + 'float', 'double', 'void', 'auto', '>', '*', '&']): + return True + + # If we see a close parenthesis, look for decltype on the other side. + # decltype would unambiguously identify a type, anything else is + # probably a parenthesized expression and not a type. + if match.group(2) == ')': + return IsDecltype( + clean_lines, linenum, len(match.group(1)) + len(match.group(2)) - 1) + + # Check for casts and cv-qualifiers. + # match.group(1) remainder + # -------------- --------- + # const_cast< type&& + # const type&& + # type const&& + if Search(r'\b(?:const_cast\s*<|static_cast\s*<|dynamic_cast\s*<|' + r'reinterpret_cast\s*<|\w+\s)\s*$', + match.group(1)): + return True + + # Look for a preceding symbol that might help differentiate the context. + # These are the cases that would be ambiguous: + # match.group(1) remainder + # -------------- --------- + # Call ( expression && + # Declaration ( type&& + # sizeof ( type&& + # if ( expression && + # while ( expression && + # for ( type&& + # for( ; expression && + # statement ; type&& + # block { type&& + # constructor { expression && + start = linenum + line = match.group(1) + match_symbol = None + while start >= 0: + # We want to skip over identifiers and commas to get to a symbol. + # Commas are skipped so that we can find the opening parenthesis + # for function parameter lists. + match_symbol = Match(r'^(.*)([^\w\s,])[\w\s,]*$', line) + if match_symbol: + break + start -= 1 + line = clean_lines.elided[start] + + if not match_symbol: + # Probably the first statement in the file is an rvalue reference + return True + + if match_symbol.group(2) == '}': + # Found closing brace, probably an indicate of this: + # block{} type&& + return True + + if match_symbol.group(2) == ';': + # Found semicolon, probably one of these: + # for(; expression && + # statement; type&& + + # Look for the previous 'for(' in the previous lines. + before_text = match_symbol.group(1) + for i in xrange(start - 1, max(start - 6, 0), -1): + before_text = clean_lines.elided[i] + before_text + if Search(r'for\s*\([^{};]*$', before_text): + # This is the condition inside a for-loop + return False + + # Did not find a for-init-statement before this semicolon, so this + # is probably a new statement and not a condition. + return True + + if match_symbol.group(2) == '{': + # Found opening brace, probably one of these: + # block{ type&& = ... ; } + # constructor{ expression && expression } + + # Look for a closing brace or a semicolon. If we see a semicolon + # first, this is probably a rvalue reference. + line = clean_lines.elided[start][0:len(match_symbol.group(1)) + 1] + end = start + depth = 1 + while True: + for ch in line: + if ch == ';': + return True + elif ch == '{': + depth += 1 + elif ch == '}': + depth -= 1 + if depth == 0: + return False + end += 1 + if end >= clean_lines.NumLines(): + break + line = clean_lines.elided[end] + # Incomplete program? + return False + + if match_symbol.group(2) == '(': + # Opening parenthesis. Need to check what's to the left of the + # parenthesis. Look back one extra line for additional context. + before_text = match_symbol.group(1) + if linenum > 1: + before_text = clean_lines.elided[linenum - 1] + before_text + before_text = match_symbol.group(1) + + # Patterns that are likely to be types: + # [](type&& + # for (type&& + # sizeof(type&& + # operator=(type&& + # + if Search(r'(?:\]|\bfor|\bsizeof|\boperator\s*\S+\s*)\s*$', before_text): + return True + + # Patterns that are likely to be expressions: + # if (expression && + # while (expression && + # : initializer(expression && + # , initializer(expression && + # ( FunctionCall(expression && + # + FunctionCall(expression && + # + (expression && + # + # The last '+' represents operators such as '+' and '-'. + if Search(r'(?:\bif|\bwhile|[-+=%^(]*>)?\s*$', + match_symbol.group(1)) + if match_func: + # Check for constructors, which don't have return types. + if Search(r'\b(?:explicit|inline)$', match_func.group(1)): + return True + implicit_constructor = Match(r'\s*(\w+)\((?:const\s+)?(\w+)', prefix) + if (implicit_constructor and + implicit_constructor.group(1) == implicit_constructor.group(2)): + return True + return IsRValueType(typenames, clean_lines, nesting_state, linenum, + len(match_func.group(1))) + + # Nothing before the function name. If this is inside a block scope, + # this is probably a function call. + return not (nesting_state.previous_stack_top and + nesting_state.previous_stack_top.IsBlockInfo()) + + if match_symbol.group(2) == '>': + # Possibly a closing bracket, check that what's on the other side + # looks like the start of a template. + return IsTemplateParameterList( + clean_lines, start, len(match_symbol.group(1))) + + # Some other symbol, usually something like "a=b&&c". This is most + # likely not a type. + return False + + +def IsDeletedOrDefault(clean_lines, linenum): + """Check if current constructor or operator is deleted or default. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if this is a deleted or default constructor. + """ + open_paren = clean_lines.elided[linenum].find('(') + if open_paren < 0: + return False + (close_line, _, close_paren) = CloseExpression( + clean_lines, linenum, open_paren) + if close_paren < 0: + return False + return Match(r'\s*=\s*(?:delete|default)\b', close_line[close_paren:]) + + +def IsRValueAllowed(clean_lines, linenum, typenames): + """Check if RValue reference is allowed on a particular line. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + typenames: set of type names from template-argument-list. + Returns: + True if line is within the region where RValue references are allowed. + """ + # Allow region marked by PUSH/POP macros + for i in xrange(linenum, 0, -1): + line = clean_lines.elided[i] + if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line): + if not line.endswith('PUSH'): + return False + for j in xrange(linenum, clean_lines.NumLines(), 1): + line = clean_lines.elided[j] + if Match(r'GOOGLE_ALLOW_RVALUE_REFERENCES_(?:PUSH|POP)', line): + return line.endswith('POP') + + # Allow operator= + line = clean_lines.elided[linenum] + if Search(r'\boperator\s*=\s*\(', line): + return IsDeletedOrDefault(clean_lines, linenum) + + # Allow constructors + match = Match(r'\s*(?:[\w<>]+::)*([\w<>]+)\s*::\s*([\w<>]+)\s*\(', line) + if match and match.group(1) == match.group(2): + return IsDeletedOrDefault(clean_lines, linenum) + if Search(r'\b(?:explicit|inline)\s+[\w<>]+\s*\(', line): + return IsDeletedOrDefault(clean_lines, linenum) + + if Match(r'\s*[\w<>]+\s*\(', line): + previous_line = 'ReturnType' + if linenum > 0: + previous_line = clean_lines.elided[linenum - 1] + if Match(r'^\s*$', previous_line) or Search(r'[{}:;]\s*$', previous_line): + return IsDeletedOrDefault(clean_lines, linenum) + + # Reject types not mentioned in template-argument-list + while line: + match = Match(r'^.*?(\w+)\s*&&(.*)$', line) + if not match: + break + if match.group(1) not in typenames: + return False + line = match.group(2) + + # All RValue types that were in template-argument-list should have + # been removed by now. Those were allowed, assuming that they will + # be forwarded. + # + # If there are no remaining RValue types left (i.e. types that were + # not found in template-argument-list), flag those as not allowed. + return line.find('&&') < 0 + + +def GetTemplateArgs(clean_lines, linenum): + """Find list of template arguments associated with this function declaration. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: Line number containing the start of the function declaration, + usually one line after the end of the template-argument-list. + Returns: + Set of type names, or empty set if this does not appear to have + any template parameters. + """ + # Find start of function + func_line = linenum + while func_line > 0: + line = clean_lines.elided[func_line] + if Match(r'^\s*$', line): + return set() + if line.find('(') >= 0: + break + func_line -= 1 + if func_line == 0: + return set() + + # Collapse template-argument-list into a single string + argument_list = '' + match = Match(r'^(\s*template\s*)<', clean_lines.elided[func_line]) + if match: + # template-argument-list on the same line as function name + start_col = len(match.group(1)) + _, end_line, end_col = CloseExpression(clean_lines, func_line, start_col) + if end_col > -1 and end_line == func_line: + start_col += 1 # Skip the opening bracket + argument_list = clean_lines.elided[func_line][start_col:end_col] + + elif func_line > 1: + # template-argument-list one line before function name + match = Match(r'^(.*)>\s*$', clean_lines.elided[func_line - 1]) + if match: + end_col = len(match.group(1)) + _, start_line, start_col = ReverseCloseExpression( + clean_lines, func_line - 1, end_col) + if start_col > -1: + start_col += 1 # Skip the opening bracket + while start_line < func_line - 1: + argument_list += clean_lines.elided[start_line][start_col:] + start_col = 0 + start_line += 1 + argument_list += clean_lines.elided[func_line - 1][start_col:end_col] + + if not argument_list: + return set() + + # Extract type names + typenames = set() + while True: + match = Match(r'^[,\s]*(?:typename|class)(?:\.\.\.)?\s+(\w+)(.*)$', + argument_list) + if not match: + break + typenames.add(match.group(1)) + argument_list = match.group(2) + return typenames + + +def CheckRValueReference(filename, clean_lines, linenum, nesting_state, error): + """Check for rvalue references. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Find lines missing spaces around &&. + # TODO(unknown): currently we don't check for rvalue references + # with spaces surrounding the && to avoid false positives with + # boolean expressions. + line = clean_lines.elided[linenum] + match = Match(r'^(.*\S)&&', line) + if not match: + match = Match(r'(.*)&&\S', line) + if (not match) or '(&&)' in line or Search(r'\boperator\s*$', match.group(1)): + return + + # Either poorly formed && or an rvalue reference, check the context + # to get a more accurate error message. Mostly we want to determine + # if what's to the left of "&&" is a type or not. + typenames = GetTemplateArgs(clean_lines, linenum) + and_pos = len(match.group(1)) + if IsRValueType(typenames, clean_lines, nesting_state, linenum, and_pos): + if not IsRValueAllowed(clean_lines, linenum, typenames): + error(filename, linenum, 'build/c++11', 3, + 'RValue references are an unapproved C++ feature.') + else: + error(filename, linenum, 'whitespace/operators', 3, + 'Missing spaces around &&') + + +def CheckSectionSpacing(filename, clean_lines, class_info, linenum, error): + """Checks for additional blank line issues related to sections. + + Currently the only thing checked here is blank line before protected/private. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + class_info: A _ClassInfo objects. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Skip checks if the class is small, where small means 25 lines or less. + # 25 lines seems like a good cutoff since that's the usual height of + # terminals, and any class that can't fit in one screen can't really + # be considered "small". + # + # Also skip checks if we are on the first line. This accounts for + # classes that look like + # class Foo { public: ... }; + # + # If we didn't find the end of the class, last_line would be zero, + # and the check will be skipped by the first condition. + if (class_info.last_line - class_info.starting_linenum <= 24 or + linenum <= class_info.starting_linenum): + return + + matched = Match(r'\s*(public|protected|private):', clean_lines.lines[linenum]) + if matched: + # Issue warning if the line before public/protected/private was + # not a blank line, but don't do this if the previous line contains + # "class" or "struct". This can happen two ways: + # - We are at the beginning of the class. + # - We are forward-declaring an inner class that is semantically + # private, but needed to be public for implementation reasons. + # Also ignores cases where the previous line ends with a backslash as can be + # common when defining classes in C macros. + prev_line = clean_lines.lines[linenum - 1] + if (not IsBlankLine(prev_line) and + not Search(r'\b(class|struct)\b', prev_line) and + not Search(r'\\$', prev_line)): + # Try a bit harder to find the beginning of the class. This is to + # account for multi-line base-specifier lists, e.g.: + # class Derived + # : public Base { + end_class_head = class_info.starting_linenum + for i in range(class_info.starting_linenum, linenum): + if Search(r'\{\s*$', clean_lines.lines[i]): + end_class_head = i + break + if end_class_head < linenum - 1: + error(filename, linenum, 'whitespace/blank_line', 3, + '"%s:" should be preceded by a blank line' % matched.group(1)) + + +def GetPreviousNonBlankLine(clean_lines, linenum): + """Return the most recent non-blank line and its line number. + + Args: + clean_lines: A CleansedLines instance containing the file contents. + linenum: The number of the line to check. + + Returns: + A tuple with two elements. The first element is the contents of the last + non-blank line before the current line, or the empty string if this is the + first non-blank line. The second is the line number of that line, or -1 + if this is the first non-blank line. + """ + + prevlinenum = linenum - 1 + while prevlinenum >= 0: + prevline = clean_lines.elided[prevlinenum] + if not IsBlankLine(prevline): # if not a blank line... + return (prevline, prevlinenum) + prevlinenum -= 1 + return ('', -1) + + +def CheckBraces(filename, clean_lines, linenum, error): + """Looks for misplaced braces (e.g. at the end of line). + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] # get rid of comments and strings + + if Match(r'\s*{\s*$', line): + # We allow an open brace to start a line in the case where someone is using + # braces in a block to explicitly create a new scope, which is commonly used + # to control the lifetime of stack-allocated variables. Braces are also + # used for brace initializers inside function calls. We don't detect this + # perfectly: we just don't complain if the last non-whitespace character on + # the previous non-blank line is ',', ';', ':', '(', '{', or '}', or if the + # previous line starts a preprocessor block. + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if (not Search(r'[,;:}{(]\s*$', prevline) and + not Match(r'\s*#', prevline)): + error(filename, linenum, 'whitespace/braces', 4, + '{ should almost always be at the end of the previous line') + + # An else clause should be on the same line as the preceding closing brace. + if Match(r'\s*else\b\s*(?:if\b|\{|$)', line): + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if Match(r'\s*}\s*$', prevline): + error(filename, linenum, 'whitespace/newline', 4, + 'An else should appear on the same line as the preceding }') + + # If braces come on one side of an else, they should be on both. + # However, we have to worry about "else if" that spans multiple lines! + if Search(r'else if\s*\(', line): # could be multi-line if + brace_on_left = bool(Search(r'}\s*else if\s*\(', line)) + # find the ( after the if + pos = line.find('else if') + pos = line.find('(', pos) + if pos > 0: + (endline, _, endpos) = CloseExpression(clean_lines, linenum, pos) + brace_on_right = endline[endpos:].find('{') != -1 + if brace_on_left != brace_on_right: # must be brace after if + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + elif Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): + error(filename, linenum, 'readability/braces', 5, + 'If an else has a brace on one side, it should have it on both') + + # Likewise, an else should never have the else clause on the same line + if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): + error(filename, linenum, 'whitespace/newline', 4, + 'Else clause should never be on same line as else (use 2 lines)') + + # In the same way, a do/while should never be on one line + if Match(r'\s*do [^\s{]', line): + error(filename, linenum, 'whitespace/newline', 4, + 'do/while clauses should not be on a single line') + + # Check single-line if/else bodies. The style guide says 'curly braces are not + # required for single-line statements'. We additionally allow multi-line, + # single statements, but we reject anything with more than one semicolon in + # it. This means that the first semicolon after the if should be at the end of + # its line, and the line after that should have an indent level equal to or + # lower than the if. We also check for ambiguous if/else nesting without + # braces. + if_else_match = Search(r'\b(if\s*\(|else\b)', line) + if if_else_match and not Match(r'\s*#', line): + if_indent = GetIndentLevel(line) + endline, endlinenum, endpos = line, linenum, if_else_match.end() + if_match = Search(r'\bif\s*\(', line) + if if_match: + # This could be a multiline if condition, so find the end first. + pos = if_match.end() - 1 + (endline, endlinenum, endpos) = CloseExpression(clean_lines, linenum, pos) + # Check for an opening brace, either directly after the if or on the next + # line. If found, this isn't a single-statement conditional. + if (not Match(r'\s*{', endline[endpos:]) + and not (Match(r'\s*$', endline[endpos:]) + and endlinenum < (len(clean_lines.elided) - 1) + and Match(r'\s*{', clean_lines.elided[endlinenum + 1]))): + while (endlinenum < len(clean_lines.elided) + and ';' not in clean_lines.elided[endlinenum][endpos:]): + endlinenum += 1 + endpos = 0 + if endlinenum < len(clean_lines.elided): + endline = clean_lines.elided[endlinenum] + # We allow a mix of whitespace and closing braces (e.g. for one-liner + # methods) and a single \ after the semicolon (for macros) + endpos = endline.find(';') + if not Match(r';[\s}]*(\\?)$', endline[endpos:]): + # Semicolon isn't the last character, there's something trailing. + # Output a warning if the semicolon is not contained inside + # a lambda expression. + if not Match(r'^[^{};]*\[[^\[\]]*\][^{}]*\{[^{}]*\}\s*\)*[;,]\s*$', + endline): + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + elif endlinenum < len(clean_lines.elided) - 1: + # Make sure the next line is dedented + next_line = clean_lines.elided[endlinenum + 1] + next_indent = GetIndentLevel(next_line) + # With ambiguous nested if statements, this will error out on the + # if that *doesn't* match the else, regardless of whether it's the + # inner one or outer one. + if (if_match and Match(r'\s*else\b', next_line) + and next_indent != if_indent): + error(filename, linenum, 'readability/braces', 4, + 'Else clause should be indented at the same level as if. ' + 'Ambiguous nested if/else chains require braces.') + elif next_indent > if_indent: + error(filename, linenum, 'readability/braces', 4, + 'If/else bodies with multiple statements require braces') + + +def CheckTrailingSemicolon(filename, clean_lines, linenum, error): + """Looks for redundant trailing semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + line = clean_lines.elided[linenum] + + # Block bodies should not be followed by a semicolon. Due to C++11 + # brace initialization, there are more places where semicolons are + # required than not, so we use a whitelist approach to check these + # rather than a blacklist. These are the places where "};" should + # be replaced by just "}": + # 1. Some flavor of block following closing parenthesis: + # for (;;) {}; + # while (...) {}; + # switch (...) {}; + # Function(...) {}; + # if (...) {}; + # if (...) else if (...) {}; + # + # 2. else block: + # if (...) else {}; + # + # 3. const member function: + # Function(...) const {}; + # + # 4. Block following some statement: + # x = 42; + # {}; + # + # 5. Block at the beginning of a function: + # Function(...) { + # {}; + # } + # + # Note that naively checking for the preceding "{" will also match + # braces inside multi-dimensional arrays, but this is fine since + # that expression will not contain semicolons. + # + # 6. Block following another block: + # while (true) {} + # {}; + # + # 7. End of namespaces: + # namespace {}; + # + # These semicolons seems far more common than other kinds of + # redundant semicolons, possibly due to people converting classes + # to namespaces. For now we do not warn for this case. + # + # Try matching case 1 first. + match = Match(r'^(.*\)\s*)\{', line) + if match: + # Matched closing parenthesis (case 1). Check the token before the + # matching opening parenthesis, and don't warn if it looks like a + # macro. This avoids these false positives: + # - macro that defines a base class + # - multi-line macro that defines a base class + # - macro that defines the whole class-head + # + # But we still issue warnings for macros that we know are safe to + # warn, specifically: + # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P + # - TYPED_TEST + # - INTERFACE_DEF + # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: + # + # We implement a whitelist of safe macros instead of a blacklist of + # unsafe macros, even though the latter appears less frequently in + # google code and would have been easier to implement. This is because + # the downside for getting the whitelist wrong means some extra + # semicolons, while the downside for getting the blacklist wrong + # would result in compile errors. + # + # In addition to macros, we also don't want to warn on + # - Compound literals + # - Lambdas + # - alignas specifier with anonymous structs: + closing_brace_pos = match.group(1).rfind(')') + opening_parenthesis = ReverseCloseExpression( + clean_lines, linenum, closing_brace_pos) + if opening_parenthesis[2] > -1: + line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] + macro = Search(r'\b([A-Z_]+)\s*$', line_prefix) + func = Match(r'^(.*\])\s*$', line_prefix) + if ((macro and + macro.group(1) not in ( + 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', + 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', + 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or + (func and not Search(r'\boperator\s*\[\s*\]', func.group(1))) or + Search(r'\b(?:struct|union)\s+alignas\s*$', line_prefix) or + Search(r'\s+=\s*$', line_prefix)): + match = None + if (match and + opening_parenthesis[1] > 1 and + Search(r'\]\s*$', clean_lines.elided[opening_parenthesis[1] - 1])): + # Multi-line lambda-expression + match = None + + else: + # Try matching cases 2-3. + match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) + if not match: + # Try matching cases 4-6. These are always matched on separate lines. + # + # Note that we can't simply concatenate the previous line to the + # current line and do a single match, otherwise we may output + # duplicate warnings for the blank line case: + # if (cond) { + # // blank line + # } + prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] + if prevline and Search(r'[;{}]\s*$', prevline): + match = Match(r'^(\s*)\{', line) + + # Check matching closing brace + if match: + (endline, endlinenum, endpos) = CloseExpression( + clean_lines, linenum, len(match.group(1))) + if endpos > -1 and Match(r'^\s*;', endline[endpos:]): + # Current {} pair is eligible for semicolon check, and we have found + # the redundant semicolon, output warning here. + # + # Note: because we are scanning forward for opening braces, and + # outputting warnings for the matching closing brace, if there are + # nested blocks with trailing semicolons, we will get the error + # messages in reversed order. + error(filename, endlinenum, 'readability/braces', 4, + "You don't need a ; after a }") + + +def CheckEmptyBlockBody(filename, clean_lines, linenum, error): + """Look for empty loop/conditional body with only a single semicolon. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Search for loop keywords at the beginning of the line. Because only + # whitespaces are allowed before the keywords, this will also ignore most + # do-while-loops, since those lines should start with closing brace. + # + # We also check "if" blocks here, since an empty conditional block + # is likely an error. + line = clean_lines.elided[linenum] + matched = Match(r'\s*(for|while|if)\s*\(', line) + if matched: + # Find the end of the conditional expression + (end_line, end_linenum, end_pos) = CloseExpression( + clean_lines, linenum, line.find('(')) + + # Output warning if what follows the condition expression is a semicolon. + # No warning for all other cases, including whitespace or newline, since we + # have a separate check for semicolons preceded by whitespace. + if end_pos >= 0 and Match(r';', end_line[end_pos:]): + if matched.group(1) == 'if': + error(filename, end_linenum, 'whitespace/empty_conditional_body', 5, + 'Empty conditional bodies should use {}') + else: + error(filename, end_linenum, 'whitespace/empty_loop_body', 5, + 'Empty loop bodies should use {} or continue') + + +def FindCheckMacro(line): + """Find a replaceable CHECK-like macro. + + Args: + line: line to search on. + Returns: + (macro name, start position), or (None, -1) if no replaceable + macro is found. + """ + for macro in _CHECK_MACROS: + i = line.find(macro) + if i >= 0: + # Find opening parenthesis. Do a regular expression match here + # to make sure that we are matching the expected CHECK macro, as + # opposed to some other macro that happens to contain the CHECK + # substring. + matched = Match(r'^(.*\b' + macro + r'\s*)\(', line) + if not matched: + continue + return (macro, len(matched.group(1))) + return (None, -1) + + +def CheckCheck(filename, clean_lines, linenum, error): + """Checks the use of CHECK and EXPECT macros. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + + # Decide the set of replacement macros that should be suggested + lines = clean_lines.elided + (check_macro, start_pos) = FindCheckMacro(lines[linenum]) + if not check_macro: + return + + # Find end of the boolean expression by matching parentheses + (last_line, end_line, end_pos) = CloseExpression( + clean_lines, linenum, start_pos) + if end_pos < 0: + return + + # If the check macro is followed by something other than a + # semicolon, assume users will log their own custom error messages + # and don't suggest any replacements. + if not Match(r'\s*;', last_line[end_pos:]): + return + + if linenum == end_line: + expression = lines[linenum][start_pos + 1:end_pos - 1] + else: + expression = lines[linenum][start_pos + 1:] + for i in xrange(linenum + 1, end_line): + expression += lines[i] + expression += last_line[0:end_pos - 1] + + # Parse expression so that we can take parentheses into account. + # This avoids false positives for inputs like "CHECK((a < 4) == b)", + # which is not replaceable by CHECK_LE. + lhs = '' + rhs = '' + operator = None + while expression: + matched = Match(r'^\s*(<<|<<=|>>|>>=|->\*|->|&&|\|\||' + r'==|!=|>=|>|<=|<|\()(.*)$', expression) + if matched: + token = matched.group(1) + if token == '(': + # Parenthesized operand + expression = matched.group(2) + (end, _) = FindEndOfExpressionInLine(expression, 0, ['(']) + if end < 0: + return # Unmatched parenthesis + lhs += '(' + expression[0:end] + expression = expression[end:] + elif token in ('&&', '||'): + # Logical and/or operators. This means the expression + # contains more than one term, for example: + # CHECK(42 < a && a < b); + # + # These are not replaceable with CHECK_LE, so bail out early. + return + elif token in ('<<', '<<=', '>>', '>>=', '->*', '->'): + # Non-relational operator + lhs += token + expression = matched.group(2) + else: + # Relational operator + operator = token + rhs = matched.group(2) + break + else: + # Unparenthesized operand. Instead of appending to lhs one character + # at a time, we do another regular expression match to consume several + # characters at once if possible. Trivial benchmark shows that this + # is more efficient when the operands are longer than a single + # character, which is generally the case. + matched = Match(r'^([^-=!<>()&|]+)(.*)$', expression) + if not matched: + matched = Match(r'^(\s*\S)(.*)$', expression) + if not matched: + break + lhs += matched.group(1) + expression = matched.group(2) + + # Only apply checks if we got all parts of the boolean expression + if not (lhs and operator and rhs): + return + + # Check that rhs do not contain logical operators. We already know + # that lhs is fine since the loop above parses out && and ||. + if rhs.find('&&') > -1 or rhs.find('||') > -1: + return + + # At least one of the operands must be a constant literal. This is + # to avoid suggesting replacements for unprintable things like + # CHECK(variable != iterator) + # + # The following pattern matches decimal, hex integers, strings, and + # characters (in that order). + lhs = lhs.strip() + rhs = rhs.strip() + match_constant = r'^([-+]?(\d+|0[xX][0-9a-fA-F]+)[lLuU]{0,3}|".*"|\'.*\')$' + if Match(match_constant, lhs) or Match(match_constant, rhs): + # Note: since we know both lhs and rhs, we can provide a more + # descriptive error message like: + # Consider using CHECK_EQ(x, 42) instead of CHECK(x == 42) + # Instead of: + # Consider using CHECK_EQ instead of CHECK(a == b) + # + # We are still keeping the less descriptive message because if lhs + # or rhs gets long, the error message might become unreadable. + error(filename, linenum, 'readability/check', 2, + 'Consider using %s instead of %s(a %s b)' % ( + _CHECK_REPLACEMENT[check_macro][operator], + check_macro, operator)) + + +def CheckAltTokens(filename, clean_lines, linenum, error): + """Check alternative keywords being used in boolean expressions. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Avoid preprocessor lines + if Match(r'^\s*#', line): + return + + # Last ditch effort to avoid multi-line comments. This will not help + # if the comment started before the current line or ended after the + # current line, but it catches most of the false positives. At least, + # it provides a way to workaround this warning for people who use + # multi-line comments in preprocessor macros. + # + # TODO(unknown): remove this once cpplint has better support for + # multi-line comments. + if line.find('/*') >= 0 or line.find('*/') >= 0: + return + + for match in _ALT_TOKEN_REPLACEMENT_PATTERN.finditer(line): + error(filename, linenum, 'readability/alt_tokens', 2, + 'Use operator %s instead of %s' % ( + _ALT_TOKEN_REPLACEMENT[match.group(1)], match.group(1))) + + +def GetLineWidth(line): + """Determines the width of the line in column positions. + + Args: + line: A string, which may be a Unicode string. + + Returns: + The width of the line in column positions, accounting for Unicode + combining characters and wide characters. + """ + if isinstance(line, unicode): + width = 0 + for uc in unicodedata.normalize('NFC', line): + if unicodedata.east_asian_width(uc) in ('W', 'F'): + width += 2 + elif not unicodedata.combining(uc): + width += 1 + return width + else: + return len(line) + + +def CheckStyle(filename, clean_lines, linenum, file_extension, nesting_state, + error): + """Checks rules from the 'C++ style rules' section of cppguide.html. + + Most of these rules are hard to test (naming, comment style), but we + do what we can. In particular we check for 2-space indents, line lengths, + tab usage, spaces inside code, etc. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + + # Don't use "elided" lines here, otherwise we can't check commented lines. + # Don't want to use "raw" either, because we don't want to check inside C++11 + # raw strings, + raw_lines = clean_lines.lines_without_raw_strings + line = raw_lines[linenum] + + if line.find('\t') != -1: + error(filename, linenum, 'whitespace/tab', 1, + 'Tab found; better to use spaces') + + # One or three blank spaces at the beginning of the line is weird; it's + # hard to reconcile that with 2-space indents. + # NOTE: here are the conditions rob pike used for his tests. Mine aren't + # as sophisticated, but it may be worth becoming so: RLENGTH==initial_spaces + # if(RLENGTH > 20) complain = 0; + # if(match($0, " +(error|private|public|protected):")) complain = 0; + # if(match(prev, "&& *$")) complain = 0; + # if(match(prev, "\\|\\| *$")) complain = 0; + # if(match(prev, "[\",=><] *$")) complain = 0; + # if(match($0, " <<")) complain = 0; + # if(match(prev, " +for \\(")) complain = 0; + # if(prevodd && match(prevprev, " +for \\(")) complain = 0; + scope_or_label_pattern = r'\s*\w+\s*:\s*\\?$' + classinfo = nesting_state.InnermostClass() + initial_spaces = 0 + cleansed_line = clean_lines.elided[linenum] + while initial_spaces < len(line) and line[initial_spaces] == ' ': + initial_spaces += 1 + if line and line[-1].isspace(): + error(filename, linenum, 'whitespace/end_of_line', 4, + 'Line ends in whitespace. Consider deleting these extra spaces.') + # There are certain situations we allow one space, notably for + # section labels, and also lines containing multi-line raw strings. + elif ((initial_spaces == 1 or initial_spaces == 3) and + not Match(scope_or_label_pattern, cleansed_line) and + not (clean_lines.raw_lines[linenum] != line and + Match(r'^\s*""', line))): + error(filename, linenum, 'whitespace/indent', 3, + 'Weird number of spaces at line-start. ' + 'Are you using a 2-space indent?') + + # Check if the line is a header guard. + is_header_guard = False + if file_extension == 'h': + cppvar = GetHeaderGuardCPPVariable(filename) + if (line.startswith('#ifndef %s' % cppvar) or + line.startswith('#define %s' % cppvar) or + line.startswith('#endif // %s' % cppvar)): + is_header_guard = True + # #include lines and header guards can be long, since there's no clean way to + # split them. + # + # URLs can be long too. It's possible to split these, but it makes them + # harder to cut&paste. + # + # The "$Id:...$" comment may also get very long without it being the + # developers fault. + if (not line.startswith('#include') and not is_header_guard and + not Match(r'^\s*//.*http(s?)://\S*$', line) and + not Match(r'^// \$Id:.*#[0-9]+ \$$', line)): + line_width = GetLineWidth(line) + extended_length = int((_line_length * 1.25)) + if line_width > extended_length: + error(filename, linenum, 'whitespace/line_length', 4, + 'Lines should very rarely be longer than %i characters' % + extended_length) + elif line_width > _line_length: + error(filename, linenum, 'whitespace/line_length', 2, + 'Lines should be <= %i characters long' % _line_length) + + if (cleansed_line.count(';') > 1 and + # for loops are allowed two ;'s (and may run over two lines). + cleansed_line.find('for') == -1 and + (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or + GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and + # It's ok to have many commands in a switch case that fits in 1 line + not ((cleansed_line.find('case ') != -1 or + cleansed_line.find('default:') != -1) and + cleansed_line.find('break;') != -1)): + error(filename, linenum, 'whitespace/newline', 0, + 'More than one command on the same line') + + # Some more style checks + CheckBraces(filename, clean_lines, linenum, error) + CheckTrailingSemicolon(filename, clean_lines, linenum, error) + CheckEmptyBlockBody(filename, clean_lines, linenum, error) + CheckAccess(filename, clean_lines, linenum, nesting_state, error) + CheckSpacing(filename, clean_lines, linenum, nesting_state, error) + CheckOperatorSpacing(filename, clean_lines, linenum, error) + CheckParenthesisSpacing(filename, clean_lines, linenum, error) + CheckCommaSpacing(filename, clean_lines, linenum, error) + CheckBracesSpacing(filename, clean_lines, linenum, error) + CheckSpacingForFunctionCall(filename, clean_lines, linenum, error) + CheckRValueReference(filename, clean_lines, linenum, nesting_state, error) + CheckCheck(filename, clean_lines, linenum, error) + CheckAltTokens(filename, clean_lines, linenum, error) + classinfo = nesting_state.InnermostClass() + if classinfo: + CheckSectionSpacing(filename, clean_lines, classinfo, linenum, error) + + +_RE_PATTERN_INCLUDE = re.compile(r'^\s*#\s*include\s*([<"])([^>"]*)[>"].*$') +# Matches the first component of a filename delimited by -s and _s. That is: +# _RE_FIRST_COMPONENT.match('foo').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo-bar_baz.cc').group(0) == 'foo' +# _RE_FIRST_COMPONENT.match('foo_bar-baz.cc').group(0) == 'foo' +_RE_FIRST_COMPONENT = re.compile(r'^[^-_.]+') + + +def _DropCommonSuffixes(filename): + """Drops common suffixes like _test.cc or -inl.h from filename. + + For example: + >>> _DropCommonSuffixes('foo/foo-inl.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/bar/foo.cc') + 'foo/bar/foo' + >>> _DropCommonSuffixes('foo/foo_internal.h') + 'foo/foo' + >>> _DropCommonSuffixes('foo/foo_unusualinternal.h') + 'foo/foo_unusualinternal' + + Args: + filename: The input filename. + + Returns: + The filename with the common suffix removed. + """ + for suffix in ('test.cc', 'regtest.cc', 'unittest.cc', + 'inl.h', 'impl.h', 'internal.h'): + if (filename.endswith(suffix) and len(filename) > len(suffix) and + filename[-len(suffix) - 1] in ('-', '_')): + return filename[:-len(suffix) - 1] + return os.path.splitext(filename)[0] + + +def _IsTestFilename(filename): + """Determines if the given filename has a suffix that identifies it as a test. + + Args: + filename: The input filename. + + Returns: + True if 'filename' looks like a test, False otherwise. + """ + if (filename.endswith('_test.cc') or + filename.endswith('_unittest.cc') or + filename.endswith('_regtest.cc')): + return True + else: + return False + + +def _ClassifyInclude(fileinfo, include, is_system): + """Figures out what kind of header 'include' is. + + Args: + fileinfo: The current file cpplint is running over. A FileInfo instance. + include: The path to a #included file. + is_system: True if the #include used <> rather than "". + + Returns: + One of the _XXX_HEADER constants. + + For example: + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'stdio.h', True) + _C_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'string', True) + _CPP_SYS_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/foo.h', False) + _LIKELY_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo_unknown_extension.cc'), + ... 'bar/foo_other_ext.h', False) + _POSSIBLE_MY_HEADER + >>> _ClassifyInclude(FileInfo('foo/foo.cc'), 'foo/bar.h', False) + _OTHER_HEADER + """ + # This is a list of all standard c++ header files, except + # those already checked for above. + is_cpp_h = include in _CPP_HEADERS + + if is_system: + if is_cpp_h: + return _CPP_SYS_HEADER + else: + return _C_SYS_HEADER + + # If the target file and the include we're checking share a + # basename when we drop common extensions, and the include + # lives in . , then it's likely to be owned by the target file. + target_dir, target_base = ( + os.path.split(_DropCommonSuffixes(fileinfo.RepositoryName()))) + include_dir, include_base = os.path.split(_DropCommonSuffixes(include)) + if target_base == include_base and ( + include_dir == target_dir or + include_dir == os.path.normpath(target_dir + '/../public')): + return _LIKELY_MY_HEADER + + # If the target and include share some initial basename + # component, it's possible the target is implementing the + # include, so it's allowed to be first, but we'll never + # complain if it's not there. + target_first_component = _RE_FIRST_COMPONENT.match(target_base) + include_first_component = _RE_FIRST_COMPONENT.match(include_base) + if (target_first_component and include_first_component and + target_first_component.group(0) == + include_first_component.group(0)): + return _POSSIBLE_MY_HEADER + + return _OTHER_HEADER + + + +def CheckIncludeLine(filename, clean_lines, linenum, include_state, error): + """Check rules that are applicable to #include lines. + + Strings on #include lines are NOT removed from elided line, to make + certain tasks easier. However, to prevent false positives, checks + applicable to #include lines in CheckLanguage must be put here. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + include_state: An _IncludeState instance in which the headers are inserted. + error: The function to call with any errors found. + """ + fileinfo = FileInfo(filename) + line = clean_lines.lines[linenum] + + # "include" should use the new style "foo/bar.h" instead of just "bar.h" + # Only do this check if the included header follows google naming + # conventions. If not, assume that it's a 3rd party API that + # requires special include conventions. + # + # We also make an exception for Lua headers, which follow google + # naming convention but not the include convention. + match = Match(r'#include\s*"([^/]+\.h)"', line) + if match and not _THIRD_PARTY_HEADERS_PATTERN.match(match.group(1)): + error(filename, linenum, 'build/include', 4, + 'Include the directory when naming .h files') + + # we shouldn't include a file more than once. actually, there are a + # handful of instances where doing so is okay, but in general it's + # not. + match = _RE_PATTERN_INCLUDE.search(line) + if match: + include = match.group(2) + is_system = (match.group(1) == '<') + duplicate_line = include_state.FindHeader(include) + if duplicate_line >= 0: + error(filename, linenum, 'build/include', 4, + '"%s" already included at %s:%s' % + (include, filename, duplicate_line)) + elif (include.endswith('.cc') and + os.path.dirname(fileinfo.RepositoryName()) != os.path.dirname(include)): + error(filename, linenum, 'build/include', 4, + 'Do not include .cc files from other packages') + elif not _THIRD_PARTY_HEADERS_PATTERN.match(include): + include_state.include_list[-1].append((include, linenum)) + + # We want to ensure that headers appear in the right order: + # 1) for foo.cc, foo.h (preferred location) + # 2) c system files + # 3) cpp system files + # 4) for foo.cc, foo.h (deprecated location) + # 5) other google headers + # + # We classify each include statement as one of those 5 types + # using a number of techniques. The include_state object keeps + # track of the highest type seen, and complains if we see a + # lower type after that. + error_message = include_state.CheckNextIncludeOrder( + _ClassifyInclude(fileinfo, include, is_system)) + if error_message: + error(filename, linenum, 'build/include_order', 4, + '%s. Should be: %s.h, c system, c++ system, other.' % + (error_message, fileinfo.BaseName())) + canonical_include = include_state.CanonicalizeAlphabeticalOrder(include) + if not include_state.IsInAlphabeticalOrder( + clean_lines, linenum, canonical_include): + error(filename, linenum, 'build/include_alpha', 4, + 'Include "%s" not in alphabetical order' % include) + include_state.SetLastHeader(canonical_include) + + + +def _GetTextInside(text, start_pattern): + r"""Retrieves all the text between matching open and close parentheses. + + Given a string of lines and a regular expression string, retrieve all the text + following the expression and between opening punctuation symbols like + (, [, or {, and the matching close-punctuation symbol. This properly nested + occurrences of the punctuations, so for the text like + printf(a(), b(c())); + a call to _GetTextInside(text, r'printf\(') will return 'a(), b(c())'. + start_pattern must match string having an open punctuation symbol at the end. + + Args: + text: The lines to extract text. Its comments and strings must be elided. + It can be single line and can span multiple lines. + start_pattern: The regexp string indicating where to start extracting + the text. + Returns: + The extracted text. + None if either the opening string or ending punctuation could not be found. + """ + # TODO(unknown): Audit cpplint.py to see what places could be profitably + # rewritten to use _GetTextInside (and use inferior regexp matching today). + + # Give opening punctuations to get the matching close-punctuations. + matching_punctuation = {'(': ')', '{': '}', '[': ']'} + closing_punctuation = set(matching_punctuation.itervalues()) + + # Find the position to start extracting text. + match = re.search(start_pattern, text, re.M) + if not match: # start_pattern not found in text. + return None + start_position = match.end(0) + + assert start_position > 0, ( + 'start_pattern must ends with an opening punctuation.') + assert text[start_position - 1] in matching_punctuation, ( + 'start_pattern must ends with an opening punctuation.') + # Stack of closing punctuations we expect to have in text after position. + punctuation_stack = [matching_punctuation[text[start_position - 1]]] + position = start_position + while punctuation_stack and position < len(text): + if text[position] == punctuation_stack[-1]: + punctuation_stack.pop() + elif text[position] in closing_punctuation: + # A closing punctuation without matching opening punctuations. + return None + elif text[position] in matching_punctuation: + punctuation_stack.append(matching_punctuation[text[position]]) + position += 1 + if punctuation_stack: + # Opening punctuations left without matching close-punctuations. + return None + # punctuations match. + return text[start_position:position - 1] + + +# Patterns for matching call-by-reference parameters. +# +# Supports nested templates up to 2 levels deep using this messy pattern: +# < (?: < (?: < [^<>]* +# > +# | [^<>] )* +# > +# | [^<>] )* +# > +_RE_PATTERN_IDENT = r'[_a-zA-Z]\w*' # =~ [[:alpha:]][[:alnum:]]* +_RE_PATTERN_TYPE = ( + r'(?:const\s+)?(?:typename\s+|class\s+|struct\s+|union\s+|enum\s+)?' + r'(?:\w|' + r'\s*<(?:<(?:<[^<>]*>|[^<>])*>|[^<>])*>|' + r'::)+') +# A call-by-reference parameter ends with '& identifier'. +_RE_PATTERN_REF_PARAM = re.compile( + r'(' + _RE_PATTERN_TYPE + r'(?:\s*(?:\bconst\b|[*]))*\s*' + r'&\s*' + _RE_PATTERN_IDENT + r')\s*(?:=[^,()]+)?[,)]') +# A call-by-const-reference parameter either ends with 'const& identifier' +# or looks like 'const type& identifier' when 'type' is atomic. +_RE_PATTERN_CONST_REF_PARAM = ( + r'(?:.*\s*\bconst\s*&\s*' + _RE_PATTERN_IDENT + + r'|const\s+' + _RE_PATTERN_TYPE + r'\s*&\s*' + _RE_PATTERN_IDENT + r')') + + +def CheckLanguage(filename, clean_lines, linenum, file_extension, + include_state, nesting_state, error): + """Checks rules from the 'C++ language rules' section of cppguide.html. + + Some of these rules are hard to test (function overloading, using + uint32 inappropriately), but we do the best we can. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + file_extension: The extension (without the dot) of the filename. + include_state: An _IncludeState instance in which the headers are inserted. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # If the line is empty or consists of entirely a comment, no need to + # check it. + line = clean_lines.elided[linenum] + if not line: + return + + match = _RE_PATTERN_INCLUDE.search(line) + if match: + CheckIncludeLine(filename, clean_lines, linenum, include_state, error) + return + + # Reset include state across preprocessor directives. This is meant + # to silence warnings for conditional includes. + match = Match(r'^\s*#\s*(if|ifdef|ifndef|elif|else|endif)\b', line) + if match: + include_state.ResetSection(match.group(1)) + + # Make Windows paths like Unix. + fullname = os.path.abspath(filename).replace('\\', '/') + + # Perform other checks now that we are sure that this is not an include line + CheckCasts(filename, clean_lines, linenum, error) + CheckGlobalStatic(filename, clean_lines, linenum, error) + CheckPrintf(filename, clean_lines, linenum, error) + + if file_extension == 'h': + # TODO(unknown): check that 1-arg constructors are explicit. + # How to tell it's a constructor? + # (handled in CheckForNonStandardConstructs for now) + # TODO(unknown): check that classes declare or disable copy/assign + # (level 1 error) + pass + + # Check if people are using the verboten C basic types. The only exception + # we regularly allow is "unsigned short port" for port. + if Search(r'\bshort port\b', line): + if not Search(r'\bunsigned short port\b', line): + error(filename, linenum, 'runtime/int', 4, + 'Use "unsigned short" for ports, not "short"') + else: + match = Search(r'\b(short|long(?! +double)|long long)\b', line) + if match: + error(filename, linenum, 'runtime/int', 4, + 'Use int16/int64/etc, rather than the C type %s' % match.group(1)) + + # Check if some verboten operator overloading is going on + # TODO(unknown): catch out-of-line unary operator&: + # class X {}; + # int operator&(const X& x) { return 42; } // unary operator& + # The trick is it's hard to tell apart from binary operator&: + # class Y { int operator&(const Y& x) { return 23; } }; // binary operator& + if Search(r'\boperator\s*&\s*\(\s*\)', line): + error(filename, linenum, 'runtime/operator', 4, + 'Unary operator& is dangerous. Do not use it.') + + # Check for suspicious usage of "if" like + # } if (a == b) { + if Search(r'\}\s*if\s*\(', line): + error(filename, linenum, 'readability/braces', 4, + 'Did you mean "else if"? If not, start a new line for "if".') + + # Check for potential format string bugs like printf(foo). + # We constrain the pattern not to pick things like DocidForPrintf(foo). + # Not perfect but it can catch printf(foo.c_str()) and printf(foo->c_str()) + # TODO(unknown): Catch the following case. Need to change the calling + # convention of the whole function to process multiple line to handle it. + # printf( + # boy_this_is_a_really_long_variable_that_cannot_fit_on_the_prev_line); + printf_args = _GetTextInside(line, r'(?i)\b(string)?printf\s*\(') + if printf_args: + match = Match(r'([\w.\->()]+)$', printf_args) + if match and match.group(1) != '__VA_ARGS__': + function_name = re.search(r'\b((?:string)?printf)\s*\(', + line, re.I).group(1) + error(filename, linenum, 'runtime/printf', 4, + 'Potential format string bug. Do %s("%%s", %s) instead.' + % (function_name, match.group(1))) + + # Check for potential memset bugs like memset(buf, sizeof(buf), 0). + match = Search(r'memset\s*\(([^,]*),\s*([^,]*),\s*0\s*\)', line) + if match and not Match(r"^''|-?[0-9]+|0x[0-9A-Fa-f]$", match.group(2)): + error(filename, linenum, 'runtime/memset', 4, + 'Did you mean "memset(%s, 0, %s)"?' + % (match.group(1), match.group(2))) + + if Search(r'\busing namespace\b', line): + error(filename, linenum, 'build/namespaces', 5, + 'Do not use namespace using-directives. ' + 'Use using-declarations instead.') + + # Detect variable-length arrays. + match = Match(r'\s*(.+::)?(\w+) [a-z]\w*\[(.+)];', line) + if (match and match.group(2) != 'return' and match.group(2) != 'delete' and + match.group(3).find(']') == -1): + # Split the size using space and arithmetic operators as delimiters. + # If any of the resulting tokens are not compile time constants then + # report the error. + tokens = re.split(r'\s|\+|\-|\*|\/|<<|>>]', match.group(3)) + is_const = True + skip_next = False + for tok in tokens: + if skip_next: + skip_next = False + continue + + if Search(r'sizeof\(.+\)', tok): continue + if Search(r'arraysize\(\w+\)', tok): continue + + tok = tok.lstrip('(') + tok = tok.rstrip(')') + if not tok: continue + if Match(r'\d+', tok): continue + if Match(r'0[xX][0-9a-fA-F]+', tok): continue + if Match(r'k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?k[A-Z0-9]\w*', tok): continue + if Match(r'(.+::)?[A-Z][A-Z0-9_]*', tok): continue + # A catch all for tricky sizeof cases, including 'sizeof expression', + # 'sizeof(*type)', 'sizeof(const type)', 'sizeof(struct StructName)' + # requires skipping the next token because we split on ' ' and '*'. + if tok.startswith('sizeof'): + skip_next = True + continue + is_const = False + break + if not is_const: + error(filename, linenum, 'runtime/arrays', 1, + 'Do not use variable-length arrays. Use an appropriately named ' + "('k' followed by CamelCase) compile-time constant for the size.") + + # Check for use of unnamed namespaces in header files. Registration + # macros are typically OK, so we allow use of "namespace {" on lines + # that end with backslashes. + if (file_extension == 'h' + and Search(r'\bnamespace\s*{', line) + and line[-1] != '\\'): + error(filename, linenum, 'build/namespaces', 4, + 'Do not use unnamed namespaces in header files. See ' + 'http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Namespaces' + ' for more information.') + + +def CheckGlobalStatic(filename, clean_lines, linenum, error): + """Check for unsafe global or static objects. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Match two lines at a time to support multiline declarations + if linenum + 1 < clean_lines.NumLines() and not Search(r'[;({]', line): + line += clean_lines.elided[linenum + 1].strip() + + # Check for people declaring static/global STL strings at the top level. + # This is dangerous because the C++ language does not guarantee that + # globals with constructors are initialized before the first access. + match = Match( + r'((?:|static +)(?:|const +))string +([a-zA-Z0-9_:]+)\b(.*)', + line) + + # Remove false positives: + # - String pointers (as opposed to values). + # string *pointer + # const string *pointer + # string const *pointer + # string *const pointer + # + # - Functions and template specializations. + # string Function(... + # string Class::Method(... + # + # - Operators. These are matched separately because operator names + # cross non-word boundaries, and trying to match both operators + # and functions at the same time would decrease accuracy of + # matching identifiers. + # string Class::operator*() + if (match and + not Search(r'\bstring\b(\s+const)?\s*\*\s*(const\s+)?\w', line) and + not Search(r'\boperator\W', line) and + not Match(r'\s*(<.*>)?(::[a-zA-Z0-9_]+)*\s*\(([^"]|$)', match.group(3))): + error(filename, linenum, 'runtime/string', 4, + 'For a static/global string constant, use a C style string instead: ' + '"%schar %s[]".' % + (match.group(1), match.group(2))) + + if Search(r'\b([A-Za-z0-9_]*_)\(\1\)', line): + error(filename, linenum, 'runtime/init', 4, + 'You seem to be initializing a member variable with itself.') + + +def CheckPrintf(filename, clean_lines, linenum, error): + """Check for printf related issues. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # When snprintf is used, the second argument shouldn't be a literal. + match = Search(r'snprintf\s*\(([^,]*),\s*([0-9]*)\s*,', line) + if match and match.group(2) != '0': + # If 2nd arg is zero, snprintf is used to calculate size. + error(filename, linenum, 'runtime/printf', 3, + 'If you can, use sizeof(%s) instead of %s as the 2nd arg ' + 'to snprintf.' % (match.group(1), match.group(2))) + + # Check if some verboten C functions are being used. + if Search(r'\bsprintf\s*\(', line): + error(filename, linenum, 'runtime/printf', 5, + 'Never use sprintf. Use snprintf instead.') + match = Search(r'\b(strcpy|strcat)\s*\(', line) + if match: + error(filename, linenum, 'runtime/printf', 4, + 'Almost always, snprintf is better than %s' % match.group(1)) + + +def IsDerivedFunction(clean_lines, linenum): + """Check if current line contains an inherited function. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains a function with "override" + virt-specifier. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + match = Match(r'^([^()]*\w+)\(', clean_lines.elided[i]) + if match: + # Look for "override" after the matching closing parenthesis + line, _, closing_paren = CloseExpression( + clean_lines, i, len(match.group(1))) + return (closing_paren >= 0 and + Search(r'\boverride\b', line[closing_paren:])) + return False + + +def IsOutOfLineMethodDefinition(clean_lines, linenum): + """Check if current line contains an out-of-line method definition. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line contains an out-of-line method definition. + """ + # Scan back a few lines for start of current function + for i in xrange(linenum, max(-1, linenum - 10), -1): + if Match(r'^([^()]*\w+)\(', clean_lines.elided[i]): + return Match(r'^[^()]*\w+::\w+\(', clean_lines.elided[i]) is not None + return False + + +def IsInitializerList(clean_lines, linenum): + """Check if current line is inside constructor initializer list. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + Returns: + True if current line appears to be inside constructor initializer + list, False otherwise. + """ + for i in xrange(linenum, 1, -1): + line = clean_lines.elided[i] + if i == linenum: + remove_function_body = Match(r'^(.*)\{\s*$', line) + if remove_function_body: + line = remove_function_body.group(1) + + if Search(r'\s:\s*\w+[({]', line): + # A lone colon tend to indicate the start of a constructor + # initializer list. It could also be a ternary operator, which + # also tend to appear in constructor initializer lists as + # opposed to parameter lists. + return True + if Search(r'\}\s*,\s*$', line): + # A closing brace followed by a comma is probably the end of a + # brace-initialized member in constructor initializer list. + return True + if Search(r'[{};]\s*$', line): + # Found one of the following: + # - A closing brace or semicolon, probably the end of the previous + # function. + # - An opening brace, probably the start of current class or namespace. + # + # Current line is probably not inside an initializer list since + # we saw one of those things without seeing the starting colon. + return False + + # Got to the beginning of the file without seeing the start of + # constructor initializer list. + return False + + +def CheckForNonConstReference(filename, clean_lines, linenum, + nesting_state, error): + """Check for non-const references. + + Separate from CheckLanguage since it scans backwards from current + line, instead of scanning forward. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: The function to call with any errors found. + """ + # Do nothing if there is no '&' on current line. + line = clean_lines.elided[linenum] + if '&' not in line: + return + + # If a function is inherited, current function doesn't have much of + # a choice, so any non-const references should not be blamed on + # derived function. + if IsDerivedFunction(clean_lines, linenum): + return + + # Don't warn on out-of-line method definitions, as we would warn on the + # in-line declaration, if it isn't marked with 'override'. + if IsOutOfLineMethodDefinition(clean_lines, linenum): + return + + # Long type names may be broken across multiple lines, usually in one + # of these forms: + # LongType + # ::LongTypeContinued &identifier + # LongType:: + # LongTypeContinued &identifier + # LongType< + # ...>::LongTypeContinued &identifier + # + # If we detected a type split across two lines, join the previous + # line to current line so that we can match const references + # accordingly. + # + # Note that this only scans back one line, since scanning back + # arbitrary number of lines would be expensive. If you have a type + # that spans more than 2 lines, please use a typedef. + if linenum > 1: + previous = None + if Match(r'\s*::(?:[\w<>]|::)+\s*&\s*\S', line): + # previous_line\n + ::current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+[\w<>])\s*$', + clean_lines.elided[linenum - 1]) + elif Match(r'\s*[a-zA-Z_]([\w<>]|::)+\s*&\s*\S', line): + # previous_line::\n + current_line + previous = Search(r'\b((?:const\s*)?(?:[\w<>]|::)+::)\s*$', + clean_lines.elided[linenum - 1]) + if previous: + line = previous.group(1) + line.lstrip() + else: + # Check for templated parameter that is split across multiple lines + endpos = line.rfind('>') + if endpos > -1: + (_, startline, startpos) = ReverseCloseExpression( + clean_lines, linenum, endpos) + if startpos > -1 and startline < linenum: + # Found the matching < on an earlier line, collect all + # pieces up to current line. + line = '' + for i in xrange(startline, linenum + 1): + line += clean_lines.elided[i].strip() + + # Check for non-const references in function parameters. A single '&' may + # found in the following places: + # inside expression: binary & for bitwise AND + # inside expression: unary & for taking the address of something + # inside declarators: reference parameter + # We will exclude the first two cases by checking that we are not inside a + # function body, including one that was just introduced by a trailing '{'. + # TODO(unknown): Doesn't account for 'catch(Exception& e)' [rare]. + if (nesting_state.previous_stack_top and + not (isinstance(nesting_state.previous_stack_top, _ClassInfo) or + isinstance(nesting_state.previous_stack_top, _NamespaceInfo))): + # Not at toplevel, not within a class, and not within a namespace + return + + # Avoid initializer lists. We only need to scan back from the + # current line for something that starts with ':'. + # + # We don't need to check the current line, since the '&' would + # appear inside the second set of parentheses on the current line as + # opposed to the first set. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 10), -1): + previous_line = clean_lines.elided[i] + if not Search(r'[),]\s*$', previous_line): + break + if Match(r'^\s*:\s+\S', previous_line): + return + + # Avoid preprocessors + if Search(r'\\\s*$', line): + return + + # Avoid constructor initializer lists + if IsInitializerList(clean_lines, linenum): + return + + # We allow non-const references in a few standard places, like functions + # called "swap()" or iostream operators like "<<" or ">>". Do not check + # those function parameters. + # + # We also accept & in static_assert, which looks like a function but + # it's actually a declaration expression. + whitelisted_functions = (r'(?:[sS]wap(?:<\w:+>)?|' + r'operator\s*[<>][<>]|' + r'static_assert|COMPILE_ASSERT' + r')\s*\(') + if Search(whitelisted_functions, line): + return + elif not Search(r'\S+\([^)]*$', line): + # Don't see a whitelisted function on this line. Actually we + # didn't see any function name on this line, so this is likely a + # multi-line parameter list. Try a bit harder to catch this case. + for i in xrange(2): + if (linenum > i and + Search(whitelisted_functions, clean_lines.elided[linenum - i - 1])): + return + + decls = ReplaceAll(r'{[^}]*}', ' ', line) # exclude function body + for parameter in re.findall(_RE_PATTERN_REF_PARAM, decls): + if not Match(_RE_PATTERN_CONST_REF_PARAM, parameter): + error(filename, linenum, 'runtime/references', 2, + 'Is this a non-const reference? ' + 'If so, make const or use a pointer: ' + + ReplaceAll(' *<', '<', parameter)) + + +def CheckCasts(filename, clean_lines, linenum, error): + """Various cast related checks. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Check to see if they're using an conversion function cast. + # I just try to capture the most common basic types, though there are more. + # Parameterless conversion functions, such as bool(), are allowed as they are + # probably a member operator declaration or default constructor. + match = Search( + r'(\bnew\s+|\S<\s*(?:const\s+)?)?\b' + r'(int|float|double|bool|char|int32|uint32|int64|uint64)' + r'(\([^)].*)', line) + expecting_function = ExpectingFunctionArgs(clean_lines, linenum) + if match and not expecting_function: + matched_type = match.group(2) + + # matched_new_or_template is used to silence two false positives: + # - New operators + # - Template arguments with function types + # + # For template arguments, we match on types immediately following + # an opening bracket without any spaces. This is a fast way to + # silence the common case where the function type is the first + # template argument. False negative with less-than comparison is + # avoided because those operators are usually followed by a space. + # + # function // bracket + no space = false positive + # value < double(42) // bracket + space = true positive + matched_new_or_template = match.group(1) + + # Avoid arrays by looking for brackets that come after the closing + # parenthesis. + if Match(r'\([^()]+\)\s*\[', match.group(3)): + return + + # Other things to ignore: + # - Function pointers + # - Casts to pointer types + # - Placement new + # - Alias declarations + matched_funcptr = match.group(3) + if (matched_new_or_template is None and + not (matched_funcptr and + (Match(r'\((?:[^() ]+::\s*\*\s*)?[^() ]+\)\s*\(', + matched_funcptr) or + matched_funcptr.startswith('(*)'))) and + not Match(r'\s*using\s+\S+\s*=\s*' + matched_type, line) and + not Search(r'new\(\S+\)\s*' + matched_type, line)): + error(filename, linenum, 'readability/casting', 4, + 'Using deprecated casting style. ' + 'Use static_cast<%s>(...) instead' % + matched_type) + + if not expecting_function: + CheckCStyleCast(filename, clean_lines, linenum, 'static_cast', + r'\((int|float|double|bool|char|u?int(16|32|64))\)', error) + + # This doesn't catch all cases. Consider (const char * const)"hello". + # + # (char *) "foo" should always be a const_cast (reinterpret_cast won't + # compile). + if CheckCStyleCast(filename, clean_lines, linenum, 'const_cast', + r'\((char\s?\*+\s?)\)\s*"', error): + pass + else: + # Check pointer casts for other than string constants + CheckCStyleCast(filename, clean_lines, linenum, 'reinterpret_cast', + r'\((\w+\s?\*+\s?)\)', error) + + # In addition, we look for people taking the address of a cast. This + # is dangerous -- casts can assign to temporaries, so the pointer doesn't + # point where you think. + # + # Some non-identifier character is required before the '&' for the + # expression to be recognized as a cast. These are casts: + # expression = &static_cast(temporary()); + # function(&(int*)(temporary())); + # + # This is not a cast: + # reference_type&(int* function_param); + match = Search( + r'(?:[^\w]&\(([^)*][^)]*)\)[\w(])|' + r'(?:[^\w]&(static|dynamic|down|reinterpret)_cast\b)', line) + if match: + # Try a better error message when the & is bound to something + # dereferenced by the casted pointer, as opposed to the casted + # pointer itself. + parenthesis_error = False + match = Match(r'^(.*&(?:static|dynamic|down|reinterpret)_cast\b)<', line) + if match: + _, y1, x1 = CloseExpression(clean_lines, linenum, len(match.group(1))) + if x1 >= 0 and clean_lines.elided[y1][x1] == '(': + _, y2, x2 = CloseExpression(clean_lines, y1, x1) + if x2 >= 0: + extended_line = clean_lines.elided[y2][x2:] + if y2 < clean_lines.NumLines() - 1: + extended_line += clean_lines.elided[y2 + 1] + if Match(r'\s*(?:->|\[)', extended_line): + parenthesis_error = True + + if parenthesis_error: + error(filename, linenum, 'readability/casting', 4, + ('Are you taking an address of something dereferenced ' + 'from a cast? Wrapping the dereferenced expression in ' + 'parentheses will make the binding more obvious')) + else: + error(filename, linenum, 'runtime/casting', 4, + ('Are you taking an address of a cast? ' + 'This is dangerous: could be a temp var. ' + 'Take the address before doing the cast, rather than after')) + + +def CheckCStyleCast(filename, clean_lines, linenum, cast_type, pattern, error): + """Checks for a C-style cast by looking for the pattern. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + cast_type: The string for the C++ cast to recommend. This is either + reinterpret_cast, static_cast, or const_cast, depending. + pattern: The regular expression used to find C-style casts. + error: The function to call with any errors found. + + Returns: + True if an error was emitted. + False otherwise. + """ + line = clean_lines.elided[linenum] + match = Search(pattern, line) + if not match: + return False + + # Exclude lines with keywords that tend to look like casts + context = line[0:match.start(1) - 1] + if Match(r'.*\b(?:sizeof|alignof|alignas|[_A-Z][_A-Z0-9]*)\s*$', context): + return False + + # Try expanding current context to see if we one level of + # parentheses inside a macro. + if linenum > 0: + for i in xrange(linenum - 1, max(0, linenum - 5), -1): + context = clean_lines.elided[i] + context + if Match(r'.*\b[_A-Z][_A-Z0-9]*\s*\((?:\([^()]*\)|[^()])*$', context): + return False + + # operator++(int) and operator--(int) + if context.endswith(' operator++') or context.endswith(' operator--'): + return False + + # A single unnamed argument for a function tends to look like old + # style cast. If we see those, don't issue warnings for deprecated + # casts, instead issue warnings for unnamed arguments where + # appropriate. + # + # These are things that we want warnings for, since the style guide + # explicitly require all parameters to be named: + # Function(int); + # Function(int) { + # ConstMember(int) const; + # ConstMember(int) const { + # ExceptionMember(int) throw (...); + # ExceptionMember(int) throw (...) { + # PureVirtual(int) = 0; + # [](int) -> bool { + # + # These are functions of some sort, where the compiler would be fine + # if they had named parameters, but people often omit those + # identifiers to reduce clutter: + # (FunctionPointer)(int); + # (FunctionPointer)(int) = value; + # Function((function_pointer_arg)(int)) + # Function((function_pointer_arg)(int), int param) + # ; + # <(FunctionPointerTemplateArgument)(int)>; + remainder = line[match.end(0):] + if Match(r'^\s*(?:;|const\b|throw\b|final\b|override\b|[=>{),]|->)', + remainder): + # Looks like an unnamed parameter. + + # Don't warn on any kind of template arguments. + if Match(r'^\s*>', remainder): + return False + + # Don't warn on assignments to function pointers, but keep warnings for + # unnamed parameters to pure virtual functions. Note that this pattern + # will also pass on assignments of "0" to function pointers, but the + # preferred values for those would be "nullptr" or "NULL". + matched_zero = Match(r'^\s=\s*(\S+)\s*;', remainder) + if matched_zero and matched_zero.group(1) != '0': + return False + + # Don't warn on function pointer declarations. For this we need + # to check what came before the "(type)" string. + if Match(r'.*\)\s*$', line[0:match.start(0)]): + return False + + # Don't warn if the parameter is named with block comments, e.g.: + # Function(int /*unused_param*/); + raw_line = clean_lines.raw_lines[linenum] + if '/*' in raw_line: + return False + + # Passed all filters, issue warning here. + error(filename, linenum, 'readability/function', 3, + 'All parameters should be named in a function') + return True + + # At this point, all that should be left is actual casts. + error(filename, linenum, 'readability/casting', 4, + 'Using C-style cast. Use %s<%s>(...) instead' % + (cast_type, match.group(1))) + + return True + + +def ExpectingFunctionArgs(clean_lines, linenum): + """Checks whether where function type arguments are expected. + + Args: + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + + Returns: + True if the line at 'linenum' is inside something that expects arguments + of function types. + """ + line = clean_lines.elided[linenum] + return (Match(r'^\s*MOCK_(CONST_)?METHOD\d+(_T)?\(', line) or + (linenum >= 2 and + (Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\((?:\S+,)?\s*$', + clean_lines.elided[linenum - 1]) or + Match(r'^\s*MOCK_(?:CONST_)?METHOD\d+(?:_T)?\(\s*$', + clean_lines.elided[linenum - 2]) or + Search(r'\bstd::m?function\s*\<\s*$', + clean_lines.elided[linenum - 1])))) + + +_HEADERS_CONTAINING_TEMPLATES = ( + ('', ('deque',)), + ('', ('unary_function', 'binary_function', + 'plus', 'minus', 'multiplies', 'divides', 'modulus', + 'negate', + 'equal_to', 'not_equal_to', 'greater', 'less', + 'greater_equal', 'less_equal', + 'logical_and', 'logical_or', 'logical_not', + 'unary_negate', 'not1', 'binary_negate', 'not2', + 'bind1st', 'bind2nd', + 'pointer_to_unary_function', + 'pointer_to_binary_function', + 'ptr_fun', + 'mem_fun_t', 'mem_fun', 'mem_fun1_t', 'mem_fun1_ref_t', + 'mem_fun_ref_t', + 'const_mem_fun_t', 'const_mem_fun1_t', + 'const_mem_fun_ref_t', 'const_mem_fun1_ref_t', + 'mem_fun_ref', + )), + ('', ('numeric_limits',)), + ('', ('list',)), + ('', ('map', 'multimap',)), + ('', ('allocator',)), + ('', ('queue', 'priority_queue',)), + ('', ('set', 'multiset',)), + ('', ('stack',)), + ('', ('char_traits', 'basic_string',)), + ('', ('tuple',)), + ('', ('pair',)), + ('', ('vector',)), + + # gcc extensions. + # Note: std::hash is their hash, ::hash is our hash + ('', ('hash_map', 'hash_multimap',)), + ('', ('hash_set', 'hash_multiset',)), + ('', ('slist',)), + ) + +_RE_PATTERN_STRING = re.compile(r'\bstring\b') + +_re_pattern_algorithm_header = [] +for _template in ('copy', 'max', 'min', 'min_element', 'sort', 'swap', + 'transform'): + # Match max(..., ...), max(..., ...), but not foo->max, foo.max or + # type::max(). + _re_pattern_algorithm_header.append( + (re.compile(r'[^>.]\b' + _template + r'(<.*?>)?\([^\)]'), + _template, + '')) + +_re_pattern_templates = [] +for _header, _templates in _HEADERS_CONTAINING_TEMPLATES: + for _template in _templates: + _re_pattern_templates.append( + (re.compile(r'(\<|\b)' + _template + r'\s*\<'), + _template + '<>', + _header)) + + +def FilesBelongToSameModule(filename_cc, filename_h): + """Check if these two filenames belong to the same module. + + The concept of a 'module' here is a as follows: + foo.h, foo-inl.h, foo.cc, foo_test.cc and foo_unittest.cc belong to the + same 'module' if they are in the same directory. + some/path/public/xyzzy and some/path/internal/xyzzy are also considered + to belong to the same module here. + + If the filename_cc contains a longer path than the filename_h, for example, + '/absolute/path/to/base/sysinfo.cc', and this file would include + 'base/sysinfo.h', this function also produces the prefix needed to open the + header. This is used by the caller of this function to more robustly open the + header file. We don't have access to the real include paths in this context, + so we need this guesswork here. + + Known bugs: tools/base/bar.cc and base/bar.h belong to the same module + according to this implementation. Because of this, this function gives + some false positives. This should be sufficiently rare in practice. + + Args: + filename_cc: is the path for the .cc file + filename_h: is the path for the header path + + Returns: + Tuple with a bool and a string: + bool: True if filename_cc and filename_h belong to the same module. + string: the additional prefix needed to open the header file. + """ + + if not filename_cc.endswith('.cc'): + return (False, '') + filename_cc = filename_cc[:-len('.cc')] + if filename_cc.endswith('_unittest'): + filename_cc = filename_cc[:-len('_unittest')] + elif filename_cc.endswith('_test'): + filename_cc = filename_cc[:-len('_test')] + filename_cc = filename_cc.replace('/public/', '/') + filename_cc = filename_cc.replace('/internal/', '/') + + if not filename_h.endswith('.h'): + return (False, '') + filename_h = filename_h[:-len('.h')] + if filename_h.endswith('-inl'): + filename_h = filename_h[:-len('-inl')] + filename_h = filename_h.replace('/public/', '/') + filename_h = filename_h.replace('/internal/', '/') + + files_belong_to_same_module = filename_cc.endswith(filename_h) + common_path = '' + if files_belong_to_same_module: + common_path = filename_cc[:-len(filename_h)] + return files_belong_to_same_module, common_path + + +def UpdateIncludeState(filename, include_dict, io=codecs): + """Fill up the include_dict with new includes found from the file. + + Args: + filename: the name of the header to read. + include_dict: a dictionary in which the headers are inserted. + io: The io factory to use to read the file. Provided for testability. + + Returns: + True if a header was successfully added. False otherwise. + """ + headerfile = None + try: + headerfile = io.open(filename, 'r', 'utf8', 'replace') + except IOError: + return False + linenum = 0 + for line in headerfile: + linenum += 1 + clean_line = CleanseComments(line) + match = _RE_PATTERN_INCLUDE.search(clean_line) + if match: + include = match.group(2) + include_dict.setdefault(include, linenum) + return True + + +def CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error, + io=codecs): + """Reports for missing stl includes. + + This function will output warnings to make sure you are including the headers + necessary for the stl containers and functions that you use. We only give one + reason to include a header. For example, if you use both equal_to<> and + less<> in a .h file, only one (the latter in the file) of these will be + reported as a reason to include the . + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + include_state: An _IncludeState instance. + error: The function to call with any errors found. + io: The IO factory to use to read the header file. Provided for unittest + injection. + """ + required = {} # A map of header name to linenumber and the template entity. + # Example of required: { '': (1219, 'less<>') } + + for linenum in xrange(clean_lines.NumLines()): + line = clean_lines.elided[linenum] + if not line or line[0] == '#': + continue + + # String is special -- it is a non-templatized type in STL. + matched = _RE_PATTERN_STRING.search(line) + if matched: + # Don't warn about strings in non-STL namespaces: + # (We check only the first match per line; good enough.) + prefix = line[:matched.start()] + if prefix.endswith('std::') or not prefix.endswith('::'): + required[''] = (linenum, 'string') + + for pattern, template, header in _re_pattern_algorithm_header: + if pattern.search(line): + required[header] = (linenum, template) + + # The following function is just a speed up, no semantics are changed. + if not '<' in line: # Reduces the cpu time usage by skipping lines. + continue + + for pattern, template, header in _re_pattern_templates: + if pattern.search(line): + required[header] = (linenum, template) + + # The policy is that if you #include something in foo.h you don't need to + # include it again in foo.cc. Here, we will look at possible includes. + # Let's flatten the include_state include_list and copy it into a dictionary. + include_dict = dict([item for sublist in include_state.include_list + for item in sublist]) + + # Did we find the header for this file (if any) and successfully load it? + header_found = False + + # Use the absolute path so that matching works properly. + abs_filename = FileInfo(filename).FullName() + + # For Emacs's flymake. + # If cpplint is invoked from Emacs's flymake, a temporary file is generated + # by flymake and that file name might end with '_flymake.cc'. In that case, + # restore original file name here so that the corresponding header file can be + # found. + # e.g. If the file name is 'foo_flymake.cc', we should search for 'foo.h' + # instead of 'foo_flymake.h' + abs_filename = re.sub(r'_flymake\.cc$', '.cc', abs_filename) + + # include_dict is modified during iteration, so we iterate over a copy of + # the keys. + header_keys = include_dict.keys() + for header in header_keys: + (same_module, common_path) = FilesBelongToSameModule(abs_filename, header) + fullpath = common_path + header + if same_module and UpdateIncludeState(fullpath, include_dict, io): + header_found = True + + # If we can't find the header file for a .cc, assume it's because we don't + # know where to look. In that case we'll give up as we're not sure they + # didn't include it in the .h file. + # TODO(unknown): Do a better job of finding .h files so we are confident that + # not having the .h file means there isn't one. + if filename.endswith('.cc') and not header_found: + return + + # All the lines have been processed, report the errors found. + for required_header_unstripped in required: + template = required[required_header_unstripped][1] + if required_header_unstripped.strip('<>"') not in include_dict: + error(filename, required[required_header_unstripped][0], + 'build/include_what_you_use', 4, + 'Add #include ' + required_header_unstripped + ' for ' + template) + + +_RE_PATTERN_EXPLICIT_MAKEPAIR = re.compile(r'\bmake_pair\s*<') + + +def CheckMakePairUsesDeduction(filename, clean_lines, linenum, error): + """Check that make_pair's template arguments are deduced. + + G++ 4.6 in C++11 mode fails badly if make_pair's template arguments are + specified explicitly, and such use isn't intended in any case. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + match = _RE_PATTERN_EXPLICIT_MAKEPAIR.search(line) + if match: + error(filename, linenum, 'build/explicit_make_pair', + 4, # 4 = high confidence + 'For C++11-compatibility, omit template arguments from make_pair' + ' OR use pair directly OR if appropriate, construct a pair directly') + + +def CheckDefaultLambdaCaptures(filename, clean_lines, linenum, error): + """Check that default lambda captures are not used. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # A lambda introducer specifies a default capture if it starts with "[=" + # or if it starts with "[&" _not_ followed by an identifier. + match = Match(r'^(.*)\[\s*(?:=|&[^\w])', line) + if match: + # Found a potential error, check what comes after the lambda-introducer. + # If it's not open parenthesis (for lambda-declarator) or open brace + # (for compound-statement), it's not a lambda. + line, _, pos = CloseExpression(clean_lines, linenum, len(match.group(1))) + if pos >= 0 and Match(r'^\s*[{(]', line[pos:]): + error(filename, linenum, 'build/c++11', + 4, # 4 = high confidence + 'Default lambda captures are an unapproved C++ feature.') + + +def CheckRedundantVirtual(filename, clean_lines, linenum, error): + """Check if line contains a redundant "virtual" function-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for "virtual" on current line. + line = clean_lines.elided[linenum] + virtual = Match(r'^(.*)(\bvirtual\b)(.*)$', line) + if not virtual: return + + # Ignore "virtual" keywords that are near access-specifiers. These + # are only used in class base-specifier and do not apply to member + # functions. + if (Search(r'\b(public|protected|private)\s+$', virtual.group(1)) or + Match(r'^\s+(public|protected|private)\b', virtual.group(3))): + return + + # Ignore the "virtual" keyword from virtual base classes. Usually + # there is a column on the same line in these cases (virtual base + # classes are rare in google3 because multiple inheritance is rare). + if Match(r'^.*[^:]:[^:].*$', line): return + + # Look for the next opening parenthesis. This is the start of the + # parameter list (possibly on the next line shortly after virtual). + # TODO(unknown): doesn't work if there are virtual functions with + # decltype() or other things that use parentheses, but csearch suggests + # that this is rare. + end_col = -1 + end_line = -1 + start_col = len(virtual.group(2)) + for start_line in xrange(linenum, min(linenum + 3, clean_lines.NumLines())): + line = clean_lines.elided[start_line][start_col:] + parameter_list = Match(r'^([^(]*)\(', line) + if parameter_list: + # Match parentheses to find the end of the parameter list + (_, end_line, end_col) = CloseExpression( + clean_lines, start_line, start_col + len(parameter_list.group(1))) + break + start_col = 0 + + if end_col < 0: + return # Couldn't find end of parameter list, give up + + # Look for "override" or "final" after the parameter list + # (possibly on the next few lines). + for i in xrange(end_line, min(end_line + 3, clean_lines.NumLines())): + line = clean_lines.elided[i][end_col:] + match = Search(r'\b(override|final)\b', line) + if match: + error(filename, linenum, 'readability/inheritance', 4, + ('"virtual" is redundant since function is ' + 'already declared as "%s"' % match.group(1))) + + # Set end_col to check whole lines after we are done with the + # first line. + end_col = 0 + if Search(r'[^\w]\s*$', line): + break + + +def CheckRedundantOverrideOrFinal(filename, clean_lines, linenum, error): + """Check if line contains a redundant "override" or "final" virt-specifier. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + # Look for closing parenthesis nearby. We need one to confirm where + # the declarator ends and where the virt-specifier starts to avoid + # false positives. + line = clean_lines.elided[linenum] + declarator_end = line.rfind(')') + if declarator_end >= 0: + fragment = line[declarator_end:] + else: + if linenum > 1 and clean_lines.elided[linenum - 1].rfind(')') >= 0: + fragment = line + else: + return + + # Check that at most one of "override" or "final" is present, not both + if Search(r'\boverride\b', fragment) and Search(r'\bfinal\b', fragment): + error(filename, linenum, 'readability/inheritance', 4, + ('"override" is redundant since function is ' + 'already declared as "final"')) + + + + +# Returns true if we are at a new block, and it is directly +# inside of a namespace. +def IsBlockInNameSpace(nesting_state, is_forward_declaration): + """Checks that the new block is directly in a namespace. + + Args: + nesting_state: The _NestingState object that contains info about our state. + is_forward_declaration: If the class is a forward declared class. + Returns: + Whether or not the new block is directly in a namespace. + """ + if is_forward_declaration: + if len(nesting_state.stack) >= 1 and ( + isinstance(nesting_state.stack[-1], _NamespaceInfo)): + return True + else: + return False + + return (len(nesting_state.stack) > 1 and + nesting_state.stack[-1].check_namespace_indentation and + isinstance(nesting_state.stack[-2], _NamespaceInfo)) + + +def ShouldCheckNamespaceIndentation(nesting_state, is_namespace_indent_item, + raw_lines_no_comments, linenum): + """This method determines if we should apply our namespace indentation check. + + Args: + nesting_state: The current nesting state. + is_namespace_indent_item: If we just put a new class on the stack, True. + If the top of the stack is not a class, or we did not recently + add the class, False. + raw_lines_no_comments: The lines without the comments. + linenum: The current line number we are processing. + + Returns: + True if we should apply our namespace indentation check. Currently, it + only works for classes and namespaces inside of a namespace. + """ + + is_forward_declaration = IsForwardClassDeclaration(raw_lines_no_comments, + linenum) + + if not (is_namespace_indent_item or is_forward_declaration): + return False + + # If we are in a macro, we do not want to check the namespace indentation. + if IsMacroDefinition(raw_lines_no_comments, linenum): + return False + + return IsBlockInNameSpace(nesting_state, is_forward_declaration) + + +# Call this method if the line is directly inside of a namespace. +# If the line above is blank (excluding comments) or the start of +# an inner namespace, it cannot be indented. +def CheckItemIndentationInNamespace(filename, raw_lines_no_comments, linenum, + error): + line = raw_lines_no_comments[linenum] + if Match(r'^\s+', line): + error(filename, linenum, 'runtime/indentation_namespace', 4, + 'Do not indent within a namespace') + + +def ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions=[]): + """Processes a single line in the file. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + clean_lines: An array of strings, each representing a line of the file, + with comments stripped. + line: Number of line being processed. + include_state: An _IncludeState instance in which the headers are inserted. + function_state: A _FunctionState instance which counts function lines, etc. + nesting_state: A NestingState instance which maintains information about + the current stack of nested blocks being parsed. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + raw_lines = clean_lines.raw_lines + ParseNolintSuppressions(filename, raw_lines[line], line, error) + nesting_state.Update(filename, clean_lines, line, error) + CheckForNamespaceIndentation(filename, nesting_state, clean_lines, line, + error) + if nesting_state.InAsmBlock(): return + CheckForFunctionLengths(filename, clean_lines, line, function_state, error) + CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error) + CheckStyle(filename, clean_lines, line, file_extension, nesting_state, error) + CheckLanguage(filename, clean_lines, line, file_extension, include_state, + nesting_state, error) + CheckForNonConstReference(filename, clean_lines, line, nesting_state, error) + CheckForNonStandardConstructs(filename, clean_lines, line, + nesting_state, error) + CheckVlogArguments(filename, clean_lines, line, error) + CheckPosixThreading(filename, clean_lines, line, error) + CheckInvalidIncrement(filename, clean_lines, line, error) + CheckMakePairUsesDeduction(filename, clean_lines, line, error) + CheckDefaultLambdaCaptures(filename, clean_lines, line, error) + CheckRedundantVirtual(filename, clean_lines, line, error) + CheckRedundantOverrideOrFinal(filename, clean_lines, line, error) + for check_fn in extra_check_functions: + check_fn(filename, clean_lines, line, error) + +def FlagCxx11Features(filename, clean_lines, linenum, error): + """Flag those c++11 features that we only allow in certain places. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + + # Flag unapproved C++11 headers. + include = Match(r'\s*#\s*include\s+[<"]([^<"]+)[">]', line) + if include and include.group(1) in ('cfenv', + 'condition_variable', + 'fenv.h', + 'future', + 'mutex', + 'thread', + 'chrono', + 'ratio', + 'regex', + 'system_error', + ): + error(filename, linenum, 'build/c++11', 5, + ('<%s> is an unapproved C++11 header.') % include.group(1)) + + # The only place where we need to worry about C++11 keywords and library + # features in preprocessor directives is in macro definitions. + if Match(r'\s*#', line) and not Match(r'\s*#\s*define\b', line): return + + # These are classes and free functions. The classes are always + # mentioned as std::*, but we only catch the free functions if + # they're not found by ADL. They're alphabetical by header. + for top_name in ( + # type_traits + 'alignment_of', + 'aligned_union', + ): + if Search(r'\bstd::%s\b' % top_name, line): + error(filename, linenum, 'build/c++11', 5, + ('std::%s is an unapproved C++11 class or function. Send c-style ' + 'an example of where it would make your code more readable, and ' + 'they may let you use it.') % top_name) + + +def ProcessFileData(filename, file_extension, lines, error, + extra_check_functions=[]): + """Performs lint checks and reports any errors to the given error function. + + Args: + filename: Filename of the file that is being processed. + file_extension: The extension (dot not included) of the file. + lines: An array of strings, each representing a line of the file, with the + last element being empty if the file is terminated with a newline. + error: A callable to which errors are reported, which takes 4 arguments: + filename, line number, error level, and message + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + lines = (['// marker so line numbers and indices both start at 1'] + lines + + ['// marker so line numbers end in a known way']) + + include_state = _IncludeState() + function_state = _FunctionState() + nesting_state = NestingState() + + ResetNolintSuppressions() + + CheckForCopyright(filename, lines, error) + + RemoveMultiLineComments(filename, lines, error) + clean_lines = CleansedLines(lines) + + if file_extension == 'h': + CheckForHeaderGuard(filename, clean_lines, error) + + for line in xrange(clean_lines.NumLines()): + ProcessLine(filename, file_extension, clean_lines, line, + include_state, function_state, nesting_state, error, + extra_check_functions) + FlagCxx11Features(filename, clean_lines, line, error) + nesting_state.CheckCompletedBlocks(filename, error) + + CheckForIncludeWhatYouUse(filename, clean_lines, include_state, error) + + # Check that the .cc file has included its header if it exists. + if file_extension == 'cc': + CheckHeaderFileIncluded(filename, include_state, error) + + # We check here rather than inside ProcessLine so that we see raw + # lines rather than "cleaned" lines. + CheckForBadCharacters(filename, lines, error) + + CheckForNewlineAtEOF(filename, lines, error) + +def ProcessConfigOverrides(filename): + """ Loads the configuration files and processes the config overrides. + + Args: + filename: The name of the file being processed by the linter. + + Returns: + False if the current |filename| should not be processed further. + """ + + abs_filename = os.path.abspath(filename) + cfg_filters = [] + keep_looking = True + while keep_looking: + abs_path, base_name = os.path.split(abs_filename) + if not base_name: + break # Reached the root directory. + + cfg_file = os.path.join(abs_path, "CPPLINT.cfg") + abs_filename = abs_path + if not os.path.isfile(cfg_file): + continue + + try: + with open(cfg_file) as file_handle: + for line in file_handle: + line, _, _ = line.partition('#') # Remove comments. + if not line.strip(): + continue + + name, _, val = line.partition('=') + name = name.strip() + val = val.strip() + if name == 'set noparent': + keep_looking = False + elif name == 'filter': + cfg_filters.append(val) + elif name == 'exclude_files': + # When matching exclude_files pattern, use the base_name of + # the current file name or the directory name we are processing. + # For example, if we are checking for lint errors in /foo/bar/baz.cc + # and we found the .cfg file at /foo/CPPLINT.cfg, then the config + # file's "exclude_files" filter is meant to be checked against "bar" + # and not "baz" nor "bar/baz.cc". + if base_name: + pattern = re.compile(val) + if pattern.match(base_name): + sys.stderr.write('Ignoring "%s": file excluded by "%s". ' + 'File path component "%s" matches ' + 'pattern "%s"\n' % + (filename, cfg_file, base_name, val)) + return False + elif name == 'linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + sys.stderr.write('Line length must be numeric.') + else: + sys.stderr.write( + 'Invalid configuration option (%s) in file %s\n' % + (name, cfg_file)) + + except IOError: + sys.stderr.write( + "Skipping config file '%s': Can't open for reading\n" % cfg_file) + keep_looking = False + + # Apply all the accumulated filters in reverse order (top-level directory + # config options having the least priority). + for filter in reversed(cfg_filters): + _AddFilters(filter) + + return True + + +def ProcessFile(filename, vlevel, extra_check_functions=[]): + """Does google-lint on a single file. + + Args: + filename: The name of the file to parse. + + vlevel: The level of errors to report. Every error of confidence + >= verbose_level will be reported. 0 is a good default. + + extra_check_functions: An array of additional check functions that will be + run on each source line. Each function takes 4 + arguments: filename, clean_lines, line, error + """ + + _SetVerboseLevel(vlevel) + _BackupFilters() + + if not ProcessConfigOverrides(filename): + _RestoreFilters() + return + + lf_lines = [] + crlf_lines = [] + try: + # Support the UNIX convention of using "-" for stdin. Note that + # we are not opening the file with universal newline support + # (which codecs doesn't support anyway), so the resulting lines do + # contain trailing '\r' characters if we are reading a file that + # has CRLF endings. + # If after the split a trailing '\r' is present, it is removed + # below. + if filename == '-': + lines = codecs.StreamReaderWriter(sys.stdin, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace').read().split('\n') + else: + lines = codecs.open(filename, 'r', 'utf8', 'replace').read().split('\n') + + # Remove trailing '\r'. + # The -1 accounts for the extra trailing blank line we get from split() + for linenum in range(len(lines) - 1): + if lines[linenum].endswith('\r'): + lines[linenum] = lines[linenum].rstrip('\r') + crlf_lines.append(linenum + 1) + else: + lf_lines.append(linenum + 1) + + except IOError: + sys.stderr.write( + "Skipping input '%s': Can't open for reading\n" % filename) + _RestoreFilters() + return + + # Note, if no dot is found, this will give the entire filename as the ext. + file_extension = filename[filename.rfind('.') + 1:] + + # When reading from stdin, the extension is unknown, so no cpplint tests + # should rely on the extension. + if filename != '-' and file_extension not in _valid_extensions: + sys.stderr.write('Ignoring %s; not a valid file name ' + '(%s)\n' % (filename, ', '.join(_valid_extensions))) + else: + ProcessFileData(filename, file_extension, lines, Error, + extra_check_functions) + + # If end-of-line sequences are a mix of LF and CR-LF, issue + # warnings on the lines with CR. + # + # Don't issue any warnings if all lines are uniformly LF or CR-LF, + # since critique can handle these just fine, and the style guide + # doesn't dictate a particular end of line sequence. + # + # We can't depend on os.linesep to determine what the desired + # end-of-line sequence should be, since that will return the + # server-side end-of-line sequence. + if lf_lines and crlf_lines: + # Warn on every line with CR. An alternative approach might be to + # check whether the file is mostly CRLF or just LF, and warn on the + # minority, we bias toward LF here since most tools prefer LF. + for linenum in crlf_lines: + Error(filename, linenum, 'whitespace/newline', 1, + 'Unexpected \\r (^M) found; better to use only \\n') + + sys.stderr.write('Done processing %s\n' % filename) + _RestoreFilters() + + +def PrintUsage(message): + """Prints a brief usage string and exits, optionally with an error message. + + Args: + message: The optional error message. + """ + sys.stderr.write(_USAGE) + if message: + sys.exit('\nFATAL ERROR: ' + message) + else: + sys.exit(1) + + +def PrintCategories(): + """Prints a list of all the error-categories used by error messages. + + These are the categories used to filter messages via --filter. + """ + sys.stderr.write(''.join(' %s\n' % cat for cat in _ERROR_CATEGORIES)) + sys.exit(0) + + +def ParseArguments(args): + """Parses the command line arguments. + + This may set the output format and verbosity level as side-effects. + + Args: + args: The command line arguments: + + Returns: + The list of filenames to lint. + """ + try: + (opts, filenames) = getopt.getopt(args, '', ['help', 'output=', 'verbose=', + 'counting=', + 'filter=', + 'root=', + 'linelength=', + 'extensions=']) + except getopt.GetoptError: + PrintUsage('Invalid arguments.') + + verbosity = _VerboseLevel() + output_format = _OutputFormat() + filters = '' + counting_style = '' + + for (opt, val) in opts: + if opt == '--help': + PrintUsage(None) + elif opt == '--output': + if val not in ('emacs', 'vs7', 'eclipse'): + PrintUsage('The only allowed output formats are emacs, vs7 and eclipse.') + output_format = val + elif opt == '--verbose': + verbosity = int(val) + elif opt == '--filter': + filters = val + if not filters: + PrintCategories() + elif opt == '--counting': + if val not in ('total', 'toplevel', 'detailed'): + PrintUsage('Valid counting options are total, toplevel, and detailed') + counting_style = val + elif opt == '--root': + global _root + _root = val + elif opt == '--linelength': + global _line_length + try: + _line_length = int(val) + except ValueError: + PrintUsage('Line length must be digits.') + elif opt == '--extensions': + global _valid_extensions + try: + _valid_extensions = set(val.split(',')) + except ValueError: + PrintUsage('Extensions must be comma seperated list.') + + if not filenames: + PrintUsage('No files were specified.') + + _SetOutputFormat(output_format) + _SetVerboseLevel(verbosity) + _SetFilters(filters) + _SetCountingStyle(counting_style) + + return filenames + + +def main(): + filenames = ParseArguments(sys.argv[1:]) + + # Change stderr to write with replacement characters so we don't die + # if we try to print something containing non-ASCII characters. + sys.stderr = codecs.StreamReaderWriter(sys.stderr, + codecs.getreader('utf8'), + codecs.getwriter('utf8'), + 'replace') + + _cpplint_state.ResetErrorCounts() + for filename in filenames: + ProcessFile(filename, _cpplint_state.verbose_level) + _cpplint_state.PrintErrorCounts() + + sys.exit(_cpplint_state.error_count > 0) + + +if __name__ == '__main__': + main() diff --git a/examples/callback_api/Makefile b/examples/callback_api/Makefile new file mode 100644 index 00000000..45d60d85 --- /dev/null +++ b/examples/callback_api/Makefile @@ -0,0 +1,2 @@ +all: + clang++ -I../../ -Wall -g -o example main.cc diff --git a/examples/callback_api/main.cc b/examples/callback_api/main.cc new file mode 100644 index 00000000..1911501f --- /dev/null +++ b/examples/callback_api/main.cc @@ -0,0 +1,171 @@ +// +// An example of how to use callback API. +// This example is minimum and incomplete. Just showing the usage of callback +// API. +// You need to implement your own Mesh data struct constrution based on this +// example in practical. +// +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include +#include +#include +#include +#include +#include + +typedef struct { + std::vector vertices; + std::vector normals; + std::vector texcoords; + std::vector v_indices; + std::vector vn_indices; + std::vector vt_indices; + + std::vector materials; + +} MyMesh; + +void vertex_cb(void *user_data, float x, float y, float z, float w) { + MyMesh *mesh = reinterpret_cast(user_data); + printf("v[%ld] = %f, %f, %f (w %f)\n", mesh->vertices.size() / 3, x, y, z, w); + + mesh->vertices.push_back(x); + mesh->vertices.push_back(y); + mesh->vertices.push_back(z); + // Discard w +} + +void normal_cb(void *user_data, float x, float y, float z) { + MyMesh *mesh = reinterpret_cast(user_data); + printf("vn[%ld] = %f, %f, %f\n", mesh->normals.size() / 3, x, y, z); + + mesh->normals.push_back(x); + mesh->normals.push_back(y); + mesh->normals.push_back(z); +} + +void texcoord_cb(void *user_data, float x, float y, float z) { + MyMesh *mesh = reinterpret_cast(user_data); + printf("vt[%ld] = %f, %f, %f\n", mesh->texcoords.size() / 3, x, y, z); + + mesh->texcoords.push_back(x); + mesh->texcoords.push_back(y); + mesh->texcoords.push_back(z); +} + +void index_cb(void *user_data, tinyobj::index_t *indices, int num_indices) { + // NOTE: the value of each index is raw value. + // For example, the application must manually adjust the index with offset + // (e.g. v_indices.size()) when the value is negative(whic means relative + // index). + // Also, the first index starts with 1, not 0. + // See fixIndex() function in tiny_obj_loader.h for details. + // Also, 0 is set for the index value which + // does not exist in .obj + MyMesh *mesh = reinterpret_cast(user_data); + + for (int i = 0; i < num_indices; i++) { + tinyobj::index_t idx = indices[i]; + printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), idx.vertex_index, + idx.normal_index, idx.texcoord_index); + + if (idx.vertex_index != 0) { + mesh->v_indices.push_back(idx.vertex_index); + } + if (idx.normal_index != 0) { + mesh->vn_indices.push_back(idx.normal_index); + } + if (idx.texcoord_index != 0) { + mesh->vt_indices.push_back(idx.texcoord_index); + } + } +} + +void usemtl_cb(void *user_data, const char *name, int material_idx) { + MyMesh *mesh = reinterpret_cast(user_data); + if ((material_idx > -1) && (material_idx < mesh->materials.size())) { + printf("usemtl. material id = %d(name = %s)\n", material_idx, + mesh->materials[material_idx].name.c_str()); + } else { + printf("usemtl. name = %s\n", name); + } +} + +void mtllib_cb(void *user_data, const tinyobj::material_t *materials, + int num_materials) { + MyMesh *mesh = reinterpret_cast(user_data); + printf("mtllib. # of materials = %d\n", num_materials); + + for (int i = 0; i < num_materials; i++) { + mesh->materials.push_back(materials[i]); + } +} + +void group_cb(void *user_data, const char **names, int num_names) { + // MyMesh *mesh = reinterpret_cast(user_data); + printf("group : name = \n"); + + for (int i = 0; i < num_names; i++) { + printf(" %s\n", names[i]); + } +} + +void object_cb(void *user_data, const char *name) { + // MyMesh *mesh = reinterpret_cast(user_data); + printf("object : name = %s\n", name); +} + +int main(int argc, char **argv) { + tinyobj::callback_t cb; + cb.vertex_cb = vertex_cb; + cb.normal_cb = normal_cb; + cb.texcoord_cb = texcoord_cb; + cb.index_cb = index_cb; + cb.usemtl_cb = usemtl_cb; + cb.mtllib_cb = mtllib_cb; + cb.group_cb = group_cb; + cb.object_cb = object_cb; + + MyMesh mesh; + std::string warn; + std::string err; + std::string filename = "../../models/cornell_box.obj"; + if (argc > 1) { + filename = std::string(argv[1]); + } + std::ifstream ifs(filename.c_str()); + + if (ifs.fail()) { + std::cerr << "file not found." << std::endl; + return EXIT_FAILURE; + } + + tinyobj::MaterialFileReader mtlReader("../../models/"); + + bool ret = tinyobj::LoadObjWithCallback(ifs, cb, &mesh, &mtlReader, &warn, &err); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + std::cerr << "Failed to parse .obj" << std::endl; + return EXIT_FAILURE; + } + + printf("# of vertices = %ld\n", mesh.vertices.size() / 3); + printf("# of normals = %ld\n", mesh.normals.size() / 3); + printf("# of texcoords = %ld\n", mesh.texcoords.size() / 2); + printf("# of vertex indices = %ld\n", mesh.v_indices.size()); + printf("# of normal indices = %ld\n", mesh.vn_indices.size()); + printf("# of texcoord indices = %ld\n", mesh.vt_indices.size()); + printf("# of materials = %ld\n", mesh.materials.size()); + + return EXIT_SUCCESS; +} diff --git a/examples/obj_sticher/obj_sticher.cc b/examples/obj_sticher/obj_sticher.cc index 1d230b80..1057d470 100644 --- a/examples/obj_sticher/obj_sticher.cc +++ b/examples/obj_sticher/obj_sticher.cc @@ -1,32 +1,70 @@ // -// Stiches multiple .obj files into one .obj. +// Stiches multiple .obj files into one .obj. // -#include "../../tiny_obj_loader.h" #include "obj_writer.h" +#include "../../tiny_obj_loader.h" + #include #include #include #include typedef std::vector Shape; +typedef std::vector Material; +typedef tinyobj::attrib_t Attribute; void StichObjs( - std::vector& out, - const std::vector& shapes) + tinyobj::attrib_t& out_attribute, + std::vector& out_shape, + std::vector& out_material, + const std::vector& attributes, + const std::vector& shapes, + const std::vector& materials) { - int numShapes = 0; - for (size_t i = 0; i < shapes.size(); i++) { - numShapes += (int)shapes[i].size(); + // The amount of attributes, shape-vectors and material-vecotrs should be the same. + if(attributes.size() != shapes.size() && attributes.size() != materials.size()){ + std::cerr << "Size of attributes, shapes and Materials don't fit!" << attributes.size() << " " << shapes.size() <<" " << materials.size() << std::endl;; + exit(1); + } + int num_shapes = 0; + // 4 values (vertices, normals, texcoords, colors) + std::vector num_attributes(4, 0); + int num_materials = 0; + for(int i = 0; i < shapes.size(); i++){ + num_shapes += shapes[i].size(); + } + for(int i = 0; i < attributes.size(); i++){ + num_attributes[0] += attributes[i].vertices.size(); + num_attributes[1] += attributes[i].normals.size(); + num_attributes[2] += attributes[i].texcoords.size(); + num_attributes[3] += attributes[i].colors.size(); + } + for(int i = 0; i < materials.size(); i++){ + num_materials += materials[i].size(); } - printf("Total # of shapes = %d\n", numShapes); - - size_t face_offset = 0; + // More performant, than push_back + out_attribute.vertices.resize(num_attributes[0]); + out_attribute.normals.resize(num_attributes[1]); + out_attribute.texcoords.resize(num_attributes[2]); + out_attribute.colors.resize(num_attributes[3]); + out_shape.resize(num_shapes); + out_material.resize(num_materials); + + int material_id_offset = 0; + int shape_id_offset = 0; + int vertex_idx_offset = 0; + int normal_idx_offset = 0; + int texcoord_idx_offset = 0; + int color_idx_offset = 0; + + // shapes.size() = attributes.size() = materials.size() for (size_t i = 0; i < shapes.size(); i++) { - for (size_t k = 0; k < shapes[i].size(); k++) { + // Copy shapes + for (size_t k = 0; k < shapes[i].size(); k++) { std::string new_name = shapes[i][k].name; // Add suffix char buf[1024]; @@ -34,22 +72,51 @@ StichObjs( new_name += std::string(buf); printf("shape[%ld][%ld].name = %s\n", i, k, shapes[i][k].name.c_str()); - assert((shapes[i][k].mesh.indices.size() % 3) == 0); - assert((shapes[i][k].mesh.positions.size() % 3) == 0); tinyobj::shape_t new_shape = shapes[i][k]; + // Add material offset. + for(size_t f = 0; f < new_shape.mesh.material_ids.size(); f++) { + new_shape.mesh.material_ids[f] += material_id_offset; + } + // Add indices offset. + for(size_t f = 0; f < new_shape.mesh.indices.size(); f++){ + tinyobj::index_t& ref = new_shape.mesh.indices[f]; + if(ref.vertex_index > -1){ + ref.vertex_index += vertex_idx_offset; + } + if(ref.normal_index > -1){ + ref.normal_index += normal_idx_offset; + } + if(ref.texcoord_index > -1){ + ref.texcoord_index += texcoord_idx_offset; + } + } + new_shape.name = new_name; printf("shape[%ld][%ld].new_name = %s\n", i, k, new_shape.name.c_str()); - out.push_back(new_shape); + out_shape[shape_id_offset++] = new_shape; } + + // Copy materials + for (size_t k = 0; k < materials[i].size(); k++) { + out_material[material_id_offset++] = materials[i][k]; + } + + // Copy attributes (3 floats per vertex, 3 floats per normal, 2 floats per texture-coordinate, 3 floats per color) + // You could also include a check here, if the sizes are dividable by 3 (resp. 2), but it's safe to simply assume, they do. + std::copy(attributes[i].vertices.begin(), attributes[i].vertices.end(), out_attribute.vertices.begin() + vertex_idx_offset * 3); + vertex_idx_offset += attributes[i].vertices.size() / 3; + std::copy(attributes[i].normals.begin(), attributes[i].normals.end(), out_attribute.normals.begin() + normal_idx_offset * 3); + normal_idx_offset += attributes[i].normals.size() / 3; + std::copy(attributes[i].texcoords.begin(), attributes[i].texcoords.end(), out_attribute.texcoords.begin() + texcoord_idx_offset * 2); + texcoord_idx_offset += attributes[i].texcoords.size() / 2; + std::copy(attributes[i].colors.begin(), attributes[i].colors.end(), out_attribute.colors.begin() + color_idx_offset); + color_idx_offset += attributes[i].colors.size(); } } -int -main( - int argc, - char **argv) +int main(int argc, char **argv) { if (argc < 3) { printf("Usage: obj_sticher input0.obj input1.obj ... output.obj\n"); @@ -59,25 +126,37 @@ main( int num_objfiles = argc - 2; std::string out_filename = std::string(argv[argc-1]); // last element - std::vector shapes; - shapes.resize(num_objfiles); + std::vector attributes(num_objfiles); + std::vector shapes(num_objfiles); + std::vector materials(num_objfiles); for (int i = 0; i < num_objfiles; i++) { std::cout << "Loading " << argv[i+1] << " ... " << std::flush; - - std::string err = tinyobj::LoadObj(shapes[i], argv[i+1]); + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attributes[i], &shapes[i], &materials[i], &warn, &err, argv[i+1]); + if (!warn.empty()) { + std::cerr << "WARN:" << warn << std::endl; + } + if (!err.empty()) { std::cerr << err << std::endl; + } + if (!ret) { exit(1); } std::cout << "DONE." << std::endl; } - std::vector out; - StichObjs(out, shapes); + Attribute out_attribute; + Shape out_shape; + Material out_material; + StichObjs(out_attribute, out_shape, out_material, attributes, shapes, materials); - bool ret = WriteObj(out_filename, out); + bool coordTransform = true; + bool ret = WriteObj(out_filename, out_attribute, out_shape, out_material, coordTransform); assert(ret); return 0; diff --git a/examples/obj_sticher/obj_writer.cc b/examples/obj_sticher/obj_writer.cc index f44d3f33..31a2c895 100644 --- a/examples/obj_sticher/obj_writer.cc +++ b/examples/obj_sticher/obj_writer.cc @@ -11,31 +11,27 @@ static std::string GetFileBasename(const std::string& FileName) return ""; } -bool WriteMat(const std::string& filename, std::vector shapes) { +bool WriteMat(const std::string& filename, const std::vector& materials) { FILE* fp = fopen(filename.c_str(), "w"); if (!fp) { fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str()); return false; } - std::map mtl_table; + for (size_t i = 0; i < materials.size(); i++) { - for (size_t i = 0; i < shapes.size(); i++) { - mtl_table[shapes[i].material.name] = shapes[i].material; - } - - for (std::map::iterator it = mtl_table.begin(); it != mtl_table.end(); it++) { - - tinyobj::material_t mat = it->second; + tinyobj::material_t mat = materials[i]; fprintf(fp, "newmtl %s\n", mat.name.c_str()); fprintf(fp, "Ka %f %f %f\n", mat.ambient[0], mat.ambient[1], mat.ambient[2]); fprintf(fp, "Kd %f %f %f\n", mat.diffuse[0], mat.diffuse[1], mat.diffuse[2]); fprintf(fp, "Ks %f %f %f\n", mat.specular[0], mat.specular[1], mat.specular[2]); - fprintf(fp, "Kt %f %f %f\n", mat.transmittance[0], mat.specular[1], mat.specular[2]); + fprintf(fp, "Kt %f %f %f\n", mat.transmittance[0], mat.transmittance[1], mat.transmittance[2]); fprintf(fp, "Ke %f %f %f\n", mat.emission[0], mat.emission[1], mat.emission[2]); fprintf(fp, "Ns %f\n", mat.shininess); fprintf(fp, "Ni %f\n", mat.ior); + fprintf(fp, "illum %d\n", mat.illum); + fprintf(fp, "\n"); // @todo { texture } } @@ -44,7 +40,7 @@ bool WriteMat(const std::string& filename, std::vector shapes) return true; } -bool WriteObj(const std::string& filename, std::vector shapes) { +bool WriteObj(const std::string& filename, const tinyobj::attrib_t& attributes, const std::vector& shapes, const std::vector& materials, bool coordTransform) { FILE* fp = fopen(filename.c_str(), "w"); if (!fp) { fprintf(stderr, "Failed to open file [ %s ] for write.\n", filename.c_str()); @@ -54,16 +50,53 @@ bool WriteObj(const std::string& filename, std::vector shapes) std::string basename = GetFileBasename(filename); std::string material_filename = basename + ".mtl"; - int v_offset = 0; - int vn_offset = 0; - int vt_offset = 0; + int prev_material_id = -1; - fprintf(fp, "mtllib %s\n", material_filename.c_str()); + fprintf(fp, "mtllib %s\n\n", material_filename.c_str()); - for (size_t i = 0; i < shapes.size(); i++) { + // facevarying vtx + for (size_t k = 0; k < attributes.vertices.size(); k+=3) { + if (coordTransform) { + fprintf(fp, "v %f %f %f\n", + attributes.vertices[k + 0], + attributes.vertices[k + 2], + -attributes.vertices[k + 1]); + } else { + fprintf(fp, "v %f %f %f\n", + attributes.vertices[k + 0], + attributes.vertices[k + 1], + attributes.vertices[k + 2]); + } + } - bool has_vn = false; - bool has_vt = false; + fprintf(fp, "\n"); + + // facevarying normal + for (size_t k = 0; k < attributes.normals.size(); k += 3) { + if (coordTransform) { + fprintf(fp, "vn %f %f %f\n", + attributes.normals[k + 0], + attributes.normals[k + 2], + -attributes.normals[k + 1]); + } else { + fprintf(fp, "vn %f %f %f\n", + attributes.normals[k + 0], + attributes.normals[k + 1], + attributes.normals[k + 2]); + } + } + + fprintf(fp, "\n"); + + // facevarying texcoord + for (size_t k = 0; k < attributes.texcoords.size(); k += 2) { + fprintf(fp, "vt %f %f\n", + attributes.texcoords[k + 0], + attributes.texcoords[k + 1]); + } + + for (size_t i = 0; i < shapes.size(); i++) { + fprintf(fp, "\n"); if (shapes[i].name.empty()) { fprintf(fp, "g Unknown\n"); @@ -71,76 +104,53 @@ bool WriteObj(const std::string& filename, std::vector shapes) fprintf(fp, "g %s\n", shapes[i].name.c_str()); } - if (!shapes[i].material.name.empty()) { - fprintf(fp, "usemtl %s\n", shapes[i].material.name.c_str()); + bool has_vn = false; + bool has_vt = false; + // Assumes normals and textures are set shape-wise. + if(shapes[i].mesh.indices.size() > 0){ + has_vn = shapes[i].mesh.indices[0].normal_index != -1; + has_vt = shapes[i].mesh.indices[0].texcoord_index != -1; } - // facevarying vtx - for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { - for (int j = 0; j < 3; j++) { - int idx = shapes[i].mesh.indices[3*k+j]; - fprintf(fp, "v %f %f %f\n", - shapes[i].mesh.positions[3*idx+0], - shapes[i].mesh.positions[3*idx+1], - shapes[i].mesh.positions[3*idx+2]); + // face + int face_index = 0; + for (size_t k = 0; k < shapes[i].mesh.indices.size(); k += shapes[i].mesh.num_face_vertices[face_index++]) { + // Check Materials + int material_id = shapes[i].mesh.material_ids[face_index]; + if (material_id != prev_material_id) { + std::string material_name = materials[material_id].name; + fprintf(fp, "usemtl %s\n", material_name.c_str()); + prev_material_id = material_id; } - } - // facevarying normal - if (shapes[i].mesh.normals.size() > 0) { - for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { - for (int j = 0; j < 3; j++) { - int idx = shapes[i].mesh.indices[3*k+j]; - fprintf(fp, "vn %f %f %f\n", - shapes[i].mesh.normals[3*idx+0], - shapes[i].mesh.normals[3*idx+1], - shapes[i].mesh.normals[3*idx+2]); + unsigned char v_per_f = shapes[i].mesh.num_face_vertices[face_index]; + // Imperformant, but if you want to have variable vertices per face, you need some kind of a dynamic loop. + fprintf(fp, "f"); + for(int l = 0; l < v_per_f; l++){ + const tinyobj::index_t& ref = shapes[i].mesh.indices[k + l]; + if(has_vn && has_vt){ + // v0/t0/vn0 + fprintf(fp, " %d/%d/%d", ref.vertex_index + 1, ref.texcoord_index + 1, ref.normal_index + 1); + continue; } - } - } - if (shapes[i].mesh.normals.size() > 0) has_vn = true; - - // facevarying texcoord - if (shapes[i].mesh.texcoords.size() > 0) { - for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { - for (int j = 0; j < 3; j++) { - int idx = shapes[i].mesh.indices[3*k+j]; - fprintf(fp, "vt %f %f\n", - shapes[i].mesh.texcoords[2*idx+0], - shapes[i].mesh.texcoords[2*idx+1]); + if(has_vn && !has_vt){ + // v0//vn0 + fprintf(fp, " %d//%d", ref.vertex_index + 1, ref.normal_index + 1); + continue; + } + if(!has_vn && has_vt){ + // v0/vt0 + fprintf(fp, " %d/%d", ref.vertex_index + 1, ref.texcoord_index + 1); + continue; + } + if(!has_vn && !has_vt){ + // v0 v1 v2 + fprintf(fp, " %d", ref.vertex_index + 1); + continue; } } + fprintf(fp, "\n"); } - if (shapes[i].mesh.texcoords.size() > 0) has_vt = true; - - // face - for (size_t k = 0; k < shapes[i].mesh.indices.size() / 3; k++) { - - // Face index is 1-base. - //int v0 = shapes[i].mesh.indices[3*k+0] + 1 + v_offset; - //int v1 = shapes[i].mesh.indices[3*k+1] + 1 + v_offset; - //int v2 = shapes[i].mesh.indices[3*k+2] + 1 + v_offset; - int v0 = (3*k + 0) + 1 + v_offset; - int v1 = (3*k + 1) + 1 + v_offset; - int v2 = (3*k + 2) + 1 + v_offset; - - if (has_vn && has_vt) { - fprintf(fp, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", - v0, v0, v0, v1, v1, v1, v2, v2, v2); - } else if (has_vn && !has_vt) { - fprintf(fp, "f %d//%d %d//%d %d//%d\n", v0, v0, v1, v1, v2, v2); - } else if (!has_vn && has_vt) { - fprintf(fp, "f %d/%d %d/%d %d/%d\n", v0, v0, v1, v1, v2, v2); - } else { - fprintf(fp, "f %d %d %d\n", v0, v1, v2); - } - - } - - v_offset += shapes[i].mesh.indices.size(); - //vn_offset += shapes[i].mesh.normals.size() / 3; - //vt_offset += shapes[i].mesh.texcoords.size() / 2; - } fclose(fp); @@ -148,9 +158,7 @@ bool WriteObj(const std::string& filename, std::vector shapes) // // Write material file // - bool ret = WriteMat(material_filename, shapes); + bool ret = WriteMat(material_filename, materials); return ret; } - - diff --git a/examples/obj_sticher/obj_writer.h b/examples/obj_sticher/obj_writer.h index e31e0e43..262c6816 100644 --- a/examples/obj_sticher/obj_writer.h +++ b/examples/obj_sticher/obj_writer.h @@ -3,7 +3,6 @@ #include "../../tiny_obj_loader.h" -extern bool WriteObj(const std::string& filename, std::vector shapes); - +extern bool WriteObj(const std::string& filename, const tinyobj::attrib_t& attributes, const std::vector& shapes, const std::vector& materials, bool coordTransform = false); #endif // __OBJ_WRITER_H__ diff --git a/examples/skin_weight/Makefile b/examples/skin_weight/Makefile new file mode 100644 index 00000000..59e4e3c3 --- /dev/null +++ b/examples/skin_weight/Makefile @@ -0,0 +1,2 @@ +all: + clang++ -std=c++11 -o skin_weight -I../../ -g main.cc diff --git a/examples/skin_weight/README.md b/examples/skin_weight/README.md new file mode 100644 index 00000000..800d8472 --- /dev/null +++ b/examples/skin_weight/README.md @@ -0,0 +1,7 @@ +This example printf skin weight of vertex(`vw`). TinyObjLoader extension. + +## Run example + +``` +$ ./skin_weight ../../models/skin-weight.obj +``` diff --git a/examples/skin_weight/main.cc b/examples/skin_weight/main.cc new file mode 100644 index 00000000..a3770afe --- /dev/null +++ b/examples/skin_weight/main.cc @@ -0,0 +1,103 @@ +// +// g++ -g -std=c++11 main.cc +// +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include +#include +#include +#include +#include +#include + +#include // C++11 + +#ifdef __clang__ +#pragma clang diagnostic push +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#endif + +static void ConstructVertexWeight( + const std::vector &vertices, + const std::vector &skin_weights, + std::vector *vertex_skin_weights) +{ + size_t num_vertices = vertices.size() / 3; + + vertex_skin_weights->resize(num_vertices); + + for (size_t i = 0; i < skin_weights.size(); i++) { + const tinyobj::skin_weight_t &skin = skin_weights[i]; + + assert(skin.vertex_id >= 0); + assert(skin.vertex_id < num_vertices); + + (*vertex_skin_weights)[skin.vertex_id] = skin; + } + + // now you can lookup i'th vertex skin weight by `vertex_skin_weights[i]` + + +} + +static bool TestLoadObj(const char* filename, const char* basepath = nullptr, + bool triangulate = true) { + std::cout << "Loading " << filename << std::endl; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename, + basepath, triangulate); + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + if (!ret) { + printf("Failed to load/parse .obj.\n"); + return false; + } + + std::vector vertex_skin_weights; + + ConstructVertexWeight( + attrib.vertices, + attrib.skin_weights, + &vertex_skin_weights); + + for (size_t v = 0; v < vertex_skin_weights.size(); v++) { + std::cout << "vertex[" << v << "] num_weights = " << vertex_skin_weights[v].weightValues.size() << "\n"; + for (size_t w = 0; w < vertex_skin_weights[v].weightValues.size(); w++) { + std::cout << " w[" << w << "] joint = " << vertex_skin_weights[v].weightValues[w].joint_id + << ", weight = " << vertex_skin_weights[v].weightValues[w].weight << "\n"; + } + } + + return true; +} + + +int main(int argc, char** argv) { + if (argc < 2) { + std::cerr << "Need input.obj\n"; + return EXIT_FAILURE; + } + + const char* basepath = nullptr; + if (argc > 2) { + basepath = argv[2]; + } + assert(true == TestLoadObj(argv[1], basepath)); + + return 0; +} diff --git a/examples/viewer/.gitignore b/examples/viewer/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/examples/viewer/.gitignore @@ -0,0 +1 @@ +build diff --git a/examples/viewer/CMakeLists.txt b/examples/viewer/CMakeLists.txt new file mode 100644 index 00000000..df3af268 --- /dev/null +++ b/examples/viewer/CMakeLists.txt @@ -0,0 +1,29 @@ +# cmake -S . -B build && cmake --build build && build/app + +# cmake -S . -B build -G "Ninja" && cmake --build build && build/app +# cmake -S . -B build -G "CodeBlocks - Ninja" && cmake --build build && build/app +# cmake -S . -B build -G "Visual Studio 17 2022" && cmake --build build && build/app + +cmake_minimum_required(VERSION 3.18) +project( app VERSION 0.1 ) + +file(GLOB SOURCE_FILES "*.c*" ) +add_executable(app ${SOURCE_FILES}) + +find_package( OpenGL REQUIRED ) +find_package( glfw3 REQUIRED ) + +set(ADDITIONAL_LIBRARIES "") +if(WIN32) +set(ADDITIONAL_LIBRARIES winmm) +endif() + +set(GLEW_LIBRARY "") +if(UNIX) +set(GLEW_LIBRARY GLEW) +else() +find_package( glew REQUIRED ) +set(GLEW_LIBRARY GLEW::glew) +endif() + +target_link_libraries(${PROJECT_NAME} OpenGL::GL OpenGL::GLU glfw ${ADDITIONAL_LIBRARIES} ${GLEW_LIBRARY} ) diff --git a/examples/viewer/README.md b/examples/viewer/README.md new file mode 100644 index 00000000..76207bfa --- /dev/null +++ b/examples/viewer/README.md @@ -0,0 +1,45 @@ +# Simple .obj viewer with glew + glfw3 + OpenGL + +## Requirements + +* premake5 +* glfw3 +* glew +* xcursor +* xinerama + +## Build on MaCOSX + +Install glfw3 and glew using brew. +Then, + + $ premake5 gmake + $ make + +## Build on Linux + +Set `PKG_CONFIG_PATH` or Edit path to glfw3 and glew in `premake4.lua` + +Then, + + $ premake5 gmake + $ make + +## Build on Windows. + +* Visual Studio 2013 +* Windows 64bit + * 32bit may work. + +Put glfw3 and glew library somewhere and replace include and lib path in `premake4.lua` + +Then, + + > premake5.exe vs2013 + +## TODO + +* [ ] Alpha texturing. +* [ ] Support per-face material. +* [ ] Use shader-based GL rendering. +* [ ] PBR shader support. diff --git a/examples/viewer/premake4.lua b/examples/viewer/premake4.lua new file mode 100644 index 00000000..4e1e54f0 --- /dev/null +++ b/examples/viewer/premake4.lua @@ -0,0 +1,44 @@ +solution "objview" + -- location ( "build" ) + configurations { "Release", "Debug" } + platforms {"native", "x64", "x32"} + + project "objview" + + kind "ConsoleApp" + language "C++" + files { "viewer.cc", "trackball.cc" } + includedirs { "./" } + includedirs { "../../" } + + configuration { "linux" } + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + linkoptions { "-pthread" } + + configuration { "windows" } + -- Path to GLFW3 + includedirs { '../../../../local/glfw-3.1.2.bin.WIN64/include' } + libdirs { '../../../../local/glfw-3.1.2.bin.WIN64/lib-vc2013' } + -- Path to GLEW + includedirs { '../../../../local/glew-1.13.0/include' } + libdirs { '../../../../local/glew-1.13.0/lib/Release/x64' } + + links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } + defines { "_CRT_SECURE_NO_WARNINGS" } + + configuration { "macosx" } + includedirs { "/usr/local/include" } + buildoptions { "-Wno-deprecated-declarations" } + libdirs { "/usr/local/lib" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } + + configuration "Debug" + defines { "DEBUG" } + flags { "Symbols", "ExtraWarnings"} + + configuration "Release" + defines { "NDEBUG" } + flags { "Optimize", "ExtraWarnings"} + diff --git a/examples/viewer/stb_image.h b/examples/viewer/stb_image.h new file mode 100644 index 00000000..d60371b9 --- /dev/null +++ b/examples/viewer/stb_image.h @@ -0,0 +1,7897 @@ +/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/examples/viewer/trackball.cc b/examples/viewer/trackball.cc new file mode 100644 index 00000000..86ff3b3f --- /dev/null +++ b/examples/viewer/trackball.cc @@ -0,0 +1,292 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * Trackball code: + * + * Implementation of a virtual trackball. + * Implemented by Gavin Bell, lots of ideas from Thant Tessman and + * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129. + * + * Vector manip code: + * + * Original code from: + * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli + * + * Much mucking with by: + * Gavin Bell + */ +#include +#include "trackball.h" + +/* + * This size should really be based on the distance from the center of + * rotation to the point on the object underneath the mouse. That + * point would then track the mouse as closely as possible. This is a + * simple example, though, so that is left as an Exercise for the + * Programmer. + */ +#define TRACKBALLSIZE (0.8) + +/* + * Local function prototypes (not defined in trackball.h) + */ +static float tb_project_to_sphere(float, float, float); +static void normalize_quat(float[4]); + +static void vzero(float *v) { + v[0] = 0.0; + v[1] = 0.0; + v[2] = 0.0; +} + +static void vset(float *v, float x, float y, float z) { + v[0] = x; + v[1] = y; + v[2] = z; +} + +static void vsub(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] - src2[0]; + dst[1] = src1[1] - src2[1]; + dst[2] = src1[2] - src2[2]; +} + +static void vcopy(const float *v1, float *v2) { + register int i; + for (i = 0; i < 3; i++) + v2[i] = v1[i]; +} + +static void vcross(const float *v1, const float *v2, float *cross) { + float temp[3]; + + temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]); + temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]); + temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]); + vcopy(temp, cross); +} + +static float vlength(const float *v) { + return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +} + +static void vscale(float *v, float div) { + v[0] *= div; + v[1] *= div; + v[2] *= div; +} + +static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); } + +static float vdot(const float *v1, const float *v2) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +static void vadd(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] + src2[0]; + dst[1] = src1[1] + src2[1]; + dst[2] = src1[2] + src2[2]; +} + +/* + * Ok, simulate a track-ball. Project the points onto the virtual + * trackball, then figure out the axis of rotation, which is the cross + * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0) + * Note: This is a deformed trackball-- is a trackball in the center, + * but is deformed into a hyperbolic sheet of rotation away from the + * center. This particular function was chosen after trying out + * several variations. + * + * It is assumed that the arguments to this routine are in the range + * (-1.0 ... 1.0) + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) { + float a[3]; /* Axis of rotation */ + float phi; /* how much to rotate about axis */ + float p1[3], p2[3], d[3]; + float t; + + if (p1x == p2x && p1y == p2y) { + /* Zero rotation */ + vzero(q); + q[3] = 1.0; + return; + } + + /* + * First, figure out z-coordinates for projection of P1 and P2 to + * deformed sphere + */ + vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y)); + vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y)); + + /* + * Now, we want the cross product of P1 and P2 + */ + vcross(p2, p1, a); + + /* + * Figure out how much to rotate around that axis. + */ + vsub(p1, p2, d); + t = vlength(d) / (2.0 * TRACKBALLSIZE); + + /* + * Avoid problems with out-of-control values... + */ + if (t > 1.0) + t = 1.0; + if (t < -1.0) + t = -1.0; + phi = 2.0 * asin(t); + + axis_to_quat(a, phi, q); +} + +/* + * Given an axis and angle, compute quaternion. + */ +void axis_to_quat(float a[3], float phi, float q[4]) { + vnormal(a); + vcopy(a, q); + vscale(q, sin(phi / 2.0)); + q[3] = cos(phi / 2.0); +} + +/* + * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet + * if we are away from the center of the sphere. + */ +static float tb_project_to_sphere(float r, float x, float y) { + float d, t, z; + + d = sqrt(x * x + y * y); + if (d < r * 0.70710678118654752440) { /* Inside sphere */ + z = sqrt(r * r - d * d); + } else { /* On hyperbola */ + t = r / 1.41421356237309504880; + z = t * t / d; + } + return z; +} + +/* + * Given two rotations, e1 and e2, expressed as quaternion rotations, + * figure out the equivalent single rotation and stuff it into dest. + * + * This routine also normalizes the result every RENORMCOUNT times it is + * called, to keep error from creeping in. + * + * NOTE: This routine is written so that q1 or q2 may be the same + * as dest (or each other). + */ + +#define RENORMCOUNT 97 + +void add_quats(float q1[4], float q2[4], float dest[4]) { + static int count = 0; + float t1[4], t2[4], t3[4]; + float tf[4]; + + vcopy(q1, t1); + vscale(t1, q2[3]); + + vcopy(q2, t2); + vscale(t2, q1[3]); + + vcross(q2, q1, t3); + vadd(t1, t2, tf); + vadd(t3, tf, tf); + tf[3] = q1[3] * q2[3] - vdot(q1, q2); + + dest[0] = tf[0]; + dest[1] = tf[1]; + dest[2] = tf[2]; + dest[3] = tf[3]; + + if (++count > RENORMCOUNT) { + count = 0; + normalize_quat(dest); + } +} + +/* + * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0 + * If they don't add up to 1.0, dividing by their magnitued will + * renormalize them. + * + * Note: See the following for more information on quaternions: + * + * - Shoemake, K., Animating rotation with quaternion curves, Computer + * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985. + * - Pletinckx, D., Quaternion calculus as a basic tool in computer + * graphics, The Visual Computer 5, 2-13, 1989. + */ +static void normalize_quat(float q[4]) { + int i; + float mag; + + mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]); + for (i = 0; i < 4; i++) + q[i] /= mag; +} + +/* + * Build a rotation matrix, given a quaternion rotation. + * + */ +void build_rotmatrix(float m[4][4], const float q[4]) { + m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]); + m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]); + m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]); + m[0][3] = 0.0; + + m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]); + m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]); + m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]); + m[1][3] = 0.0; + + m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]); + m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]); + m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]); + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; +} diff --git a/examples/viewer/trackball.h b/examples/viewer/trackball.h new file mode 100644 index 00000000..b1f94374 --- /dev/null +++ b/examples/viewer/trackball.h @@ -0,0 +1,75 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * trackball.h + * A virtual trackball implementation + * Written by Gavin Bell for Silicon Graphics, November 1988. + */ + +/* + * Pass the x and y coordinates of the last and current positions of + * the mouse, scaled so they are from (-1.0 ... 1.0). + * + * The resulting rotation is returned as a quaternion rotation in the + * first paramater. + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y); + +void negate_quat(float *q, float *qn); + +/* + * Given two quaternions, add them together to get a third quaternion. + * Adding quaternions to get a compound rotation is analagous to adding + * translations to get a compound translation. When incrementally + * adding rotations, the first argument here should be the new + * rotation, the second and third the total rotation (which will be + * over-written with the resulting new total rotation). + */ +void add_quats(float *q1, float *q2, float *dest); + +/* + * A useful function, builds a rotation matrix in Matrix based on + * given quaternion. + */ +void build_rotmatrix(float m[4][4], const float q[4]); + +/* + * This function computes a quaternion based on an axis (defined by + * the given vector) and an angle about which to rotate. The angle is + * expressed in radians. The result is put into the third argument. + */ +void axis_to_quat(float a[3], float phi, float q[4]); diff --git a/examples/viewer/viewer.cc b/examples/viewer/viewer.cc new file mode 100644 index 00000000..875cf181 --- /dev/null +++ b/examples/viewer/viewer.cc @@ -0,0 +1,1085 @@ +// +// Simple .obj viewer(vertex only) +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#define TINYOBJLOADER_IMPLEMENTATION +// TINYOBJLOADER_USE_MAPBOX_EARCUT: Enable better triangulation. Requires C++11 +//#define TINYOBJLOADER_USE_MAPBOX_EARCUT +#include "../../tiny_obj_loader.h" + +#include "trackball.h" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _WIN32 +#ifdef __cplusplus +extern "C" { +#endif +#include + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#include +#ifdef __cplusplus +} +#endif +#pragma comment(lib, "winmm.lib") +#else +#if defined(__unix__) || defined(__APPLE__) +#include +#else +#include +#endif +#endif + +class timerutil { + public: +#ifdef _WIN32 + typedef DWORD time_t; + + timerutil() { ::timeBeginPeriod(1); } + ~timerutil() { ::timeEndPeriod(1); } + + void start() { t_[0] = ::timeGetTime(); } + void end() { t_[1] = ::timeGetTime(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / 1000); } + time_t msec() { return (time_t)((t_[1] - t_[0])); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000); } + time_t current() { return ::timeGetTime(); } + +#else +#if defined(__unix__) || defined(__APPLE__) + typedef unsigned long int time_t; + + void start() { gettimeofday(tv + 0, &tz); } + void end() { gettimeofday(tv + 1, &tz); } + + time_t sec() { return (time_t)(tv[1].tv_sec - tv[0].tv_sec); } + time_t msec() { + return this->sec() * 1000 + + (time_t)((tv[1].tv_usec - tv[0].tv_usec) / 1000); + } + time_t usec() { + return this->sec() * 1000000 + (time_t)(tv[1].tv_usec - tv[0].tv_usec); + } + time_t current() { + struct timeval t; + gettimeofday(&t, NULL); + return (time_t)(t.tv_sec * 1000 + t.tv_usec); + } + +#else // C timer + // using namespace std; + typedef clock_t time_t; + + void start() { t_[0] = clock(); } + void end() { t_[1] = clock(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / CLOCKS_PER_SEC); } + time_t msec() { return (time_t)((t_[1] - t_[0]) * 1000 / CLOCKS_PER_SEC); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000000 / CLOCKS_PER_SEC); } + time_t current() { return (time_t)clock(); } + +#endif +#endif + + private: +#ifdef _WIN32 + DWORD t_[2]; +#else +#if defined(__unix__) || defined(__APPLE__) + struct timeval tv[2]; + struct timezone tz; +#else + time_t t_[2]; +#endif +#endif +}; + +typedef struct { + GLuint vb_id; // vertex buffer id + int numTriangles; + size_t material_id; +} DrawObject; + +std::vector gDrawObjects; + +int width = 768; +int height = 768; + +double prevMouseX, prevMouseY; +bool mouseLeftPressed; +bool mouseMiddlePressed; +bool mouseRightPressed; +float curr_quat[4]; +float prev_quat[4]; +float eye[3], lookat[3], up[3]; +bool g_show_wire = true; +bool g_cull_face = false; + +GLFWwindow* window; + +static std::string GetBaseDir(const std::string& filepath) { + if (filepath.find_last_of("/\\") != std::string::npos) + return filepath.substr(0, filepath.find_last_of("/\\")); + return ""; +} + +static bool FileExists(const std::string& abs_filename) { + bool ret; + FILE* fp = fopen(abs_filename.c_str(), "rb"); + if (fp) { + ret = true; + fclose(fp); + } else { + ret = false; + } + + return ret; +} + +static void CheckErrors(std::string desc) { + GLenum e = glGetError(); + if (e != GL_NO_ERROR) { + fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); + exit(20); + } +} + +static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { + float v10[3]; + v10[0] = v1[0] - v0[0]; + v10[1] = v1[1] - v0[1]; + v10[2] = v1[2] - v0[2]; + + float v20[3]; + v20[0] = v2[0] - v0[0]; + v20[1] = v2[1] - v0[1]; + v20[2] = v2[2] - v0[2]; + + N[0] = v10[1] * v20[2] - v10[2] * v20[1]; + N[1] = v10[2] * v20[0] - v10[0] * v20[2]; + N[2] = v10[0] * v20[1] - v10[1] * v20[0]; + + float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + N[0] /= len; + N[1] /= len; + N[2] /= len; + } +} + +namespace // Local utility functions +{ +struct vec3 { + float v[3]; + vec3() { + v[0] = 0.0f; + v[1] = 0.0f; + v[2] = 0.0f; + } +}; + +void normalizeVector(vec3 &v) { + float len2 = v.v[0] * v.v[0] + v.v[1] * v.v[1] + v.v[2] * v.v[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + v.v[0] /= len; + v.v[1] /= len; + v.v[2] /= len; + } +} + +/* + There are 2 approaches here to automatically generating vertex normals. The + old approach (computeSmoothingNormals) doesn't handle multiple smoothing + groups properly, as it effectively merges all smoothing groups present in the + OBJ file into a single group. However, it can be useful when the OBJ file + contains vertex normals which you want to use, but is missing some, as it + will attempt to fill in the missing normals without generating new shapes. + + The new approach (computeSmoothingShapes, computeAllSmoothingNormals) handles + multiple smoothing groups but is a bit more complicated, as handling this + correctly requires potentially generating new vertices (and hence shapes). + In general, the new approach is most useful if your OBJ file is missing + vertex normals entirely, and instead relies on smoothing groups to correctly + generate them as a pre-process. That said, it can be used to reliably + generate vertex normals in the general case. If you want to always generate + normals in this way, simply force set regen_all_normals to true below. By + default, it's only true when there are no vertex normals present. One other + thing to keep in mind is that the statistics printed apply to the model + *prior* to shape regeneration, so you'd need to print them again if you want + to see the new statistics. + + TODO(syoyo): import computeSmoothingShapes and computeAllSmoothingNormals to + tinyobjloader as utility functions. +*/ + +// Check if `mesh_t` contains smoothing group id. +bool hasSmoothingGroup(const tinyobj::shape_t& shape) +{ + for (size_t i = 0; i < shape.mesh.smoothing_group_ids.size(); i++) { + if (shape.mesh.smoothing_group_ids[i] > 0) { + return true; + } + } + return false; +} + +void computeSmoothingNormals(const tinyobj::attrib_t& attrib, const tinyobj::shape_t& shape, + std::map& smoothVertexNormals) { + smoothVertexNormals.clear(); + std::map::iterator iter; + + for (size_t f = 0; f < shape.mesh.indices.size() / 3; f++) { + // Get the three indexes of the face (all faces are triangular) + tinyobj::index_t idx0 = shape.mesh.indices[3 * f + 0]; + tinyobj::index_t idx1 = shape.mesh.indices[3 * f + 1]; + tinyobj::index_t idx2 = shape.mesh.indices[3 * f + 2]; + + // Get the three vertex indexes and coordinates + int vi[3]; // indexes + float v[3][3]; // coordinates + + for (int k = 0; k < 3; k++) { + vi[0] = idx0.vertex_index; + vi[1] = idx1.vertex_index; + vi[2] = idx2.vertex_index; + assert(vi[0] >= 0); + assert(vi[1] >= 0); + assert(vi[2] >= 0); + + v[0][k] = attrib.vertices[3 * vi[0] + k]; + v[1][k] = attrib.vertices[3 * vi[1] + k]; + v[2][k] = attrib.vertices[3 * vi[2] + k]; + } + + // Compute the normal of the face + float normal[3]; + CalcNormal(normal, v[0], v[1], v[2]); + + // Add the normal to the three vertexes + for (size_t i = 0; i < 3; ++i) { + iter = smoothVertexNormals.find(vi[i]); + if (iter != smoothVertexNormals.end()) { + // add + iter->second.v[0] += normal[0]; + iter->second.v[1] += normal[1]; + iter->second.v[2] += normal[2]; + } else { + smoothVertexNormals[vi[i]].v[0] = normal[0]; + smoothVertexNormals[vi[i]].v[1] = normal[1]; + smoothVertexNormals[vi[i]].v[2] = normal[2]; + } + } + + } // f + + // Normalize the normals, that is, make them unit vectors + for (iter = smoothVertexNormals.begin(); iter != smoothVertexNormals.end(); + iter++) { + normalizeVector(iter->second); + } + +} // computeSmoothingNormals + +static void computeAllSmoothingNormals(tinyobj::attrib_t& attrib, + std::vector& shapes) { + vec3 p[3]; + for (size_t s = 0, slen = shapes.size(); s < slen; ++s) { + const tinyobj::shape_t& shape(shapes[s]); + size_t facecount = shape.mesh.num_face_vertices.size(); + assert(shape.mesh.smoothing_group_ids.size()); + + for (size_t f = 0, flen = facecount; f < flen; ++f) { + for (unsigned int v = 0; v < 3; ++v) { + tinyobj::index_t idx = shape.mesh.indices[3*f + v]; + assert(idx.vertex_index != -1); + p[v].v[0] = attrib.vertices[3*idx.vertex_index ]; + p[v].v[1] = attrib.vertices[3*idx.vertex_index+1]; + p[v].v[2] = attrib.vertices[3*idx.vertex_index+2]; + } + + // cross(p[1] - p[0], p[2] - p[0]) + float nx = (p[1].v[1] - p[0].v[1]) * (p[2].v[2] - p[0].v[2]) - + (p[1].v[2] - p[0].v[2]) * (p[2].v[1] - p[0].v[1]); + float ny = (p[1].v[2] - p[0].v[2]) * (p[2].v[0] - p[0].v[0]) - + (p[1].v[0] - p[0].v[0]) * (p[2].v[2] - p[0].v[2]); + float nz = (p[1].v[0] - p[0].v[0]) * (p[2].v[1] - p[0].v[1]) - + (p[1].v[1] - p[0].v[1]) * (p[2].v[0] - p[0].v[0]); + + // Don't normalize here. + for (unsigned int v = 0; v < 3; ++v) { + tinyobj::index_t idx = shape.mesh.indices[3*f + v]; + attrib.normals[3*idx.normal_index ] += nx; + attrib.normals[3*idx.normal_index+1] += ny; + attrib.normals[3*idx.normal_index+2] += nz; + } + } + } + + assert(attrib.normals.size() % 3 == 0); + for (size_t i = 0, nlen = attrib.normals.size() / 3; i < nlen; ++i) { + tinyobj::real_t& nx = attrib.normals[3*i ]; + tinyobj::real_t& ny = attrib.normals[3*i+1]; + tinyobj::real_t& nz = attrib.normals[3*i+2]; + tinyobj::real_t len = sqrtf(nx*nx + ny*ny + nz*nz); + tinyobj::real_t scale = len == 0 ? 0 : 1 / len; + nx *= scale; + ny *= scale; + nz *= scale; + } +} + +static void computeSmoothingShape(tinyobj::attrib_t& inattrib, tinyobj::shape_t& inshape, + std::vector>& sortedids, + unsigned int idbegin, unsigned int idend, + std::vector& outshapes, + tinyobj::attrib_t& outattrib) { + unsigned int sgroupid = sortedids[idbegin].first; + bool hasmaterials = inshape.mesh.material_ids.size(); + // Make a new shape from the set of faces in the range [idbegin, idend). + outshapes.emplace_back(); + tinyobj::shape_t& outshape = outshapes.back(); + outshape.name = inshape.name; + // Skip lines and points. + + std::unordered_map remap; + for (unsigned int id = idbegin; id < idend; ++id) { + unsigned int face = sortedids[id].second; + + outshape.mesh.num_face_vertices.push_back(3); // always triangles + if (hasmaterials) + outshape.mesh.material_ids.push_back(inshape.mesh.material_ids[face]); + outshape.mesh.smoothing_group_ids.push_back(sgroupid); + // Skip tags. + + for (unsigned int v = 0; v < 3; ++v) { + tinyobj::index_t inidx = inshape.mesh.indices[3*face + v], outidx; + assert(inidx.vertex_index != -1); + auto iter = remap.find(inidx.vertex_index); + // Smooth group 0 disables smoothing so no shared vertices in that case. + if (sgroupid && iter != remap.end()) { + outidx.vertex_index = (*iter).second; + outidx.normal_index = outidx.vertex_index; + outidx.texcoord_index = (inidx.texcoord_index == -1) ? -1 : outidx.vertex_index; + } + else { + assert(outattrib.vertices.size() % 3 == 0); + unsigned int offset = static_cast(outattrib.vertices.size() / 3); + outidx.vertex_index = outidx.normal_index = offset; + outidx.texcoord_index = (inidx.texcoord_index == -1) ? -1 : offset; + outattrib.vertices.push_back(inattrib.vertices[3*inidx.vertex_index ]); + outattrib.vertices.push_back(inattrib.vertices[3*inidx.vertex_index+1]); + outattrib.vertices.push_back(inattrib.vertices[3*inidx.vertex_index+2]); + outattrib.normals.push_back(0.0f); + outattrib.normals.push_back(0.0f); + outattrib.normals.push_back(0.0f); + if (inidx.texcoord_index != -1) { + outattrib.texcoords.push_back(inattrib.texcoords[2*inidx.texcoord_index ]); + outattrib.texcoords.push_back(inattrib.texcoords[2*inidx.texcoord_index+1]); + } + remap[inidx.vertex_index] = offset; + } + outshape.mesh.indices.push_back(outidx); + } + } +} + +static void computeSmoothingShapes(tinyobj::attrib_t &inattrib, + std::vector& inshapes, + std::vector& outshapes, + tinyobj::attrib_t& outattrib) { + for (size_t s = 0, slen = inshapes.size() ; s < slen; ++s) { + tinyobj::shape_t& inshape = inshapes[s]; + + unsigned int numfaces = static_cast(inshape.mesh.smoothing_group_ids.size()); + assert(numfaces); + std::vector> sortedids(numfaces); + for (unsigned int i = 0; i < numfaces; ++i) + sortedids[i] = std::make_pair(inshape.mesh.smoothing_group_ids[i], i); + sort(sortedids.begin(), sortedids.end()); + + unsigned int activeid = sortedids[0].first; + unsigned int id = activeid, idbegin = 0, idend = 0; + // Faces are now bundled by smoothing group id, create shapes from these. + while (idbegin < numfaces) { + while (activeid == id && ++idend < numfaces) + id = sortedids[idend].first; + computeSmoothingShape(inattrib, inshape, sortedids, idbegin, idend, + outshapes, outattrib); + activeid = id; + idbegin = idend; + } + } +} + +} // namespace + +static bool LoadObjAndConvert(float bmin[3], float bmax[3], + std::vector* drawObjects, + std::vector& materials, + std::map& textures, + const char* filename) { + tinyobj::attrib_t inattrib; + std::vector inshapes; + + timerutil tm; + + tm.start(); + + std::string base_dir = GetBaseDir(filename); + if (base_dir.empty()) { + base_dir = "."; + } +#ifdef _WIN32 + base_dir += "\\"; +#else + base_dir += "/"; +#endif + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&inattrib, &inshapes, &materials, &warn, &err, filename, + base_dir.c_str()); + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + if (!err.empty()) { + std::cerr << err << std::endl; + } + + tm.end(); + + if (!ret) { + std::cerr << "Failed to load " << filename << std::endl; + return false; + } + + printf("Parsing time: %d [ms]\n", (int)tm.msec()); + + printf("# of vertices = %d\n", (int)(inattrib.vertices.size()) / 3); + printf("# of normals = %d\n", (int)(inattrib.normals.size()) / 3); + printf("# of texcoords = %d\n", (int)(inattrib.texcoords.size()) / 2); + printf("# of materials = %d\n", (int)materials.size()); + printf("# of shapes = %d\n", (int)inshapes.size()); + + // Append `default` material + materials.push_back(tinyobj::material_t()); + + for (size_t i = 0; i < materials.size(); i++) { + printf("material[%d].diffuse_texname = %s\n", int(i), + materials[i].diffuse_texname.c_str()); + } + + // Load diffuse textures + { + for (size_t m = 0; m < materials.size(); m++) { + tinyobj::material_t* mp = &materials[m]; + + if (mp->diffuse_texname.length() > 0) { + // Only load the texture if it is not already loaded + if (textures.find(mp->diffuse_texname) == textures.end()) { + GLuint texture_id; + int w, h; + int comp; + + std::string texture_filename = mp->diffuse_texname; + if (!FileExists(texture_filename)) { + // Append base dir. + texture_filename = base_dir + mp->diffuse_texname; + if (!FileExists(texture_filename)) { + std::cerr << "Unable to find file: " << mp->diffuse_texname + << std::endl; + exit(1); + } + } + + unsigned char* image = + stbi_load(texture_filename.c_str(), &w, &h, &comp, STBI_default); + if (!image) { + std::cerr << "Unable to load texture: " << texture_filename + << std::endl; + exit(1); + } + std::cout << "Loaded texture: " << texture_filename << ", w = " << w + << ", h = " << h << ", comp = " << comp << std::endl; + + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if (comp == 3) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, + GL_UNSIGNED_BYTE, image); + } else if (comp == 4) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, + GL_UNSIGNED_BYTE, image); + } else { + assert(0); // TODO + } + glBindTexture(GL_TEXTURE_2D, 0); + stbi_image_free(image); + textures.insert(std::make_pair(mp->diffuse_texname, texture_id)); + } + } + } + } + + bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); + bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); + + bool regen_all_normals = inattrib.normals.size() == 0; + tinyobj::attrib_t outattrib; + std::vector outshapes; + if (regen_all_normals) { + computeSmoothingShapes(inattrib, inshapes, outshapes, outattrib); + computeAllSmoothingNormals(outattrib, outshapes); + } + + std::vector& shapes = regen_all_normals ? outshapes : inshapes; + tinyobj::attrib_t& attrib = regen_all_normals ? outattrib : inattrib; + + { + for (size_t s = 0; s < shapes.size(); s++) { + DrawObject o; + std::vector buffer; // pos(3float), normal(3float), color(3float) + + // Check for smoothing group and compute smoothing normals + std::map smoothVertexNormals; + if (!regen_all_normals && (hasSmoothingGroup(shapes[s]) > 0)) { + std::cout << "Compute smoothingNormal for shape [" << s << "]" << std::endl; + computeSmoothingNormals(attrib, shapes[s], smoothVertexNormals); + } + + for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) { + tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0]; + tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1]; + tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2]; + + int current_material_id = shapes[s].mesh.material_ids[f]; + + if ((current_material_id < 0) || + (current_material_id >= static_cast(materials.size()))) { + // Invaid material ID. Use default material. + current_material_id = + materials.size() - + 1; // Default material is added to the last item in `materials`. + } + // if (current_material_id >= materials.size()) { + // std::cerr << "Invalid material index: " << current_material_id << + // std::endl; + //} + // + float diffuse[3]; + for (size_t i = 0; i < 3; i++) { + diffuse[i] = materials[current_material_id].diffuse[i]; + } + float tc[3][2]; + if (attrib.texcoords.size() > 0) { + if ((idx0.texcoord_index < 0) || (idx1.texcoord_index < 0) || + (idx2.texcoord_index < 0)) { + // face does not contain valid uv index. + tc[0][0] = 0.0f; + tc[0][1] = 0.0f; + tc[1][0] = 0.0f; + tc[1][1] = 0.0f; + tc[2][0] = 0.0f; + tc[2][1] = 0.0f; + } else { + assert(attrib.texcoords.size() > + size_t(2 * idx0.texcoord_index + 1)); + assert(attrib.texcoords.size() > + size_t(2 * idx1.texcoord_index + 1)); + assert(attrib.texcoords.size() > + size_t(2 * idx2.texcoord_index + 1)); + + // Flip Y coord. + tc[0][0] = attrib.texcoords[2 * idx0.texcoord_index]; + tc[0][1] = 1.0f - attrib.texcoords[2 * idx0.texcoord_index + 1]; + tc[1][0] = attrib.texcoords[2 * idx1.texcoord_index]; + tc[1][1] = 1.0f - attrib.texcoords[2 * idx1.texcoord_index + 1]; + tc[2][0] = attrib.texcoords[2 * idx2.texcoord_index]; + tc[2][1] = 1.0f - attrib.texcoords[2 * idx2.texcoord_index + 1]; + } + } else { + tc[0][0] = 0.0f; + tc[0][1] = 0.0f; + tc[1][0] = 0.0f; + tc[1][1] = 0.0f; + tc[2][0] = 0.0f; + tc[2][1] = 0.0f; + } + + float v[3][3]; + for (int k = 0; k < 3; k++) { + int f0 = idx0.vertex_index; + int f1 = idx1.vertex_index; + int f2 = idx2.vertex_index; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); + + v[0][k] = attrib.vertices[3 * f0 + k]; + v[1][k] = attrib.vertices[3 * f1 + k]; + v[2][k] = attrib.vertices[3 * f2 + k]; + bmin[k] = std::min(v[0][k], bmin[k]); + bmin[k] = std::min(v[1][k], bmin[k]); + bmin[k] = std::min(v[2][k], bmin[k]); + bmax[k] = std::max(v[0][k], bmax[k]); + bmax[k] = std::max(v[1][k], bmax[k]); + bmax[k] = std::max(v[2][k], bmax[k]); + } + + float n[3][3]; + { + bool invalid_normal_index = false; + if (attrib.normals.size() > 0) { + int nf0 = idx0.normal_index; + int nf1 = idx1.normal_index; + int nf2 = idx2.normal_index; + + if ((nf0 < 0) || (nf1 < 0) || (nf2 < 0)) { + // normal index is missing from this face. + invalid_normal_index = true; + } else { + for (int k = 0; k < 3; k++) { + assert(size_t(3 * nf0 + k) < attrib.normals.size()); + assert(size_t(3 * nf1 + k) < attrib.normals.size()); + assert(size_t(3 * nf2 + k) < attrib.normals.size()); + n[0][k] = attrib.normals[3 * nf0 + k]; + n[1][k] = attrib.normals[3 * nf1 + k]; + n[2][k] = attrib.normals[3 * nf2 + k]; + } + } + } else { + invalid_normal_index = true; + } + + if (invalid_normal_index && !smoothVertexNormals.empty()) { + // Use smoothing normals + int f0 = idx0.vertex_index; + int f1 = idx1.vertex_index; + int f2 = idx2.vertex_index; + + if (f0 >= 0 && f1 >= 0 && f2 >= 0) { + n[0][0] = smoothVertexNormals[f0].v[0]; + n[0][1] = smoothVertexNormals[f0].v[1]; + n[0][2] = smoothVertexNormals[f0].v[2]; + + n[1][0] = smoothVertexNormals[f1].v[0]; + n[1][1] = smoothVertexNormals[f1].v[1]; + n[1][2] = smoothVertexNormals[f1].v[2]; + + n[2][0] = smoothVertexNormals[f2].v[0]; + n[2][1] = smoothVertexNormals[f2].v[1]; + n[2][2] = smoothVertexNormals[f2].v[2]; + + invalid_normal_index = false; + } + } + + if (invalid_normal_index) { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; + n[1][1] = n[0][1]; + n[1][2] = n[0][2]; + n[2][0] = n[0][0]; + n[2][1] = n[0][1]; + n[2][2] = n[0][2]; + } + } + + for (int k = 0; k < 3; k++) { + buffer.push_back(v[k][0]); + buffer.push_back(v[k][1]); + buffer.push_back(v[k][2]); + buffer.push_back(n[k][0]); + buffer.push_back(n[k][1]); + buffer.push_back(n[k][2]); + // Combine normal and diffuse to get color. + float normal_factor = 0.2; + float diffuse_factor = 1 - normal_factor; + float c[3] = {n[k][0] * normal_factor + diffuse[0] * diffuse_factor, + n[k][1] * normal_factor + diffuse[1] * diffuse_factor, + n[k][2] * normal_factor + diffuse[2] * diffuse_factor}; + float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + c[0] /= len; + c[1] /= len; + c[2] /= len; + } + buffer.push_back(c[0] * 0.5 + 0.5); + buffer.push_back(c[1] * 0.5 + 0.5); + buffer.push_back(c[2] * 0.5 + 0.5); + + buffer.push_back(tc[k][0]); + buffer.push_back(tc[k][1]); + } + } + + o.vb_id = 0; + o.numTriangles = 0; + + // OpenGL viewer does not support texturing with per-face material. + if (shapes[s].mesh.material_ids.size() > 0 && + shapes[s].mesh.material_ids.size() > s) { + o.material_id = shapes[s].mesh.material_ids[0]; // use the material ID + // of the first face. + } else { + o.material_id = materials.size() - 1; // = ID for default material. + } + printf("shape[%d] material_id %d\n", int(s), int(o.material_id)); + + if (buffer.size() > 0) { + glGenBuffers(1, &o.vb_id); + glBindBuffer(GL_ARRAY_BUFFER, o.vb_id); + glBufferData(GL_ARRAY_BUFFER, buffer.size() * sizeof(float), + &buffer.at(0), GL_STATIC_DRAW); + o.numTriangles = buffer.size() / (3 + 3 + 3 + 2) / + 3; // 3:vtx, 3:normal, 3:col, 2:texcoord + + printf("shape[%d] # of triangles = %d\n", static_cast(s), + o.numTriangles); + } + + drawObjects->push_back(o); + } + } + + printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]); + printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); + + return true; +} + +static void reshapeFunc(GLFWwindow* window, int w, int h) { + int fb_w, fb_h; + // Get actual framebuffer size. + glfwGetFramebufferSize(window, &fb_w, &fb_h); + + glViewport(0, 0, fb_w, fb_h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + width = w; + height = h; +} + +static void keyboardFunc(GLFWwindow* window, int key, int scancode, int action, + int mods) { + (void)window; + (void)scancode; + (void)mods; + if (action == GLFW_PRESS || action == GLFW_REPEAT) { + // Move camera + float mv_x = 0, mv_y = 0, mv_z = 0; + if (key == GLFW_KEY_K) + mv_x += 1; + else if (key == GLFW_KEY_J) + mv_x += -1; + else if (key == GLFW_KEY_L) + mv_y += 1; + else if (key == GLFW_KEY_H) + mv_y += -1; + else if (key == GLFW_KEY_P) + mv_z += 1; + else if (key == GLFW_KEY_N) + mv_z += -1; + // camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05); + // Close window + if (key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) { + glfwSetWindowShouldClose(window, GL_TRUE); + } + + if (key == GLFW_KEY_W) { + // toggle wireframe + g_show_wire = !g_show_wire; + } + + if (key == GLFW_KEY_C) { + // cull option + g_cull_face = !g_cull_face; + } + + // init_frame = true; + } +} + +static void clickFunc(GLFWwindow* window, int button, int action, int mods) { + (void)window; + (void)mods; + if (button == GLFW_MOUSE_BUTTON_LEFT) { + if (action == GLFW_PRESS) { + mouseLeftPressed = true; + trackball(prev_quat, 0.0, 0.0, 0.0, 0.0); + } else if (action == GLFW_RELEASE) { + mouseLeftPressed = false; + } + } + if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (action == GLFW_PRESS) { + mouseRightPressed = true; + } else if (action == GLFW_RELEASE) { + mouseRightPressed = false; + } + } + if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + if (action == GLFW_PRESS) { + mouseMiddlePressed = true; + } else if (action == GLFW_RELEASE) { + mouseMiddlePressed = false; + } + } +} + +static void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) { + (void)window; + float rotScale = 1.0f; + float transScale = 2.0f; + + if (mouseLeftPressed) { + trackball(prev_quat, rotScale * (2.0f * prevMouseX - width) / (float)width, + rotScale * (height - 2.0f * prevMouseY) / (float)height, + rotScale * (2.0f * mouse_x - width) / (float)width, + rotScale * (height - 2.0f * mouse_y) / (float)height); + + add_quats(prev_quat, curr_quat, curr_quat); + } else if (mouseMiddlePressed) { + eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; + } else if (mouseRightPressed) { + eye[2] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height; + } + + // Update mouse point + prevMouseX = mouse_x; + prevMouseY = mouse_y; +} + +static void Draw(const std::vector& drawObjects, + std::vector& materials, + std::map& textures) { + glPolygonMode(GL_FRONT, GL_FILL); + if (g_cull_face) { + glPolygonMode(GL_BACK, GL_LINE); + } else { + glPolygonMode(GL_BACK, GL_FILL); + } + + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.0, 1.0); + GLsizei stride = (3 + 3 + 3 + 2) * sizeof(float); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb_id < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb_id); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glBindTexture(GL_TEXTURE_2D, 0); + if ((o.material_id < materials.size())) { + std::string diffuse_texname = materials[o.material_id].diffuse_texname; + if (textures.find(diffuse_texname) != textures.end()) { + glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]); + } + } + glVertexPointer(3, GL_FLOAT, stride, (const void*)0); + glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3)); + glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6)); + glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + glBindTexture(GL_TEXTURE_2D, 0); + } + + // draw wireframe + if (g_show_wire) { + glDisable(GL_POLYGON_OFFSET_FILL); + glPolygonMode(GL_FRONT, GL_LINE); + glPolygonMode(GL_BACK, GL_LINE); + + glColor3f(0.0f, 0.0f, 0.4f); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb_id < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb_id); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3, GL_FLOAT, stride, (const void*)0); + glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3)); + glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6)); + glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + } + } +} + +static void Init() { + trackball(curr_quat, 0, 0, 0, 0); + + eye[0] = 0.0f; + eye[1] = 0.0f; + eye[2] = 3.0f; + + lookat[0] = 0.0f; + lookat[1] = 0.0f; + lookat[2] = 0.0f; + + up[0] = 0.0f; + up[1] = 1.0f; + up[2] = 0.0f; +} + +int main(int argc, char** argv) { + if (argc < 2) { + std::cout << "Needs input.obj\n" << std::endl; + return 0; + } + + Init(); + + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW." << std::endl; + return -1; + } + + window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL); + if (window == NULL) { + std::cerr << "Failed to open GLFW window. " << std::endl; + glfwTerminate(); + return 1; + } + + std::cout << "W : Toggle wireframe\n"; + std::cout << "C : Toggle face culling\n"; + //std::cout << "K, J, H, L, P, N : Move camera\n"; + std::cout << "Q, Esc : quit\n"; + + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + + // Callback + glfwSetWindowSizeCallback(window, reshapeFunc); + glfwSetKeyCallback(window, keyboardFunc); + glfwSetMouseButtonCallback(window, clickFunc); + glfwSetCursorPosCallback(window, motionFunc); + + glewExperimental = true; + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW." << std::endl; + return -1; + } + + reshapeFunc(window, width, height); + + float bmin[3], bmax[3]; + std::vector materials; + std::map textures; + if (false == LoadObjAndConvert(bmin, bmax, &gDrawObjects, materials, textures, + argv[1])) { + return -1; + } + + float maxExtent = 0.5f * (bmax[0] - bmin[0]); + if (maxExtent < 0.5f * (bmax[1] - bmin[1])) { + maxExtent = 0.5f * (bmax[1] - bmin[1]); + } + if (maxExtent < 0.5f * (bmax[2] - bmin[2])) { + maxExtent = 0.5f * (bmax[2] - bmin[2]); + } + + while (glfwWindowShouldClose(window) == GL_FALSE) { + glfwPollEvents(); + glClearColor(0.1f, 0.2f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + + // camera & rotate + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + GLfloat mat[4][4]; + gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], + up[1], up[2]); + build_rotmatrix(mat, curr_quat); + glMultMatrixf(&mat[0][0]); + + // Fit to -1, 1 + glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent); + + // Centerize object. + glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]), + -0.5 * (bmax[2] + bmin[2])); + + Draw(gDrawObjects, materials, textures); + + glfwSwapBuffers(window); + } + + glfwTerminate(); +} diff --git a/examples/voxelize/Makefile b/examples/voxelize/Makefile new file mode 100644 index 00000000..98189d93 --- /dev/null +++ b/examples/voxelize/Makefile @@ -0,0 +1,2 @@ +all: + g++ -o voxelizer main.cc diff --git a/examples/voxelize/README.md b/examples/voxelize/README.md new file mode 100644 index 00000000..8a113b6d --- /dev/null +++ b/examples/voxelize/README.md @@ -0,0 +1,5 @@ +# Voxelize .obj and export it as also in .obj + +## Third party library + +This example uses https://github.com/karimnaaji/voxelizer, which is licensed under MIT Liense. diff --git a/examples/voxelize/main.cc b/examples/voxelize/main.cc new file mode 100644 index 00000000..6f119395 --- /dev/null +++ b/examples/voxelize/main.cc @@ -0,0 +1,75 @@ +#define VOXELIZER_IMPLEMENTATION +#include "voxelizer.h" + +#define TINYOBJLOADER_IMPLEMENTATION +#include "../../tiny_obj_loader.h" + +bool Voxelize(const char* filename, float voxelsizex, float voxelsizey, float voxelsizez, float precision) +{ + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename); + + if (!err.empty()) { + printf("err: %s\n", err.c_str()); + } + + if (!ret) { + printf("failed to load : %s\n", filename); + return false; + } + + if (shapes.size() == 0) { + printf("err: # of shapes are zero.\n"); + return false; + } + + // Only use first shape. + { + vx_mesh_t* mesh; + vx_mesh_t* result; + + mesh = vx_mesh_alloc(attrib.vertices.size(), shapes[0].mesh.indices.size()); + + for (size_t f = 0; f < shapes[0].mesh.indices.size(); f++) { + mesh->indices[f] = shapes[0].mesh.indices[f].vertex_index; + } + + for (size_t v = 0; v < attrib.vertices.size() / 3; v++) { + mesh->vertices[v].x = attrib.vertices[3*v+0]; + mesh->vertices[v].y = attrib.vertices[3*v+1]; + mesh->vertices[v].z = attrib.vertices[3*v+2]; + } + + result = vx_voxelize(mesh, voxelsizex, voxelsizey, voxelsizez, precision); + + printf("Number of vertices: %ld\n", result->nvertices); + printf("Number of indices: %ld\n", result->nindices); + } + return true; +} + + +int +main( + int argc, + char** argv) +{ + if (argc < 4) { + printf("Usage: voxelize input.obj voxelsizex voxelsizey voxelsizez precision\n"); + exit(-1); + } + + const char* filename = argv[1]; + float voxelsizex = atof(argv[2]); + float voxelsizey = atof(argv[3]); + float voxelsizez = atof(argv[4]); + float prec = atof(argv[5]); + bool ret = Voxelize(filename, voxelsizex, voxelsizey, voxelsizez, prec); + + return ret ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/examples/voxelize/voxelizer.h b/examples/voxelize/voxelizer.h new file mode 100644 index 00000000..49586954 --- /dev/null +++ b/examples/voxelize/voxelizer.h @@ -0,0 +1,764 @@ +// +// LICENCE: +// The MIT License (MIT) +// +// Copyright (c) 2016 Karim Naaji, karim.naaji@gmail.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE +// +// REFERENCES: +// http://matthias-mueller-fischer.ch/publications/tetraederCollision.pdf +// http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/tribox2.txt +// +// HOWTO: +// #define VOXELIZER_IMPLEMENTATION +// #define VOXELIZER_DEBUG // Only if assertions need to be checked +// #include "voxelizer.h" +// +// HISTORY: +// - version 0.9.0: Initial +// +// TODO: +// - Triangle face merging +// - Add colors from input mesh +// - Potential issue with voxel bigger than triangle +// + +#ifndef VOXELIZER_H +#define VOXELIZER_H + +// ------------------------------------------------------------------------------------------------ +// VOXELIZER PUBLIC API +// + +#ifndef VOXELIZER_HELPERS +#include // malloc, calloc, free +#endif + +typedef struct vx_vertex { + union { + float v[3]; + struct { + float x; + float y; + float z; + }; + }; +} vx_vertex_t; + +typedef struct vx_mesh { + vx_vertex_t* vertices; // Contiguous mesh vertices + vx_vertex_t* normals; // Contiguous mesh normals + unsigned int* indices; // Mesh indices + unsigned int* normalindices; // Mesh normal indices + size_t nindices; // The number of normal indices + size_t nvertices; // The number of vertices + size_t nnormals; // The number of normals +} vx_mesh_t; + +vx_mesh_t* vx_voxelize(vx_mesh_t* _mesh, // The input mesh + float voxelsizex, // Voxel size on X-axis + float voxelsizey, // Voxel size on Y-axis + float voxelsizez, // Voxel size on Z-axis + float precision); // A precision factor that reduces "holes" artifact + // usually a precision = voxelsize / 10. works ok. + +void vx_mesh_free(vx_mesh_t* _mesh); +vx_mesh_t* vx_mesh_alloc(int nindices, int nvertices); + +// Voxelizer Helpers, define your own if needed +#ifndef VOXELIZER_HELPERS +#define VOXELIZER_HELPERS 1 +#define VX_MIN(a, b) (a > b ? b : a) +#define VX_MAX(a, b) (a > b ? a : b) +#define VX_FINDMINMAX(x0, x1, x2, min, max) \ + min = max = x0; \ + if (x1 < min) min = x1; \ + if (x1 > max) max = x1; \ + if (x2 < min) min = x2; \ + if (x2 > max) max = x2; +#define VX_CLAMP(v, lo, hi) VX_MAX(lo, VX_MIN(hi, v)) +#define VX_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define VX_FREE(T) free(T) +#define VX_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define VX_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#ifdef VOXELIZER_DEBUG +#define VX_ASSERT(STMT) if (!(STMT)) { *(int *)0 = 0; } +#else +#define VX_ASSERT(STMT) +#endif // VOXELIZER_DEBUG +#endif // VOXELIZER_HELPERS + +// +// END VOXELIZER PUBLIC API +// ------------------------------------------------------------------------------------------------ + +#endif // VOXELIZER_H + +#ifdef VOXELIZER_IMPLEMENTATION + +#include // ceil, fabs & al. +#include // hughh +#include // memcpy + +#define VOXELIZER_EPSILON (0.0000001) +#define VOXELIZER_NORMAL_INDICES_SIZE (6) +#define VOXELIZER_INDICES_SIZE (36) +#define VOXELIZER_HASH_TABLE_SIZE (4096) + +unsigned int vx_voxel_indices[VOXELIZER_INDICES_SIZE] = { + 0, 1, 2, + 0, 2, 3, + 3, 2, 6, + 3, 6, 7, + 0, 7, 4, + 0, 3, 7, + 4, 7, 5, + 7, 6, 5, + 0, 4, 5, + 0, 5, 1, + 1, 5, 6, + 1, 6, 2, +}; + +float vx_normals[18] = { + 0.0, -1.0, 0.0, + 0.0, 1.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + -1.0, 0.0, 0.0, + 0.0, 0.0, -1.0, +}; + +unsigned int vx_normal_indices[VOXELIZER_NORMAL_INDICES_SIZE] = { + 3, 2, 1, 5, 4, 0, +}; + +typedef struct vx_aabb { + vx_vertex_t min; + vx_vertex_t max; +} vx_aabb_t; + +typedef struct vx_edge { + vx_vertex_t p1; + vx_vertex_t p2; +} vx_edge_t; + +typedef struct vx_triangle { + vx_vertex_t p1; + vx_vertex_t p2; + vx_vertex_t p3; +} vx_triangle_t; + +typedef struct vx_hash_table_node { + struct vx_hash_table_node* next; + struct vx_hash_table_node* prev; + void* data; +} vx_hash_table_node_t; + +typedef struct vx_hash_table { + vx_hash_table_node_t** elements; + size_t size; +} vx_hash_table_t; + +vx_hash_table_t* vx__hash_table_alloc(size_t size) +{ + vx_hash_table_t* table = VX_MALLOC(vx_hash_table_t, 1); + if (!table) { return NULL; } + table->size = size; + table->elements = VX_MALLOC(vx_hash_table_node_t*, size); + if (!table->elements) { return NULL; } + for (size_t i = 0; i < table->size; ++i) { + table->elements[i] = NULL; + } + return table; +} + +void vx__hash_table_free(vx_hash_table_t* table, bool freedata) +{ + for (size_t i = 0; i < table->size; ++i) { + vx_hash_table_node_t* node = table->elements[i]; + + if (node) { + if (node->next) { + while (node->next) { + node = node->next; + if (freedata) { + VX_FREE(node->prev->data); + } + VX_FREE(node->prev); + } + VX_FREE(node); + } else { + VX_FREE(node->data); + VX_FREE(node); + } + } + } + + VX_FREE(table->elements); + VX_FREE(table); +} + +bool vx__hash_table_insert(vx_hash_table_t* table, + size_t hash, + void* data, + bool (*compfunc)(void* d1, void* d2)) +{ + if (!table->elements[hash]) { + table->elements[hash] = VX_MALLOC(vx_hash_table_node_t, 1); + table->elements[hash]->prev = NULL; + table->elements[hash]->next = NULL; + table->elements[hash]->data = data; + } else { + vx_hash_table_node_t* node = table->elements[hash]; + + if (compfunc && compfunc(node->data, data)) { + return false; + } + + while (node->next) { + node = node->next; + if (compfunc && compfunc(node->data, data)) { + return false; + } + } + + vx_hash_table_node_t* nnode = VX_MALLOC(vx_hash_table_node_t, 1); + + nnode->prev = node; + nnode->next = NULL; + nnode->data = data; + node->next = nnode; + } + return true; +} + +void vx_mesh_free(vx_mesh_t* m) +{ + VX_FREE(m->vertices); + m->vertices = NULL; + m->nvertices = 0; + VX_FREE(m->indices); + m->indices = NULL; + m->nindices = 0; + if (m->normals) { VX_FREE(m->normals); } + VX_FREE(m); +} + +vx_mesh_t* vx_mesh_alloc(int nvertices, int nindices) +{ + vx_mesh_t* m = VX_MALLOC(vx_mesh_t, 1); + if (!m) { return NULL; } + m->indices = VX_CALLOC(unsigned int, nindices); + if (!m->indices) { return NULL; } + m->vertices = VX_CALLOC(vx_vertex_t, nvertices); + if (!m->vertices) { return NULL; } + m->normals = NULL; + m->nindices = nindices; + m->nvertices = nvertices; + return m; +} + +float vx__map_to_voxel(float position, float voxelSize, bool min) +{ + float vox = (position + (position < 0.f ? -1.f : 1.f) * voxelSize * 0.5f) / voxelSize; + return (min ? floor(vox) : ceil(vox)) * voxelSize; +} + +vx_vertex_t vx__vertex_cross(vx_vertex_t* v1, vx_vertex_t* v2) +{ + vx_vertex_t cross; + cross.x = v1->y * v2->z - v1->z * v2->y; + cross.y = v1->z * v2->x - v1->x * v2->z; + cross.z = v1->x * v2->y - v1->y * v2->x; + return cross; +} + +bool vx__vertex_equals(vx_vertex_t* v1, vx_vertex_t* v2) { + return fabs(v1->x - v2->x) < VOXELIZER_EPSILON && + fabs(v1->y - v2->y) < VOXELIZER_EPSILON && + fabs(v1->z - v2->z) < VOXELIZER_EPSILON; +} + +bool vx__vertex_comp_func(void* a, void* b) +{ + return vx__vertex_equals((vx_vertex_t*) a, (vx_vertex_t*) b); +} + +void vx__vertex_sub(vx_vertex_t* a, vx_vertex_t* b) +{ + a->x -= b->x; + a->y -= b->y; + a->z -= b->z; +} + +void vx__vertex_add(vx_vertex_t* a, vx_vertex_t* b) +{ + a->x += b->x; + a->y += b->y; + a->z += b->z; +} + +void vx__vertex_multiply(vx_vertex_t* a, float v) +{ + a->x *= v; + a->y *= v; + a->z *= v; +} + +float vx__vertex_dot(vx_vertex_t* v1, vx_vertex_t* v2) +{ + return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; +} + +int vx__plane_box_overlap(vx_vertex_t* normal, + float d, + vx_vertex_t* halfboxsize) +{ + vx_vertex_t vmin, vmax; + + for (int dim = 0; dim <= 2; dim++) { + if (normal->v[dim] > 0.0f) { + vmin.v[dim] = -halfboxsize->v[dim]; + vmax.v[dim] = halfboxsize->v[dim]; + } else { + vmin.v[dim] = halfboxsize->v[dim]; + vmax.v[dim] = -halfboxsize->v[dim]; + } + } + + if (vx__vertex_dot(normal, &vmin) + d > 0.0f) { + return false; + } + + if (vx__vertex_dot(normal, &vmax) + d >= 0.0f) { + return true; + } + + return false; +} + +#define AXISTEST_X01(a, b, fa, fb) \ + p1 = a * v1.y - b * v1.z; \ + p3 = a * v3.y - b * v3.z; \ + if (p1 < p3) { \ + min = p1; max = p3; \ + } else { \ + min = p3; max = p1; \ + } \ + rad = fa * halfboxsize.y + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } \ + +#define AXISTEST_X2(a, b, fa, fb) \ + p1 = a * v1.y - b * v1.z; \ + p2 = a * v2.y - b * v2.z; \ + if (p1 < p2) { \ + min = p1; max = p2; \ + } else { \ + min = p2; max = p1; \ + } \ + rad = fa * halfboxsize.y + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } \ + +#define AXISTEST_Y02(a, b, fa, fb) \ + p1 = -a * v1.x + b * v1.z; \ + p3 = -a * v3.x + b * v3.z; \ + if (p1 < p3) { \ + min = p1; max = p3; \ + } else { \ + min = p3; max = p1; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } \ + +#define AXISTEST_Y1(a, b, fa, fb) \ + p1 = -a * v1.x + b * v1.z; \ + p2 = -a * v2.x + b * v2.z; \ + if (p1 < p2) { \ + min = p1; max = p2; \ + } else { \ + min = p2; max = p1; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.z; \ + if (min > rad || max < -rad) { \ + return false; \ + } + +#define AXISTEST_Z12(a, b, fa, fb) \ + p2 = a * v2.x - b * v2.y; \ + p3 = a * v3.x - b * v3.y; \ + if (p3 < p2) { \ + min = p3; max = p2; \ + } else { \ + min = p2; max = p3; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.y; \ + if (min > rad || max < -rad) { \ + return false; \ + } + +#define AXISTEST_Z0(a, b, fa, fb) \ + p1 = a * v1.x - b * v1.y; \ + p2 = a * v2.x - b * v2.y; \ + if (p1 < p2) { \ + min = p1; max = p2; \ + } else { \ + min = p2; max = p1; \ + } \ + rad = fa * halfboxsize.x + fb * halfboxsize.y; \ + if (min > rad || max < -rad) { \ + return false; \ + } + +int vx__triangle_box_overlap(vx_vertex_t boxcenter, + vx_vertex_t halfboxsize, + vx_triangle_t triangle) +{ + vx_vertex_t v1, v2, v3, normal, e1, e2, e3; + float min, max, d, p1, p2, p3, rad, fex, fey, fez; + + v1 = triangle.p1; + v2 = triangle.p2; + v3 = triangle.p3; + + vx__vertex_sub(&v1, &boxcenter); + vx__vertex_sub(&v2, &boxcenter); + vx__vertex_sub(&v3, &boxcenter); + + e1 = v2; + e2 = v3; + e3 = v1; + + vx__vertex_sub(&e1, &v1); + vx__vertex_sub(&e2, &v2); + vx__vertex_sub(&e3, &v3); + + fex = fabs(e1.x); + fey = fabs(e1.y); + fez = fabs(e1.z); + + AXISTEST_X01(e1.z, e1.y, fez, fey); + AXISTEST_Y02(e1.z, e1.x, fez, fex); + AXISTEST_Z12(e1.y, e1.x, fey, fex); + + fex = fabs(e2.x); + fey = fabs(e2.y); + fez = fabs(e2.z); + + AXISTEST_X01(e2.z, e2.y, fez, fey); + AXISTEST_Y02(e2.z, e2.x, fez, fex); + AXISTEST_Z0(e2.y, e2.x, fey, fex); + + fex = fabs(e3.x); + fey = fabs(e3.y); + fez = fabs(e3.z); + + AXISTEST_X2(e3.z, e3.y, fez, fey); + AXISTEST_Y1(e3.z, e3.x, fez, fex); + AXISTEST_Z12(e3.y, e3.x, fey, fex); + + VX_FINDMINMAX(v1.x, v2.x, v3.x, min, max); + if (min > halfboxsize.x || max < -halfboxsize.x) { + return false; + } + + VX_FINDMINMAX(v1.y, v2.y, v3.y, min, max); + if (min > halfboxsize.y || max < -halfboxsize.y) { + return false; + } + + VX_FINDMINMAX(v1.z, v2.z, v3.z, min, max); + if (min > halfboxsize.z || max < -halfboxsize.z) { + return false; + } + + normal = vx__vertex_cross(&e1, &e2); + d = -vx__vertex_dot(&normal, &v1); + + if (!vx__plane_box_overlap(&normal, d, &halfboxsize)) { + return false; + } + + return true; +} + +#undef AXISTEST_X2 +#undef AXISTEST_X01 +#undef AXISTEST_Y1 +#undef AXISTEST_Y02 +#undef AXISTEST_Z0 +#undef AXISTEST_Z12 + +float vx__triangle_area(vx_triangle_t* triangle) { + vx_vertex_t ab = triangle->p2; + vx_vertex_t ac = triangle->p3; + + vx__vertex_sub(&ab, &triangle->p1); + vx__vertex_sub(&ac, &triangle->p1); + + float a0 = ab.y * ac.z - ab.z * ac.y; + float a1 = ab.z * ac.x - ab.x * ac.z; + float a2 = ab.x * ac.y - ab.y * ac.x; + + return sqrtf(powf(a0, 2.f) + powf(a1, 2.f) + powf(a2, 2.f)) * 0.5f; +} + +void vx__aabb_init(vx_aabb_t* aabb) +{ + aabb->max.x = aabb->max.y = aabb->max.z = -INFINITY; + aabb->min.x = aabb->min.y = aabb->min.z = INFINITY; +} + +vx_aabb_t vx__triangle_aabb(vx_triangle_t* triangle) +{ + vx_aabb_t aabb; + + vx__aabb_init(&aabb); + + aabb.max.x = VX_MAX(aabb.max.x, triangle->p1.x); aabb.max.x = VX_MAX(aabb.max.x, + triangle->p2.x); aabb.max.x = VX_MAX(aabb.max.x, triangle->p3.x); + aabb.max.y = VX_MAX(aabb.max.y, triangle->p1.y); aabb.max.y = VX_MAX(aabb.max.y, + triangle->p2.y); aabb.max.y = VX_MAX(aabb.max.y, triangle->p3.y); + aabb.max.z = VX_MAX(aabb.max.z, triangle->p1.z); aabb.max.z = VX_MAX(aabb.max.z, + triangle->p2.z); aabb.max.z = VX_MAX(aabb.max.z, triangle->p3.z); + + aabb.min.x = VX_MIN(aabb.min.x, triangle->p1.x); aabb.min.x = VX_MIN(aabb.min.x, + triangle->p2.x); aabb.min.x = VX_MIN(aabb.min.x, triangle->p3.x); + aabb.min.y = VX_MIN(aabb.min.y, triangle->p1.y); aabb.min.y = VX_MIN(aabb.min.y, + triangle->p2.y); aabb.min.y = VX_MIN(aabb.min.y, triangle->p3.y); + aabb.min.z = VX_MIN(aabb.min.z, triangle->p1.z); aabb.min.z = VX_MIN(aabb.min.z, + triangle->p2.z); aabb.min.z = VX_MIN(aabb.min.z, triangle->p3.z); + + return aabb; +} + +vx_vertex_t vx__aabb_center(vx_aabb_t* a) +{ + vx_vertex_t boxcenter = a->min; + vx__vertex_add(&boxcenter, &a->max); + vx__vertex_multiply(&boxcenter, 0.5f); + + return boxcenter; +} + +vx_vertex_t vx__aabb_half_size(vx_aabb_t* a) +{ + vx_vertex_t size; + + size.x = fabs(a->max.x - a->min.x) * 0.5f; + size.y = fabs(a->max.y - a->min.y) * 0.5f; + size.z = fabs(a->max.z - a->min.z) * 0.5f; + + return size; +} + +vx_aabb_t vx__aabb_merge(vx_aabb_t* a, vx_aabb_t* b) +{ + vx_aabb_t merge; + + merge.min.x = VX_MIN(a->min.x, b->min.x); + merge.min.y = VX_MIN(a->min.y, b->min.y); + merge.min.z = VX_MIN(a->min.z, b->min.z); + + merge.max.x = VX_MAX(a->max.x, b->max.x); + merge.max.y = VX_MAX(a->max.y, b->max.y); + merge.max.z = VX_MAX(a->max.z, b->max.z); + + return merge; +} + +size_t vx__vertex_hash(vx_vertex_t pos, size_t n) +{ + size_t a = (size_t)(pos.x * 73856093); + size_t b = (size_t)(pos.y * 19349663); + size_t c = (size_t)(pos.z * 83492791); + + return (a ^ b ^ c) % n; +} + +void vx__add_voxel(vx_mesh_t* mesh, + vx_vertex_t* pos, + float* vertices) +{ + for (size_t i = 0; i < 8; ++i) { + size_t index = i+mesh->nvertices; + + mesh->vertices[index].x = vertices[i*3+0] + pos->x; + mesh->vertices[index].y = vertices[i*3+1] + pos->y; + mesh->vertices[index].z = vertices[i*3+2] + pos->z; + } + + int j = -1; + for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) { + if (i % 6 == 0) { + j++; + } + mesh->normalindices[i+mesh->nindices] = vx_normal_indices[j]; + } + + for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) { + mesh->indices[i+mesh->nindices] = vx_voxel_indices[i] + mesh->nvertices; + } + + mesh->nindices += VOXELIZER_INDICES_SIZE; + mesh->nvertices += 8; +} + +vx_mesh_t* vx_voxelize(vx_mesh_t* m, + float voxelsizex, + float voxelsizey, + float voxelsizez, + float precision) +{ + vx_mesh_t* outmesh = NULL; + vx_hash_table_t* table = NULL; + size_t voxels = 0; + + float halfsizex = voxelsizex * 0.5f; + float halfsizey = voxelsizey * 0.5f; + float halfsizez = voxelsizez * 0.5f; + + table = vx__hash_table_alloc(VOXELIZER_HASH_TABLE_SIZE); + + for (int i = 0; i < m->nindices; i += 3) { + vx_triangle_t triangle; + + VX_ASSERT(m->indices[i+0] < m->nvertices); + VX_ASSERT(m->indices[i+1] < m->nvertices); + VX_ASSERT(m->indices[i+2] < m->nvertices); + + triangle.p1 = m->vertices[m->indices[i+0]]; + triangle.p2 = m->vertices[m->indices[i+1]]; + triangle.p3 = m->vertices[m->indices[i+2]]; + + if (vx__triangle_area(&triangle) < VOXELIZER_EPSILON) { + // triangle with 0 area + continue; + } + + vx_aabb_t aabb = vx__triangle_aabb(&triangle); + + aabb.min.x = vx__map_to_voxel(aabb.min.x, voxelsizex, true); + aabb.min.y = vx__map_to_voxel(aabb.min.y, voxelsizey, true); + aabb.min.z = vx__map_to_voxel(aabb.min.z, voxelsizez, true); + + aabb.max.x = vx__map_to_voxel(aabb.max.x, voxelsizex, false); + aabb.max.y = vx__map_to_voxel(aabb.max.y, voxelsizey, false); + aabb.max.z = vx__map_to_voxel(aabb.max.z, voxelsizez, false); + + for (float x = aabb.min.x; x < aabb.max.x; x += voxelsizex) { + for (float y = aabb.min.y; y < aabb.max.y; y += voxelsizey) { + for (float z = aabb.min.z; z < aabb.max.z; z += voxelsizez) { + vx_aabb_t saabb; + + saabb.min.x = x - halfsizex; + saabb.min.y = y - halfsizey; + saabb.min.z = z - halfsizez; + saabb.max.x = x + halfsizex; + saabb.max.y = y + halfsizey; + saabb.max.z = z + halfsizez; + + vx_vertex_t boxcenter = vx__aabb_center(&saabb); + vx_vertex_t halfsize = vx__aabb_half_size(&saabb); + + // HACK: some holes might appear, this + // precision factor reduces the artifact + halfsize.x += precision; + halfsize.y += precision; + halfsize.z += precision; + + if (vx__triangle_box_overlap(boxcenter, halfsize, triangle)) { + vx_vertex_t* nodedata = VX_MALLOC(vx_vertex_t, 1); + *nodedata = boxcenter; + + size_t hash = vx__vertex_hash(boxcenter, VOXELIZER_HASH_TABLE_SIZE); + + bool insert = vx__hash_table_insert(table, + hash, + nodedata, + vx__vertex_comp_func); + + if (insert) { + voxels++; + } + } + } + } + } + } + + outmesh = VX_MALLOC(vx_mesh_t, 1); + size_t nvertices = voxels * 8; + size_t nindices = voxels * VOXELIZER_INDICES_SIZE; + outmesh->nnormals = VOXELIZER_NORMAL_INDICES_SIZE; + outmesh->vertices = VX_CALLOC(vx_vertex_t, nvertices); + outmesh->normals = VX_CALLOC(vx_vertex_t, 6); + outmesh->indices = VX_CALLOC(unsigned int, nindices); + outmesh->normalindices = VX_CALLOC(unsigned int, nindices); + outmesh->nindices = 0; + outmesh->nvertices = 0; + + memcpy(outmesh->normals, vx_normals, 18 * sizeof(float)); + + float vertices[24] = { + -halfsizex, halfsizey, halfsizez, + -halfsizex, -halfsizey, halfsizez, + halfsizex, -halfsizey, halfsizez, + halfsizex, halfsizey, halfsizez, + -halfsizex, halfsizey, -halfsizez, + -halfsizex, -halfsizey, -halfsizez, + halfsizex, -halfsizey, -halfsizez, + halfsizex, halfsizey, -halfsizez, + }; + + for (size_t i = 0; i < table->size; ++i) { + if (table->elements[i] != NULL) { + vx_hash_table_node_t* node = table->elements[i]; + + if (!node) { + continue; + } + + vx_vertex_t* p = (vx_vertex_t*) node->data; + vx__add_voxel(outmesh, p, vertices); + + while (node->next) { + node = node->next; + p = (vx_vertex_t*) node->data; + vx__add_voxel(outmesh, p, vertices); + } + } + } + + vx__hash_table_free(table, true); + + return outmesh; +} + +#undef VOXELIZER_EPSILON +#undef VOXELIZER_INDICES_SIZE +#undef VOXELIZER_HASH_TABLE_SIZE + +#endif // VX_VOXELIZER_IMPLEMENTATION diff --git a/experimental/README.md b/experimental/README.md new file mode 100644 index 00000000..a0603af9 --- /dev/null +++ b/experimental/README.md @@ -0,0 +1,26 @@ +# Experimental code for .obj loader. + +* Multi-threaded optimized parser : tinyobj_loader_opt.h + +## Requirements + +* C++-11 compiler + +## How to build + +``` +$ premak5 gmake +``` + +## Compile options + +* zstd compressed .obj support. `--with-zstd` premake option. +* gzip compressed .obj support. `--with-zlib` premake option. + +## Notes on AMD GPU + Linux + +You may need to link with libdrm(`-ldrm`). + +## Licenses + +* lfpAlloc : MIT license. diff --git a/experimental/lfpAlloc/Allocator.hpp b/experimental/lfpAlloc/Allocator.hpp new file mode 100644 index 00000000..4dddaabf --- /dev/null +++ b/experimental/lfpAlloc/Allocator.hpp @@ -0,0 +1,89 @@ +#ifndef LF_POOL_ALLOCATOR +#define LF_POOL_ALLOCATOR + +#include +#include +#include + +namespace lfpAlloc { +template +class lfpAllocator { +public: + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = T const&; + + template + struct rebind { + typedef lfpAllocator other; + }; + + lfpAllocator() {} + + template + lfpAllocator(lfpAllocator&&) noexcept {} + + template + lfpAllocator(const lfpAllocator&) noexcept {} + + T* allocate(std::size_t count) { + if (sizeof(T) * count <= + alignof(std::max_align_t) * NumPools - sizeof(void*)) { + return reinterpret_cast( + dispatcher_.allocate(sizeof(T) * count)); + } else { + return new T[count]; + } + } + + void deallocate(T* p, std::size_t count) noexcept { + if (sizeof(T) * count <= + alignof(std::max_align_t) * NumPools - sizeof(void*)) { + dispatcher_.deallocate(p, sizeof(T) * count); + } else { + delete[] p; + } + } + + // Should not be required, but allocator_traits is not complete in + // gcc 4.9.1 + template + void destroy(U* p) { + p->~U(); + } + + template + void construct(U* p, Args&&... args) { + new (p) U(std::forward(args)...); + } + + template + friend bool operator==(const lfpAllocator&, + const lfpAllocator&) noexcept; + + template + friend class lfpAllocator; + +private: + static PoolDispatcher dispatcher_; +}; + +template +PoolDispatcher lfpAllocator::dispatcher_; + +template +inline bool operator==(const lfpAllocator&, + const lfpAllocator&) noexcept { + return N == M; +} + +template +inline bool operator!=(const lfpAllocator& left, + const lfpAllocator& right) noexcept { + return !(left == right); +} +} + +#endif diff --git a/experimental/lfpAlloc/ChunkList.hpp b/experimental/lfpAlloc/ChunkList.hpp new file mode 100644 index 00000000..760c670f --- /dev/null +++ b/experimental/lfpAlloc/ChunkList.hpp @@ -0,0 +1,116 @@ +#ifndef LF_POOL_ALLOC_CHUNK_LIST +#define LF_POOL_ALLOC_CHUNK_LIST + +#include +#include +#include + +#ifndef LFP_ALLOW_BLOCKING +static_assert(ATOMIC_POINTER_LOCK_FREE == 2, + "Atomic pointer is not lock-free."); +#endif + +namespace lfpAlloc { + +template +struct Cell { + uint8_t val_[Size]; + Cell* next_ = this + 1; +}; + +// For small types (less than the size of void*), no additional +// space is needed, so union val_ with next_ to avoid overhead. +template <> +struct Cell<0> { + Cell() : next_{this + 1} {} + union { + uint8_t val_[sizeof(Cell*)]; + Cell* next_; + }; +}; + +template +struct Chunk { + Chunk() noexcept { + auto& last = memBlock_[AllocationsPerChunk - 1]; + last.next_ = nullptr; + } + Cell memBlock_[AllocationsPerChunk]; +}; + +template +struct Node { + Node() : val_(), next_(nullptr) {} + Node(const T& val) : val_(val), next_(nullptr) {} + T val_; + std::atomic*> next_; +}; + +template +class ChunkList { + static constexpr auto CellSize = + (Size > sizeof(void*)) ? Size - sizeof(void*) : 0; + using Chunk_t = Chunk; + using Cell_t = Cell; + + using ChunkNode = Node; + using CellNode = Node; + +public: + static ChunkList& getInstance() { + static ChunkList c; + return c; + } + + Cell_t* allocateChain() { + CellNode* recentHead = head_.load(); + CellNode* currentNext = nullptr; + do { + // If there are no available chains, allocate a new chunk + if (!recentHead) { + ChunkNode* currentHandle; + + // Make a new node + auto newChunk = new ChunkNode(); + + // Add the chunk to the chain + do { + currentHandle = handle_.load(); + newChunk->next_ = currentHandle; + } while ( + !handle_.compare_exchange_weak(currentHandle, newChunk)); + return &newChunk->val_.memBlock_[0]; + } + + currentNext = recentHead->next_; + } while (!head_.compare_exchange_weak(recentHead, currentNext)); + + auto retnValue = recentHead->val_; + delete recentHead; + return retnValue; + } + + void deallocateChain(Cell_t* newCell) { + if (!newCell) { + return; + } + CellNode* currentHead = head_.load(); + + // Construct a new node to be added to the linked list + CellNode* newHead = new CellNode(newCell); + + // Add the chain to the linked list + do { + newHead->next_.store(currentHead, std::memory_order_release); + } while (!head_.compare_exchange_weak(currentHead, newHead)); + } + +private: + ChunkList() : handle_(nullptr), head_(nullptr) {} + + std::atomic handle_; + std::atomic head_; +}; +} + +#endif diff --git a/experimental/lfpAlloc/LICENSE b/experimental/lfpAlloc/LICENSE new file mode 100644 index 00000000..b9e2c10a --- /dev/null +++ b/experimental/lfpAlloc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Adam Schwalm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/experimental/lfpAlloc/Pool.hpp b/experimental/lfpAlloc/Pool.hpp new file mode 100644 index 00000000..370dab7a --- /dev/null +++ b/experimental/lfpAlloc/Pool.hpp @@ -0,0 +1,48 @@ +#ifndef LF_POOL_ALLOC_POOL +#define LF_POOL_ALLOC_POOL + +#include +#include + +namespace lfpAlloc { +template +class Pool { + using ChunkList_t = ChunkList; + +public: + static constexpr auto CellSize = + (Size > sizeof(void*)) ? Size - sizeof(void*) : 0; + using Cell_t = Cell; + + Pool() : head_(nullptr) {} + + ~Pool() { ChunkList_t::getInstance().deallocateChain(head_); } + + void* allocate() { + // Head loaded from head_ + Cell_t* currentHead = head_; + Cell_t* next; + + // Out of cells to allocate + if (!currentHead) { + currentHead = ChunkList_t::getInstance().allocateChain(); + } + + next = currentHead->next_; + head_ = next; + return ¤tHead->val_; + } + + void deallocate(void* p) noexcept { + auto newHead = reinterpret_cast(p); + Cell_t* currentHead = head_; + newHead->next_ = currentHead; + head_ = newHead; + } + +private: + Cell_t* head_; +}; +} + +#endif diff --git a/experimental/lfpAlloc/PoolDispatcher.hpp b/experimental/lfpAlloc/PoolDispatcher.hpp new file mode 100644 index 00000000..e4d1427f --- /dev/null +++ b/experimental/lfpAlloc/PoolDispatcher.hpp @@ -0,0 +1,79 @@ +#ifndef LF_POOL_DISPATCHER +#define LF_POOL_DISPATCHER + +#include +#include +#include +#include + +#ifndef LFP_ALLOCATIONS_PER_CHUNK +#define LFP_ALLOCATIONS_PER_CHUNK 64 * 100 +#endif + +namespace lfpAlloc { +namespace detail { + +template +struct Pools : Pools {}; + +template +struct Pools<0, Size...> { + using type = std::tuple...>; +}; +} + +template +class PoolDispatcher { +public: + void* allocate(std::size_t size) { return dispatchAllocate<0>(size); } + + void deallocate(void* p, std::size_t size) noexcept { + dispatchDeallocate<0>(p, size); + } + +private: + thread_local static typename detail::Pools::type pools_; + static_assert(NumPools > 0, "Invalid number of pools"); + + template + typename std::enable_if < + Index::type + dispatchAllocate(std::size_t const& requestSize) { + if (requestSize <= std::get(pools_).CellSize) { + return std::get(pools_).allocate(); + } else { + return dispatchAllocate(requestSize); + } + } + + template + typename std::enable_if::type + dispatchAllocate(std::size_t const&) { + assert(false && "Invalid allocation size."); + return nullptr; + } + + template + typename std::enable_if < + Index::type + dispatchDeallocate(void* p, std::size_t const& requestSize) noexcept { + if (requestSize <= std::get(pools_).CellSize) { + std::get(pools_).deallocate(p); + } else { + dispatchDeallocate(p, requestSize); + } + } + + template + typename std::enable_if::type + dispatchDeallocate(void*, std::size_t const&) noexcept { + assert(false && "Invalid deallocation size."); + } +}; + +template +thread_local typename detail::Pools::type + PoolDispatcher::pools_; +} + +#endif diff --git a/experimental/lfpAlloc/Utils.hpp b/experimental/lfpAlloc/Utils.hpp new file mode 100644 index 00000000..8740a795 --- /dev/null +++ b/experimental/lfpAlloc/Utils.hpp @@ -0,0 +1,20 @@ +#include + +namespace lfpAlloc { +namespace detail { +template +struct Log { + enum { value = 1 + Log::value }; +}; + +template +struct Log<1, base> { + enum { value = 0 }; +}; + +template +struct Log<0, base> { + enum { value = 0 }; +}; +} +} diff --git a/experimental/premake5.lua b/experimental/premake5.lua new file mode 100644 index 00000000..29511e17 --- /dev/null +++ b/experimental/premake5.lua @@ -0,0 +1,94 @@ +newoption { + trigger = "with-zlib", + description = "Build with zlib." +} + +newoption { + trigger = "with-zstd", + description = "Build with ZStandard compression." +} + +newoption { + trigger = "clang", + description = "Use clang compiler." +} + +newoption { + trigger = "asan", + description = "Enable AddressSanitizer(gcc or clang only)." +} + +solution "objview" + -- location ( "build" ) + configurations { "Release", "Debug" } + platforms {"native", "x64", "x32"} + + project "objview" + + kind "ConsoleApp" + language "C++" + files { "viewer.cc", "trackball.cc" } + includedirs { "./" } + includedirs { "../../" } + + flags { "c++11" } + + if _OPTIONS['clang'] then + toolset "clang" + end + + if _OPTIONS['with-zlib'] then + defines { 'ENABLE_ZLIB' } + links { 'z' } + end + + if _OPTIONS['asan'] then + buildoptions { '-fsanitize=address' } + linkoptions { '-fsanitize=address' } + end + + if _OPTIONS['with-zstd'] then + print("with-zstd") + defines { 'ENABLE_ZSTD' } + -- Set path to zstd installed dir. + includedirs { '$$HOME/local/include' } + libdirs { '$$HOME/local/lib' } + links { 'zstd' } + end + + -- Uncomment if you want address sanitizer(gcc/clang only) + --buildoptions { "-fsanitize=address" } + --linkoptions { "-fsanitize=address" } + + configuration { "linux" } + linkoptions { "`pkg-config --libs glfw3`" } + links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" } + linkoptions { "-pthread" } + + configuration { "windows" } + -- Path to GLFW3 + includedirs { '../../../local/glfw-3.2.bin.WIN64/include' } + libdirs { '../../../local/glfw-3.2.bin.WIN64/lib-vc2015' } + -- Path to GLEW + includedirs { '../../../local/glew-1.13.0/include' } + libdirs { '../../../local/glew-1.13.0/lib/Release/x64' } + + links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" } + defines { "_CRT_SECURE_NO_WARNINGS" } + defines { "NOMINMAX" } + + configuration { "macosx" } + includedirs { "/usr/local/include" } + buildoptions { "-Wno-deprecated-declarations" } + libdirs { "/usr/local/lib" } + links { "glfw3", "GLEW" } + linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" } + + configuration "Debug" + defines { "DEBUG" } + flags { "Symbols"} + + configuration "Release" + defines { "NDEBUG" } + flags { "Optimize"} + diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h new file mode 100644 index 00000000..d2b007db --- /dev/null +++ b/experimental/tinyobj_loader_opt.h @@ -0,0 +1,1722 @@ +// +// Optimized wavefront .obj loader. +// Requires lfpAlloc and C++11 +// + +/* +The MIT License (MIT) + +Copyright (c) 2012-2017 Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#ifndef TINOBJ_LOADER_OPT_H_ +#define TINOBJ_LOADER_OPT_H_ + +#ifdef _WIN32 +#define atoll(S) _atoi64(S) +#include +#else +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // C++11 +#include // C++11 +#include // C++11 + +#include "lfpAlloc/Allocator.hpp" + +namespace tinyobj_opt { + +// ---------------------------------------------------------------------------- +// Small vector class useful for multi-threaded environment. +// +// stack_container.h +// +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template +class StackAllocator : public std::allocator { + public: + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) {} + + // Casts the buffer in its right type. + T *stack_buffer() { return reinterpret_cast(stack_buffer_); } + const T *stack_buffer() const { + return reinterpret_cast(stack_buffer_); + } + + // + // IMPORTANT: Take care to ensure that stack_buffer_ is aligned + // since it is used to mimic an array of T. + // Be careful while declaring any unaligned types (like bool) + // before stack_buffer_. + // + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + char stack_buffer_[sizeof(T[stack_capacity])]; + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template + struct rebind { + typedef StackAllocator other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator &rhs) + : source_(rhs.source_) {} + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from ) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO(Google): If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template + StackAllocator(const StackAllocator &other) + : source_(NULL) { + (void)other; + } + + explicit StackAllocator(Source *source) : source_(source) {} + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n, void *hint = 0) { + if (source_ != NULL && !source_->used_stack_buffer_ && + n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator::allocate(n, hint); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ != NULL && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator::deallocate(p, n); + } + + private: + Source *source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType &container() { return container_; } + const ContainerType &container() const { return container_; } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType *operator->() { return &container_; } + const ContainerType *operator->() const { return &container_; } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source &stack_data() const { return stack_data_; } +#endif + + protected: + typename Allocator::Source stack_data_; + unsigned char pad_[7]; + Allocator allocator_; + ContainerType container_; + + // DISALLOW_EVIL_CONSTRUCTORS(StackContainer); + StackContainer(const StackContainer &); + void operator=(const StackContainer &); +}; + +// StackVector +// +// Example: +// StackVector foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template +class StackVector + : public StackContainer >, + stack_capacity> { + public: + StackVector() + : StackContainer >, + stack_capacity>() {} + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector &other) + : StackContainer >, + stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector &operator=( + const StackVector &other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T &operator[](size_t i) { return this->container().operator[](i); } + const T &operator[](size_t i) const { + return this->container().operator[](i); + } +}; + +// ---------------------------------------------------------------------------- + +typedef struct { + std::string name; + + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + float emission[3]; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + int dummy; // Suppress padding warning. + + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + float roughness; // [0, 1] default 0 + float metallic; // [0, 1] default 0 + float sheen; // [0, 1] default 0 + float clearcoat_thickness; // [0, 1] default 0 + float clearcoat_roughness; // [0, 1] default 0 + float anisotropy; // aniso. [0, 1] default 0 + float anisotropy_rotation; // anisor. [0, 1] default 0 + std::string roughness_texname; // map_Pr + std::string metallic_texname; // map_Pm + std::string sheen_texname; // map_Ps + std::string emissive_texname; // map_Ke + std::string normal_texname; // norm. For normal mapping. + + std::map unknown_parameter; +} material_t; + +typedef struct { + std::string name; // group name or object name. + // Shape's corresponding faces are accessed by attrib.indices[face_offset, + // face_offset + length] NOTE: you'll need to sum up + // attrib.face_num_verts[face_offset, face_offset + length] to find actual + // number of faces. + unsigned int face_offset; + unsigned int length; +} shape_t; + +struct index_t { + int vertex_index, texcoord_index, normal_index; + index_t() : vertex_index(-1), texcoord_index(-1), normal_index(-1) {} + explicit index_t(int idx) + : vertex_index(idx), texcoord_index(idx), normal_index(idx) {} + index_t(int vidx, int vtidx, int vnidx) + : vertex_index(vidx), texcoord_index(vtidx), normal_index(vnidx) {} +}; + +typedef struct { + std::vector > vertices; + std::vector > normals; + std::vector > texcoords; + std::vector > indices; + + // # of vertices for each face. + // 3 for triangle, 4 for qual, ... + // If triangulation is enabled and the original face are quad, + // face_num_verts will be 6(3 + 3) + std::vector > face_num_verts; + + // Per-face material IDs. + std::vector > material_ids; +} attrib_t; + +typedef StackVector ShortString; + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +static inline void skip_space(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t') { + (*token)++; + } +} + +static inline void skip_space_and_cr(const char **token) { + while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') { + (*token)++; + } +} + +static inline int until_space(const char *token) { + const char *p = token; + while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') { + p++; + } + + return p - token; +} + +static inline int length_until_newline(const char *token, int n) { + int len = 0; + + // Assume token[n-1] = '\0' + for (len = 0; len < n - 1; len++) { + if (token[len] == '\n') { + break; + } + if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) { + break; + } + } + + return len; +} + +// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work +static inline int my_atoi(const char *c) { + int value = 0; + int sign = 1; + if (*c == '+' || *c == '-') { + if (*c == '-') sign = -1; + c++; + } + while (((*c) >= '0') && ((*c) <= '9')) { // isdigit(*c) + value *= 10; + value += (int)(*c - '0'); + c++; + } + return value * sign; +} + +// Make index zero-base, and also support relative index. +static inline int fixIndex(int idx, int n) { + if (idx > 0) return idx - 1; + if (idx == 0) return 0; + return n + idx; // negative value = relative +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static index_t parseRawTriple(const char **token) { + index_t vi( + static_cast(0x80000000)); // 0x80000000 = -2147483648 = invalid + + vi.vertex_index = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.normal_index = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; + } + + // i/j/k or i/j + vi.texcoord_index = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.normal_index = my_atoi((*token)); + while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' && + (*token)[0] != '\t' && (*token)[0] != '\r') { + (*token)++; + } + return vi; +} + +static inline bool parseString(ShortString *s, const char **token) { + skip_space(token); + size_t e = until_space((*token)); + (*s)->insert((*s)->end(), (*token), (*token) + e); + (*token) += e; + return true; +} + +static inline int parseInt(const char **token) { + skip_space(token); + int i = my_atoi((*token)); + (*token) += until_space((*token)); + return i; +} + +// Tries to parse a floating point number located at s. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// The following situations triggers a failure: +// - s >= s_end. +// - parse failure. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) { + if (s >= s_end) { + return false; + } + + double mantissa = 0.0; + // This exponent is base 2 rather than 10. + // However the exponent we parse is supposed to be one of ten, + // thus we must take care to convert the exponent/and or the + // mantissa to a * 2^E, where a is the mantissa and E is the + // exponent. + // To get the final double we will use ldexp, it requires the + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // How many characters were read in a loop. + int read = 0; + // Tells whether a loop terminated due to reaching s_end. + bool end_not_reached = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // We must make sure we actually got something. + if (read == 0) goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // pow(10.0, -read) + double frac_value = 1.0; + for (int f = 0; f < read; f++) { + frac_value *= 0.1; + } + mantissa += static_cast(*curr - 0x30) * frac_value; + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') { + curr++; + // Figure out if a sign is present and if it is. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble : + *result = (sign == '+' ? 1 : -1) * + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) + : mantissa); + return true; +fail: + return false; +} + +static inline float parseFloat(const char **token) { + skip_space(token); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + float f = static_cast(atof(*token)); + (*token) += strcspn((*token), " \t\r"); +#else + const char *end = (*token) + until_space((*token)); + double val = 0.0; + tryParseDouble((*token), end, &val); + float f = static_cast(val); + (*token) = end; +#endif + return f; +} + +static inline void parseFloat2(float *x, float *y, const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); +} + +static inline void parseFloat3(float *x, float *y, float *z, + const char **token) { + (*x) = parseFloat(token); + (*y) = parseFloat(token); + (*z) = parseFloat(token); +} + +static void InitMaterial(material_t *material) { + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->alpha_texname = ""; + for (int i = 0; i < 3; i++) { + material->ambient[i] = 0.f; + material->diffuse[i] = 0.f; + material->specular[i] = 0.f; + material->transmittance[i] = 0.f; + material->emission[i] = 0.f; + } + material->illum = 0; + material->dissolve = 1.f; + material->shininess = 1.f; + material->ior = 1.f; + material->unknown_parameter.clear(); +} + +static void LoadMtl(std::map *material_map, + std::vector *materials, + std::istream *inStream) { + // Create a default material anyway. + material_t material; + InitMaterial(&material); + + size_t maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream->peek() != -1) { + inStream->getline(&buf[0], static_cast(maxchars)); + + std::string linebuf(&buf[0]); + + // Trim trailing whitespace. + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); + } + + // initial temporary material + InitMaterial(&material); + + // set new mtl name + char namebuf[4096]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = namebuf; + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(&r, &g, &b, &token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + // diffuse + if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(&r, &g, &b, &token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(&r, &g, &b, &token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + // transmittance + if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || + (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { + token += 2; + float r, g, b; + parseFloat3(&r, &g, &b, &token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseFloat(&token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { + token += 2; + float r, g, b; + parseFloat3(&r, &g, &b, &token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + // shininess + if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseFloat(&token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + // dissolve + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseFloat(&token); + continue; + } + + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + // Invert value of Tr(assume Tr is in range [0, 1]) + material.dissolve = 1.0f - parseFloat(&token); + continue; + } + + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseFloat(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseFloat(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseFloat(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseFloat(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseFloat(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseFloat(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseFloat(&token); + continue; + } + + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { + token += 7; + material.ambient_texname = token; + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + material.diffuse_texname = token; + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + material.specular_highlight_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + material.bump_texname = token; + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + material.bump_texname = token; + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + material.displacement_texname = token; + continue; + } + + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + material.roughness_texname = token; + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + material.metallic_texname = token; + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + material.sheen_texname = token; + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + material.emissive_texname = token; + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + material.normal_texname = token; + continue; + } + + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, static_cast(len)); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } + } + // flush last material. + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); +} + +typedef enum { + COMMAND_EMPTY, + COMMAND_V, + COMMAND_VN, + COMMAND_VT, + COMMAND_F, + COMMAND_G, + COMMAND_O, + COMMAND_USEMTL, + COMMAND_MTLLIB, + +} CommandType; + +typedef struct { + float vx, vy, vz; + float nx, ny, nz; + float tx, ty; + + // for f + std::vector > f; + // std::vector f; + std::vector > f_num_verts; + + const char *group_name; + unsigned int group_name_len; + const char *object_name; + unsigned int object_name_len; + const char *material_name; + unsigned int material_name_len; + + const char *mtllib_name; + unsigned int mtllib_name_len; + + CommandType type; +} Command; + +struct CommandCount { + size_t num_v; + size_t num_vn; + size_t num_vt; + size_t num_f; + size_t num_indices; + CommandCount() { + num_v = 0; + num_vn = 0; + num_vt = 0; + num_f = 0; + num_indices = 0; + } +}; + +class LoadOption { + public: + LoadOption() : req_num_threads(-1), triangulate(true), verbose(false) {} + + int req_num_threads; + bool triangulate; + bool verbose; +}; + +/// Parse wavefront .obj(.obj string data is expanded to linear char array +/// `buf') +/// -1 to req_num_threads use the number of HW threads in the running system. +bool parseObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, const char *buf, size_t len, + const LoadOption &option); + +} // namespace tinyobj_opt + +#endif // TINOBJ_LOADER_OPT_H_ + +#ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION + +namespace tinyobj_opt { + +static bool parseLine(Command *command, const char *p, size_t p_len, + bool triangulate = true) { + // @todo { operate directly on pointer `p'. to do that, add range check for + // string operatoion against `p', since `p' is not null-terminated at p[p_len] + // } + char linebuf[4096]; + assert(p_len < 4095); + memcpy(linebuf, p, p_len); + linebuf[p_len] = '\0'; + + const char *token = linebuf; + + command->type = COMMAND_EMPTY; + + // Skip leading space. + skip_space(&token); + + assert(token); + if (token[0] == '\0') { // empty line + return false; + } + + if (token[0] == '#') { // comment line + return false; + } + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + float x = 0.0f, y = 0.0f, z = 0.0f; + parseFloat3(&x, &y, &z, &token); + command->vx = x; + command->vy = y; + command->vz = z; + command->type = COMMAND_V; + return true; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + float x = 0.0f, y = 0.0f, z = 0.0f; + parseFloat3(&x, &y, &z, &token); + command->nx = x; + command->ny = y; + command->nz = z; + command->type = COMMAND_VN; + return true; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + float x = 0.0f, y = 0.0f; + parseFloat2(&x, &y, &token); + command->tx = x; + command->ty = y; + command->type = COMMAND_VT; + return true; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + skip_space(&token); + + StackVector f; + + while (!IS_NEW_LINE(token[0])) { + index_t vi = parseRawTriple(&token); + skip_space_and_cr(&token); + + f->push_back(vi); + } + + command->type = COMMAND_F; + + if (triangulate) { + index_t i0 = f[0]; + index_t i1(-1); + index_t i2 = f[1]; + + for (size_t k = 2; k < f->size(); k++) { + i1 = i2; + i2 = f[k]; + command->f.emplace_back(i0); + command->f.emplace_back(i1); + command->f.emplace_back(i2); + + command->f_num_verts.emplace_back(3); + } + + } else { + for (size_t k = 0; k < f->size(); k++) { + command->f.emplace_back(f[k]); + } + + command->f_num_verts.emplace_back(f->size()); + } + + return true; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + + // int newMaterialId = -1; + // if (material_map.find(namebuf) != material_map.end()) { + // newMaterialId = material_map[namebuf]; + //} else { + // // { error!! material not found } + //} + + // if (newMaterialId != materialId) { + // materialId = newMaterialId; + //} + + // command->material_name = .insert(command->material_name->end(), namebuf, + // namebuf + strlen(namebuf)); + // command->material_name->push_back('\0'); + skip_space(&token); + command->material_name = p + (token - linebuf); + command->material_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_USEMTL; + + return true; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + // By specification, `mtllib` should be appear only once in .obj + token += 7; + + skip_space(&token); + command->mtllib_name = p + (token - linebuf); + command->mtllib_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_MTLLIB; + + return true; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + // @todo { multiple group name. } + token += 2; + + command->group_name = p + (token - linebuf); + command->group_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_G; + + return true; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + token += 2; + + command->object_name = p + (token - linebuf); + command->object_name_len = + length_until_newline(token, p_len - (token - linebuf)) + 1; + command->type = COMMAND_O; + + return true; + } + + return false; +} + +typedef struct { + size_t pos; + size_t len; +} LineInfo; + +// Idea come from https://github.com/antonmks/nvParse +// 1. mmap file +// 2. find newline(\n, \r\n, \r) and list of line data. +// 3. Do parallel parsing for each line. +// 4. Reconstruct final mesh data structure. + +// Raise # of max threads if you have more CPU cores... +// In 2018, 32 cores are getting common in high-end workstaion PC. +#define kMaxThreads (32) + +static inline bool is_line_ending(const char *p, size_t i, size_t end_i) { + if (p[i] == '\0') return true; + if (p[i] == '\n') return true; // this includes \r\n + if (p[i] == '\r') { + if (((i + 1) < end_i) && (p[i + 1] != '\n')) { // detect only \r case + return true; + } + } + return false; +} + +bool parseObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, const char *buf, size_t len, + const LoadOption &option) { + attrib->vertices.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->indices.clear(); + attrib->face_num_verts.clear(); + attrib->material_ids.clear(); + shapes->clear(); + + if (len < 1) return false; + + auto num_threads = (option.req_num_threads < 0) + ? std::thread::hardware_concurrency() + : option.req_num_threads; + num_threads = + (std::max)(1, (std::min)(static_cast(num_threads), kMaxThreads)); + + if (option.verbose) { + std::cout << "# of threads = " << num_threads << std::endl; + } + + auto t1 = std::chrono::high_resolution_clock::now(); + + std::vector > + line_infos[kMaxThreads]; + for (size_t t = 0; t < static_cast(num_threads); t++) { + // Pre allocate enough memory. len / 128 / num_threads is just a heuristic + // value. + line_infos[t].reserve(len / 128 / num_threads); + } + + std::chrono::duration ms_linedetection; + std::chrono::duration ms_alloc; + std::chrono::duration ms_parse; + std::chrono::duration ms_load_mtl; + std::chrono::duration ms_merge; + std::chrono::duration ms_construct; + + // 1. Find '\n' and create line data. + { + StackVector workers; + + auto start_time = std::chrono::high_resolution_clock::now(); + auto chunk_size = len / num_threads; + + for (size_t t = 0; t < static_cast(num_threads); t++) { + workers->push_back(std::thread([&, t]() { + auto start_idx = (t + 0) * chunk_size; + auto end_idx = (std::min)((t + 1) * chunk_size, len - 1); + if (t == static_cast((num_threads - 1))) { + end_idx = len - 1; + } + + // true if the line currently read must be added to the current line + // info + bool new_line_found = + (t == 0) || is_line_ending(buf, start_idx - 1, end_idx); + + size_t prev_pos = start_idx; + for (size_t i = start_idx; i < end_idx; i++) { + if (is_line_ending(buf, i, end_idx)) { + if (!new_line_found) { + // first linebreak found in (chunk > 0), and a line before this + // linebreak belongs to previous chunk, so skip it. + prev_pos = i + 1; + new_line_found = true; + } else { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + if (info.len > 0) { + line_infos[t].push_back(info); + } + + prev_pos = i + 1; + } + } + } + + // If at least one line started in this chunk, find where it ends in the + // rest of the buffer + if (new_line_found && (t < num_threads) && (buf[end_idx - 1] != '\n')) { + for (size_t i = end_idx; i < len; i++) { + if (is_line_ending(buf, i, len)) { + LineInfo info; + info.pos = prev_pos; + info.len = i - prev_pos; + + if (info.len > 0) { + line_infos[t].push_back(info); + } + + break; + } + } + } + })); + } + + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); + } + + auto end_time = std::chrono::high_resolution_clock::now(); + + ms_linedetection = end_time - start_time; + } + + auto line_sum = 0; + for (size_t t = 0; t < num_threads; t++) { + // std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl; + line_sum += line_infos[t].size(); + } + // std::cout << "# of lines = " << line_sum << std::endl; + + std::vector commands[kMaxThreads]; + + // 2. allocate buffer + auto t_alloc_start = std::chrono::high_resolution_clock::now(); + { + for (size_t t = 0; t < num_threads; t++) { + commands[t].reserve(line_infos[t].size()); + } + } + + CommandCount command_count[kMaxThreads]; + // Array index to `mtllib` line. According to wavefront .obj spec, `mtllib' + // should appear only once in .obj. + int mtllib_t_index = -1; + int mtllib_i_index = -1; + + ms_alloc = std::chrono::high_resolution_clock::now() - t_alloc_start; + + // 2. parse each line in parallel. + { + StackVector workers; + auto t_start = std::chrono::high_resolution_clock::now(); + + for (size_t t = 0; t < num_threads; t++) { + workers->push_back(std::thread([&, t]() { + for (size_t i = 0; i < line_infos[t].size(); i++) { + Command command; + bool ret = parseLine(&command, &buf[line_infos[t][i].pos], + line_infos[t][i].len, option.triangulate); + if (ret) { + if (command.type == COMMAND_V) { + command_count[t].num_v++; + } else if (command.type == COMMAND_VN) { + command_count[t].num_vn++; + } else if (command.type == COMMAND_VT) { + command_count[t].num_vt++; + } else if (command.type == COMMAND_F) { + command_count[t].num_f += command.f.size(); + command_count[t].num_indices += command.f_num_verts.size(); + } + + if (command.type == COMMAND_MTLLIB) { + // Save the indices of the `mtllib` command in `commands` to easily find it later + mtllib_t_index = t; + mtllib_i_index = commands[t].size(); + } + + commands[t].emplace_back(std::move(command)); + } + } + })); + } + + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); + } + + auto t_end = std::chrono::high_resolution_clock::now(); + + ms_parse = t_end - t_start; + } + + std::map material_map; + + // Load material(if exits) + if (mtllib_i_index >= 0 && mtllib_t_index >= 0 && + commands[mtllib_t_index][mtllib_i_index].mtllib_name && + commands[mtllib_t_index][mtllib_i_index].mtllib_name_len > 0) { + std::string material_filename = + std::string(commands[mtllib_t_index][mtllib_i_index].mtllib_name, + commands[mtllib_t_index][mtllib_i_index].mtllib_name_len); + // std::cout << "mtllib :" << material_filename << std::endl; + + auto t1 = std::chrono::high_resolution_clock::now(); + if (material_filename.back() == '\r') { + material_filename.pop_back(); + } + std::ifstream ifs(material_filename); + if (ifs.good()) { + LoadMtl(&material_map, materials, &ifs); + + // std::cout << "maetrials = " << materials.size() << std::endl; + + ifs.close(); + } + + auto t2 = std::chrono::high_resolution_clock::now(); + + ms_load_mtl = t2 - t1; + } + + auto command_sum = 0; + for (size_t t = 0; t < num_threads; t++) { + // std::cout << t << ": # of commands = " << commands[t].size() << + // std::endl; + command_sum += commands[t].size(); + } + // std::cout << "# of commands = " << command_sum << std::endl; + + size_t num_v = 0; + size_t num_vn = 0; + size_t num_vt = 0; + size_t num_f = 0; + size_t num_indices = 0; + for (size_t t = 0; t < num_threads; t++) { + num_v += command_count[t].num_v; + num_vn += command_count[t].num_vn; + num_vt += command_count[t].num_vt; + num_f += command_count[t].num_f; + num_indices += command_count[t].num_indices; + } + + // std::cout << "# v " << num_v << std::endl; + // std::cout << "# vn " << num_vn << std::endl; + // std::cout << "# vt " << num_vt << std::endl; + // std::cout << "# f " << num_f << std::endl; + + // 4. merge + // @todo { parallelize merge. } + { + auto t_start = std::chrono::high_resolution_clock::now(); + + attrib->vertices.resize(num_v * 3); + attrib->normals.resize(num_vn * 3); + attrib->texcoords.resize(num_vt * 2); + attrib->indices.resize(num_f); + attrib->face_num_verts.resize(num_indices); + attrib->material_ids.resize(num_indices, -1); + + size_t v_offsets[kMaxThreads]; + size_t n_offsets[kMaxThreads]; + size_t t_offsets[kMaxThreads]; + size_t f_offsets[kMaxThreads]; + size_t face_offsets[kMaxThreads]; + + v_offsets[0] = 0; + n_offsets[0] = 0; + t_offsets[0] = 0; + f_offsets[0] = 0; + face_offsets[0] = 0; + + for (size_t t = 1; t < num_threads; t++) { + v_offsets[t] = v_offsets[t - 1] + command_count[t - 1].num_v; + n_offsets[t] = n_offsets[t - 1] + command_count[t - 1].num_vn; + t_offsets[t] = t_offsets[t - 1] + command_count[t - 1].num_vt; + f_offsets[t] = f_offsets[t - 1] + command_count[t - 1].num_f; + face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_indices; + } + + StackVector workers; + + for (size_t t = 0; t < num_threads; t++) { + workers->push_back(std::thread([&, t]() { + size_t v_count = v_offsets[t]; + size_t n_count = n_offsets[t]; + size_t t_count = t_offsets[t]; + size_t f_count = f_offsets[t]; + size_t face_count = face_offsets[t]; + + for (size_t i = 0; i < commands[t].size(); i++) { + if (commands[t][i].type == COMMAND_EMPTY) { + continue; + } else if (commands[t][i].type == COMMAND_USEMTL) { + if (commands[t][i].material_name && + commands[t][i].material_name_len > 0 && + // check if there are still faces after this command + face_count < num_indices) { + // Find next face + bool found = false; + size_t i_start = i + 1, t_next, i_next; + for (t_next = t; t_next < num_threads; t_next++) { + for (i_next = i_start; i_next < commands[t_next].size(); + i_next++) { + if (commands[t_next][i_next].type == COMMAND_F) { + found = true; + break; + } + } + if (found) break; + i_start = 0; + } + // Assign material to this face + if (found) { + std::string material_name(commands[t][i].material_name, + commands[t][i].material_name_len); + for (size_t k = 0; + k < commands[t_next][i_next].f_num_verts.size(); k++) { + if (material_map.find(material_name) != material_map.end()) { + attrib->material_ids[face_count + k] = + material_map[material_name]; + } else { + // Assign invalid material ID + // Set a different value than the default, to + // prevent following faces from being assigned a valid + // material + attrib->material_ids[face_count + k] = -2; + } + } + } + } + } else if (commands[t][i].type == COMMAND_V) { + attrib->vertices[3 * v_count + 0] = commands[t][i].vx; + attrib->vertices[3 * v_count + 1] = commands[t][i].vy; + attrib->vertices[3 * v_count + 2] = commands[t][i].vz; + v_count++; + } else if (commands[t][i].type == COMMAND_VN) { + attrib->normals[3 * n_count + 0] = commands[t][i].nx; + attrib->normals[3 * n_count + 1] = commands[t][i].ny; + attrib->normals[3 * n_count + 2] = commands[t][i].nz; + n_count++; + } else if (commands[t][i].type == COMMAND_VT) { + attrib->texcoords[2 * t_count + 0] = commands[t][i].tx; + attrib->texcoords[2 * t_count + 1] = commands[t][i].ty; + t_count++; + } else if (commands[t][i].type == COMMAND_F) { + for (size_t k = 0; k < commands[t][i].f.size(); k++) { + index_t &vi = commands[t][i].f[k]; + int vertex_index = fixIndex(vi.vertex_index, v_count); + int texcoord_index = fixIndex(vi.texcoord_index, t_count); + int normal_index = fixIndex(vi.normal_index, n_count); + attrib->indices[f_count + k] = + index_t(vertex_index, texcoord_index, normal_index); + } + for (size_t k = 0; k < commands[t][i].f_num_verts.size(); k++) { + attrib->face_num_verts[face_count + k] = + commands[t][i].f_num_verts[k]; + } + + f_count += commands[t][i].f.size(); + face_count += commands[t][i].f_num_verts.size(); + } + } + })); + } + + for (size_t t = 0; t < workers->size(); t++) { + workers[t].join(); + } + + // To each face with uninitialized material id, + // assign the material id of the last face preceding it that has one + for (size_t face_count = 1; face_count < num_indices; ++face_count) + if (attrib->material_ids[face_count] == -1) + attrib->material_ids[face_count] = attrib->material_ids[face_count - 1]; + + auto t_end = std::chrono::high_resolution_clock::now(); + ms_merge = t_end - t_start; + } + + auto t4 = std::chrono::high_resolution_clock::now(); + + // 5. Construct shape information. + { + auto t_start = std::chrono::high_resolution_clock::now(); + + // @todo { Can we boost the performance by multi-threaded execution? } + int face_count = 0; + shape_t shape; + shape.face_offset = 0; + shape.length = 0; + int face_prev_offset = 0; + for (size_t t = 0; t < num_threads; t++) { + for (size_t i = 0; i < commands[t].size(); i++) { + if (commands[t][i].type == COMMAND_O || + commands[t][i].type == COMMAND_G) { + std::string name; + if (commands[t][i].type == COMMAND_O) { + name = std::string(commands[t][i].object_name, + commands[t][i].object_name_len); + } else { + name = std::string(commands[t][i].group_name, + commands[t][i].group_name_len); + } + + if (face_count == 0) { + // 'o' or 'g' appears before any 'f' + shape.name = name; + shape.face_offset = face_count; + face_prev_offset = face_count; + } else { + if (shapes->size() == 0) { + // 'o' or 'g' after some 'v' lines. + // create a shape with null name + shape.length = face_count - face_prev_offset; + face_prev_offset = face_count; + + shapes->push_back(shape); + + } else { + if ((face_count - face_prev_offset) > 0) { + // push previous shape + shape.length = face_count - face_prev_offset; + shapes->push_back(shape); + face_prev_offset = face_count; + } + } + + // redefine shape. + shape.name = name; + shape.face_offset = face_count; + shape.length = 0; + } + } + if (commands[t][i].type == COMMAND_F) { + // Consider generation of multiple faces per `f` line by triangulation + face_count += commands[t][i].f_num_verts.size(); + } + } + } + + if ((face_count - face_prev_offset) > 0) { + shape.length = face_count - shape.face_offset; + if (shape.length > 0) { + shapes->push_back(shape); + } + } else { + // Guess no 'v' line occurrence after 'o' or 'g', so discards current + // shape information. + } + + auto t_end = std::chrono::high_resolution_clock::now(); + + ms_construct = t_end - t_start; + } + + std::chrono::duration ms_total = t4 - t1; + if (option.verbose) { + std::cout << "total parsing time: " << ms_total.count() << " ms\n"; + std::cout << " line detection : " << ms_linedetection.count() << " ms\n"; + std::cout << " alloc buf : " << ms_alloc.count() << " ms\n"; + std::cout << " parse : " << ms_parse.count() << " ms\n"; + std::cout << " merge : " << ms_merge.count() << " ms\n"; + std::cout << " construct : " << ms_construct.count() << " ms\n"; + std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n"; + std::cout << "# of vertices = " << attrib->vertices.size() << std::endl; + std::cout << "# of normals = " << attrib->normals.size() << std::endl; + std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl; + std::cout << "# of face indices = " << attrib->indices.size() << std::endl; + std::cout << "# of indices = " << attrib->material_ids.size() << std::endl; + std::cout << "# of shapes = " << shapes->size() << std::endl; + } + + return true; +} + +} // namespace tinyobj_opt + +#endif // TINYOBJ_LOADER_OPT_IMPLEMENTATION diff --git a/experimental/trackball.cc b/experimental/trackball.cc new file mode 100644 index 00000000..27642e83 --- /dev/null +++ b/experimental/trackball.cc @@ -0,0 +1,292 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * Trackball code: + * + * Implementation of a virtual trackball. + * Implemented by Gavin Bell, lots of ideas from Thant Tessman and + * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129. + * + * Vector manip code: + * + * Original code from: + * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli + * + * Much mucking with by: + * Gavin Bell + */ +#include +#include "trackball.h" + +/* + * This size should really be based on the distance from the center of + * rotation to the point on the object underneath the mouse. That + * point would then track the mouse as closely as possible. This is a + * simple example, though, so that is left as an Exercise for the + * Programmer. + */ +#define TRACKBALLSIZE (0.8) + +/* + * Local function prototypes (not defined in trackball.h) + */ +static float tb_project_to_sphere(float, float, float); +static void normalize_quat(float[4]); + +static void vzero(float *v) { + v[0] = 0.0; + v[1] = 0.0; + v[2] = 0.0; +} + +static void vset(float *v, float x, float y, float z) { + v[0] = x; + v[1] = y; + v[2] = z; +} + +static void vsub(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] - src2[0]; + dst[1] = src1[1] - src2[1]; + dst[2] = src1[2] - src2[2]; +} + +static void vcopy(const float *v1, float *v2) { + int i; + for (i = 0; i < 3; i++) + v2[i] = v1[i]; +} + +static void vcross(const float *v1, const float *v2, float *cross) { + float temp[3]; + + temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]); + temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]); + temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]); + vcopy(temp, cross); +} + +static float vlength(const float *v) { + return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +} + +static void vscale(float *v, float div) { + v[0] *= div; + v[1] *= div; + v[2] *= div; +} + +static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); } + +static float vdot(const float *v1, const float *v2) { + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; +} + +static void vadd(const float *src1, const float *src2, float *dst) { + dst[0] = src1[0] + src2[0]; + dst[1] = src1[1] + src2[1]; + dst[2] = src1[2] + src2[2]; +} + +/* + * Ok, simulate a track-ball. Project the points onto the virtual + * trackball, then figure out the axis of rotation, which is the cross + * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0) + * Note: This is a deformed trackball-- is a trackball in the center, + * but is deformed into a hyperbolic sheet of rotation away from the + * center. This particular function was chosen after trying out + * several variations. + * + * It is assumed that the arguments to this routine are in the range + * (-1.0 ... 1.0) + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) { + float a[3]; /* Axis of rotation */ + float phi; /* how much to rotate about axis */ + float p1[3], p2[3], d[3]; + float t; + + if (p1x == p2x && p1y == p2y) { + /* Zero rotation */ + vzero(q); + q[3] = 1.0; + return; + } + + /* + * First, figure out z-coordinates for projection of P1 and P2 to + * deformed sphere + */ + vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y)); + vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y)); + + /* + * Now, we want the cross product of P1 and P2 + */ + vcross(p2, p1, a); + + /* + * Figure out how much to rotate around that axis. + */ + vsub(p1, p2, d); + t = vlength(d) / (2.0 * TRACKBALLSIZE); + + /* + * Avoid problems with out-of-control values... + */ + if (t > 1.0) + t = 1.0; + if (t < -1.0) + t = -1.0; + phi = 2.0 * asin(t); + + axis_to_quat(a, phi, q); +} + +/* + * Given an axis and angle, compute quaternion. + */ +void axis_to_quat(float a[3], float phi, float q[4]) { + vnormal(a); + vcopy(a, q); + vscale(q, sin(phi / 2.0)); + q[3] = cos(phi / 2.0); +} + +/* + * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet + * if we are away from the center of the sphere. + */ +static float tb_project_to_sphere(float r, float x, float y) { + float d, t, z; + + d = sqrt(x * x + y * y); + if (d < r * 0.70710678118654752440) { /* Inside sphere */ + z = sqrt(r * r - d * d); + } else { /* On hyperbola */ + t = r / 1.41421356237309504880; + z = t * t / d; + } + return z; +} + +/* + * Given two rotations, e1 and e2, expressed as quaternion rotations, + * figure out the equivalent single rotation and stuff it into dest. + * + * This routine also normalizes the result every RENORMCOUNT times it is + * called, to keep error from creeping in. + * + * NOTE: This routine is written so that q1 or q2 may be the same + * as dest (or each other). + */ + +#define RENORMCOUNT 97 + +void add_quats(float q1[4], float q2[4], float dest[4]) { + static int count = 0; + float t1[4], t2[4], t3[4]; + float tf[4]; + + vcopy(q1, t1); + vscale(t1, q2[3]); + + vcopy(q2, t2); + vscale(t2, q1[3]); + + vcross(q2, q1, t3); + vadd(t1, t2, tf); + vadd(t3, tf, tf); + tf[3] = q1[3] * q2[3] - vdot(q1, q2); + + dest[0] = tf[0]; + dest[1] = tf[1]; + dest[2] = tf[2]; + dest[3] = tf[3]; + + if (++count > RENORMCOUNT) { + count = 0; + normalize_quat(dest); + } +} + +/* + * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0 + * If they don't add up to 1.0, dividing by their magnitued will + * renormalize them. + * + * Note: See the following for more information on quaternions: + * + * - Shoemake, K., Animating rotation with quaternion curves, Computer + * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985. + * - Pletinckx, D., Quaternion calculus as a basic tool in computer + * graphics, The Visual Computer 5, 2-13, 1989. + */ +static void normalize_quat(float q[4]) { + int i; + float mag; + + mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]); + for (i = 0; i < 4; i++) + q[i] /= mag; +} + +/* + * Build a rotation matrix, given a quaternion rotation. + * + */ +void build_rotmatrix(float m[4][4], const float q[4]) { + m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]); + m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]); + m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]); + m[0][3] = 0.0; + + m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]); + m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]); + m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]); + m[1][3] = 0.0; + + m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]); + m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]); + m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]); + m[2][3] = 0.0; + + m[3][0] = 0.0; + m[3][1] = 0.0; + m[3][2] = 0.0; + m[3][3] = 1.0; +} diff --git a/experimental/trackball.h b/experimental/trackball.h new file mode 100644 index 00000000..b1f94374 --- /dev/null +++ b/experimental/trackball.h @@ -0,0 +1,75 @@ +/* + * (c) Copyright 1993, 1994, Silicon Graphics, Inc. + * ALL RIGHTS RESERVED + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the above + * copyright notice appear in all copies and that both the copyright notice + * and this permission notice appear in supporting documentation, and that + * the name of Silicon Graphics, Inc. not be used in advertising + * or publicity pertaining to distribution of the software without specific, + * written prior permission. + * + * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS" + * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON + * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT, + * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY + * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION, + * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF + * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE + * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE. + * + * US Government Users Restricted Rights + * Use, duplication, or disclosure by the Government is subject to + * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph + * (c)(1)(ii) of the Rights in Technical Data and Computer Software + * clause at DFARS 252.227-7013 and/or in similar or successor + * clauses in the FAR or the DOD or NASA FAR Supplement. + * Unpublished-- rights reserved under the copyright laws of the + * United States. Contractor/manufacturer is Silicon Graphics, + * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311. + * + * OpenGL(TM) is a trademark of Silicon Graphics, Inc. + */ +/* + * trackball.h + * A virtual trackball implementation + * Written by Gavin Bell for Silicon Graphics, November 1988. + */ + +/* + * Pass the x and y coordinates of the last and current positions of + * the mouse, scaled so they are from (-1.0 ... 1.0). + * + * The resulting rotation is returned as a quaternion rotation in the + * first paramater. + */ +void trackball(float q[4], float p1x, float p1y, float p2x, float p2y); + +void negate_quat(float *q, float *qn); + +/* + * Given two quaternions, add them together to get a third quaternion. + * Adding quaternions to get a compound rotation is analagous to adding + * translations to get a compound translation. When incrementally + * adding rotations, the first argument here should be the new + * rotation, the second and third the total rotation (which will be + * over-written with the resulting new total rotation). + */ +void add_quats(float *q1, float *q2, float *dest); + +/* + * A useful function, builds a rotation matrix in Matrix based on + * given quaternion. + */ +void build_rotmatrix(float m[4][4], const float q[4]); + +/* + * This function computes a quaternion based on an axis (defined by + * the given vector) and an angle about which to rotate. The angle is + * expressed in radians. The result is put into the third argument. + */ +void axis_to_quat(float a[3], float phi, float q[4]); diff --git a/experimental/viewer.cc b/experimental/viewer.cc new file mode 100644 index 00000000..4886b784 --- /dev/null +++ b/experimental/viewer.cc @@ -0,0 +1,748 @@ +// +// Simple .obj viewer(vertex only) +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(ENABLE_ZLIB) +#include +#endif + +#if defined(ENABLE_ZSTD) +#include +#endif + +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#include "trackball.h" + +#define TINYOBJ_LOADER_OPT_IMPLEMENTATION +#include "tinyobj_loader_opt.h" + +typedef struct { + GLuint vb; // vertex buffer + int numTriangles; +} DrawObject; + +std::vector gDrawObjects; + +int width = 768; +int height = 768; + +double prevMouseX, prevMouseY; +bool mouseLeftPressed; +bool mouseMiddlePressed; +bool mouseRightPressed; +float curr_quat[4]; +float prev_quat[4]; +float eye[3], lookat[3], up[3]; + +GLFWwindow* window; + +void CheckErrors(std::string desc) { + GLenum e = glGetError(); + if (e != GL_NO_ERROR) { + fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); + exit(20); + } +} + +void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { + float v10[3]; + v10[0] = v1[0] - v0[0]; + v10[1] = v1[1] - v0[1]; + v10[2] = v1[2] - v0[2]; + + float v20[3]; + v20[0] = v2[0] - v0[0]; + v20[1] = v2[1] - v0[1]; + v20[2] = v2[2] - v0[2]; + + N[0] = v20[1] * v10[2] - v20[2] * v10[1]; + N[1] = v20[2] * v10[0] - v20[0] * v10[2]; + N[2] = v20[0] * v10[1] - v20[1] * v10[0]; + + float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2]; + if (len2 > 0.0f) { + float len = sqrtf(len2); + + N[0] /= len; + N[1] /= len; + } +} + +const char *mmap_file(size_t *len, const char* filename) +{ + (*len) = 0; +#ifdef _WIN32 + HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); + assert(file != INVALID_HANDLE_VALUE); + + HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); + assert(fileMapping != INVALID_HANDLE_VALUE); + + LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0); + auto fileMapViewChar = (const char*)fileMapView; + assert(fileMapView != NULL); + + LARGE_INTEGER fileSize; + fileSize.QuadPart = 0; + GetFileSizeEx(file, &fileSize); + + (*len) = static_cast(fileSize.QuadPart); + return fileMapViewChar; + +#else + + FILE* f = fopen(filename, "rb" ); + if (!f) { + fprintf(stderr, "Failed to open file : %s\n", filename); + return nullptr; + } + fseek(f, 0, SEEK_END); + long fileSize = ftell(f); + fclose(f); + + if (fileSize < 16) { + fprintf(stderr, "Empty or invalid .obj : %s\n", filename); + return nullptr; + } + + struct stat sb; + char *p; + int fd; + + fd = open (filename, O_RDONLY); + if (fd == -1) { + perror ("open"); + return nullptr; + } + + if (fstat (fd, &sb) == -1) { + perror ("fstat"); + return nullptr; + } + + if (!S_ISREG (sb.st_mode)) { + fprintf (stderr, "%s is not a file\n", filename); + return nullptr; + } + + p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0); + + if (p == MAP_FAILED) { + perror ("mmap"); + return nullptr; + } + + if (close (fd) == -1) { + perror ("close"); + return nullptr; + } + + (*len) = fileSize; + + return p; + +#endif +} + +bool gz_load(std::vector* buf, const char* filename) +{ +#ifdef ENABLE_ZLIB + gzFile file; + file = gzopen (filename, "r"); + if (! file) { + fprintf (stderr, "gzopen of '%s' failed: %s.\n", filename, + strerror (errno)); + exit (EXIT_FAILURE); + return false; + } + while (1) { + int err; + int bytes_read; + unsigned char buffer[1024]; + bytes_read = gzread (file, buffer, 1024); + buf->insert(buf->end(), buffer, buffer + 1024); + //printf ("%s", buffer); + if (bytes_read < 1024) { + if (gzeof (file)) { + break; + } + else { + const char * error_string; + error_string = gzerror (file, & err); + if (err) { + fprintf (stderr, "Error: %s.\n", error_string); + exit (EXIT_FAILURE); + return false; + } + } + } + } + gzclose (file); + return true; +#else + return false; +#endif +} + +#ifdef ENABLE_ZSTD +static off_t fsize_X(const char *filename) +{ + struct stat st; + if (stat(filename, &st) == 0) return st.st_size; + /* error */ + printf("stat: %s : %s \n", filename, strerror(errno)); + exit(1); +} + +static FILE* fopen_X(const char *filename, const char *instruction) +{ + FILE* const inFile = fopen(filename, instruction); + if (inFile) return inFile; + /* error */ + printf("fopen: %s : %s \n", filename, strerror(errno)); + exit(2); +} + +static void* malloc_X(size_t size) +{ + void* const buff = malloc(size); + if (buff) return buff; + /* error */ + printf("malloc: %s \n", strerror(errno)); + exit(3); +} +#endif + +bool zstd_load(std::vector* buf, const char* filename) +{ +#ifdef ENABLE_ZSTD + off_t const buffSize = fsize_X(filename); + FILE* const inFile = fopen_X(filename, "rb"); + void* const buffer = malloc_X(buffSize); + size_t const readSize = fread(buffer, 1, buffSize, inFile); + if (readSize != (size_t)buffSize) { + printf("fread: %s : %s \n", filename, strerror(errno)); + exit(4); + } + fclose(inFile); + + unsigned long long const rSize = ZSTD_getDecompressedSize(buffer, buffSize); + if (rSize==0) { + printf("%s : original size unknown \n", filename); + exit(5); + } + + buf->resize(rSize); + + size_t const dSize = ZSTD_decompress(buf->data(), rSize, buffer, buffSize); + + if (dSize != rSize) { + printf("error decoding %s : %s \n", filename, ZSTD_getErrorName(dSize)); + exit(7); + } + + free(buffer); + + return true; +#else + return false; +#endif +} + +const char* get_file_data(size_t *len, const char* filename) +{ + + const char *ext = strrchr(filename, '.'); + + size_t data_len = 0; + const char* data = nullptr; + + if (strcmp(ext, ".gz") == 0) { + // gzipped data. + + std::vector buf; + bool ret = gz_load(&buf, filename); + + if (ret) { + char *p = static_cast(malloc(buf.size() + 1)); // @fixme { implement deleter } + memcpy(p, &buf.at(0), buf.size()); + p[buf.size()] = '\0'; + data = p; + data_len = buf.size(); + } + + } else if (strcmp(ext, ".zst") == 0) { + printf("zstd\n"); + // Zstandard data. + + std::vector buf; + bool ret = zstd_load(&buf, filename); + + if (ret) { + char *p = static_cast(malloc(buf.size() + 1)); // @fixme { implement deleter } + memcpy(p, &buf.at(0), buf.size()); + p[buf.size()] = '\0'; + data = p; + data_len = buf.size(); + } + } else { + + data = mmap_file(&data_len, filename); + + } + + (*len) = data_len; + return data; +} + + +bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads, bool verbose) +{ + tinyobj_opt::attrib_t attrib; + std::vector shapes; + std::vector materials; + + auto load_t_begin = std::chrono::high_resolution_clock::now(); + size_t data_len = 0; + const char* data = get_file_data(&data_len, filename); + if (data == nullptr) { + printf("failed to load file\n"); + exit(-1); + return false; + } + auto load_t_end = std::chrono::high_resolution_clock::now(); + std::chrono::duration load_ms = load_t_end - load_t_begin; + if (verbose) { + std::cout << "filesize: " << data_len << std::endl; + std::cout << "load time: " << load_ms.count() << " [msecs]" << std::endl; + } + + + tinyobj_opt::LoadOption option; + option.req_num_threads = num_threads; + option.verbose = verbose; + bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option); + + if (!ret) { + std::cerr << "Failed to parse .obj" << std::endl; + return false; + } + bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); + bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); + + //std::cout << "vertices.size() = " << attrib.vertices.size() << std::endl; + //std::cout << "normals.size() = " << attrib.normals.size() << std::endl; + + { + DrawObject o; + std::vector vb; // pos(3float), normal(3float), color(3float) + size_t face_offset = 0; + for (size_t v = 0; v < attrib.face_num_verts.size(); v++) { + assert(attrib.face_num_verts[v] % 3 == 0); // assume all triangle face(multiple of 3). + for (size_t f = 0; f < attrib.face_num_verts[v] / 3; f++) { + tinyobj_opt::index_t idx0 = attrib.indices[face_offset+3*f+0]; + tinyobj_opt::index_t idx1 = attrib.indices[face_offset+3*f+1]; + tinyobj_opt::index_t idx2 = attrib.indices[face_offset+3*f+2]; + + float v[3][3]; + for (int k = 0; k < 3; k++) { + int f0 = idx0.vertex_index; + int f1 = idx1.vertex_index; + int f2 = idx2.vertex_index; + assert(f0 >= 0); + assert(f1 >= 0); + assert(f2 >= 0); + + v[0][k] = attrib.vertices[3*f0+k]; + v[1][k] = attrib.vertices[3*f1+k]; + v[2][k] = attrib.vertices[3*f2+k]; + bmin[k] = std::min(v[0][k], bmin[k]); + bmin[k] = std::min(v[1][k], bmin[k]); + bmin[k] = std::min(v[2][k], bmin[k]); + bmax[k] = std::max(v[0][k], bmax[k]); + bmax[k] = std::max(v[1][k], bmax[k]); + bmax[k] = std::max(v[2][k], bmax[k]); + } + + float n[3][3]; + + if (attrib.normals.size() > 0) { + int nf0 = idx0.normal_index; + int nf1 = idx1.normal_index; + int nf2 = idx2.normal_index; + + if (nf0 >= 0 && nf1 >= 0 && nf2 >= 0) { + assert(3*nf0+2 < attrib.normals.size()); + assert(3*nf1+2 < attrib.normals.size()); + assert(3*nf2+2 < attrib.normals.size()); + for (int k = 0; k < 3; k++) { + n[0][k] = attrib.normals[3*nf0+k]; + n[1][k] = attrib.normals[3*nf1+k]; + n[2][k] = attrib.normals[3*nf2+k]; + } + } else { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; + n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; + } + } else { + // compute geometric normal + CalcNormal(n[0], v[0], v[1], v[2]); + n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2]; + n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2]; + } + + for (int k = 0; k < 3; k++) { + vb.push_back(v[k][0]); + vb.push_back(v[k][1]); + vb.push_back(v[k][2]); + vb.push_back(n[k][0]); + vb.push_back(n[k][1]); + vb.push_back(n[k][2]); + // Use normal as color. + float c[3] = {n[k][0], n[k][1], n[k][2]}; + float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; + if (len2 > 1.0e-6f) { + float len = sqrtf(len2); + + c[0] /= len; + c[1] /= len; + c[2] /= len; + } + vb.push_back(c[0] * 0.5 + 0.5); + vb.push_back(c[1] * 0.5 + 0.5); + vb.push_back(c[2] * 0.5 + 0.5); + } + } + face_offset += attrib.face_num_verts[v]; + } + + o.vb = 0; + o.numTriangles = 0; + if (vb.size() > 0) { + glGenBuffers(1, &o.vb); + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW); + o.numTriangles = vb.size() / 9 / 3; + } + + gDrawObjects.push_back(o); + } + + printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]); + printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); + + return true; +} + +void reshapeFunc(GLFWwindow* window, int w, int h) +{ + (void)window; + // for retinal display. + int fb_w, fb_h; + glfwGetFramebufferSize(window, &fb_w, &fb_h); + + glViewport(0, 0, fb_w, fb_h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + width = w; + height = h; +} + +void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, int mods) { + (void)window; + (void)scancode; + (void)mods; + if(action == GLFW_PRESS || action == GLFW_REPEAT){ + // Move camera + float mv_x = 0, mv_y = 0, mv_z = 0; + if(key == GLFW_KEY_K) mv_x += 1; + else if(key == GLFW_KEY_J) mv_x += -1; + else if(key == GLFW_KEY_L) mv_y += 1; + else if(key == GLFW_KEY_H) mv_y += -1; + else if(key == GLFW_KEY_P) mv_z += 1; + else if(key == GLFW_KEY_N) mv_z += -1; + //camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05); + // Close window + if(key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GL_TRUE); + + //init_frame = true; + } +} + +void clickFunc(GLFWwindow* window, int button, int action, int mods){ + (void)window; + (void)mods; + if(button == GLFW_MOUSE_BUTTON_LEFT){ + if(action == GLFW_PRESS){ + mouseLeftPressed = true; + trackball(prev_quat, 0.0, 0.0, 0.0, 0.0); + } else if(action == GLFW_RELEASE){ + mouseLeftPressed = false; + } + } + if(button == GLFW_MOUSE_BUTTON_RIGHT){ + if(action == GLFW_PRESS){ + mouseRightPressed = true; + } else if(action == GLFW_RELEASE){ + mouseRightPressed = false; + } + } + if(button == GLFW_MOUSE_BUTTON_MIDDLE){ + if(action == GLFW_PRESS){ + mouseMiddlePressed = true; + } else if(action == GLFW_RELEASE){ + mouseMiddlePressed = false; + } + } +} + +void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){ + (void)window; + float rotScale = 1.0f; + float transScale = 2.0f; + + if(mouseLeftPressed){ + trackball(prev_quat, + rotScale * (2.0f * prevMouseX - width) / (float)width, + rotScale * (height - 2.0f * prevMouseY) / (float)height, + rotScale * (2.0f * mouse_x - width) / (float)width, + rotScale * (height - 2.0f * mouse_y) / (float)height); + + add_quats(prev_quat, curr_quat, curr_quat); + } else if (mouseMiddlePressed) { + eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width; + eye[1] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height; + } else if (mouseRightPressed) { + eye[2] += transScale * (mouse_y - prevMouseY) / (float)height; + lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height; + } + + // Update mouse point + prevMouseX = mouse_x; + prevMouseY = mouse_y; +} + +void Draw(const std::vector& drawObjects) +{ + glPolygonMode(GL_FRONT, GL_FILL); + glPolygonMode(GL_BACK, GL_FILL); + + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.0, 1.0); + glColor3f(1.0f, 1.0f, 1.0f); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 36, (const void*)0); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); + glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float)*6)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + } + + // draw wireframe + glDisable(GL_POLYGON_OFFSET_FILL); + glPolygonMode(GL_FRONT, GL_LINE); + glPolygonMode(GL_BACK, GL_LINE); + + glColor3f(0.0f, 0.0f, 0.4f); + for (size_t i = 0; i < drawObjects.size(); i++) { + DrawObject o = drawObjects[i]; + if (o.vb < 1) { + continue; + } + + glBindBuffer(GL_ARRAY_BUFFER, o.vb); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 36, (const void*)0); + glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3)); + + glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); + CheckErrors("drawarrays"); + } +} + +static void Init() { + trackball(curr_quat, 0, 0, 0, 0); + + eye[0] = 0.0f; + eye[1] = 0.0f; + eye[2] = 3.0f; + + lookat[0] = 0.0f; + lookat[1] = 0.0f; + lookat[2] = 0.0f; + + up[0] = 0.0f; + up[1] = 1.0f; + up[2] = 0.0f; +} + + +int main(int argc, char **argv) +{ + if (argc < 2) { + std::cout << "view input.obj " << std::endl; + return 0; + } + + bool benchmark_only = false; + int num_threads = -1; + bool verbose = false; + + if (argc > 2) { + num_threads = atoi(argv[2]); + } + + if (argc > 3) { + benchmark_only = (atoi(argv[3]) > 0) ? true : false; + } + + if (argc > 4) { + verbose = true; + } + + if (benchmark_only) { + + tinyobj_opt::attrib_t attrib; + std::vector shapes; + std::vector materials; + + size_t data_len = 0; + const char* data = get_file_data(&data_len, argv[1]); + if (data == nullptr) { + printf("failed to load file\n"); + exit(-1); + return false; + } + + if (data_len < 4) { + printf("Empty file\n"); + exit(-1); + return false; + } + printf("filesize: %d\n", (int)data_len); + tinyobj_opt::LoadOption option; + option.req_num_threads = num_threads; + option.verbose = true; + + bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option); + + return ret; + } + + Init(); + + std::cout << "Initialize GLFW..." << std::endl; + + if(!glfwInit()){ + std::cerr << "Failed to initialize GLFW." << std::endl; + return -1; + } + + std::cout << "GLFW Init OK." << std::endl; + + + window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL); + if(window == NULL){ + std::cerr << "Failed to open GLFW window. " << std::endl; + glfwTerminate(); + return 1; + } + + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + + // Callback + glfwSetWindowSizeCallback(window, reshapeFunc); + glfwSetKeyCallback(window, keyboardFunc); + glfwSetMouseButtonCallback(window, clickFunc); + glfwSetCursorPosCallback(window, motionFunc); + + glewExperimental = true; + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW." << std::endl; + return -1; + } + + reshapeFunc(window, width, height); + + float bmin[3], bmax[3]; + if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads, verbose)) { + printf("failed to load & conv\n"); + return -1; + } + + float maxExtent = 0.5f * (bmax[0] - bmin[0]); + if (maxExtent < 0.5f * (bmax[1] - bmin[1])) { + maxExtent = 0.5f * (bmax[1] - bmin[1]); + } + if (maxExtent < 0.5f * (bmax[2] - bmin[2])) { + maxExtent = 0.5f * (bmax[2] - bmin[2]); + } + + while(glfwWindowShouldClose(window) == GL_FALSE) { + glfwPollEvents(); + glClearColor(0.1f, 0.2f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glEnable(GL_DEPTH_TEST); + + // camera & rotate + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + GLfloat mat[4][4]; + gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]); + build_rotmatrix(mat, curr_quat); + glMultMatrixf(&mat[0][0]); + + // Fit to -1, 1 + glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent); + + // Centerize object. + glTranslatef(-0.5*(bmax[0] + bmin[0]), -0.5*(bmax[1] + bmin[1]), -0.5*(bmax[2] + bmin[2])); + + Draw(gDrawObjects); + + glfwSwapBuffers(window); + } + + glfwTerminate(); +} diff --git a/fuzzer/README.md b/fuzzer/README.md new file mode 100644 index 00000000..1cd63a29 --- /dev/null +++ b/fuzzer/README.md @@ -0,0 +1,51 @@ +# Fuzzing test + +Do fuzzing test for tinyobjloader + +## Supported API + +* [x] ParseFromString + +## Requirements + +* clang with fuzzer support(`-fsanitize=fuzzer`. at least clang 8.0 should work) + +## Setup + +### Ubuntu 18.04 + +``` +$ sudo apt install clang++-8 +$ sudo apt install libfuzzer-8-dev +``` + +Optionally, if you didn't set `update-alternatives` you can set `clang++` to point to `clang++8` + +``` +$ sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 10 +$ sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 10 +``` + +## How to compile + +Fuzz target is compiled with the rest of the project when environment variable `LIB_FUZZING_ENGINE` is defined when running cmake +With clang, you can compile with +``` +$ export LIB_FUZZING_ENGINE=-fsanitize=fuzzer +$ mkdir build && cd build +$ cmake .. -DBUILD_SHARED_LIBS=OFF +$ make -j $(nproc) +``` + +## How to run + +Increase memory limit. e.g. `-rss_limit_mb=2000` +cf libfuzzer.info for all options + +``` +$ ./fuzz_ParseFromString -rss_limit_mb=2000 +``` + +## Regression tests + +See `regression_runner/` diff --git a/fuzzer/afl.tar.gz b/fuzzer/afl.tar.gz new file mode 100644 index 00000000..40a924f3 Binary files /dev/null and b/fuzzer/afl.tar.gz differ diff --git a/fuzzer/fuzz_ParseFromString.cc b/fuzzer/fuzz_ParseFromString.cc new file mode 100644 index 00000000..aa45f89a --- /dev/null +++ b/fuzzer/fuzz_ParseFromString.cc @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +#define TINYOBJLOADER_IMPLEMENTATION // define this in only *one* .cc +#include "tiny_obj_loader.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + tinyobj::ObjReaderConfig reader_config; + tinyobj::ObjReader reader; + if (Size < 2) { + return 0; + } + for (size_t i = 0; i < Size-1; i++) { + if (Data[i] == 0) { + std::string obj_text (reinterpret_cast(Data), i); + std::string mtl_text (reinterpret_cast(Data+i+1), Size-i-1); + reader.ParseFromString(obj_text, mtl_text,reader_config); + return 0; + } + } + return 0; +} + diff --git a/fuzzer/regression_runner/Makefile b/fuzzer/regression_runner/Makefile new file mode 100644 index 00000000..f2c38a0d --- /dev/null +++ b/fuzzer/regression_runner/Makefile @@ -0,0 +1,2 @@ +all: + clang++ -fsanitize=address,undefined ../../loader_example.cc diff --git a/fuzzer/regression_runner/README.md b/fuzzer/regression_runner/README.md new file mode 100644 index 00000000..f59b9f6a --- /dev/null +++ b/fuzzer/regression_runner/README.md @@ -0,0 +1,11 @@ +# Run fuzzer regression tests + +Currently we only support Linux + clang. + +## How to run + +``` +$ make +$ ./a.out ../regressions/ +``` + diff --git a/fuzzer/regressions/clusterfuzz-testcase-minimized-fuzz_ParseFromString-4877060179886080 b/fuzzer/regressions/clusterfuzz-testcase-minimized-fuzz_ParseFromString-4877060179886080 new file mode 100644 index 00000000..e5094497 Binary files /dev/null and b/fuzzer/regressions/clusterfuzz-testcase-minimized-fuzz_ParseFromString-4877060179886080 differ diff --git a/fuzzer/runner.py b/fuzzer/runner.py new file mode 100644 index 00000000..0c06d4ba --- /dev/null +++ b/fuzzer/runner.py @@ -0,0 +1,20 @@ +import os, sys +import glob +import subprocess + +def main(): + for g in glob.glob("../tests/afl/id*"): + print(g) + + cmd = ["../a.out", g] + + proc = subprocess.Popen(cmd) + try: + outs, errs = proc.communicate(timeout=15) + print(outs) + except TimeoutExpired: + proc.kill() + outs, errs = proc.communicate() + + +main() diff --git a/images/sanmugel.png b/images/sanmugel.png new file mode 100644 index 00000000..32ea150f Binary files /dev/null and b/images/sanmugel.png differ diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100644 index 00000000..bc81f8f7 --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1,12 @@ +# A simple test for the minimal standard C++ library +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := tinyobjloader +LOCAL_SRC_FILES := ../tiny_obj_loader.cc + +LOCAL_C_INCLUDES := ../ + +include $(BUILD_STATIC_LIBRARY) diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 00000000..e5d31919 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,2 @@ +APP_ABI := all +APP_STL := stlport_static diff --git a/jni/Makefile b/jni/Makefile new file mode 100644 index 00000000..1f138538 --- /dev/null +++ b/jni/Makefile @@ -0,0 +1,2 @@ +all: + ndk-build diff --git a/jni/README b/jni/README new file mode 100644 index 00000000..f93bc08c --- /dev/null +++ b/jni/README @@ -0,0 +1 @@ +Just tests compilation with Android NDK r10. diff --git a/loader_example.cc b/loader_example.cc new file mode 100644 index 00000000..21feb684 --- /dev/null +++ b/loader_example.cc @@ -0,0 +1,437 @@ +// +// g++ loader_example.cc +// +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#ifdef __cplusplus +extern "C" { +#endif +#include +#include +#ifdef __cplusplus +} +#endif +#pragma comment(lib, "winmm.lib") +#else +#if defined(__unix__) || defined(__APPLE__) +#include +#else +#include +#endif +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#endif + +class timerutil { + public: +#ifdef _WIN32 + typedef DWORD time_t; + + timerutil() { ::timeBeginPeriod(1); } + ~timerutil() { ::timeEndPeriod(1); } + + void start() { t_[0] = ::timeGetTime(); } + void end() { t_[1] = ::timeGetTime(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / 1000); } + time_t msec() { return (time_t)((t_[1] - t_[0])); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000); } + time_t current() { return ::timeGetTime(); } + +#else +#if defined(__unix__) || defined(__APPLE__) + typedef unsigned long int time_t; + + void start() { gettimeofday(tv + 0, &tz); } + void end() { gettimeofday(tv + 1, &tz); } + + time_t sec() { return static_cast(tv[1].tv_sec - tv[0].tv_sec); } + time_t msec() { + return this->sec() * 1000 + + static_cast((tv[1].tv_usec - tv[0].tv_usec) / 1000); + } + time_t usec() { + return this->sec() * 1000000 + + static_cast(tv[1].tv_usec - tv[0].tv_usec); + } + time_t current() { + struct timeval t; + gettimeofday(&t, NULL); + return static_cast(t.tv_sec * 1000 + t.tv_usec); + } + +#else // C timer + // using namespace std; + typedef clock_t time_t; + + void start() { t_[0] = clock(); } + void end() { t_[1] = clock(); } + + time_t sec() { return (time_t)((t_[1] - t_[0]) / CLOCKS_PER_SEC); } + time_t msec() { return (time_t)((t_[1] - t_[0]) * 1000 / CLOCKS_PER_SEC); } + time_t usec() { return (time_t)((t_[1] - t_[0]) * 1000000 / CLOCKS_PER_SEC); } + time_t current() { return (time_t)clock(); } + +#endif +#endif + + private: +#ifdef _WIN32 + DWORD t_[2]; +#else +#if defined(__unix__) || defined(__APPLE__) + struct timeval tv[2]; + struct timezone tz; +#else + time_t t_[2]; +#endif +#endif +}; + +static void PrintInfo(const tinyobj::attrib_t& attrib, + const std::vector& shapes, + const std::vector& materials) { + std::cout << "# of vertices : " << (attrib.vertices.size() / 3) << std::endl; + std::cout << "# of normals : " << (attrib.normals.size() / 3) << std::endl; + std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2) + << std::endl; + + std::cout << "# of shapes : " << shapes.size() << std::endl; + std::cout << "# of materials : " << materials.size() << std::endl; + + for (size_t v = 0; v < attrib.vertices.size() / 3; v++) { + printf(" v[%ld] = (%f, %f, %f)\n", static_cast(v), + static_cast(attrib.vertices[3 * v + 0]), + static_cast(attrib.vertices[3 * v + 1]), + static_cast(attrib.vertices[3 * v + 2])); + } + + for (size_t v = 0; v < attrib.normals.size() / 3; v++) { + printf(" n[%ld] = (%f, %f, %f)\n", static_cast(v), + static_cast(attrib.normals[3 * v + 0]), + static_cast(attrib.normals[3 * v + 1]), + static_cast(attrib.normals[3 * v + 2])); + } + + for (size_t v = 0; v < attrib.texcoords.size() / 2; v++) { + printf(" uv[%ld] = (%f, %f)\n", static_cast(v), + static_cast(attrib.texcoords[2 * v + 0]), + static_cast(attrib.texcoords[2 * v + 1])); + } + + // For each shape + for (size_t i = 0; i < shapes.size(); i++) { + printf("shape[%ld].name = %s\n", static_cast(i), + shapes[i].name.c_str()); + printf("Size of shape[%ld].mesh.indices: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.indices.size())); + printf("Size of shape[%ld].lines.indices: %lu\n", static_cast(i), + static_cast(shapes[i].lines.indices.size())); + printf("Size of shape[%ld].points.indices: %lu\n", static_cast(i), + static_cast(shapes[i].points.indices.size())); + + size_t index_offset = 0; + + assert(shapes[i].mesh.num_face_vertices.size() == + shapes[i].mesh.material_ids.size()); + + assert(shapes[i].mesh.num_face_vertices.size() == + shapes[i].mesh.smoothing_group_ids.size()); + + printf("shape[%ld].num_faces: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.num_face_vertices.size())); + + // For each face + for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) { + size_t fnum = shapes[i].mesh.num_face_vertices[f]; + + printf(" face[%ld].fnum = %ld\n", static_cast(f), + static_cast(fnum)); + + // For each vertex in the face + for (size_t v = 0; v < fnum; v++) { + tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; + printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), + static_cast(v), idx.vertex_index, idx.normal_index, + idx.texcoord_index); + } + + printf(" face[%ld].material_id = %d\n", static_cast(f), + shapes[i].mesh.material_ids[f]); + printf(" face[%ld].smoothing_group_id = %d\n", static_cast(f), + shapes[i].mesh.smoothing_group_ids[f]); + + index_offset += fnum; + } + + printf("shape[%ld].num_tags: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.tags.size())); + for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { + printf(" tag[%ld] = %s ", static_cast(t), + shapes[i].mesh.tags[t].name.c_str()); + printf(" ints: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) { + printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); + if (j < (shapes[i].mesh.tags[t].intValues.size() - 1)) { + printf(", "); + } + } + printf("]"); + + printf(" floats: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) { + printf("%f", static_cast( + shapes[i].mesh.tags[t].floatValues[j])); + if (j < (shapes[i].mesh.tags[t].floatValues.size() - 1)) { + printf(", "); + } + } + printf("]"); + + printf(" strings: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size() - 1)) { + printf(", "); + } + } + printf("]"); + printf("\n"); + } + } + + for (size_t i = 0; i < materials.size(); i++) { + printf("material[%ld].name = %s\n", static_cast(i), + materials[i].name.c_str()); + printf(" material.Ka = (%f, %f ,%f)\n", + static_cast(materials[i].ambient[0]), + static_cast(materials[i].ambient[1]), + static_cast(materials[i].ambient[2])); + printf(" material.Kd = (%f, %f ,%f)\n", + static_cast(materials[i].diffuse[0]), + static_cast(materials[i].diffuse[1]), + static_cast(materials[i].diffuse[2])); + printf(" material.Ks = (%f, %f ,%f)\n", + static_cast(materials[i].specular[0]), + static_cast(materials[i].specular[1]), + static_cast(materials[i].specular[2])); + printf(" material.Tr = (%f, %f ,%f)\n", + static_cast(materials[i].transmittance[0]), + static_cast(materials[i].transmittance[1]), + static_cast(materials[i].transmittance[2])); + printf(" material.Ke = (%f, %f ,%f)\n", + static_cast(materials[i].emission[0]), + static_cast(materials[i].emission[1]), + static_cast(materials[i].emission[2])); + printf(" material.Ns = %f\n", + static_cast(materials[i].shininess)); + printf(" material.Ni = %f\n", static_cast(materials[i].ior)); + printf(" material.dissolve = %f\n", + static_cast(materials[i].dissolve)); + printf(" material.illum = %d\n", materials[i].illum); + printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); + printf(" material.map_Ns = %s\n", + materials[i].specular_highlight_texname.c_str()); + printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); + printf(" bump_multiplier = %f\n", static_cast(materials[i].bump_texopt.bump_multiplier)); + printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); + printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); + printf(" <>\n"); + printf(" material.Pr = %f\n", static_cast(materials[i].roughness)); + printf(" material.Pm = %f\n", static_cast(materials[i].metallic)); + printf(" material.Ps = %f\n", static_cast(materials[i].sheen)); + printf(" material.Pc = %f\n", static_cast(materials[i].clearcoat_thickness)); + printf(" material.Pcr = %f\n", static_cast(materials[i].clearcoat_roughness)); + printf(" material.aniso = %f\n", static_cast(materials[i].anisotropy)); + printf(" material.anisor = %f\n", static_cast(materials[i].anisotropy_rotation)); + printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str()); + printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str()); + printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str()); + printf(" material.map_Ps = %s\n", materials[i].sheen_texname.c_str()); + printf(" material.norm = %s\n", materials[i].normal_texname.c_str()); + std::map::const_iterator it( + materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd( + materials[i].unknown_parameter.end()); + + for (; it != itEnd; it++) { + printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + } + printf("\n"); + } +} + +static bool TestLoadObj(const char* filename, const char* basepath = NULL, + bool triangulate = true) { + std::cout << "Loading " << filename << std::endl; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + timerutil t; + t.start(); + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename, + basepath, triangulate); + t.end(); + printf("Parsing time: %lu [msecs]\n", t.msec()); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + if (!ret) { + printf("Failed to load/parse .obj.\n"); + return false; + } + + PrintInfo(attrib, shapes, materials); + + return true; +} + +static bool TestStreamLoadObj() { + std::cout << "Stream Loading " << std::endl; + + std::stringstream objStream; + objStream << "mtllib cube.mtl\n" + "\n" + "v 0.000000 2.000000 2.000000\n" + "v 0.000000 0.000000 2.000000\n" + "v 2.000000 0.000000 2.000000\n" + "v 2.000000 2.000000 2.000000\n" + "v 0.000000 2.000000 0.000000\n" + "v 0.000000 0.000000 0.000000\n" + "v 2.000000 0.000000 0.000000\n" + "v 2.000000 2.000000 0.000000\n" + "# 8 vertices\n" + "\n" + "g front cube\n" + "usemtl white\n" + "f 1 2 3 4\n" + "g back cube\n" + "# expects white material\n" + "f 8 7 6 5\n" + "g right cube\n" + "usemtl red\n" + "f 4 3 7 8\n" + "g top cube\n" + "usemtl white\n" + "f 5 1 4 8\n" + "g left cube\n" + "usemtl green\n" + "f 5 6 2 1\n" + "g bottom cube\n" + "usemtl white\n" + "f 2 6 7 3\n" + "# 6 elements"; + + std::string matStream( + "newmtl white\n" + "Ka 0 0 0\n" + "Kd 1 1 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl red\n" + "Ka 0 0 0\n" + "Kd 1 0 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl green\n" + "Ka 0 0 0\n" + "Kd 0 1 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl blue\n" + "Ka 0 0 0\n" + "Kd 0 0 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl light\n" + "Ka 20 20 20\n" + "Kd 1 1 1\n" + "Ks 0 0 0"); + + using namespace tinyobj; + class MaterialStringStreamReader : public MaterialReader { + public: + MaterialStringStreamReader(const std::string& matSStream) + : m_matSStream(matSStream) {} + virtual ~MaterialStringStreamReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string& matId, + std::vector* materials, + std::map* matMap, + std::string* warn, + std::string* err) TINYOBJ_OVERRIDE { + (void)err; + (void)matId; + LoadMtl(matMap, materials, &m_matSStream, warn, err); + + return true; + } + + private: + std::stringstream m_matSStream; + }; + + MaterialStringStreamReader matSSReader(matStream); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, &objStream, + &matSSReader); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + return false; + } + + PrintInfo(attrib, shapes, materials); + + return true; +} + +int main(int argc, char** argv) { + if (argc > 1) { + const char* basepath = "models/"; + if (argc > 2) { + basepath = argv[2]; + } + assert(true == TestLoadObj(argv[1], basepath)); + } else { + // assert(true == TestLoadObj("cornell_box.obj")); + // assert(true == TestLoadObj("cube.obj")); + assert(true == TestStreamLoadObj()); + assert(true == + TestLoadObj("models/catmark_torus_creases0.obj", "models/", false)); + } + + return 0; +} diff --git a/mapbox/LICENSE b/mapbox/LICENSE new file mode 100644 index 00000000..8bafb577 --- /dev/null +++ b/mapbox/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2015, Mapbox + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/mapbox/earcut.hpp b/mapbox/earcut.hpp new file mode 100644 index 00000000..01bd7e96 --- /dev/null +++ b/mapbox/earcut.hpp @@ -0,0 +1,820 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mapbox { + +namespace util { + +template struct nth { + inline static typename std::tuple_element::type + get(const T& t) { return std::get(t); }; +}; + +} + +namespace detail { + +template +class Earcut { +public: + std::vector indices; + std::size_t vertices = 0; + + template + void operator()(const Polygon& points); + +private: + struct Node { + Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {} + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + Node(Node&&) = delete; + Node& operator=(Node&&) = delete; + + const N i; + const double x; + const double y; + + // previous and next vertice nodes in a polygon ring + Node* prev = nullptr; + Node* next = nullptr; + + // z-order curve value + int32_t z = 0; + + // previous and next nodes in z-order + Node* prevZ = nullptr; + Node* nextZ = nullptr; + + // indicates whether this is a steiner point + bool steiner = false; + }; + + template Node* linkedList(const Ring& points, const bool clockwise); + Node* filterPoints(Node* start, Node* end = nullptr); + void earcutLinked(Node* ear, int pass = 0); + bool isEar(Node* ear); + bool isEarHashed(Node* ear); + Node* cureLocalIntersections(Node* start); + void splitEarcut(Node* start); + template Node* eliminateHoles(const Polygon& points, Node* outerNode); + Node* eliminateHole(Node* hole, Node* outerNode); + Node* findHoleBridge(Node* hole, Node* outerNode); + bool sectorContainsSector(const Node* m, const Node* p); + void indexCurve(Node* start); + Node* sortLinked(Node* list); + int32_t zOrder(const double x_, const double y_); + Node* getLeftmost(Node* start); + bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const; + bool isValidDiagonal(Node* a, Node* b); + double area(const Node* p, const Node* q, const Node* r) const; + bool equals(const Node* p1, const Node* p2); + bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2); + bool onSegment(const Node* p, const Node* q, const Node* r); + int sign(double val); + bool intersectsPolygon(const Node* a, const Node* b); + bool locallyInside(const Node* a, const Node* b); + bool middleInside(const Node* a, const Node* b); + Node* splitPolygon(Node* a, Node* b); + template Node* insertNode(std::size_t i, const Point& p, Node* last); + void removeNode(Node* p); + + bool hashing; + double minX, maxX; + double minY, maxY; + double inv_size = 0; + + template > + class ObjectPool { + public: + ObjectPool() { } + ObjectPool(std::size_t blockSize_) { + reset(blockSize_); + } + ~ObjectPool() { + clear(); + } + template + T* construct(Args&&... args) { + if (currentIndex >= blockSize) { + currentBlock = alloc_traits::allocate(alloc, blockSize); + allocations.emplace_back(currentBlock); + currentIndex = 0; + } + T* object = ¤tBlock[currentIndex++]; + alloc_traits::construct(alloc, object, std::forward(args)...); + return object; + } + void reset(std::size_t newBlockSize) { + for (auto allocation : allocations) { + alloc_traits::deallocate(alloc, allocation, blockSize); + } + allocations.clear(); + blockSize = std::max(1, newBlockSize); + currentBlock = nullptr; + currentIndex = blockSize; + } + void clear() { reset(blockSize); } + private: + T* currentBlock = nullptr; + std::size_t currentIndex = 1; + std::size_t blockSize = 1; + std::vector allocations; + Alloc alloc; + typedef typename std::allocator_traits alloc_traits; + }; + ObjectPool nodes; +}; + +template template +void Earcut::operator()(const Polygon& points) { + // reset + indices.clear(); + vertices = 0; + + if (points.empty()) return; + + double x; + double y; + int threshold = 80; + std::size_t len = 0; + + for (size_t i = 0; threshold >= 0 && i < points.size(); i++) { + threshold -= static_cast(points[i].size()); + len += points[i].size(); + } + + //estimate size of nodes and indices + nodes.reset(len * 3 / 2); + indices.reserve(len + points[0].size()); + + Node* outerNode = linkedList(points[0], true); + if (!outerNode || outerNode->prev == outerNode->next) return; + + if (points.size() > 1) outerNode = eliminateHoles(points, outerNode); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + hashing = threshold < 0; + if (hashing) { + Node* p = outerNode->next; + minX = maxX = outerNode->x; + minY = maxY = outerNode->y; + do { + x = p->x; + y = p->y; + minX = std::min(minX, x); + minY = std::min(minY, y); + maxX = std::max(maxX, x); + maxY = std::max(maxY, y); + p = p->next; + } while (p != outerNode); + + // minX, minY and size are later used to transform coords into integers for z-order calculation + inv_size = std::max(maxX - minX, maxY - minY); + inv_size = inv_size != .0 ? (1. / inv_size) : .0; + } + + earcutLinked(outerNode); + + nodes.clear(); +} + +// create a circular doubly linked list from polygon points in the specified winding order +template template +typename Earcut::Node* +Earcut::linkedList(const Ring& points, const bool clockwise) { + using Point = typename Ring::value_type; + double sum = 0; + const std::size_t len = points.size(); + std::size_t i, j; + Node* last = nullptr; + + // calculate original winding order of a polygon ring + for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) { + const auto& p1 = points[i]; + const auto& p2 = points[j]; + const double p20 = util::nth<0, Point>::get(p2); + const double p10 = util::nth<0, Point>::get(p1); + const double p11 = util::nth<1, Point>::get(p1); + const double p21 = util::nth<1, Point>::get(p2); + sum += (p20 - p10) * (p11 + p21); + } + + // link points into circular doubly-linked list in the specified winding order + if (clockwise == (sum > 0)) { + for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last); + } else { + for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last); + } + + if (last && equals(last, last->next)) { + removeNode(last); + last = last->next; + } + + vertices += len; + + return last; +} + +// eliminate colinear or duplicate points +template +typename Earcut::Node* +Earcut::filterPoints(Node* start, Node* end) { + if (!end) end = start; + + Node* p = start; + bool again; + do { + again = false; + + if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) { + removeNode(p); + p = end = p->prev; + + if (p == p->next) break; + again = true; + + } else { + p = p->next; + } + } while (again || p != end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +template +void Earcut::earcutLinked(Node* ear, int pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && hashing) indexCurve(ear); + + Node* stop = ear; + Node* prev; + Node* next; + + int iterations = 0; + + // iterate through ears, slicing them one by one + while (ear->prev != ear->next) { + iterations++; + prev = ear->prev; + next = ear->next; + + if (hashing ? isEarHashed(ear) : isEar(ear)) { + // cut off the triangle + indices.emplace_back(prev->i); + indices.emplace_back(ear->i); + indices.emplace_back(next->i); + + removeNode(ear); + + // skipping the next vertice leads to less sliver triangles + ear = next->next; + stop = next->next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear == stop) { + // try filtering points and slicing again + if (!pass) earcutLinked(filterPoints(ear), 1); + + // if this didn't work, try curing all small self-intersections locally + else if (pass == 1) { + ear = cureLocalIntersections(filterPoints(ear)); + earcutLinked(ear, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass == 2) splitEarcut(ear); + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +template +bool Earcut::isEar(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + Node* p = ear->next->next; + + while (p != ear->prev) { + if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->next; + } + + return true; +} + +template +bool Earcut::isEarHashed(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + const double minTX = std::min(a->x, std::min(b->x, c->x)); + const double minTY = std::min(a->y, std::min(b->y, c->y)); + const double maxTX = std::max(a->x, std::max(b->x, c->x)); + const double maxTY = std::max(a->y, std::max(b->y, c->y)); + + // z-order range for the current triangle bbox; + const int32_t minZ = zOrder(minTX, minTY); + const int32_t maxZ = zOrder(maxTX, maxTY); + + // first look for points inside the triangle in increasing z-order + Node* p = ear->nextZ; + + while (p && p->z <= maxZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->nextZ; + } + + // then look for points in decreasing z-order + p = ear->prevZ; + + while (p && p->z >= minZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->prevZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +template +typename Earcut::Node* +Earcut::cureLocalIntersections(Node* start) { + Node* p = start; + do { + Node* a = p->prev; + Node* b = p->next->next; + + // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) + if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) { + indices.emplace_back(a->i); + indices.emplace_back(p->i); + indices.emplace_back(b->i); + + // remove two nodes involved + removeNode(p); + removeNode(p->next); + + p = start = b; + } + p = p->next; + } while (p != start); + + return filterPoints(p); +} + +// try splitting polygon into two and triangulate them independently +template +void Earcut::splitEarcut(Node* start) { + // look for a valid diagonal that divides the polygon into two + Node* a = start; + do { + Node* b = a->next->next; + while (b != a->prev) { + if (a->i != b->i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + Node* c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a->next); + c = filterPoints(c, c->next); + + // run earcut on each half + earcutLinked(a); + earcutLinked(c); + return; + } + b = b->next; + } + a = a->next; + } while (a != start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +template template +typename Earcut::Node* +Earcut::eliminateHoles(const Polygon& points, Node* outerNode) { + const size_t len = points.size(); + + std::vector queue; + for (size_t i = 1; i < len; i++) { + Node* list = linkedList(points[i], false); + if (list) { + if (list == list->next) list->steiner = true; + queue.push_back(getLeftmost(list)); + } + } + std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) { + return a->x < b->x; + }); + + // process holes from left to right + for (size_t i = 0; i < queue.size(); i++) { + outerNode = eliminateHole(queue[i], outerNode); + outerNode = filterPoints(outerNode, outerNode->next); + } + + return outerNode; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +template +typename Earcut::Node* +Earcut::eliminateHole(Node* hole, Node* outerNode) { + Node* bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } + + Node* bridgeReverse = splitPolygon(bridge, hole); + + // filter collinear points around the cuts + Node* filteredBridge = filterPoints(bridge, bridge->next); + filterPoints(bridgeReverse, bridgeReverse->next); + + // Check if input node was removed by the filtering + return outerNode == bridge ? filteredBridge : outerNode; +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +template +typename Earcut::Node* +Earcut::findHoleBridge(Node* hole, Node* outerNode) { + Node* p = outerNode; + double hx = hole->x; + double hy = hole->y; + double qx = -std::numeric_limits::infinity(); + Node* m = nullptr; + + // find a segment intersected by a ray from the hole's leftmost Vertex to the left; + // segment's endpoint with lesser x will be potential connection Vertex + do { + if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) { + double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y); + if (x <= hx && x > qx) { + qx = x; + if (x == hx) { + if (hy == p->y) return p; + if (hy == p->next->y) return p->next; + } + m = p->x < p->next->x ? p : p->next; + } + } + p = p->next; + } while (p != outerNode); + + if (!m) return 0; + + if (hx == qx) return m; // hole touches outer segment; pick leftmost endpoint + + // look for points inside the triangle of hole Vertex, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the Vertex of the minimum angle with the ray as connection Vertex + + const Node* stop = m; + double tanMin = std::numeric_limits::infinity(); + double tanCur = 0; + + p = m; + double mx = m->x; + double my = m->y; + + do { + if (hx >= p->x && p->x >= mx && hx != p->x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) { + + tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential + + if (locallyInside(p, hole) && + (tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) { + m = p; + tanMin = tanCur; + } + } + + p = p->next; + } while (p != stop); + + return m; +} + +// whether sector in vertex m contains sector in vertex p in the same coordinates +template +bool Earcut::sectorContainsSector(const Node* m, const Node* p) { + return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0; +} + +// interlink polygon nodes in z-order +template +void Earcut::indexCurve(Node* start) { + assert(start); + Node* p = start; + + do { + p->z = p->z ? p->z : zOrder(p->x, p->y); + p->prevZ = p->prev; + p->nextZ = p->next; + p = p->next; + } while (p != start); + + p->prevZ->nextZ = nullptr; + p->prevZ = nullptr; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +template +typename Earcut::Node* +Earcut::sortLinked(Node* list) { + assert(list); + Node* p; + Node* q; + Node* e; + Node* tail; + int i, numMerges, pSize, qSize; + int inSize = 1; + + for (;;) { + p = list; + list = nullptr; + tail = nullptr; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + pSize++; + q = q->nextZ; + if (!q) break; + } + + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize == 0) { + e = q; + q = q->nextZ; + qSize--; + } else if (qSize == 0 || !q) { + e = p; + p = p->nextZ; + pSize--; + } else if (p->z <= q->z) { + e = p; + p = p->nextZ; + pSize--; + } else { + e = q; + q = q->nextZ; + qSize--; + } + + if (tail) tail->nextZ = e; + else list = e; + + e->prevZ = tail; + tail = e; + } + + p = q; + } + + tail->nextZ = nullptr; + + if (numMerges <= 1) return list; + + inSize *= 2; + } +} + +// z-order of a Vertex given coords and size of the data bounding box +template +int32_t Earcut::zOrder(const double x_, const double y_) { + // coords are transformed into non-negative 15-bit integer range + int32_t x = static_cast(32767.0 * (x_ - minX) * inv_size); + int32_t y = static_cast(32767.0 * (y_ - minY) * inv_size); + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +template +typename Earcut::Node* +Earcut::getLeftmost(Node* start) { + Node* p = start; + Node* leftmost = start; + do { + if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y)) + leftmost = p; + p = p->next; + } while (p != start); + + return leftmost; +} + +// check if a point lies within a convex triangle +template +bool Earcut::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const { + return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && + (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && + (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0; +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +template +bool Earcut::isValidDiagonal(Node* a, Node* b) { + return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges + ((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors + (equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case +} + +// signed area of a triangle +template +double Earcut::area(const Node* p, const Node* q, const Node* r) const { + return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); +} + +// check if two points are equal +template +bool Earcut::equals(const Node* p1, const Node* p2) { + return p1->x == p2->x && p1->y == p2->y; +} + +// check if two segments intersect +template +bool Earcut::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) { + int o1 = sign(area(p1, q1, p2)); + int o2 = sign(area(p1, q1, q2)); + int o3 = sign(area(p2, q2, p1)); + int o4 = sign(area(p2, q2, q1)); + + if (o1 != o2 && o3 != o4) return true; // general case + + if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + + return false; +} + +// for collinear points p, q, r, check if point q lies on segment pr +template +bool Earcut::onSegment(const Node* p, const Node* q, const Node* r) { + return q->x <= std::max(p->x, r->x) && + q->x >= std::min(p->x, r->x) && + q->y <= std::max(p->y, r->y) && + q->y >= std::min(p->y, r->y); +} + +template +int Earcut::sign(double val) { + return (0.0 < val) - (val < 0.0); +} + +// check if a polygon diagonal intersects any polygon segments +template +bool Earcut::intersectsPolygon(const Node* a, const Node* b) { + const Node* p = a; + do { + if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i && + intersects(p, p->next, a, b)) return true; + p = p->next; + } while (p != a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +template +bool Earcut::locallyInside(const Node* a, const Node* b) { + return area(a->prev, a, a->next) < 0 ? + area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 : + area(a, b, a->prev) < 0 || area(a, a->next, b) < 0; +} + +// check if the middle Vertex of a polygon diagonal is inside the polygon +template +bool Earcut::middleInside(const Node* a, const Node* b) { + const Node* p = a; + bool inside = false; + double px = (a->x + b->x) / 2; + double py = (a->y + b->y) / 2; + do { + if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y && + (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x)) + inside = !inside; + p = p->next; + } while (p != a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits +// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a +// single ring +template +typename Earcut::Node* +Earcut::splitPolygon(Node* a, Node* b) { + Node* a2 = nodes.construct(a->i, a->x, a->y); + Node* b2 = nodes.construct(b->i, b->x, b->y); + Node* an = a->next; + Node* bp = b->prev; + + a->next = b; + b->prev = a; + + a2->next = an; + an->prev = a2; + + b2->next = a2; + a2->prev = b2; + + bp->next = b2; + b2->prev = bp; + + return b2; +} + +// create a node and util::optionally link it with previous one (in a circular doubly linked list) +template template +typename Earcut::Node* +Earcut::insertNode(std::size_t i, const Point& pt, Node* last) { + Node* p = nodes.construct(static_cast(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt)); + + if (!last) { + p->prev = p; + p->next = p; + + } else { + assert(last); + p->next = last->next; + p->prev = last; + last->next->prev = p; + last->next = p; + } + return p; +} + +template +void Earcut::removeNode(Node* p) { + p->next->prev = p->prev; + p->prev->next = p->next; + + if (p->prevZ) p->prevZ->nextZ = p->nextZ; + if (p->nextZ) p->nextZ->prevZ = p->prevZ; +} +} + +template +std::vector earcut(const Polygon& poly) { + mapbox::detail::Earcut earcut; + earcut(poly); + return std::move(earcut.indices); +} +} diff --git a/models/catmark_torus_creases0.obj b/models/catmark_torus_creases0.obj new file mode 100644 index 00000000..bf18f158 --- /dev/null +++ b/models/catmark_torus_creases0.obj @@ -0,0 +1,101 @@ +# +# Copyright 2013 Pixar +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http:#www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +# This file uses centimeters as units for non-parametric coordinates. + +v 1.25052 0.517982 0.353553 +v 0.597239 0.247384 0.353553 +v 0.597239 0.247384 -0.353553 +v 1.25052 0.517982 -0.353553 +v 0.517982 1.25052 0.353553 +v 0.247384 0.597239 0.353553 +v 0.247384 0.597239 -0.353553 +v 0.517982 1.25052 -0.353553 +v -0.517982 1.25052 0.353553 +v -0.247384 0.597239 0.353553 +v -0.247384 0.597239 -0.353553 +v -0.517982 1.25052 -0.353553 +v -1.25052 0.517982 0.353553 +v -0.597239 0.247384 0.353553 +v -0.597239 0.247384 -0.353553 +v -1.25052 0.517982 -0.353553 +v -1.25052 -0.517982 0.353553 +v -0.597239 -0.247384 0.353553 +v -0.597239 -0.247384 -0.353553 +v -1.25052 -0.517982 -0.353553 +v -0.517982 -1.25052 0.353553 +v -0.247384 -0.597239 0.353553 +v -0.247384 -0.597239 -0.353553 +v -0.517982 -1.25052 -0.353553 +v 0.517982 -1.25052 0.353553 +v 0.247384 -0.597239 0.353553 +v 0.247384 -0.597239 -0.353553 +v 0.517982 -1.25052 -0.353553 +v 1.25052 -0.517982 0.353553 +v 0.597239 -0.247384 0.353553 +v 0.597239 -0.247384 -0.353553 +v 1.25052 -0.517982 -0.353553 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 5/1/1 6/2/2 2/3/3 1/4/4 +f 6/1/5 7/2/6 3/3/7 2/4/8 +f 7/1/9 8/2/10 4/3/11 3/4/12 +f 8/1/13 5/2/14 1/3/15 4/4/16 +f 9/1/17 10/2/18 6/3/19 5/4/20 +f 10/1/21 11/2/22 7/3/23 6/4/24 +f 11/1/25 12/2/26 8/3/27 7/4/28 +f 12/1/29 9/2/30 5/3/31 8/4/32 +f 13/1/33 14/2/34 10/3/35 9/4/36 +f 14/1/37 15/2/38 11/3/39 10/4/40 +f 15/1/41 16/2/42 12/3/43 11/4/44 +f 16/1/45 13/2/46 9/3/47 12/4/48 +f 17/1/49 18/2/50 14/3/51 13/4/52 +f 18/1/53 19/2/54 15/3/55 14/4/56 +f 19/1/57 20/2/58 16/3/59 15/4/60 +f 20/1/61 17/2/62 13/3/63 16/4/64 +f 21/1/65 22/2/66 18/3/67 17/4/68 +f 22/1/69 23/2/70 19/3/71 18/4/72 +f 23/1/73 24/2/74 20/3/75 19/4/76 +f 24/1/77 21/2/78 17/3/79 20/4/80 +f 25/1/81 26/2/82 22/3/83 21/4/84 +f 26/1/85 27/2/86 23/3/87 22/4/88 +f 27/1/89 28/2/90 24/3/91 23/4/92 +f 28/1/93 25/2/94 21/3/95 24/4/96 +f 29/1/97 30/2/98 26/3/99 25/4/100 +f 30/1/101 31/2/102 27/3/103 26/4/104 +f 31/1/105 32/2/106 28/3/107 27/4/108 +f 32/1/109 29/2/110 25/3/111 28/4/112 +f 1/1/113 2/2/114 30/3/115 29/4/116 +f 2/1/117 3/2/118 31/3/119 30/4/120 +f 3/1/121 4/2/122 32/3/123 31/4/124 +f 4/1/125 1/2/126 29/3/127 32/4/128 +t crease 2/1/0 1 5 4.7 +t crease 2/1/0 5 9 4.7 +t crease 2/1/0 9 13 4.7 +t crease 2/1/0 13 17 4.7 +t crease 2/1/0 17 21 4.7 +t crease 2/1/0 21 25 4.7 +t crease 2/1/0 25 29 4.7 +t crease 2/1/0 29 1 4.7 diff --git a/models/colorspace-issue-184.mtl b/models/colorspace-issue-184.mtl new file mode 100644 index 00000000..7f3f9a43 --- /dev/null +++ b/models/colorspace-issue-184.mtl @@ -0,0 +1,9 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +map_Kd -colorspace sRGB -o 0.1 diffuse.jpg +map_Ks -s 0.1 0.2 specular.jpg +map_bump -colorspace linear -bm 3 bumpmap.jpg + diff --git a/models/colorspace-issue-184.obj b/models/colorspace-issue-184.obj new file mode 100644 index 00000000..ae589cbe --- /dev/null +++ b/models/colorspace-issue-184.obj @@ -0,0 +1,7 @@ +mtllib colorspace-issue-184.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/cornell_box.mtl b/models/cornell_box.mtl similarity index 100% rename from cornell_box.mtl rename to models/cornell_box.mtl diff --git a/cornell_box.obj b/models/cornell_box.obj similarity index 100% rename from cornell_box.obj rename to models/cornell_box.obj diff --git a/models/cornell_box_multimaterial.obj b/models/cornell_box_multimaterial.obj new file mode 100644 index 00000000..68093bea --- /dev/null +++ b/models/cornell_box_multimaterial.obj @@ -0,0 +1,146 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 +usemtl green + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/models/cube-vertex-w-component.obj b/models/cube-vertex-w-component.obj new file mode 100644 index 00000000..b909f26d --- /dev/null +++ b/models/cube-vertex-w-component.obj @@ -0,0 +1,31 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 0.1 +v 0.000000 0.000000 2.000000 0.2 +v 2.000000 0.000000 2.000000 0.3 +v 2.000000 2.000000 2.000000 0.4 +v 0.000000 2.000000 0.000000 0.5 +v 0.000000 0.000000 0.000000 0.6 +v 2.000000 0.000000 0.000000 0.7 +v 2.000000 2.000000 0.000000 0.8 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/cube-vertexcol.obj b/models/cube-vertexcol.obj new file mode 100644 index 00000000..494ce214 --- /dev/null +++ b/models/cube-vertexcol.obj @@ -0,0 +1,31 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 0 0 0 +v 0.000000 0.000000 2.000000 0 0 1 +v 2.000000 0.000000 2.000000 0 1 0 +v 2.000000 2.000000 2.000000 0 1 1 +v 0.000000 2.000000 0.000000 1 0 0 +v 0.000000 0.000000 0.000000 1 0 1 +v 2.000000 0.000000 0.000000 1 1 0 +v 2.000000 2.000000 0.000000 1 1 1 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/cube.mtl b/models/cube.mtl similarity index 100% rename from cube.mtl rename to models/cube.mtl diff --git a/cube.obj b/models/cube.obj similarity index 89% rename from cube.obj rename to models/cube.obj index 9213e178..f12451f0 100644 --- a/cube.obj +++ b/models/cube.obj @@ -13,7 +13,8 @@ v 2.000000 2.000000 0.000000 g front cube usemtl white f 1 2 3 4 -g back cube +# two white spaces between 'back' and 'cube' +g back cube # expects white material f 8 7 6 5 g right cube diff --git a/models/cube_w_BOM.mtl b/models/cube_w_BOM.mtl new file mode 100644 index 00000000..96255b54 --- /dev/null +++ b/models/cube_w_BOM.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/cube_w_BOM.obj b/models/cube_w_BOM.obj new file mode 100644 index 00000000..3c395f04 --- /dev/null +++ b/models/cube_w_BOM.obj @@ -0,0 +1,32 @@ +mtllib cube_w_BOM.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +# two white spaces between 'back' and 'cube' +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/invalid-face-definition.mtl b/models/invalid-face-definition.mtl new file mode 100644 index 00000000..d3a1c7a6 --- /dev/null +++ b/models/invalid-face-definition.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/invalid-face-definition.obj b/models/invalid-face-definition.obj new file mode 100644 index 00000000..73ca6783 --- /dev/null +++ b/models/invalid-face-definition.obj @@ -0,0 +1,18 @@ +mtllib invalid-face-definition.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 +g back cube +# expects white material +f 8 7 diff --git a/models/invalid-relative-texture-index.obj b/models/invalid-relative-texture-index.obj new file mode 100644 index 00000000..ed3a571a --- /dev/null +++ b/models/invalid-relative-texture-index.obj @@ -0,0 +1,2 @@ +vt 0 0 +f 1/-1 1/-1 1/-2 \ No newline at end of file diff --git a/models/invalid-relative-vertex-index.obj b/models/invalid-relative-vertex-index.obj new file mode 100644 index 00000000..bddc54a9 --- /dev/null +++ b/models/invalid-relative-vertex-index.obj @@ -0,0 +1 @@ +f -4 -3 -2 \ No newline at end of file diff --git a/models/issue-138.mtl b/models/issue-138.mtl new file mode 100644 index 00000000..8894d7e7 --- /dev/null +++ b/models/issue-138.mtl @@ -0,0 +1,23 @@ +newmtl test1 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.5 0.2 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 + + newmtl test2 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.2 0.5 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 diff --git a/models/issue-138.obj b/models/issue-138.obj new file mode 100644 index 00000000..2465920d --- /dev/null +++ b/models/issue-138.obj @@ -0,0 +1,51 @@ + +# cube.obj +# + +mtllib issue-138.mtl + +v -0.500000 -0.500000 0.500000 +v 0.500000 -0.500000 0.500000 +v -0.500000 0.500000 0.500000 +v 0.500000 0.500000 0.500000 +v -0.500000 0.500000 -0.500000 +v 0.500000 0.500000 -0.500000 +v -0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 -0.500000 + +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 + +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 + +usemtl test1 +g test1 +s 1 +f 1/1/1 2/2/1 3/3/1 +f 3/3/1 2/2/1 4/4/1 + +usemtl test2 +g test2 + +s 2 +f 3/1/2 4/2/2 5/3/2 +f 5/3/2 4/2/2 6/4/2 +s 3 +f 5/4/3 6/3/3 7/2/3 +f 7/2/3 6/3/3 8/1/3 +s 4 +f 7/1/4 8/2/4 1/3/4 +f 1/3/4 8/2/4 2/4/4 +s 5 +f 2/1/5 8/2/5 4/3/5 +f 4/3/5 8/2/5 6/4/5 +s 6 +f 7/1/6 1/2/6 5/3/6 +f 5/3/6 1/2/6 3/4/6 diff --git a/models/issue-140-zero-face-idx.mtl b/models/issue-140-zero-face-idx.mtl new file mode 100644 index 00000000..990a3457 --- /dev/null +++ b/models/issue-140-zero-face-idx.mtl @@ -0,0 +1,2 @@ +newmtl main +Kd 1 1 1 diff --git a/models/issue-140-zero-face-idx.obj b/models/issue-140-zero-face-idx.obj new file mode 100644 index 00000000..21a60609 --- /dev/null +++ b/models/issue-140-zero-face-idx.obj @@ -0,0 +1,17 @@ +mtllib issue-140-zero-face-idx.mtl + +v -0.5 -0.5 0 +v 0.5 -0.5 0 +v 0.5 0.5 0 +v -0.5 0.5 0 + +vt 0 0 0 +vt 1 0 0 +vt 1 1 0 +vt 0 1 0 + +vn 0 0 -1 + +usemtl main +f 0/0/0 1/1/0 3/3/0 +f 1/1/0 3/3/0 2/2/0 diff --git a/models/issue-161-inconsistent-f.obj b/models/issue-161-inconsistent-f.obj new file mode 100644 index 00000000..6cd8c43e --- /dev/null +++ b/models/issue-161-inconsistent-f.obj @@ -0,0 +1,37 @@ +o cube +mtllib cube.mtl + +v -0.500000 -0.500000 0.500000 +v 0.500000 -0.500000 0.500000 +v -0.500000 0.500000 0.500000 +v 0.500000 0.500000 0.500000 +v -0.500000 0.500000 -0.500000 +v 0.500000 0.500000 -0.500000 +v -0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 -0.500000 + +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 + +g cube +usemtl cube +s 1 +f 1/1 2/2 3/3 +f 3/3 2/2 4/4 +s 2 +f 3/1 4/2 5/3 +f 5/3 4/2 6/4 +s 3 +f 5/4 6/3 7/2 +f 7/2 6/3 8/1 +s 4 +f 7/1 8/2 1/3 +f 1/3 8/2 2/4 +s 5 +f 2/1 8/2 4/3 +f 4/3 8/2 6/4 +s 6 +f 7/1 1/2 5/3 +f 5 1 3 diff --git a/models/issue-162-smoothing-group.mtl b/models/issue-162-smoothing-group.mtl new file mode 100644 index 00000000..8894d7e7 --- /dev/null +++ b/models/issue-162-smoothing-group.mtl @@ -0,0 +1,23 @@ +newmtl test1 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.5 0.2 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 + + newmtl test2 + Ns 10.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.0000 0.0000 0.0000 + Kd 0.2 0.5 0.2 + Ks 0.0000 0.0000 0.0000 + Ke 0.0000 0.0000 0.0000 diff --git a/models/issue-162-smoothing-group.obj b/models/issue-162-smoothing-group.obj new file mode 100644 index 00000000..0b0c8b35 --- /dev/null +++ b/models/issue-162-smoothing-group.obj @@ -0,0 +1,51 @@ + +# cube.obj +# + +mtllib issue-162-smoothing-group.mtl + +v -0.500000 -0.500000 0.500000 +v 0.500000 -0.500000 0.500000 +v -0.500000 0.500000 0.500000 +v 0.500000 0.500000 0.500000 +v -0.500000 0.500000 -0.500000 +v 0.500000 0.500000 -0.500000 +v -0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 -0.500000 + +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 + +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 + +usemtl test1 +g test1 +s 1 +f 1/1/1 2/2/1 3/3/1 +f 3/3/1 2/2/1 4/4/1 + +usemtl test2 +g test2 + +s off +f 3/1/2 4/2/2 5/3/2 +f 5/3/2 4/2/2 6/4/2 +s 3 +f 5/4/3 6/3/3 7/2/3 +f 7/2/3 6/3/3 8/1/3 +s 4 +f 7/1/4 8/2/4 1/3/4 +f 1/3/4 8/2/4 2/4/4 +s 0 +f 2/1/5 8/2/5 4/3/5 +f 4/3/5 8/2/5 6/4/5 +s 6 +f 7/1/6 1/2/6 5/3/6 +f 5/3/6 1/2/6 3/4/6 diff --git a/models/issue-235-usemtl-then-o.obj b/models/issue-235-usemtl-then-o.obj new file mode 100644 index 00000000..170d33e4 --- /dev/null +++ b/models/issue-235-usemtl-then-o.obj @@ -0,0 +1,21 @@ +mtllib issue-235.mtl + +v -1.000000 1.202466 1.000000 +v 1.000000 1.202466 1.000000 +v -1.000000 1.202466 -1.000000 +v 1.000000 1.202466 -1.000000 +vn 0.0000 1.0000 0.0000 +v -1.000000 0.000000 1.000000 +v 1.000000 0.000000 1.000000 +v -1.000000 0.000000 -1.000000 +v 1.000000 0.000000 -1.000000 +vn 0.0000 1.0000 0.0000 + +usemtl None +o Plane.001 +f 1//1 2//1 4//1 + +# Following geometry is ignored without fix for #235 +usemtl None1 +o Plane +f 5//2 6//2 8//2 diff --git a/models/issue-235.mtl b/models/issue-235.mtl new file mode 100644 index 00000000..63c46a43 --- /dev/null +++ b/models/issue-235.mtl @@ -0,0 +1,11 @@ +newmtl None +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl None1 +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + + diff --git a/models/issue-244-mtl-searchpaths.obj b/models/issue-244-mtl-searchpaths.obj new file mode 100644 index 00000000..58abea1c --- /dev/null +++ b/models/issue-244-mtl-searchpaths.obj @@ -0,0 +1,22 @@ +# .mtl is located at tests/assets +mtllib issue-244.mtl + +v -1.000000 1.202466 1.000000 +v 1.000000 1.202466 1.000000 +v -1.000000 1.202466 -1.000000 +v 1.000000 1.202466 -1.000000 +vn 0.0000 1.0000 0.0000 +v -1.000000 0.000000 1.000000 +v 1.000000 0.000000 1.000000 +v -1.000000 0.000000 -1.000000 +v 1.000000 0.000000 -1.000000 +vn 0.0000 1.0000 0.0000 + +usemtl None +o Plane.001 +f 1//1 2//1 4//1 + +# Following geometry is ignored without fix for #235 +usemtl None1 +o Plane +f 5//2 6//2 8//2 diff --git a/models/issue-246-usemtl-whitespace.mtl b/models/issue-246-usemtl-whitespace.mtl new file mode 100644 index 00000000..e7c91e33 --- /dev/null +++ b/models/issue-246-usemtl-whitespace.mtl @@ -0,0 +1,4 @@ +newmtl 1 +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/issue-246-usemtl-whitespace.obj b/models/issue-246-usemtl-whitespace.obj new file mode 100644 index 00000000..f159349b --- /dev/null +++ b/models/issue-246-usemtl-whitespace.obj @@ -0,0 +1,17 @@ +# .mtl is located at tests/assets +mtllib issue-246-usemtl-whitespace.mtl + +v -1.000000 1.202466 1.000000 +v 1.000000 1.202466 1.000000 +v -1.000000 1.202466 -1.000000 +v 1.000000 1.202466 -1.000000 +vn 0.0000 1.0000 0.0000 +v -1.000000 0.000000 1.000000 +v 1.000000 0.000000 1.000000 +v -1.000000 0.000000 -1.000000 +v 1.000000 0.000000 -1.000000 +vn 0.0000 1.0000 0.0000 + +usemtl 1 +o Plane.001 +f 1//1 2//1 4//1 diff --git a/models/issue-248-texres-texopt.mtl b/models/issue-248-texres-texopt.mtl new file mode 100644 index 00000000..537fa15b --- /dev/null +++ b/models/issue-248-texres-texopt.mtl @@ -0,0 +1,25 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +map_Kd -texres 512 input.jpg + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/issue-248-texres-texopt.obj b/models/issue-248-texres-texopt.obj new file mode 100644 index 00000000..43058a91 --- /dev/null +++ b/models/issue-248-texres-texopt.obj @@ -0,0 +1,32 @@ +mtllib issue-248-texres-texopt.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +# two white spaces between 'back' and 'cube' +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/issue-295-trianguation-failure.obj b/models/issue-295-trianguation-failure.obj new file mode 100644 index 00000000..f3b2649c --- /dev/null +++ b/models/issue-295-trianguation-failure.obj @@ -0,0 +1,38 @@ +#mtllib invalid.mtl +v 14678.0 0.0 9605.0 +v 14678.0 1.0 9605.0 +v 14678.0 0.0 9606.0 +v 14678.0 1.0 9606.0 +v 14678.0 0.0 9607.0 +v 14678.0 1.0 9607.0 +v 14678.0 0.0 9608.0 +v 14678.0 1.0 9608.0 +v 14679.0 0.0 9605.0 +v 14679.0 1.0 9605.0 +v 14679.0 0.0 9606.0 +v 14679.0 1.0 9606.0 +v 14679.0 0.0 9607.0 +v 14679.0 1.0 9607.0 +v 14679.0 0.0 9608.0 +v 14679.0 1.0 9608.0 +# UV +vt 0.0 0.0 +vt 1.0 0.0 +vt 1.0 1.0 +vt 0.0 1.0 +#usemtl invalid +o invalid +f 9/4 11/1 3/2 1/3 +f 4/1 12/2 10/3 2/4 +f 2/3 10/4 9/1 1/2 +f 3/2 4/3 2/4 1/1 +f 10/3 12/4 11/1 9/2 +f 11/4 13/1 5/2 3/3 +f 6/1 14/2 12/3 4/4 +f 5/2 6/3 4/4 3/1 +f 12/3 14/4 13/1 11/2 +f 13/4 15/1 7/2 5/3 +f 8/1 16/2 14/3 6/4 +f 15/2 16/3 8/4 7/1 +f 7/2 8/3 6/4 5/1 +f 14/3 16/4 15/1 13/2 diff --git a/models/issue-319-002.obj b/models/issue-319-002.obj new file mode 100644 index 00000000..8e056fa0 --- /dev/null +++ b/models/issue-319-002.obj @@ -0,0 +1,39 @@ +### +# +# OBJ File Generated by Meshlab +# +#### +# Object ZH2_001.obj +# +# Vertices: 19 +# Faces: 3 +# +#### +v 8219.830078 6406.934082 9.603000 +v 8219.632812 6406.582031 9.603000 +v 8219.632812 6406.582031 9.139000 +v 8219.973633 6405.420898 9.139000 +v 8211.128906 6404.090820 9.139000 +v 8211.128906 6404.090820 9.603000 +v 8211.469727 6402.930176 9.139000 +v 8211.469727 6402.930176 9.603000 +v 8211.133789 6402.831055 9.603000 +v 8210.793945 6403.992188 9.603000 +v 8210.713867 6404.264160 9.603000 +v 8211.840820 6403.038086 9.139000 +v 8219.899414 6404.861816 9.139000 +v 8219.755859 6405.352051 9.139000 +v 8211.985352 6402.544922 9.139000 +v 8232.911133 6378.534180 55.848999 +v 8226.281250 6376.591797 55.848999 +v 8226.341797 6376.384766 55.848999 +v 8233.450195 6378.466797 55.852001 +v 8233.450195 6378.466797 55.852001 +# 19 vertices, 0 vertices normals + +f 2 1 11 10 9 8 6 +f 5 7 12 15 13 14 4 3 +f 18 19 20 16 17 +# 3 faces, 0 coords texture + +# End of File diff --git a/models/issue-319-003.obj b/models/issue-319-003.obj new file mode 100644 index 00000000..882a25c1 --- /dev/null +++ b/models/issue-319-003.obj @@ -0,0 +1,27 @@ +#### +# +# OBJ File Generated by Meshlab +# +#### +# Object new 1.obj +# +# Vertices: 10 +# Faces: 1 +# +#### +v 8434.808594 6083.654785 2.387000 +v 8434.808594 6083.654785 71.633003 +v 8432.309570 6092.206055 71.633003 +v 8432.309570 6092.206055 63.955002 +v 8432.309570 6092.206055 2.387000 +v 8433.083984 6089.560059 71.633003 +v 8433.161133 6089.293945 71.633003 +v 8432.309570 6092.206055 64.323997 +v 8432.309570 6092.206055 67.152000 +v 8432.309570 6092.206055 68.078003 +# 10 vertices, 0 vertices normals + +f 6 7 2 1 5 4 8 9 10 3 +# 1 faces, 0 coords texture + +# End of File diff --git a/models/issue-330.obj b/models/issue-330.obj new file mode 100644 index 00000000..aa46631c --- /dev/null +++ b/models/issue-330.obj @@ -0,0 +1,12 @@ +v -105.342712 40.184242 -16.056709 +v -105.463989 40.202003 -16.003181 +v -105.564941 40.207558 -15.934708 +v -105.722252 40.151146 -16.112091 +v -105.610237 40.191372 -16.176643 +v -105.667282 40.189800 -15.864197 +v -105.751717 40.125790 -15.794304 +# 7 vertices, 0 vertices normals + +f 2 5 4 3 +f 4 6 3 +# 2 faces, 0 coords texture diff --git a/models/issue-356-leading-spaces-newmtl.mtl b/models/issue-356-leading-spaces-newmtl.mtl new file mode 100644 index 00000000..f5a388e9 --- /dev/null +++ b/models/issue-356-leading-spaces-newmtl.mtl @@ -0,0 +1,2 @@ +newmtl aaa +Ka 1.000000 1.000000 1.000000 diff --git a/models/issue-356-leading-spaces-newmtl.obj b/models/issue-356-leading-spaces-newmtl.obj new file mode 100644 index 00000000..b41984a8 --- /dev/null +++ b/models/issue-356-leading-spaces-newmtl.obj @@ -0,0 +1,2 @@ +mtllib issue-356-leading-spaces-newmtl.mtl +usemtl aaa diff --git a/models/issue-389-comment.obj b/models/issue-389-comment.obj new file mode 100644 index 00000000..cf16d926 --- /dev/null +++ b/models/issue-389-comment.obj @@ -0,0 +1,44 @@ +g Part 1 +v 0.0576127 0.0488792 0.0423 +v 0.0576127 0.0488792 0 +v -0.0483158 0.0488792 0 +v -0.0483158 0.0488792 0.0423 +v -0.0483158 -0.0139454 0 +v -0.0483158 -0.0139454 0.0423 +v 0.0576127 -0.0139454 0 +v 0.0576127 -0.0139454 0.0423 +vn 0 1 0 +vn -1 0 0 +vn 0 -1 0 +vn 1 0 0 +vn 0 0 1 +vn 0 0 -1 +o mesh0 +f 1//1 2//1 3//1 +f 3//1 4//1 1//1 +o mesh1 +f 4//2 3//2 5//2 +f 5//2 6//2 4//2 +o mesh2 +f 6//3 5//3 7//3 +f 7//3 8//3 6//3 +o mesh3 +f 8//4 7//4 2//4 +f 2//4 1//4 8//4 +o mesh4 +f 8//5 1//5 4//5 +f 4//5 6//5 8//5 +o mesh5 +f 5//6 3//6 2//6 +f 2//6 7//6 5//6 + +# Zusätzliche Linien (aus der Oberseite) +o lines +v 0.0576127 0.0488792 0.0423 # Startpunkt Linie 1 (Ecke 1 Oberseite) +v 0.0576127 0.0488792 0.2423 # Endpunkt Linie 1 (2m Höhe) +v -0.0483158 -0.0139454 0.0423 # Startpunkt Linie 2 (Ecke 6 Oberseite) +v -0.0483158 -0.0139454 0.2423 # Endpunkt Linie 2 (2m Höhe) + +# Linien +l 1 9 # Linie 1 +l 6 10 # Linie 2 diff --git a/models/issue-391.mtl b/models/issue-391.mtl new file mode 100644 index 00000000..c23ced4b --- /dev/null +++ b/models/issue-391.mtl @@ -0,0 +1,4 @@ +newmtl has_kd +Kd 1 0 0 +newmtl has_map +map_Kd test.png \ No newline at end of file diff --git a/models/issue-391.obj b/models/issue-391.obj new file mode 100644 index 00000000..06d8774b --- /dev/null +++ b/models/issue-391.obj @@ -0,0 +1,9 @@ +mtllib issue-391.mtl +v 0 0 0 +v 1 0 0 +v 0 1 0 +vn 0 0 1 +usemtl has_map +f 1//1 2//1 3//1 +usemtl has_kd +f 1//1 2//1 3//1 \ No newline at end of file diff --git a/models/issue-92.mtl b/models/issue-92.mtl new file mode 100644 index 00000000..5ebd6680 --- /dev/null +++ b/models/issue-92.mtl @@ -0,0 +1,6 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +map_Kd tmp.png + diff --git a/models/issue-92.obj b/models/issue-92.obj new file mode 100644 index 00000000..f7be3b6e --- /dev/null +++ b/models/issue-92.obj @@ -0,0 +1,7 @@ +mtllib issue-92.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/issue-95-2.mtl b/models/issue-95-2.mtl new file mode 100644 index 00000000..68d484c8 --- /dev/null +++ b/models/issue-95-2.mtl @@ -0,0 +1,5 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Tf 0.1 0.2 0.3 diff --git a/models/issue-95-2.obj b/models/issue-95-2.obj new file mode 100644 index 00000000..456f854b --- /dev/null +++ b/models/issue-95-2.obj @@ -0,0 +1,7 @@ +mtllib issue-95-2.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/issue-95.mtl b/models/issue-95.mtl new file mode 100644 index 00000000..1d29fee9 --- /dev/null +++ b/models/issue-95.mtl @@ -0,0 +1,5 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 diff --git a/models/issue-95.obj b/models/issue-95.obj new file mode 100644 index 00000000..8ee267ea --- /dev/null +++ b/models/issue-95.obj @@ -0,0 +1,7 @@ +mtllib issue-95.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/leading-decimal-dot-issue-201.mtl b/models/leading-decimal-dot-issue-201.mtl new file mode 100644 index 00000000..1d29fee9 --- /dev/null +++ b/models/leading-decimal-dot-issue-201.mtl @@ -0,0 +1,5 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 diff --git a/models/leading-decimal-dot-issue-201.obj b/models/leading-decimal-dot-issue-201.obj new file mode 100644 index 00000000..202cfdcd --- /dev/null +++ b/models/leading-decimal-dot-issue-201.obj @@ -0,0 +1,7 @@ +mtllib leading-decimal-dot-issue-201.mtl +o Test +v .8e-1 -.7e+2 -5.532511 +v .575869 -.666304 5.896140 +v .940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/leading-zero-in-exponent-notation-issue-210.mtl b/models/leading-zero-in-exponent-notation-issue-210.mtl new file mode 100644 index 00000000..1d29fee9 --- /dev/null +++ b/models/leading-zero-in-exponent-notation-issue-210.mtl @@ -0,0 +1,5 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 diff --git a/models/leading-zero-in-exponent-notation-issue-210.obj b/models/leading-zero-in-exponent-notation-issue-210.obj new file mode 100644 index 00000000..cf6f99bc --- /dev/null +++ b/models/leading-zero-in-exponent-notation-issue-210.obj @@ -0,0 +1,7 @@ +mtllib leading-zero-in-exponent-notation-issue-210.mtl +o Test +v .8e-001 -.7e+02 -5.532511 +v .575869 -.666304 5.896140 +v .940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/line-prim.obj b/models/line-prim.obj new file mode 100644 index 00000000..22f55d3f --- /dev/null +++ b/models/line-prim.obj @@ -0,0 +1,16 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g g0 +usemtl white +l 1 2 3 4 +l 5 6 7 8 diff --git a/models/map-bump.mtl b/models/map-bump.mtl new file mode 100644 index 00000000..6fb1291a --- /dev/null +++ b/models/map-bump.mtl @@ -0,0 +1,10 @@ +newmtl Material.001 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +map_Bump bump.jpg + +newmtl Material.003 +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/map-bump.obj b/models/map-bump.obj new file mode 100644 index 00000000..03071f1a --- /dev/null +++ b/models/map-bump.obj @@ -0,0 +1,817 @@ +# https://github.com/syoyo/tinyobjloader/issues/68 +# Blender v2.73 (sub 0) OBJ File: 'enemy.blend' +# www.blender.org +mtllib map-bump.mtl +o Cube +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +v 1.620345 1.000000 -5.815706 +v 1.864152 1.000000 -6.334323 +v 0.575869 -0.129842 5.896143 +v 5.440438 -1.462153 -5.818601 +v 4.896782 -1.462153 -2.744413 +v 1.000825 -0.677484 1.899605 +v 5.440438 -1.246362 -5.818600 +v 1.000825 0.852342 1.899608 +v 4.896782 -1.246362 -2.744412 +v 1.160660 -0.450871 -2.356325 +v 1.704316 -0.450871 -5.430513 +v 1.000825 -0.351920 -1.293797 +v 1.000825 1.000000 -1.293794 +v 1.160660 -0.877888 -2.356326 +v 1.704316 -0.877888 -5.430514 +v 1.000825 -1.219172 -1.452514 +v 1.000825 1.000000 -1.452511 +v 1.000825 -0.351920 1.759410 +v 1.000825 1.000000 1.759413 +v 9.097919 1.221145 -6.212147 +v 8.356775 1.221145 -2.021231 +v 1.864151 -0.109586 -6.334325 +v 0.575869 -0.398073 5.896141 +v 9.097919 0.943958 -6.212148 +v 8.356775 0.943958 -2.021233 +v 1.061916 0.113661 -1.797961 +v 1.000825 0.161258 1.899606 +v 1.000825 0.324040 -1.293795 +v 1.803060 0.113661 -5.988876 +v 1.000825 -0.109586 -1.452513 +v 1.061916 0.776753 -1.797960 +v 1.803061 0.776753 -5.988875 +v 1.000825 0.324040 1.759412 +v 0.000825 -1.219172 -5.532512 +v 0.000825 -0.666304 5.896139 +v 0.000826 1.000000 -6.334325 +v 0.000825 -0.129842 5.896140 +v 0.000825 0.852342 1.899606 +v 0.000825 -0.677484 1.899604 +v 0.000825 -0.351920 -1.293797 +v 0.000825 1.000000 -1.293796 +v 0.000825 1.000000 -1.452513 +v 0.000825 -1.219172 -1.452515 +v 0.000825 -0.351920 1.759409 +v 0.000825 1.000000 1.759411 +v 0.000826 -0.109586 -6.334326 +v 0.000825 -0.398073 5.896140 +v 0.152918 1.000000 -5.815708 +v 0.152917 1.000000 -1.971130 +v 0.940448 1.168419 -1.971128 +v 1.620345 1.168419 -5.815706 +v 0.152918 1.168419 -5.815708 +v 0.152917 1.168419 -1.971130 +v 0.921118 1.091883 -1.050430 +v 0.921118 1.091883 1.516050 +v 0.080533 1.091883 -1.050432 +v 0.080533 1.091883 1.516048 +v 0.613003 -0.553430 5.546911 +v 0.963691 -0.559956 2.248834 +v 0.613003 -0.396857 5.546912 +v 0.963691 -0.070362 2.248835 +v 1.499370 -0.994317 3.966028 +v 1.850058 -0.997914 0.667950 +v 1.499370 -0.908021 3.966029 +v 1.850058 -0.728071 0.667951 +v 1.601022 0.760960 -6.334324 +v 1.601021 0.129454 -6.334325 +v 0.263955 0.760960 -6.334325 +v 0.263955 0.129454 -6.334325 +v 1.334809 0.760960 -7.515329 +v 1.334809 0.129455 -7.515330 +v 0.530168 0.760960 -7.515330 +v 0.530168 0.129455 -7.515330 +v 1.192720 0.649445 -7.515329 +v 1.192720 0.240971 -7.515330 +v 0.672258 0.649445 -7.515330 +v 0.672258 0.240971 -7.515330 +v 1.192719 0.649444 -6.524630 +v 1.192719 0.240970 -6.524631 +v 0.672257 0.649444 -6.524631 +v 0.672257 0.240970 -6.524631 +v 3.851026 0.431116 -1.883326 +v 3.851026 0.946662 -1.883325 +v 4.592170 0.946662 -6.074241 +v 4.592169 0.431116 -6.074242 +v 4.995714 0.561404 -1.918362 +v 4.995714 1.016394 -1.918360 +v 5.736857 1.016394 -6.109276 +v 5.736857 0.561404 -6.109277 +v 3.975454 0.471731 -2.162156 +v 3.975454 0.919244 -2.162155 +v 4.618796 0.919244 -5.800034 +v 4.618795 0.471730 -5.800035 +v 4.969088 0.584825 -2.192568 +v 4.969088 0.979775 -2.192567 +v 5.612430 0.979775 -5.830446 +v 5.612429 0.584825 -5.830447 +v 0.864214 -0.673890 3.184381 +v 0.864213 0.489129 3.184384 +v 0.864213 -0.018552 3.184383 +v 0.000825 0.489129 3.184382 +v 0.000825 -0.673890 3.184381 +v 0.850955 -0.557858 3.309075 +v 0.850955 -0.175321 3.309076 +v 1.737321 -0.996758 1.728192 +v 1.737321 -0.785920 1.728193 +v -1.864151 -1.219172 -5.532511 +v -0.575869 -0.666304 5.896140 +v -0.940448 1.000000 -1.971128 +v -1.620345 1.000000 -5.815706 +v -1.864152 1.000000 -6.334323 +v -0.575869 -0.129842 5.896143 +v -5.440438 -1.462153 -5.818601 +v -4.896782 -1.462153 -2.744413 +v -1.000825 -0.677484 1.899605 +v -5.440438 -1.246362 -5.818600 +v -1.000825 0.852342 1.899608 +v -4.896782 -1.246362 -2.744412 +v -1.160660 -0.450871 -2.356325 +v -1.704316 -0.450871 -5.430513 +v -1.000825 -0.351920 -1.293797 +v -1.000825 1.000000 -1.293794 +v -1.160660 -0.877888 -2.356326 +v -1.704316 -0.877888 -5.430514 +v -1.000825 -1.219172 -1.452514 +v -1.000825 1.000000 -1.452511 +v -1.000825 -0.351920 1.759410 +v -1.000825 1.000000 1.759413 +v -9.097919 1.221145 -6.212147 +v -8.356775 1.221145 -2.021231 +v -1.864151 -0.109586 -6.334325 +v -0.575869 -0.398073 5.896141 +v -9.097919 0.943958 -6.212148 +v -8.356775 0.943958 -2.021233 +v -1.061916 0.113661 -1.797961 +v -1.000825 0.161258 1.899606 +v -1.000825 0.324040 -1.293795 +v -1.803060 0.113661 -5.988876 +v -1.000825 -0.109586 -1.452513 +v -1.061916 0.776753 -1.797960 +v -1.803061 0.776753 -5.988875 +v -1.000825 0.324040 1.759412 +v -0.000825 -1.219172 -5.532512 +v -0.000825 -0.666304 5.896139 +v -0.000826 1.000000 -6.334325 +v -0.000825 -0.129842 5.896140 +v -0.000825 0.852342 1.899606 +v -0.000825 -0.677484 1.899604 +v -0.000825 -0.351920 -1.293797 +v -0.000825 1.000000 -1.293796 +v -0.000825 1.000000 -1.452513 +v -0.000825 -1.219172 -1.452515 +v -0.000825 -0.351920 1.759409 +v -0.000825 1.000000 1.759411 +v -0.000826 -0.109586 -6.334326 +v -0.000825 -0.398073 5.896140 +v -0.152918 1.000000 -5.815708 +v -0.152917 1.000000 -1.971130 +v -0.940448 1.168419 -1.971128 +v -1.620345 1.168419 -5.815706 +v -0.152918 1.168419 -5.815708 +v -0.152917 1.168419 -1.971130 +v -0.921118 1.091883 -1.050430 +v -0.921118 1.091883 1.516050 +v -0.080533 1.091883 -1.050432 +v -0.080533 1.091883 1.516048 +v -0.613003 -0.553430 5.546911 +v -0.963691 -0.559956 2.248834 +v -0.613003 -0.396857 5.546912 +v -0.963691 -0.070362 2.248835 +v -1.499370 -0.994317 3.966028 +v -1.850058 -0.997914 0.667950 +v -1.499370 -0.908021 3.966029 +v -1.850058 -0.728071 0.667951 +v -1.601022 0.760960 -6.334324 +v -1.601021 0.129454 -6.334325 +v -0.263955 0.760960 -6.334325 +v -0.263955 0.129454 -6.334325 +v -1.334809 0.760960 -7.515329 +v -1.334809 0.129455 -7.515330 +v -0.530168 0.760960 -7.515330 +v -0.530168 0.129455 -7.515330 +v -1.192720 0.649445 -7.515329 +v -1.192720 0.240971 -7.515330 +v -0.672258 0.649445 -7.515330 +v -0.672258 0.240971 -7.515330 +v -1.192719 0.649444 -6.524630 +v -1.192719 0.240970 -6.524631 +v -0.672257 0.649444 -6.524631 +v -0.672257 0.240970 -6.524631 +v -3.851026 0.431116 -1.883326 +v -3.851026 0.946662 -1.883325 +v -4.592170 0.946662 -6.074241 +v -4.592169 0.431116 -6.074242 +v -4.995714 0.561404 -1.918362 +v -4.995714 1.016394 -1.918360 +v -5.736857 1.016394 -6.109276 +v -5.736857 0.561404 -6.109277 +v -3.975454 0.471731 -2.162156 +v -3.975454 0.919244 -2.162155 +v -4.618796 0.919244 -5.800034 +v -4.618795 0.471730 -5.800035 +v -4.969088 0.584825 -2.192568 +v -4.969088 0.979775 -2.192567 +v -5.612430 0.979775 -5.830446 +v -5.612429 0.584825 -5.830447 +v -0.864214 -0.673890 3.184381 +v -0.864213 0.489129 3.184384 +v -0.864213 -0.018552 3.184383 +v -0.000825 0.489129 3.184382 +v -0.000825 -0.673890 3.184381 +v -0.850955 -0.557858 3.309075 +v -0.850955 -0.175321 3.309076 +v -1.737321 -0.996758 1.728192 +v -1.737321 -0.785920 1.728193 +vt 0.135351 -0.558072 +vt 0.003035 -0.363507 +vt 0.092282 -0.976844 +vt -0.081322 0.947351 +vt 0.100058 1.958891 +vt 0.050091 1.852185 +vt -0.092752 1.055565 +vt -0.251711 1.059474 +vt 0.075587 0.041384 +vt -0.086008 0.279003 +vt -0.086212 0.249830 +vt -0.276044 1.968137 +vt -0.246101 1.859467 +vt 0.009828 1.911388 +vt -0.133014 1.114769 +vt 0.413322 1.261595 +vt 0.299103 0.624605 +vt 1.243955 0.407183 +vt 0.515404 1.111487 +vt 1.358173 1.044173 +vt -0.081553 0.914324 +vt 0.080042 0.676706 +vt 0.401185 0.474498 +vt 1.295541 0.331328 +vt 0.365315 1.568841 +vt 0.299111 1.575740 +vt 0.143401 0.707357 +vt 0.629403 1.011947 +vt 0.449192 0.167251 +vt 1.409760 0.968317 +vt 0.986264 1.738667 +vt 1.573373 1.877873 +vt 1.417663 1.009490 +vt 0.237182 -0.196235 +vt 0.721785 1.030226 +vt 0.830554 0.870285 +vt 0.877494 1.898608 +vt 1.351399 1.106930 +vt 0.183935 0.557301 +vt 1.507109 1.975312 +vt 0.241636 0.439088 +vt 0.114297 -0.045011 +vt 0.140593 1.808834 +vt -0.015118 0.940452 +vt 0.156405 -1.071134 +vt 0.164119 -0.998223 +vt 0.040336 -1.068281 +vt 0.104459 -1.162571 +vt -0.165787 1.882802 +vt -0.014821 1.660811 +vt -0.287852 0.283965 +vt -0.293374 0.366508 +vt -0.289630 0.900550 +vt 0.035337 -0.191272 +vt 0.247348 0.172213 +vt 0.253300 1.021193 +vt -0.283166 0.952313 +vt -0.283398 0.919286 +vt 0.039792 0.444050 +vt 0.314806 -0.339851 +vt 0.112962 -0.334889 +vt -0.288056 0.254793 +vt -0.023788 -0.973990 +vt -0.155922 -0.359599 +vt 0.220528 -1.165425 +vt 0.108710 -0.748730 +vt -0.286364 1.918670 +vt -0.291973 1.118678 +vt -0.119962 0.896379 +vt -0.123707 0.362337 +vt 0.162891 -0.598569 +vt 0.467532 -0.853353 +vt 0.201549 -1.053262 +vt 0.161663 -0.198915 +vt 0.267667 -0.752638 +vt 0.278705 -0.371021 +vt 0.526390 -0.542053 +vt 0.483821 -0.479457 +vt 0.488162 -0.883689 +vt 0.500110 -0.105561 +vt 0.564618 -0.200418 +vt -0.110331 2.127229 +vt 0.040636 1.905238 +vt -0.010786 1.578087 +vt 0.104092 1.876168 +vt 0.255058 1.654176 +vt -0.054992 2.087323 +vt 0.203048 1.901245 +vt 0.052081 2.123235 +vt 0.042658 1.943733 +vt -0.056437 1.881175 +vt 0.147710 1.941151 +vt 0.050060 2.084741 +vt 0.146264 1.735002 +vt 0.041212 1.737584 +vt 0.048615 1.878591 +vt 0.663065 1.872485 +vt 0.786311 1.691257 +vt 0.507355 1.004102 +vt 0.630601 0.822874 +vt 0.955144 1.689498 +vt 0.860727 1.828333 +vt 0.725565 1.074543 +vt 0.819981 0.935708 +vt 0.674594 1.805657 +vt 0.539432 1.051867 +vt 0.646413 0.894554 +vt 0.781576 1.648344 +vt 0.240127 -0.712141 +vn 0.994400 0.000000 0.105700 +vn 0.000000 1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.984700 0.000000 0.174100 +vn 0.211800 0.976600 0.037500 +vn -0.103300 0.000000 -0.994600 +vn 0.103300 -0.000000 0.994600 +vn 0.911400 0.378700 0.161200 +vn -0.157300 -0.987200 -0.027800 +vn 0.113700 -0.993300 0.020100 +vn 0.030600 -0.000000 0.999500 +vn -0.061100 0.998100 -0.010800 +vn -0.030600 0.000000 -0.999500 +vn -0.000000 -0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn -0.755400 0.655300 0.000000 +vn 0.000000 -1.000000 0.000000 +vn -0.000000 -0.180000 0.983700 +vn 0.000000 -0.395500 -0.918500 +vn -0.000000 0.688500 0.725200 +vn 0.000000 -0.585700 -0.810500 +vn -0.000000 0.974900 0.222500 +vn -0.000000 -1.000000 0.002800 +vn -1.000000 0.000000 -0.000000 +vn -0.000000 0.935500 0.353200 +vn 0.755400 0.655300 0.000000 +vn 0.000000 0.935500 -0.353200 +vn 0.673800 0.724900 0.143400 +vn 0.872300 -0.000000 0.489100 +vn -0.872300 0.000000 -0.489100 +vn -0.518300 -0.853500 -0.054200 +vn -0.975500 0.000000 -0.219900 +vn 0.975500 0.000000 -0.219900 +vn -0.913200 0.000000 -0.407500 +vn -0.436900 0.896200 -0.077300 +vn -0.995300 -0.000000 0.096600 +vn -0.297300 -0.953400 -0.052600 +vn 0.473900 -0.876600 0.083800 +vn 0.913200 0.000000 0.407500 +vn 0.342200 0.937700 0.060500 +vn 0.995300 -0.000000 -0.096600 +vn -0.519200 -0.853000 -0.054300 +vn 0.722400 0.676400 0.143800 +vn -0.994400 0.000000 0.105700 +vn -0.984700 0.000000 0.174100 +vn -0.211800 0.976600 0.037500 +vn 0.103300 0.000000 -0.994600 +vn -0.103300 -0.000000 0.994600 +vn -0.911400 0.378700 0.161200 +vn 0.157300 -0.987200 -0.027800 +vn -0.113700 -0.993300 0.020100 +vn -0.030600 -0.000000 0.999500 +vn 0.061100 0.998100 -0.010800 +vn 0.030600 0.000000 -0.999500 +vn -0.691900 0.713200 0.112500 +vn -0.872300 -0.000000 0.489100 +vn 0.872300 0.000000 -0.489100 +vn 0.518300 -0.853500 -0.054200 +vn 0.913200 0.000000 -0.407500 +vn 0.436900 0.896200 -0.077300 +vn 0.995300 0.000000 0.096600 +vn 0.297300 -0.953300 -0.052600 +vn -0.473900 -0.876600 0.083800 +vn -0.913200 -0.000000 0.407500 +vn -0.342200 0.937700 0.060500 +vn -0.995300 -0.000000 -0.096600 +vn 0.519200 -0.853000 -0.054300 +vn -0.714800 0.690100 0.113700 +vn 0.974400 0.089700 0.206200 +vn 0.870400 0.288400 0.399100 +vn 0.691900 0.713200 0.112500 +vn -0.518000 -0.853700 -0.053400 +vn -0.519700 -0.852700 -0.053600 +vn 0.714800 0.690100 0.113700 +vn -0.974400 0.089700 0.206200 +vn -0.870400 0.288400 0.399100 +vn -0.673800 0.724900 0.143400 +vn 0.518000 -0.853700 -0.053400 +vn 0.297300 -0.953400 -0.052600 +vn 0.519700 -0.852700 -0.053600 +vn -0.722400 0.676400 0.143800 +vn -0.000000 0.962300 0.272000 +usemtl Material.001 +s off +f 103/1/1 102/2/1 6/3/1 +f 20/4/2 5/5/2 4/6/2 +f 20/4/2 3/7/2 52/8/2 +f 36/9/3 22/10/3 11/11/3 +f 39/12/2 51/13/2 4/6/2 +f 4/6/4 54/14/4 53/15/4 +f 14/16/5 13/17/5 12/18/5 +f 18/19/6 14/16/6 10/20/6 +f 20/4/3 16/21/3 31/22/3 +f 17/23/7 8/24/7 12/18/7 +f 25/25/4 32/26/4 29/27/4 +f 10/20/4 12/18/4 8/24/4 +f 1/28/8 18/19/8 17/23/8 +f 19/29/4 17/23/4 13/17/4 +f 25/25/4 14/16/4 18/19/4 +f 18/19/9 7/30/9 8/24/9 +f 92/31/10 27/32/10 28/33/10 +f 16/21/3 22/10/3 36/9/3 +f 31/22/3 36/9/3 21/34/3 +f 90/35/11 89/36/11 28/33/11 +f 91/37/12 90/35/12 24/38/12 +f 33/39/4 13/17/4 14/16/4 +f 23/40/4 24/38/4 28/33/4 +f 33/39/3 31/22/3 15/41/3 +f 21/34/3 36/9/3 30/42/3 +f 5/5/4 35/43/4 32/26/4 +f 5/5/4 20/4/4 34/44/4 +f 33/39/4 29/27/4 34/44/4 +f 91/37/13 23/40/13 27/32/13 +f 103/1/1 26/45/1 63/46/1 +f 26/45/14 50/47/14 38/48/14 +f 39/12/15 71/49/15 72/50/15 +f 48/51/16 60/52/16 59/53/16 +f 15/41/17 21/34/17 47/54/17 +f 19/29/17 46/55/17 37/56/17 +f 39/12/2 45/57/2 52/8/2 +f 20/4/2 45/57/2 44/58/2 +f 19/29/18 15/41/18 43/59/18 +f 9/60/19 42/61/19 47/54/19 +f 22/10/20 48/51/20 41/62/20 +f 25/25/21 1/28/21 37/56/21 +f 6/3/14 40/63/14 50/47/14 +f 104/64/22 40/63/22 6/3/22 +f 2/65/23 38/48/23 105/66/23 +f 55/67/2 56/68/2 53/15/2 +f 3/7/14 53/15/14 56/68/14 +f 51/13/15 55/67/15 54/14/15 +f 52/8/24 56/68/24 55/67/24 +f 57/69/2 59/53/2 60/52/2 +f 48/51/25 22/10/25 58/70/25 +f 16/21/26 57/69/26 58/70/26 +f 16/21/27 44/58/27 59/53/27 +f 107/71/28 63/46/28 67/72/28 +f 26/45/1 2/65/1 61/73/1 +f 9/60/1 30/42/1 64/74/1 +f 101/75/1 9/60/1 62/76/1 +f 108/77/1 109/78/1 67/72/1 +f 61/73/29 65/79/29 67/72/29 +f 62/76/30 64/74/30 68/80/30 +f 62/76/31 66/81/31 108/77/31 +f 71/49/32 75/82/32 76/83/32 +f 25/25/15 49/84/15 72/50/15 +f 5/5/15 69/85/15 71/49/15 +f 25/25/15 70/86/15 69/85/15 +f 76/83/15 75/82/15 79/87/15 +f 72/50/17 76/83/17 74/88/17 +f 71/49/2 69/85/2 73/89/2 +f 70/86/33 74/88/33 73/89/33 +f 80/90/3 79/87/3 83/91/3 +f 76/83/15 80/90/15 78/92/15 +f 75/82/15 73/89/15 77/93/15 +f 74/88/15 78/92/15 77/93/15 +f 82/94/15 84/95/15 83/91/15 +f 80/90/2 84/95/2 82/94/2 +f 77/93/17 81/96/17 83/91/17 +f 77/93/24 78/92/24 82/94/24 +f 35/43/13 87/97/13 88/98/13 +f 35/43/12 34/44/12 86/99/12 +f 34/44/11 29/27/11 85/100/11 +f 32/26/10 88/98/10 85/100/10 +f 92/31/34 100/101/34 99/102/34 +f 90/35/35 91/37/35 99/102/35 +f 89/36/36 90/35/36 98/103/36 +f 89/36/37 97/104/37 100/101/37 +f 95/105/13 99/102/13 100/101/13 +f 95/105/12 94/106/12 98/103/12 +f 94/106/11 93/107/11 97/104/11 +f 96/108/10 100/101/10 97/104/10 +f 88/98/38 96/108/38 93/107/38 +f 86/99/39 85/100/39 93/107/39 +f 87/97/40 86/99/40 94/106/40 +f 87/97/41 95/105/41 96/108/41 +f 106/109/42 108/77/42 65/79/42 +f 66/81/1 68/80/1 109/78/1 +f 101/75/1 106/109/1 61/73/1 +f 64/74/43 107/71/43 109/78/43 +f 101/75/23 105/66/23 42/61/23 +f 103/1/1 107/71/1 64/74/1 +f 30/42/1 11/11/1 102/2/1 +f 212/1/44 135/45/44 115/3/44 +f 129/4/2 112/7/2 113/6/2 +f 161/8/2 112/7/2 129/4/2 +f 145/9/24 139/42/24 120/11/24 +f 113/6/2 160/13/2 148/12/2 +f 162/15/45 163/14/45 113/6/45 +f 123/16/46 119/20/46 121/18/46 +f 127/19/47 116/30/47 119/20/47 +f 140/22/24 125/21/24 129/4/24 +f 121/18/48 117/24/48 126/23/48 +f 138/27/45 141/26/45 134/25/45 +f 117/24/45 121/18/45 119/20/45 +f 126/23/49 127/19/49 110/28/49 +f 122/17/45 126/23/45 128/29/45 +f 127/19/45 123/16/45 134/25/45 +f 117/24/50 116/30/50 127/19/50 +f 137/33/51 136/32/51 201/31/51 +f 145/9/24 131/10/24 125/21/24 +f 130/34/24 145/9/24 140/22/24 +f 199/35/52 133/38/52 137/33/52 +f 200/37/53 132/40/53 133/38/53 +f 123/16/45 122/17/45 142/39/45 +f 137/33/45 133/38/45 132/40/45 +f 124/41/24 140/22/24 142/39/24 +f 130/34/24 118/60/24 139/42/24 +f 141/26/45 144/43/45 114/5/45 +f 114/5/45 144/43/45 143/44/45 +f 143/44/45 138/27/45 142/39/45 +f 136/32/54 132/40/54 200/37/54 +f 212/1/44 216/71/44 172/46/44 +f 147/48/14 159/47/14 135/45/14 +f 181/50/15 180/49/15 148/12/15 +f 168/53/26 169/52/26 157/51/26 +f 124/41/17 152/59/17 156/54/17 +f 146/56/17 155/55/17 128/29/17 +f 148/12/2 160/13/2 161/8/2 +f 129/4/2 125/21/2 153/58/2 +f 155/55/18 152/59/18 124/41/18 +f 130/34/19 156/54/19 151/61/19 +f 131/10/20 120/11/20 150/62/20 +f 134/25/21 158/84/21 146/56/21 +f 159/47/14 149/63/14 115/3/14 +f 115/3/22 149/63/22 213/64/22 +f 214/66/23 147/48/23 111/65/23 +f 162/15/2 165/68/2 164/67/2 +f 165/68/14 162/15/14 112/7/14 +f 163/14/15 164/67/15 160/13/15 +f 164/67/3 165/68/3 161/8/3 +f 166/69/2 167/70/2 169/52/2 +f 157/51/25 169/52/25 167/70/25 +f 167/70/16 166/69/16 125/21/16 +f 125/21/27 166/69/27 168/53/27 +f 216/71/55 218/78/55 176/72/55 +f 135/45/44 172/46/44 170/73/44 +f 118/60/44 171/76/44 173/74/44 +f 210/75/44 215/109/44 171/76/44 +f 217/77/44 174/79/44 176/72/44 +f 176/72/56 174/79/56 170/73/56 +f 171/76/57 175/81/57 177/80/57 +f 217/77/58 175/81/58 171/76/58 +f 185/83/33 184/82/33 180/49/33 +f 134/25/15 179/86/15 181/50/15 +f 180/49/15 178/85/15 114/5/15 +f 178/85/15 179/86/15 134/25/15 +f 189/90/15 188/87/15 184/82/15 +f 183/88/17 185/83/17 181/50/17 +f 180/49/2 184/82/2 182/89/2 +f 182/89/32 183/88/32 179/86/32 +f 189/90/24 193/95/24 192/91/24 +f 187/92/15 189/90/15 185/83/15 +f 184/82/15 188/87/15 186/93/15 +f 186/93/15 187/92/15 183/88/15 +f 192/91/15 193/95/15 191/94/15 +f 191/94/2 193/95/2 189/90/2 +f 192/91/17 190/96/17 186/93/17 +f 186/93/3 190/96/3 191/94/3 +f 197/98/54 196/97/54 144/43/54 +f 144/43/53 196/97/53 195/99/53 +f 143/44/52 195/99/52 194/100/52 +f 194/100/51 197/98/51 141/26/51 +f 208/102/59 209/101/59 201/31/59 +f 199/35/60 207/103/60 208/102/60 +f 198/36/61 206/104/61 207/103/61 +f 209/101/62 206/104/62 198/36/62 +f 209/101/54 208/102/54 204/105/54 +f 204/105/53 208/102/53 207/103/53 +f 203/106/52 207/103/52 206/104/52 +f 206/104/51 209/101/51 205/108/51 +f 202/107/63 205/108/63 197/98/63 +f 195/99/64 203/106/64 202/107/64 +f 196/97/65 204/105/65 203/106/65 +f 205/108/66 204/105/66 196/97/66 +f 174/79/67 217/77/67 215/109/67 +f 175/81/44 217/77/44 218/78/44 +f 170/73/44 215/109/44 210/75/44 +f 173/74/68 177/80/68 218/78/68 +f 151/61/23 214/66/23 210/75/23 +f 173/74/44 216/71/44 212/1/44 +f 139/42/44 212/1/44 211/2/44 +f 26/45/1 103/1/1 6/3/1 +f 3/7/2 20/4/2 4/6/2 +f 45/57/2 20/4/2 52/8/2 +f 30/42/3 36/9/3 11/11/3 +f 5/5/2 39/12/2 4/6/2 +f 3/7/4 4/6/4 53/15/4 +f 10/20/5 14/16/5 12/18/5 +f 7/30/6 18/19/6 10/20/6 +f 33/39/3 20/4/3 31/22/3 +f 13/17/7 17/23/7 12/18/7 +f 33/39/4 25/25/4 29/27/4 +f 7/30/4 10/20/4 8/24/4 +f 19/29/69 1/28/69 17/23/69 +f 33/39/4 19/29/4 13/17/4 +f 1/28/70 25/25/70 18/19/70 +f 17/23/9 18/19/9 8/24/9 +f 89/36/10 92/31/10 28/33/10 +f 31/22/3 16/21/3 36/9/3 +f 15/41/3 31/22/3 21/34/3 +f 24/38/11 90/35/11 28/33/11 +f 23/40/12 91/37/12 24/38/12 +f 25/25/4 33/39/4 14/16/4 +f 27/32/4 23/40/4 28/33/4 +f 19/29/3 33/39/3 15/41/3 +f 9/60/3 21/34/3 30/42/3 +f 25/25/4 5/5/4 32/26/4 +f 35/43/4 5/5/4 34/44/4 +f 20/4/4 33/39/4 34/44/4 +f 92/31/13 91/37/13 27/32/13 +f 107/71/1 103/1/1 63/46/1 +f 2/65/14 26/45/14 38/48/14 +f 49/84/15 39/12/15 72/50/15 +f 44/58/16 48/51/16 59/53/16 +f 43/59/17 15/41/17 47/54/17 +f 1/28/17 19/29/17 37/56/17 +f 51/13/2 39/12/2 52/8/2 +f 16/21/2 20/4/2 44/58/2 +f 46/55/18 19/29/18 43/59/18 +f 21/34/19 9/60/19 47/54/19 +f 11/11/20 22/10/20 41/62/20 +f 49/84/21 25/25/21 37/56/21 +f 26/45/14 6/3/14 50/47/14 +f 102/2/22 104/64/22 6/3/22 +f 101/75/23 2/65/23 105/66/23 +f 54/14/2 55/67/2 53/15/2 +f 52/8/14 3/7/14 56/68/14 +f 4/6/15 51/13/15 54/14/15 +f 51/13/24 52/8/24 55/67/24 +f 58/70/2 57/69/2 60/52/2 +f 60/52/25 48/51/25 58/70/25 +f 22/10/26 16/21/26 58/70/26 +f 57/69/27 16/21/27 59/53/27 +f 109/78/71 107/71/71 67/72/71 +f 63/46/1 26/45/1 61/73/1 +f 62/76/1 9/60/1 64/74/1 +f 106/109/1 101/75/1 62/76/1 +f 65/79/1 108/77/1 67/72/1 +f 63/46/29 61/73/29 67/72/29 +f 66/81/30 62/76/30 68/80/30 +f 106/109/72 62/76/72 108/77/72 +f 72/50/32 71/49/32 76/83/32 +f 70/86/15 25/25/15 72/50/15 +f 39/12/15 5/5/15 71/49/15 +f 5/5/15 25/25/15 69/85/15 +f 80/90/15 76/83/15 79/87/15 +f 70/86/17 72/50/17 74/88/17 +f 75/82/2 71/49/2 73/89/2 +f 69/85/33 70/86/33 73/89/33 +f 84/95/3 80/90/3 83/91/3 +f 74/88/15 76/83/15 78/92/15 +f 79/87/15 75/82/15 77/93/15 +f 73/89/15 74/88/15 77/93/15 +f 81/96/15 82/94/15 83/91/15 +f 78/92/2 80/90/2 82/94/2 +f 79/87/17 77/93/17 83/91/17 +f 81/96/24 77/93/24 82/94/24 +f 32/26/13 35/43/13 88/98/13 +f 87/97/12 35/43/12 86/99/12 +f 86/99/11 34/44/11 85/100/11 +f 29/27/10 32/26/10 85/100/10 +f 91/37/34 92/31/34 99/102/34 +f 98/103/35 90/35/35 99/102/35 +f 97/104/36 89/36/36 98/103/36 +f 92/31/37 89/36/37 100/101/37 +f 96/108/13 95/105/13 100/101/13 +f 99/102/12 95/105/12 98/103/12 +f 98/103/11 94/106/11 97/104/11 +f 93/107/10 96/108/10 97/104/10 +f 85/100/38 88/98/38 93/107/38 +f 94/106/39 86/99/39 93/107/39 +f 95/105/40 87/97/40 94/106/40 +f 88/98/41 87/97/41 96/108/41 +f 61/73/73 106/109/73 65/79/73 +f 108/77/1 66/81/1 109/78/1 +f 2/65/1 101/75/1 61/73/1 +f 68/80/74 64/74/74 109/78/74 +f 9/60/23 101/75/23 42/61/23 +f 30/42/1 103/1/1 64/74/1 +f 103/1/1 30/42/1 102/2/1 +f 211/2/44 212/1/44 115/3/44 +f 114/5/2 129/4/2 113/6/2 +f 154/57/2 161/8/2 129/4/2 +f 131/10/24 145/9/24 120/11/24 +f 114/5/2 113/6/2 148/12/2 +f 112/7/45 162/15/45 113/6/45 +f 122/17/46 123/16/46 121/18/46 +f 123/16/47 127/19/47 119/20/47 +f 142/39/24 140/22/24 129/4/24 +f 122/17/48 121/18/48 126/23/48 +f 142/39/45 138/27/45 134/25/45 +f 116/30/45 117/24/45 119/20/45 +f 128/29/75 126/23/75 110/28/75 +f 142/39/45 122/17/45 128/29/45 +f 110/28/76 127/19/76 134/25/76 +f 126/23/50 117/24/50 127/19/50 +f 198/36/51 137/33/51 201/31/51 +f 140/22/24 145/9/24 125/21/24 +f 124/41/24 130/34/24 140/22/24 +f 198/36/52 199/35/52 137/33/52 +f 199/35/53 200/37/53 133/38/53 +f 134/25/45 123/16/45 142/39/45 +f 136/32/45 137/33/45 132/40/45 +f 128/29/24 124/41/24 142/39/24 +f 145/9/24 130/34/24 139/42/24 +f 134/25/45 141/26/45 114/5/45 +f 129/4/45 114/5/45 143/44/45 +f 129/4/45 143/44/45 142/39/45 +f 201/31/54 136/32/54 200/37/54 +f 135/45/44 212/1/44 172/46/44 +f 111/65/14 147/48/14 135/45/14 +f 158/84/15 181/50/15 148/12/15 +f 153/58/26 168/53/26 157/51/26 +f 130/34/17 124/41/17 156/54/17 +f 110/28/17 146/56/17 128/29/17 +f 154/57/2 148/12/2 161/8/2 +f 154/57/2 129/4/2 153/58/2 +f 128/29/18 155/55/18 124/41/18 +f 118/60/19 130/34/19 151/61/19 +f 157/51/20 131/10/20 150/62/20 +f 110/28/21 134/25/21 146/56/21 +f 135/45/14 159/47/14 115/3/14 +f 211/2/22 115/3/22 213/64/22 +f 210/75/23 214/66/23 111/65/23 +f 163/14/2 162/15/2 164/67/2 +f 161/8/14 165/68/14 112/7/14 +f 113/6/15 163/14/15 160/13/15 +f 160/13/3 164/67/3 161/8/3 +f 168/53/2 166/69/2 169/52/2 +f 131/10/25 157/51/25 167/70/25 +f 131/10/16 167/70/16 125/21/16 +f 153/58/27 125/21/27 168/53/27 +f 172/46/77 216/71/77 176/72/77 +f 111/65/44 135/45/44 170/73/44 +f 139/42/44 118/60/44 173/74/44 +f 118/60/44 210/75/44 171/76/44 +f 218/78/44 217/77/44 176/72/44 +f 172/46/56 176/72/56 170/73/56 +f 173/74/57 171/76/57 177/80/57 +f 215/109/78 217/77/78 171/76/78 +f 181/50/33 185/83/33 180/49/33 +f 158/84/15 134/25/15 181/50/15 +f 148/12/15 180/49/15 114/5/15 +f 114/5/15 178/85/15 134/25/15 +f 185/83/15 189/90/15 184/82/15 +f 179/86/17 183/88/17 181/50/17 +f 178/85/2 180/49/2 182/89/2 +f 178/85/32 182/89/32 179/86/32 +f 188/87/24 189/90/24 192/91/24 +f 183/88/15 187/92/15 185/83/15 +f 182/89/15 184/82/15 186/93/15 +f 182/89/15 186/93/15 183/88/15 +f 190/96/15 192/91/15 191/94/15 +f 187/92/2 191/94/2 189/90/2 +f 188/87/17 192/91/17 186/93/17 +f 187/92/3 186/93/3 191/94/3 +f 141/26/54 197/98/54 144/43/54 +f 143/44/53 144/43/53 195/99/53 +f 138/27/52 143/44/52 194/100/52 +f 138/27/51 194/100/51 141/26/51 +f 200/37/59 208/102/59 201/31/59 +f 200/37/60 199/35/60 208/102/60 +f 199/35/61 198/36/61 207/103/61 +f 201/31/79 209/101/79 198/36/79 +f 205/108/54 209/101/54 204/105/54 +f 203/106/53 204/105/53 207/103/53 +f 202/107/52 203/106/52 206/104/52 +f 202/107/51 206/104/51 205/108/51 +f 194/100/63 202/107/63 197/98/63 +f 194/100/64 195/99/64 202/107/64 +f 195/99/65 196/97/65 203/106/65 +f 197/98/66 205/108/66 196/97/66 +f 170/73/80 174/79/80 215/109/80 +f 177/80/44 175/81/44 218/78/44 +f 111/65/44 170/73/44 210/75/44 +f 216/71/81 173/74/81 218/78/81 +f 118/60/23 151/61/23 210/75/23 +f 139/42/44 173/74/44 212/1/44 +f 120/11/44 139/42/44 211/2/44 +usemtl Material.003 +f 41/62/82 104/64/82 102/2/82 +f 211/2/82 213/64/82 150/62/82 +f 11/11/82 41/62/82 102/2/82 +f 120/11/82 211/2/82 150/62/82 diff --git a/models/missing_material_file.obj b/models/missing_material_file.obj new file mode 100644 index 00000000..9e1d98c7 --- /dev/null +++ b/models/missing_material_file.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +#mtllib no_material.mtl + +o floor +usemtl white +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/models/mtl filename with whitespace issue46.mtl b/models/mtl filename with whitespace issue46.mtl new file mode 100644 index 00000000..b79d99b0 --- /dev/null +++ b/models/mtl filename with whitespace issue46.mtl @@ -0,0 +1,4 @@ +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 diff --git a/models/mtl filename with whitespace issue46.obj b/models/mtl filename with whitespace issue46.obj new file mode 100644 index 00000000..72d1dc9e --- /dev/null +++ b/models/mtl filename with whitespace issue46.obj @@ -0,0 +1,31 @@ +mtllib invalid-file-without-spaces.mtl invalid\ file\ with\ spaces.mtl mtl\ filename\ with\ whitespace\ issue46.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl green +f 1 2 3 4 +g back cube +usemtl green +f 8 7 6 5 +g right cube +usemtl green +f 4 3 7 8 +g left cube +usemtl green +f 5 6 2 1 +g top cube +usemtl green +f 5 1 4 8 +g bottom cube +usemtl green +f 2 6 7 3 +# 6 elements diff --git a/models/mtllib-multiple-files-issue-112.mtl b/models/mtllib-multiple-files-issue-112.mtl new file mode 100644 index 00000000..5ebd6680 --- /dev/null +++ b/models/mtllib-multiple-files-issue-112.mtl @@ -0,0 +1,6 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +map_Kd tmp.png + diff --git a/models/mtllib-multiple-files-issue-112.obj b/models/mtllib-multiple-files-issue-112.obj new file mode 100644 index 00000000..9966dfb2 --- /dev/null +++ b/models/mtllib-multiple-files-issue-112.obj @@ -0,0 +1,7 @@ +mtllib invalid-file-aaa.mtl invalid-file-bbb.mtl mtllib-multiple-files-issue-112.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/no_material.obj b/models/no_material.obj new file mode 100644 index 00000000..6f3688f7 --- /dev/null +++ b/models/no_material.obj @@ -0,0 +1,133 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +o floor +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/models/norm-texopt.mtl b/models/norm-texopt.mtl new file mode 100644 index 00000000..e2d4a2c8 --- /dev/null +++ b/models/norm-texopt.mtl @@ -0,0 +1,7 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +norm -bm 3 normalmap.jpg + diff --git a/models/norm-texopt.obj b/models/norm-texopt.obj new file mode 100644 index 00000000..babe94d1 --- /dev/null +++ b/models/norm-texopt.obj @@ -0,0 +1,7 @@ +mtllib norm-texopt.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/pbr-mat-ext.mtl b/models/pbr-mat-ext.mtl new file mode 100644 index 00000000..bed905d4 --- /dev/null +++ b/models/pbr-mat-ext.mtl @@ -0,0 +1,19 @@ +# .MTL with PBR extension. +newmtl pbr +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +Ke 0.1 0.1 0.1 +Pr 0.2 +Pm 0.3 +Ps 0.4 +Pc 0.5 +Pcr 0.6 +aniso 0.7 +anisor 0.8 +map_Pr roughness.tex +map_Pm metallic.tex +map_Ps sheen.tex +map_Ke emissive.tex +norm normalmap.tex + diff --git a/models/pbr-mat-ext.obj b/models/pbr-mat-ext.obj new file mode 100644 index 00000000..bb3e3712 --- /dev/null +++ b/models/pbr-mat-ext.obj @@ -0,0 +1,10 @@ +mtllib pbr-mat-ext.mtl + +o floor +usemtl pbr +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +f 1 2 3 4 diff --git a/models/points-prim.obj b/models/points-prim.obj new file mode 100644 index 00000000..21e89c01 --- /dev/null +++ b/models/points-prim.obj @@ -0,0 +1,15 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g g0 +usemtl white +p 1 2 3 4 5 6 7 8 diff --git a/models/refl.mtl b/models/refl.mtl new file mode 100644 index 00000000..7e04f28f --- /dev/null +++ b/models/refl.mtl @@ -0,0 +1,25 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +refl reflection.tga + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/refl.obj b/models/refl.obj new file mode 100644 index 00000000..e9715f59 --- /dev/null +++ b/models/refl.obj @@ -0,0 +1,32 @@ +# Test for `refl` material parameter +mtllib refl.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/skin-weight.obj b/models/skin-weight.obj new file mode 100644 index 00000000..41f182f5 --- /dev/null +++ b/models/skin-weight.obj @@ -0,0 +1,43 @@ +mtllib cube.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +vw 0 0 1.0 +vw 1 0 0.5 1 0.5 +vw 2 1 1.0 +vw 3 2 1.0 +vw 4 3 1.0 +vw 5 0 0.25 1 0.25 2 0.25 3 0.25 +# No weight for 6th vertex +# vw 6 0 1.0 +vw 7 0 1.0 +# max 4 joints + +g front cube +usemtl white +f 1 2 3 4 +# two white spaces between 'back' and 'cube' +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/smoothing-group-two-squares.obj b/models/smoothing-group-two-squares.obj new file mode 100644 index 00000000..5b216156 --- /dev/null +++ b/models/smoothing-group-two-squares.obj @@ -0,0 +1,22 @@ +# from tinyobjloader issue #29 + +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +v 4.000000 0.000000 -3.255298 +v 4.000000 2.000000 -3.255298 +#vn 0.000000 0.000000 1.000000 +#vn 0.000000 0.000000 1.000000 +#vn 0.276597 0.000000 0.960986 +#vn 0.276597 0.000000 0.960986 +#vn 0.531611 0.000000 0.846988 +#vn 0.531611 0.000000 0.846988 +# 6 vertices + +# 6 normals + +g all +s 1 +f 1//1 2//2 3//3 4//4 +f 4//4 3//3 5//5 diff --git a/models/smoothing-normal.mtl b/models/smoothing-normal.mtl new file mode 100644 index 00000000..d3a1c7a6 --- /dev/null +++ b/models/smoothing-normal.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/smoothing-normal.obj b/models/smoothing-normal.obj new file mode 100644 index 00000000..8a89dec9 --- /dev/null +++ b/models/smoothing-normal.obj @@ -0,0 +1,35 @@ +mtllib smoothing-normal.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +s 1 +f 1 2 3 4 +#g bottom cube +#usemtl white +s 1 +f 2 6 7 3 + +g back cube +# expects white material +s off +f 8 7 6 5 +#g right cube +#usemtl red +#f 4 3 7 8 +#g top cube +#usemtl white +#f 5 1 4 8 +#g left cube +#usemtl green +#f 5 6 2 1 +# 6 elements diff --git a/models/test-nan.obj b/models/test-nan.obj new file mode 100644 index 00000000..3c689250 --- /dev/null +++ b/models/test-nan.obj @@ -0,0 +1,145 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +usemtl white +v nan -nan nan +v inf -inf inf +v 1.#IND -1.#IND 1.#IND +v 1.#INF -1.#INF 1.#INF + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +o light +usemtl light +v 343.0 548.0 227.0 +v 343.0 548.0 332.0 +v 213.0 548.0 332.0 +v 213.0 548.0 227.0 +f -4 -3 -2 -1 + +o ceiling +usemtl white +v 556.0 548.8 0.0 +v 556.0 548.8 559.2 +v 0.0 548.8 559.2 +v 0.0 548.8 0.0 +f -4 -3 -2 -1 + +o back_wall +usemtl white +v 549.6 0.0 559.2 +v 0.0 0.0 559.2 +v 0.0 548.8 559.2 +v 556.0 548.8 559.2 +f -4 -3 -2 -1 + +o front_wall +usemtl blue +v 549.6 0.0 0 +v 0.0 0.0 0 +v 0.0 548.8 0 +v 556.0 548.8 0 +#f -1 -2 -3 -4 + +o green_wall +usemtl green +v 0.0 0.0 559.2 +v 0.0 0.0 0.0 +v 0.0 548.8 0.0 +v 0.0 548.8 559.2 +f -4 -3 -2 -1 + +o red_wall +usemtl red +v 552.8 0.0 0.0 +v 549.6 0.0 559.2 +v 556.0 548.8 559.2 +v 556.0 548.8 0.0 +f -4 -3 -2 -1 + +o short_block +usemtl white + +v 130.0 165.0 65.0 +v 82.0 165.0 225.0 +v 240.0 165.0 272.0 +v 290.0 165.0 114.0 +f -4 -3 -2 -1 + +v 290.0 0.0 114.0 +v 290.0 165.0 114.0 +v 240.0 165.0 272.0 +v 240.0 0.0 272.0 +f -4 -3 -2 -1 + +v 130.0 0.0 65.0 +v 130.0 165.0 65.0 +v 290.0 165.0 114.0 +v 290.0 0.0 114.0 +f -4 -3 -2 -1 + +v 82.0 0.0 225.0 +v 82.0 165.0 225.0 +v 130.0 165.0 65.0 +v 130.0 0.0 65.0 +f -4 -3 -2 -1 + +v 240.0 0.0 272.0 +v 240.0 165.0 272.0 +v 82.0 165.0 225.0 +v 82.0 0.0 225.0 +f -4 -3 -2 -1 + +o tall_block +usemtl white + +v 423.0 330.0 247.0 +v 265.0 330.0 296.0 +v 314.0 330.0 456.0 +v 472.0 330.0 406.0 +f -4 -3 -2 -1 + +usemtl white +v 423.0 0.0 247.0 +v 423.0 330.0 247.0 +v 472.0 330.0 406.0 +v 472.0 0.0 406.0 +f -4 -3 -2 -1 + +v 472.0 0.0 406.0 +v 472.0 330.0 406.0 +v 314.0 330.0 456.0 +v 314.0 0.0 456.0 +f -4 -3 -2 -1 + +v 314.0 0.0 456.0 +v 314.0 330.0 456.0 +v 265.0 330.0 296.0 +v 265.0 0.0 296.0 +f -4 -3 -2 -1 + +v 265.0 0.0 296.0 +v 265.0 330.0 296.0 +v 423.0 330.0 247.0 +v 423.0 0.0 247.0 +f -4 -3 -2 -1 + diff --git a/models/texture-filename-with-whitespace.mtl b/models/texture-filename-with-whitespace.mtl new file mode 100644 index 00000000..70b1a4e9 --- /dev/null +++ b/models/texture-filename-with-whitespace.mtl @@ -0,0 +1,28 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +# filename with white space. +map_Kd texture 01.png + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 +# texture option + filename with white space. +bump -bm 2 bump 01.png + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/texture-filename-with-whitespace.obj b/models/texture-filename-with-whitespace.obj new file mode 100644 index 00000000..46e61e78 --- /dev/null +++ b/models/texture-filename-with-whitespace.obj @@ -0,0 +1,31 @@ +mtllib texture-filename-with-whitespace.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/models/texture-options-issue-85.mtl b/models/texture-options-issue-85.mtl new file mode 100644 index 00000000..d4d62add --- /dev/null +++ b/models/texture-options-issue-85.mtl @@ -0,0 +1,36 @@ +newmtl default +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +map_Ka -clamp on ambient.jpg +map_Kd -o 0.1 diffuse.jpg +map_Ks -s 0.1 0.2 specular.jpg +map_Ns -t 0.1 0.2 0.3 specular_highlight.jpg +map_bump -bm 3 bumpmap.jpg + +newmtl bm2 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +# blendu +map_Kd -blendu on diffuse.jpg +map_Ks -blendv off specular.jpg +map_Ns -mm 0.1 0.3 specular_highlight.jpg +# -bm after filename +map_bump -imfchan r bumpmap2.jpg -bm 1.5 + +newmtl bm3 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Kt 0.1 0.2 0.3 +# type +map_Kd -type sphere diffuse.jpg +map_Ks -type cube_top specular.jpg +map_Ns -type cube_bottom specular_highlight.jpg +map_Ka -type cube_left ambient.jpg +map_d -type cube_right alpha.jpg +map_bump -type cube_front bump.jpg +disp -type cube_back displacement.jpg diff --git a/models/texture-options-issue-85.obj b/models/texture-options-issue-85.obj new file mode 100644 index 00000000..f7abb0c5 --- /dev/null +++ b/models/texture-options-issue-85.obj @@ -0,0 +1,7 @@ +mtllib texture-options-issue-85.mtl +o Test +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +usemtl default +f 1 2 3 diff --git a/models/tr-and-d-issue-43.mtl b/models/tr-and-d-issue-43.mtl new file mode 100644 index 00000000..44fd7acd --- /dev/null +++ b/models/tr-and-d-issue-43.mtl @@ -0,0 +1,13 @@ +newmtl Material.001 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +d 0.75 +Tr 0.5 + +newmtl Material.002 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 +Tr 0.5 +d 0.75 diff --git a/models/tr-and-d-issue-43.obj b/models/tr-and-d-issue-43.obj new file mode 100644 index 00000000..d86c3cf4 --- /dev/null +++ b/models/tr-and-d-issue-43.obj @@ -0,0 +1,817 @@ +# https://github.com/syoyo/tinyobjloader/issues/68 +# Blender v2.73 (sub 0) OBJ File: 'enemy.blend' +# www.blender.org +mtllib tr-and-d-issue-43.mtl +o Cube +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +v 1.620345 1.000000 -5.815706 +v 1.864152 1.000000 -6.334323 +v 0.575869 -0.129842 5.896143 +v 5.440438 -1.462153 -5.818601 +v 4.896782 -1.462153 -2.744413 +v 1.000825 -0.677484 1.899605 +v 5.440438 -1.246362 -5.818600 +v 1.000825 0.852342 1.899608 +v 4.896782 -1.246362 -2.744412 +v 1.160660 -0.450871 -2.356325 +v 1.704316 -0.450871 -5.430513 +v 1.000825 -0.351920 -1.293797 +v 1.000825 1.000000 -1.293794 +v 1.160660 -0.877888 -2.356326 +v 1.704316 -0.877888 -5.430514 +v 1.000825 -1.219172 -1.452514 +v 1.000825 1.000000 -1.452511 +v 1.000825 -0.351920 1.759410 +v 1.000825 1.000000 1.759413 +v 9.097919 1.221145 -6.212147 +v 8.356775 1.221145 -2.021231 +v 1.864151 -0.109586 -6.334325 +v 0.575869 -0.398073 5.896141 +v 9.097919 0.943958 -6.212148 +v 8.356775 0.943958 -2.021233 +v 1.061916 0.113661 -1.797961 +v 1.000825 0.161258 1.899606 +v 1.000825 0.324040 -1.293795 +v 1.803060 0.113661 -5.988876 +v 1.000825 -0.109586 -1.452513 +v 1.061916 0.776753 -1.797960 +v 1.803061 0.776753 -5.988875 +v 1.000825 0.324040 1.759412 +v 0.000825 -1.219172 -5.532512 +v 0.000825 -0.666304 5.896139 +v 0.000826 1.000000 -6.334325 +v 0.000825 -0.129842 5.896140 +v 0.000825 0.852342 1.899606 +v 0.000825 -0.677484 1.899604 +v 0.000825 -0.351920 -1.293797 +v 0.000825 1.000000 -1.293796 +v 0.000825 1.000000 -1.452513 +v 0.000825 -1.219172 -1.452515 +v 0.000825 -0.351920 1.759409 +v 0.000825 1.000000 1.759411 +v 0.000826 -0.109586 -6.334326 +v 0.000825 -0.398073 5.896140 +v 0.152918 1.000000 -5.815708 +v 0.152917 1.000000 -1.971130 +v 0.940448 1.168419 -1.971128 +v 1.620345 1.168419 -5.815706 +v 0.152918 1.168419 -5.815708 +v 0.152917 1.168419 -1.971130 +v 0.921118 1.091883 -1.050430 +v 0.921118 1.091883 1.516050 +v 0.080533 1.091883 -1.050432 +v 0.080533 1.091883 1.516048 +v 0.613003 -0.553430 5.546911 +v 0.963691 -0.559956 2.248834 +v 0.613003 -0.396857 5.546912 +v 0.963691 -0.070362 2.248835 +v 1.499370 -0.994317 3.966028 +v 1.850058 -0.997914 0.667950 +v 1.499370 -0.908021 3.966029 +v 1.850058 -0.728071 0.667951 +v 1.601022 0.760960 -6.334324 +v 1.601021 0.129454 -6.334325 +v 0.263955 0.760960 -6.334325 +v 0.263955 0.129454 -6.334325 +v 1.334809 0.760960 -7.515329 +v 1.334809 0.129455 -7.515330 +v 0.530168 0.760960 -7.515330 +v 0.530168 0.129455 -7.515330 +v 1.192720 0.649445 -7.515329 +v 1.192720 0.240971 -7.515330 +v 0.672258 0.649445 -7.515330 +v 0.672258 0.240971 -7.515330 +v 1.192719 0.649444 -6.524630 +v 1.192719 0.240970 -6.524631 +v 0.672257 0.649444 -6.524631 +v 0.672257 0.240970 -6.524631 +v 3.851026 0.431116 -1.883326 +v 3.851026 0.946662 -1.883325 +v 4.592170 0.946662 -6.074241 +v 4.592169 0.431116 -6.074242 +v 4.995714 0.561404 -1.918362 +v 4.995714 1.016394 -1.918360 +v 5.736857 1.016394 -6.109276 +v 5.736857 0.561404 -6.109277 +v 3.975454 0.471731 -2.162156 +v 3.975454 0.919244 -2.162155 +v 4.618796 0.919244 -5.800034 +v 4.618795 0.471730 -5.800035 +v 4.969088 0.584825 -2.192568 +v 4.969088 0.979775 -2.192567 +v 5.612430 0.979775 -5.830446 +v 5.612429 0.584825 -5.830447 +v 0.864214 -0.673890 3.184381 +v 0.864213 0.489129 3.184384 +v 0.864213 -0.018552 3.184383 +v 0.000825 0.489129 3.184382 +v 0.000825 -0.673890 3.184381 +v 0.850955 -0.557858 3.309075 +v 0.850955 -0.175321 3.309076 +v 1.737321 -0.996758 1.728192 +v 1.737321 -0.785920 1.728193 +v -1.864151 -1.219172 -5.532511 +v -0.575869 -0.666304 5.896140 +v -0.940448 1.000000 -1.971128 +v -1.620345 1.000000 -5.815706 +v -1.864152 1.000000 -6.334323 +v -0.575869 -0.129842 5.896143 +v -5.440438 -1.462153 -5.818601 +v -4.896782 -1.462153 -2.744413 +v -1.000825 -0.677484 1.899605 +v -5.440438 -1.246362 -5.818600 +v -1.000825 0.852342 1.899608 +v -4.896782 -1.246362 -2.744412 +v -1.160660 -0.450871 -2.356325 +v -1.704316 -0.450871 -5.430513 +v -1.000825 -0.351920 -1.293797 +v -1.000825 1.000000 -1.293794 +v -1.160660 -0.877888 -2.356326 +v -1.704316 -0.877888 -5.430514 +v -1.000825 -1.219172 -1.452514 +v -1.000825 1.000000 -1.452511 +v -1.000825 -0.351920 1.759410 +v -1.000825 1.000000 1.759413 +v -9.097919 1.221145 -6.212147 +v -8.356775 1.221145 -2.021231 +v -1.864151 -0.109586 -6.334325 +v -0.575869 -0.398073 5.896141 +v -9.097919 0.943958 -6.212148 +v -8.356775 0.943958 -2.021233 +v -1.061916 0.113661 -1.797961 +v -1.000825 0.161258 1.899606 +v -1.000825 0.324040 -1.293795 +v -1.803060 0.113661 -5.988876 +v -1.000825 -0.109586 -1.452513 +v -1.061916 0.776753 -1.797960 +v -1.803061 0.776753 -5.988875 +v -1.000825 0.324040 1.759412 +v -0.000825 -1.219172 -5.532512 +v -0.000825 -0.666304 5.896139 +v -0.000826 1.000000 -6.334325 +v -0.000825 -0.129842 5.896140 +v -0.000825 0.852342 1.899606 +v -0.000825 -0.677484 1.899604 +v -0.000825 -0.351920 -1.293797 +v -0.000825 1.000000 -1.293796 +v -0.000825 1.000000 -1.452513 +v -0.000825 -1.219172 -1.452515 +v -0.000825 -0.351920 1.759409 +v -0.000825 1.000000 1.759411 +v -0.000826 -0.109586 -6.334326 +v -0.000825 -0.398073 5.896140 +v -0.152918 1.000000 -5.815708 +v -0.152917 1.000000 -1.971130 +v -0.940448 1.168419 -1.971128 +v -1.620345 1.168419 -5.815706 +v -0.152918 1.168419 -5.815708 +v -0.152917 1.168419 -1.971130 +v -0.921118 1.091883 -1.050430 +v -0.921118 1.091883 1.516050 +v -0.080533 1.091883 -1.050432 +v -0.080533 1.091883 1.516048 +v -0.613003 -0.553430 5.546911 +v -0.963691 -0.559956 2.248834 +v -0.613003 -0.396857 5.546912 +v -0.963691 -0.070362 2.248835 +v -1.499370 -0.994317 3.966028 +v -1.850058 -0.997914 0.667950 +v -1.499370 -0.908021 3.966029 +v -1.850058 -0.728071 0.667951 +v -1.601022 0.760960 -6.334324 +v -1.601021 0.129454 -6.334325 +v -0.263955 0.760960 -6.334325 +v -0.263955 0.129454 -6.334325 +v -1.334809 0.760960 -7.515329 +v -1.334809 0.129455 -7.515330 +v -0.530168 0.760960 -7.515330 +v -0.530168 0.129455 -7.515330 +v -1.192720 0.649445 -7.515329 +v -1.192720 0.240971 -7.515330 +v -0.672258 0.649445 -7.515330 +v -0.672258 0.240971 -7.515330 +v -1.192719 0.649444 -6.524630 +v -1.192719 0.240970 -6.524631 +v -0.672257 0.649444 -6.524631 +v -0.672257 0.240970 -6.524631 +v -3.851026 0.431116 -1.883326 +v -3.851026 0.946662 -1.883325 +v -4.592170 0.946662 -6.074241 +v -4.592169 0.431116 -6.074242 +v -4.995714 0.561404 -1.918362 +v -4.995714 1.016394 -1.918360 +v -5.736857 1.016394 -6.109276 +v -5.736857 0.561404 -6.109277 +v -3.975454 0.471731 -2.162156 +v -3.975454 0.919244 -2.162155 +v -4.618796 0.919244 -5.800034 +v -4.618795 0.471730 -5.800035 +v -4.969088 0.584825 -2.192568 +v -4.969088 0.979775 -2.192567 +v -5.612430 0.979775 -5.830446 +v -5.612429 0.584825 -5.830447 +v -0.864214 -0.673890 3.184381 +v -0.864213 0.489129 3.184384 +v -0.864213 -0.018552 3.184383 +v -0.000825 0.489129 3.184382 +v -0.000825 -0.673890 3.184381 +v -0.850955 -0.557858 3.309075 +v -0.850955 -0.175321 3.309076 +v -1.737321 -0.996758 1.728192 +v -1.737321 -0.785920 1.728193 +vt 0.135351 -0.558072 +vt 0.003035 -0.363507 +vt 0.092282 -0.976844 +vt -0.081322 0.947351 +vt 0.100058 1.958891 +vt 0.050091 1.852185 +vt -0.092752 1.055565 +vt -0.251711 1.059474 +vt 0.075587 0.041384 +vt -0.086008 0.279003 +vt -0.086212 0.249830 +vt -0.276044 1.968137 +vt -0.246101 1.859467 +vt 0.009828 1.911388 +vt -0.133014 1.114769 +vt 0.413322 1.261595 +vt 0.299103 0.624605 +vt 1.243955 0.407183 +vt 0.515404 1.111487 +vt 1.358173 1.044173 +vt -0.081553 0.914324 +vt 0.080042 0.676706 +vt 0.401185 0.474498 +vt 1.295541 0.331328 +vt 0.365315 1.568841 +vt 0.299111 1.575740 +vt 0.143401 0.707357 +vt 0.629403 1.011947 +vt 0.449192 0.167251 +vt 1.409760 0.968317 +vt 0.986264 1.738667 +vt 1.573373 1.877873 +vt 1.417663 1.009490 +vt 0.237182 -0.196235 +vt 0.721785 1.030226 +vt 0.830554 0.870285 +vt 0.877494 1.898608 +vt 1.351399 1.106930 +vt 0.183935 0.557301 +vt 1.507109 1.975312 +vt 0.241636 0.439088 +vt 0.114297 -0.045011 +vt 0.140593 1.808834 +vt -0.015118 0.940452 +vt 0.156405 -1.071134 +vt 0.164119 -0.998223 +vt 0.040336 -1.068281 +vt 0.104459 -1.162571 +vt -0.165787 1.882802 +vt -0.014821 1.660811 +vt -0.287852 0.283965 +vt -0.293374 0.366508 +vt -0.289630 0.900550 +vt 0.035337 -0.191272 +vt 0.247348 0.172213 +vt 0.253300 1.021193 +vt -0.283166 0.952313 +vt -0.283398 0.919286 +vt 0.039792 0.444050 +vt 0.314806 -0.339851 +vt 0.112962 -0.334889 +vt -0.288056 0.254793 +vt -0.023788 -0.973990 +vt -0.155922 -0.359599 +vt 0.220528 -1.165425 +vt 0.108710 -0.748730 +vt -0.286364 1.918670 +vt -0.291973 1.118678 +vt -0.119962 0.896379 +vt -0.123707 0.362337 +vt 0.162891 -0.598569 +vt 0.467532 -0.853353 +vt 0.201549 -1.053262 +vt 0.161663 -0.198915 +vt 0.267667 -0.752638 +vt 0.278705 -0.371021 +vt 0.526390 -0.542053 +vt 0.483821 -0.479457 +vt 0.488162 -0.883689 +vt 0.500110 -0.105561 +vt 0.564618 -0.200418 +vt -0.110331 2.127229 +vt 0.040636 1.905238 +vt -0.010786 1.578087 +vt 0.104092 1.876168 +vt 0.255058 1.654176 +vt -0.054992 2.087323 +vt 0.203048 1.901245 +vt 0.052081 2.123235 +vt 0.042658 1.943733 +vt -0.056437 1.881175 +vt 0.147710 1.941151 +vt 0.050060 2.084741 +vt 0.146264 1.735002 +vt 0.041212 1.737584 +vt 0.048615 1.878591 +vt 0.663065 1.872485 +vt 0.786311 1.691257 +vt 0.507355 1.004102 +vt 0.630601 0.822874 +vt 0.955144 1.689498 +vt 0.860727 1.828333 +vt 0.725565 1.074543 +vt 0.819981 0.935708 +vt 0.674594 1.805657 +vt 0.539432 1.051867 +vt 0.646413 0.894554 +vt 0.781576 1.648344 +vt 0.240127 -0.712141 +vn 0.994400 0.000000 0.105700 +vn 0.000000 1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.984700 0.000000 0.174100 +vn 0.211800 0.976600 0.037500 +vn -0.103300 0.000000 -0.994600 +vn 0.103300 -0.000000 0.994600 +vn 0.911400 0.378700 0.161200 +vn -0.157300 -0.987200 -0.027800 +vn 0.113700 -0.993300 0.020100 +vn 0.030600 -0.000000 0.999500 +vn -0.061100 0.998100 -0.010800 +vn -0.030600 0.000000 -0.999500 +vn -0.000000 -0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn -0.755400 0.655300 0.000000 +vn 0.000000 -1.000000 0.000000 +vn -0.000000 -0.180000 0.983700 +vn 0.000000 -0.395500 -0.918500 +vn -0.000000 0.688500 0.725200 +vn 0.000000 -0.585700 -0.810500 +vn -0.000000 0.974900 0.222500 +vn -0.000000 -1.000000 0.002800 +vn -1.000000 0.000000 -0.000000 +vn -0.000000 0.935500 0.353200 +vn 0.755400 0.655300 0.000000 +vn 0.000000 0.935500 -0.353200 +vn 0.673800 0.724900 0.143400 +vn 0.872300 -0.000000 0.489100 +vn -0.872300 0.000000 -0.489100 +vn -0.518300 -0.853500 -0.054200 +vn -0.975500 0.000000 -0.219900 +vn 0.975500 0.000000 -0.219900 +vn -0.913200 0.000000 -0.407500 +vn -0.436900 0.896200 -0.077300 +vn -0.995300 -0.000000 0.096600 +vn -0.297300 -0.953400 -0.052600 +vn 0.473900 -0.876600 0.083800 +vn 0.913200 0.000000 0.407500 +vn 0.342200 0.937700 0.060500 +vn 0.995300 -0.000000 -0.096600 +vn -0.519200 -0.853000 -0.054300 +vn 0.722400 0.676400 0.143800 +vn -0.994400 0.000000 0.105700 +vn -0.984700 0.000000 0.174100 +vn -0.211800 0.976600 0.037500 +vn 0.103300 0.000000 -0.994600 +vn -0.103300 -0.000000 0.994600 +vn -0.911400 0.378700 0.161200 +vn 0.157300 -0.987200 -0.027800 +vn -0.113700 -0.993300 0.020100 +vn -0.030600 -0.000000 0.999500 +vn 0.061100 0.998100 -0.010800 +vn 0.030600 0.000000 -0.999500 +vn -0.691900 0.713200 0.112500 +vn -0.872300 -0.000000 0.489100 +vn 0.872300 0.000000 -0.489100 +vn 0.518300 -0.853500 -0.054200 +vn 0.913200 0.000000 -0.407500 +vn 0.436900 0.896200 -0.077300 +vn 0.995300 0.000000 0.096600 +vn 0.297300 -0.953300 -0.052600 +vn -0.473900 -0.876600 0.083800 +vn -0.913200 -0.000000 0.407500 +vn -0.342200 0.937700 0.060500 +vn -0.995300 -0.000000 -0.096600 +vn 0.519200 -0.853000 -0.054300 +vn -0.714800 0.690100 0.113700 +vn 0.974400 0.089700 0.206200 +vn 0.870400 0.288400 0.399100 +vn 0.691900 0.713200 0.112500 +vn -0.518000 -0.853700 -0.053400 +vn -0.519700 -0.852700 -0.053600 +vn 0.714800 0.690100 0.113700 +vn -0.974400 0.089700 0.206200 +vn -0.870400 0.288400 0.399100 +vn -0.673800 0.724900 0.143400 +vn 0.518000 -0.853700 -0.053400 +vn 0.297300 -0.953400 -0.052600 +vn 0.519700 -0.852700 -0.053600 +vn -0.722400 0.676400 0.143800 +vn -0.000000 0.962300 0.272000 +usemtl Material.001 +s off +f 103/1/1 102/2/1 6/3/1 +f 20/4/2 5/5/2 4/6/2 +f 20/4/2 3/7/2 52/8/2 +f 36/9/3 22/10/3 11/11/3 +f 39/12/2 51/13/2 4/6/2 +f 4/6/4 54/14/4 53/15/4 +f 14/16/5 13/17/5 12/18/5 +f 18/19/6 14/16/6 10/20/6 +f 20/4/3 16/21/3 31/22/3 +f 17/23/7 8/24/7 12/18/7 +f 25/25/4 32/26/4 29/27/4 +f 10/20/4 12/18/4 8/24/4 +f 1/28/8 18/19/8 17/23/8 +f 19/29/4 17/23/4 13/17/4 +f 25/25/4 14/16/4 18/19/4 +f 18/19/9 7/30/9 8/24/9 +f 92/31/10 27/32/10 28/33/10 +f 16/21/3 22/10/3 36/9/3 +f 31/22/3 36/9/3 21/34/3 +f 90/35/11 89/36/11 28/33/11 +f 91/37/12 90/35/12 24/38/12 +f 33/39/4 13/17/4 14/16/4 +f 23/40/4 24/38/4 28/33/4 +f 33/39/3 31/22/3 15/41/3 +f 21/34/3 36/9/3 30/42/3 +f 5/5/4 35/43/4 32/26/4 +f 5/5/4 20/4/4 34/44/4 +f 33/39/4 29/27/4 34/44/4 +f 91/37/13 23/40/13 27/32/13 +f 103/1/1 26/45/1 63/46/1 +f 26/45/14 50/47/14 38/48/14 +f 39/12/15 71/49/15 72/50/15 +f 48/51/16 60/52/16 59/53/16 +f 15/41/17 21/34/17 47/54/17 +f 19/29/17 46/55/17 37/56/17 +f 39/12/2 45/57/2 52/8/2 +f 20/4/2 45/57/2 44/58/2 +f 19/29/18 15/41/18 43/59/18 +f 9/60/19 42/61/19 47/54/19 +f 22/10/20 48/51/20 41/62/20 +f 25/25/21 1/28/21 37/56/21 +f 6/3/14 40/63/14 50/47/14 +f 104/64/22 40/63/22 6/3/22 +f 2/65/23 38/48/23 105/66/23 +f 55/67/2 56/68/2 53/15/2 +f 3/7/14 53/15/14 56/68/14 +f 51/13/15 55/67/15 54/14/15 +f 52/8/24 56/68/24 55/67/24 +f 57/69/2 59/53/2 60/52/2 +f 48/51/25 22/10/25 58/70/25 +f 16/21/26 57/69/26 58/70/26 +f 16/21/27 44/58/27 59/53/27 +f 107/71/28 63/46/28 67/72/28 +f 26/45/1 2/65/1 61/73/1 +f 9/60/1 30/42/1 64/74/1 +f 101/75/1 9/60/1 62/76/1 +f 108/77/1 109/78/1 67/72/1 +f 61/73/29 65/79/29 67/72/29 +f 62/76/30 64/74/30 68/80/30 +f 62/76/31 66/81/31 108/77/31 +f 71/49/32 75/82/32 76/83/32 +f 25/25/15 49/84/15 72/50/15 +f 5/5/15 69/85/15 71/49/15 +f 25/25/15 70/86/15 69/85/15 +f 76/83/15 75/82/15 79/87/15 +f 72/50/17 76/83/17 74/88/17 +f 71/49/2 69/85/2 73/89/2 +f 70/86/33 74/88/33 73/89/33 +f 80/90/3 79/87/3 83/91/3 +f 76/83/15 80/90/15 78/92/15 +f 75/82/15 73/89/15 77/93/15 +f 74/88/15 78/92/15 77/93/15 +f 82/94/15 84/95/15 83/91/15 +f 80/90/2 84/95/2 82/94/2 +f 77/93/17 81/96/17 83/91/17 +f 77/93/24 78/92/24 82/94/24 +f 35/43/13 87/97/13 88/98/13 +f 35/43/12 34/44/12 86/99/12 +f 34/44/11 29/27/11 85/100/11 +f 32/26/10 88/98/10 85/100/10 +f 92/31/34 100/101/34 99/102/34 +f 90/35/35 91/37/35 99/102/35 +f 89/36/36 90/35/36 98/103/36 +f 89/36/37 97/104/37 100/101/37 +f 95/105/13 99/102/13 100/101/13 +f 95/105/12 94/106/12 98/103/12 +f 94/106/11 93/107/11 97/104/11 +f 96/108/10 100/101/10 97/104/10 +f 88/98/38 96/108/38 93/107/38 +f 86/99/39 85/100/39 93/107/39 +f 87/97/40 86/99/40 94/106/40 +f 87/97/41 95/105/41 96/108/41 +f 106/109/42 108/77/42 65/79/42 +f 66/81/1 68/80/1 109/78/1 +f 101/75/1 106/109/1 61/73/1 +f 64/74/43 107/71/43 109/78/43 +f 101/75/23 105/66/23 42/61/23 +f 103/1/1 107/71/1 64/74/1 +f 30/42/1 11/11/1 102/2/1 +f 212/1/44 135/45/44 115/3/44 +f 129/4/2 112/7/2 113/6/2 +f 161/8/2 112/7/2 129/4/2 +f 145/9/24 139/42/24 120/11/24 +f 113/6/2 160/13/2 148/12/2 +f 162/15/45 163/14/45 113/6/45 +f 123/16/46 119/20/46 121/18/46 +f 127/19/47 116/30/47 119/20/47 +f 140/22/24 125/21/24 129/4/24 +f 121/18/48 117/24/48 126/23/48 +f 138/27/45 141/26/45 134/25/45 +f 117/24/45 121/18/45 119/20/45 +f 126/23/49 127/19/49 110/28/49 +f 122/17/45 126/23/45 128/29/45 +f 127/19/45 123/16/45 134/25/45 +f 117/24/50 116/30/50 127/19/50 +f 137/33/51 136/32/51 201/31/51 +f 145/9/24 131/10/24 125/21/24 +f 130/34/24 145/9/24 140/22/24 +f 199/35/52 133/38/52 137/33/52 +f 200/37/53 132/40/53 133/38/53 +f 123/16/45 122/17/45 142/39/45 +f 137/33/45 133/38/45 132/40/45 +f 124/41/24 140/22/24 142/39/24 +f 130/34/24 118/60/24 139/42/24 +f 141/26/45 144/43/45 114/5/45 +f 114/5/45 144/43/45 143/44/45 +f 143/44/45 138/27/45 142/39/45 +f 136/32/54 132/40/54 200/37/54 +f 212/1/44 216/71/44 172/46/44 +f 147/48/14 159/47/14 135/45/14 +f 181/50/15 180/49/15 148/12/15 +f 168/53/26 169/52/26 157/51/26 +f 124/41/17 152/59/17 156/54/17 +f 146/56/17 155/55/17 128/29/17 +f 148/12/2 160/13/2 161/8/2 +f 129/4/2 125/21/2 153/58/2 +f 155/55/18 152/59/18 124/41/18 +f 130/34/19 156/54/19 151/61/19 +f 131/10/20 120/11/20 150/62/20 +f 134/25/21 158/84/21 146/56/21 +f 159/47/14 149/63/14 115/3/14 +f 115/3/22 149/63/22 213/64/22 +f 214/66/23 147/48/23 111/65/23 +f 162/15/2 165/68/2 164/67/2 +f 165/68/14 162/15/14 112/7/14 +f 163/14/15 164/67/15 160/13/15 +f 164/67/3 165/68/3 161/8/3 +f 166/69/2 167/70/2 169/52/2 +f 157/51/25 169/52/25 167/70/25 +f 167/70/16 166/69/16 125/21/16 +f 125/21/27 166/69/27 168/53/27 +f 216/71/55 218/78/55 176/72/55 +f 135/45/44 172/46/44 170/73/44 +f 118/60/44 171/76/44 173/74/44 +f 210/75/44 215/109/44 171/76/44 +f 217/77/44 174/79/44 176/72/44 +f 176/72/56 174/79/56 170/73/56 +f 171/76/57 175/81/57 177/80/57 +f 217/77/58 175/81/58 171/76/58 +f 185/83/33 184/82/33 180/49/33 +f 134/25/15 179/86/15 181/50/15 +f 180/49/15 178/85/15 114/5/15 +f 178/85/15 179/86/15 134/25/15 +f 189/90/15 188/87/15 184/82/15 +f 183/88/17 185/83/17 181/50/17 +f 180/49/2 184/82/2 182/89/2 +f 182/89/32 183/88/32 179/86/32 +f 189/90/24 193/95/24 192/91/24 +f 187/92/15 189/90/15 185/83/15 +f 184/82/15 188/87/15 186/93/15 +f 186/93/15 187/92/15 183/88/15 +f 192/91/15 193/95/15 191/94/15 +f 191/94/2 193/95/2 189/90/2 +f 192/91/17 190/96/17 186/93/17 +f 186/93/3 190/96/3 191/94/3 +f 197/98/54 196/97/54 144/43/54 +f 144/43/53 196/97/53 195/99/53 +f 143/44/52 195/99/52 194/100/52 +f 194/100/51 197/98/51 141/26/51 +f 208/102/59 209/101/59 201/31/59 +f 199/35/60 207/103/60 208/102/60 +f 198/36/61 206/104/61 207/103/61 +f 209/101/62 206/104/62 198/36/62 +f 209/101/54 208/102/54 204/105/54 +f 204/105/53 208/102/53 207/103/53 +f 203/106/52 207/103/52 206/104/52 +f 206/104/51 209/101/51 205/108/51 +f 202/107/63 205/108/63 197/98/63 +f 195/99/64 203/106/64 202/107/64 +f 196/97/65 204/105/65 203/106/65 +f 205/108/66 204/105/66 196/97/66 +f 174/79/67 217/77/67 215/109/67 +f 175/81/44 217/77/44 218/78/44 +f 170/73/44 215/109/44 210/75/44 +f 173/74/68 177/80/68 218/78/68 +f 151/61/23 214/66/23 210/75/23 +f 173/74/44 216/71/44 212/1/44 +f 139/42/44 212/1/44 211/2/44 +f 26/45/1 103/1/1 6/3/1 +f 3/7/2 20/4/2 4/6/2 +f 45/57/2 20/4/2 52/8/2 +f 30/42/3 36/9/3 11/11/3 +f 5/5/2 39/12/2 4/6/2 +f 3/7/4 4/6/4 53/15/4 +f 10/20/5 14/16/5 12/18/5 +f 7/30/6 18/19/6 10/20/6 +f 33/39/3 20/4/3 31/22/3 +f 13/17/7 17/23/7 12/18/7 +f 33/39/4 25/25/4 29/27/4 +f 7/30/4 10/20/4 8/24/4 +f 19/29/69 1/28/69 17/23/69 +f 33/39/4 19/29/4 13/17/4 +f 1/28/70 25/25/70 18/19/70 +f 17/23/9 18/19/9 8/24/9 +f 89/36/10 92/31/10 28/33/10 +f 31/22/3 16/21/3 36/9/3 +f 15/41/3 31/22/3 21/34/3 +f 24/38/11 90/35/11 28/33/11 +f 23/40/12 91/37/12 24/38/12 +f 25/25/4 33/39/4 14/16/4 +f 27/32/4 23/40/4 28/33/4 +f 19/29/3 33/39/3 15/41/3 +f 9/60/3 21/34/3 30/42/3 +f 25/25/4 5/5/4 32/26/4 +f 35/43/4 5/5/4 34/44/4 +f 20/4/4 33/39/4 34/44/4 +f 92/31/13 91/37/13 27/32/13 +f 107/71/1 103/1/1 63/46/1 +f 2/65/14 26/45/14 38/48/14 +f 49/84/15 39/12/15 72/50/15 +f 44/58/16 48/51/16 59/53/16 +f 43/59/17 15/41/17 47/54/17 +f 1/28/17 19/29/17 37/56/17 +f 51/13/2 39/12/2 52/8/2 +f 16/21/2 20/4/2 44/58/2 +f 46/55/18 19/29/18 43/59/18 +f 21/34/19 9/60/19 47/54/19 +f 11/11/20 22/10/20 41/62/20 +f 49/84/21 25/25/21 37/56/21 +f 26/45/14 6/3/14 50/47/14 +f 102/2/22 104/64/22 6/3/22 +f 101/75/23 2/65/23 105/66/23 +f 54/14/2 55/67/2 53/15/2 +f 52/8/14 3/7/14 56/68/14 +f 4/6/15 51/13/15 54/14/15 +f 51/13/24 52/8/24 55/67/24 +f 58/70/2 57/69/2 60/52/2 +f 60/52/25 48/51/25 58/70/25 +f 22/10/26 16/21/26 58/70/26 +f 57/69/27 16/21/27 59/53/27 +f 109/78/71 107/71/71 67/72/71 +f 63/46/1 26/45/1 61/73/1 +f 62/76/1 9/60/1 64/74/1 +f 106/109/1 101/75/1 62/76/1 +f 65/79/1 108/77/1 67/72/1 +f 63/46/29 61/73/29 67/72/29 +f 66/81/30 62/76/30 68/80/30 +f 106/109/72 62/76/72 108/77/72 +f 72/50/32 71/49/32 76/83/32 +f 70/86/15 25/25/15 72/50/15 +f 39/12/15 5/5/15 71/49/15 +f 5/5/15 25/25/15 69/85/15 +f 80/90/15 76/83/15 79/87/15 +f 70/86/17 72/50/17 74/88/17 +f 75/82/2 71/49/2 73/89/2 +f 69/85/33 70/86/33 73/89/33 +f 84/95/3 80/90/3 83/91/3 +f 74/88/15 76/83/15 78/92/15 +f 79/87/15 75/82/15 77/93/15 +f 73/89/15 74/88/15 77/93/15 +f 81/96/15 82/94/15 83/91/15 +f 78/92/2 80/90/2 82/94/2 +f 79/87/17 77/93/17 83/91/17 +f 81/96/24 77/93/24 82/94/24 +f 32/26/13 35/43/13 88/98/13 +f 87/97/12 35/43/12 86/99/12 +f 86/99/11 34/44/11 85/100/11 +f 29/27/10 32/26/10 85/100/10 +f 91/37/34 92/31/34 99/102/34 +f 98/103/35 90/35/35 99/102/35 +f 97/104/36 89/36/36 98/103/36 +f 92/31/37 89/36/37 100/101/37 +f 96/108/13 95/105/13 100/101/13 +f 99/102/12 95/105/12 98/103/12 +f 98/103/11 94/106/11 97/104/11 +f 93/107/10 96/108/10 97/104/10 +f 85/100/38 88/98/38 93/107/38 +f 94/106/39 86/99/39 93/107/39 +f 95/105/40 87/97/40 94/106/40 +f 88/98/41 87/97/41 96/108/41 +f 61/73/73 106/109/73 65/79/73 +f 108/77/1 66/81/1 109/78/1 +f 2/65/1 101/75/1 61/73/1 +f 68/80/74 64/74/74 109/78/74 +f 9/60/23 101/75/23 42/61/23 +f 30/42/1 103/1/1 64/74/1 +f 103/1/1 30/42/1 102/2/1 +f 211/2/44 212/1/44 115/3/44 +f 114/5/2 129/4/2 113/6/2 +f 154/57/2 161/8/2 129/4/2 +f 131/10/24 145/9/24 120/11/24 +f 114/5/2 113/6/2 148/12/2 +f 112/7/45 162/15/45 113/6/45 +f 122/17/46 123/16/46 121/18/46 +f 123/16/47 127/19/47 119/20/47 +f 142/39/24 140/22/24 129/4/24 +f 122/17/48 121/18/48 126/23/48 +f 142/39/45 138/27/45 134/25/45 +f 116/30/45 117/24/45 119/20/45 +f 128/29/75 126/23/75 110/28/75 +f 142/39/45 122/17/45 128/29/45 +f 110/28/76 127/19/76 134/25/76 +f 126/23/50 117/24/50 127/19/50 +f 198/36/51 137/33/51 201/31/51 +f 140/22/24 145/9/24 125/21/24 +f 124/41/24 130/34/24 140/22/24 +f 198/36/52 199/35/52 137/33/52 +f 199/35/53 200/37/53 133/38/53 +f 134/25/45 123/16/45 142/39/45 +f 136/32/45 137/33/45 132/40/45 +f 128/29/24 124/41/24 142/39/24 +f 145/9/24 130/34/24 139/42/24 +f 134/25/45 141/26/45 114/5/45 +f 129/4/45 114/5/45 143/44/45 +f 129/4/45 143/44/45 142/39/45 +f 201/31/54 136/32/54 200/37/54 +f 135/45/44 212/1/44 172/46/44 +f 111/65/14 147/48/14 135/45/14 +f 158/84/15 181/50/15 148/12/15 +f 153/58/26 168/53/26 157/51/26 +f 130/34/17 124/41/17 156/54/17 +f 110/28/17 146/56/17 128/29/17 +f 154/57/2 148/12/2 161/8/2 +f 154/57/2 129/4/2 153/58/2 +f 128/29/18 155/55/18 124/41/18 +f 118/60/19 130/34/19 151/61/19 +f 157/51/20 131/10/20 150/62/20 +f 110/28/21 134/25/21 146/56/21 +f 135/45/14 159/47/14 115/3/14 +f 211/2/22 115/3/22 213/64/22 +f 210/75/23 214/66/23 111/65/23 +f 163/14/2 162/15/2 164/67/2 +f 161/8/14 165/68/14 112/7/14 +f 113/6/15 163/14/15 160/13/15 +f 160/13/3 164/67/3 161/8/3 +f 168/53/2 166/69/2 169/52/2 +f 131/10/25 157/51/25 167/70/25 +f 131/10/16 167/70/16 125/21/16 +f 153/58/27 125/21/27 168/53/27 +f 172/46/77 216/71/77 176/72/77 +f 111/65/44 135/45/44 170/73/44 +f 139/42/44 118/60/44 173/74/44 +f 118/60/44 210/75/44 171/76/44 +f 218/78/44 217/77/44 176/72/44 +f 172/46/56 176/72/56 170/73/56 +f 173/74/57 171/76/57 177/80/57 +f 215/109/78 217/77/78 171/76/78 +f 181/50/33 185/83/33 180/49/33 +f 158/84/15 134/25/15 181/50/15 +f 148/12/15 180/49/15 114/5/15 +f 114/5/15 178/85/15 134/25/15 +f 185/83/15 189/90/15 184/82/15 +f 179/86/17 183/88/17 181/50/17 +f 178/85/2 180/49/2 182/89/2 +f 178/85/32 182/89/32 179/86/32 +f 188/87/24 189/90/24 192/91/24 +f 183/88/15 187/92/15 185/83/15 +f 182/89/15 184/82/15 186/93/15 +f 182/89/15 186/93/15 183/88/15 +f 190/96/15 192/91/15 191/94/15 +f 187/92/2 191/94/2 189/90/2 +f 188/87/17 192/91/17 186/93/17 +f 187/92/3 186/93/3 191/94/3 +f 141/26/54 197/98/54 144/43/54 +f 143/44/53 144/43/53 195/99/53 +f 138/27/52 143/44/52 194/100/52 +f 138/27/51 194/100/51 141/26/51 +f 200/37/59 208/102/59 201/31/59 +f 200/37/60 199/35/60 208/102/60 +f 199/35/61 198/36/61 207/103/61 +f 201/31/79 209/101/79 198/36/79 +f 205/108/54 209/101/54 204/105/54 +f 203/106/53 204/105/53 207/103/53 +f 202/107/52 203/106/52 206/104/52 +f 202/107/51 206/104/51 205/108/51 +f 194/100/63 202/107/63 197/98/63 +f 194/100/64 195/99/64 202/107/64 +f 195/99/65 196/97/65 203/106/65 +f 197/98/66 205/108/66 196/97/66 +f 170/73/80 174/79/80 215/109/80 +f 177/80/44 175/81/44 218/78/44 +f 111/65/44 170/73/44 210/75/44 +f 216/71/81 173/74/81 218/78/81 +f 118/60/23 151/61/23 210/75/23 +f 139/42/44 173/74/44 212/1/44 +f 120/11/44 139/42/44 211/2/44 +usemtl Material.002 +f 41/62/82 104/64/82 102/2/82 +f 211/2/82 213/64/82 150/62/82 +f 11/11/82 41/62/82 102/2/82 +f 120/11/82 211/2/82 150/62/82 diff --git a/models/usemtl-issue-104.obj b/models/usemtl-issue-104.obj new file mode 100644 index 00000000..51836765 --- /dev/null +++ b/models/usemtl-issue-104.obj @@ -0,0 +1,30 @@ +# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project. +# original cornell box data + # comment + +# empty line including some space + + +mtllib cornell_box.mtl + +o floor +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +v 130.0 0.0 65.0 +v 82.0 0.0 225.0 +v 240.0 0.0 272.0 +v 290.0 0.0 114.0 + +v 423.0 0.0 247.0 +v 265.0 0.0 296.0 +v 314.0 0.0 456.0 +v 472.0 0.0 406.0 + +f 1 2 3 4 +f 8 7 6 5 +f 12 11 10 9 + +usemtl white diff --git a/models/usemtl-issue-68.mtl b/models/usemtl-issue-68.mtl new file mode 100644 index 00000000..24a791eb --- /dev/null +++ b/models/usemtl-issue-68.mtl @@ -0,0 +1,9 @@ +newmtl Material.001 +Ka 0 0 0 +Kd 0 0 0 +Ks 0 0 0 + +newmtl Material.003 +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 diff --git a/models/usemtl-issue-68.obj b/models/usemtl-issue-68.obj new file mode 100644 index 00000000..dddc900e --- /dev/null +++ b/models/usemtl-issue-68.obj @@ -0,0 +1,817 @@ +# https://github.com/syoyo/tinyobjloader/issues/68 +# Blender v2.73 (sub 0) OBJ File: 'enemy.blend' +# www.blender.org +mtllib usemtl-issue-68.mtl +o Cube +v 1.864151 -1.219172 -5.532511 +v 0.575869 -0.666304 5.896140 +v 0.940448 1.000000 -1.971128 +v 1.620345 1.000000 -5.815706 +v 1.864152 1.000000 -6.334323 +v 0.575869 -0.129842 5.896143 +v 5.440438 -1.462153 -5.818601 +v 4.896782 -1.462153 -2.744413 +v 1.000825 -0.677484 1.899605 +v 5.440438 -1.246362 -5.818600 +v 1.000825 0.852342 1.899608 +v 4.896782 -1.246362 -2.744412 +v 1.160660 -0.450871 -2.356325 +v 1.704316 -0.450871 -5.430513 +v 1.000825 -0.351920 -1.293797 +v 1.000825 1.000000 -1.293794 +v 1.160660 -0.877888 -2.356326 +v 1.704316 -0.877888 -5.430514 +v 1.000825 -1.219172 -1.452514 +v 1.000825 1.000000 -1.452511 +v 1.000825 -0.351920 1.759410 +v 1.000825 1.000000 1.759413 +v 9.097919 1.221145 -6.212147 +v 8.356775 1.221145 -2.021231 +v 1.864151 -0.109586 -6.334325 +v 0.575869 -0.398073 5.896141 +v 9.097919 0.943958 -6.212148 +v 8.356775 0.943958 -2.021233 +v 1.061916 0.113661 -1.797961 +v 1.000825 0.161258 1.899606 +v 1.000825 0.324040 -1.293795 +v 1.803060 0.113661 -5.988876 +v 1.000825 -0.109586 -1.452513 +v 1.061916 0.776753 -1.797960 +v 1.803061 0.776753 -5.988875 +v 1.000825 0.324040 1.759412 +v 0.000825 -1.219172 -5.532512 +v 0.000825 -0.666304 5.896139 +v 0.000826 1.000000 -6.334325 +v 0.000825 -0.129842 5.896140 +v 0.000825 0.852342 1.899606 +v 0.000825 -0.677484 1.899604 +v 0.000825 -0.351920 -1.293797 +v 0.000825 1.000000 -1.293796 +v 0.000825 1.000000 -1.452513 +v 0.000825 -1.219172 -1.452515 +v 0.000825 -0.351920 1.759409 +v 0.000825 1.000000 1.759411 +v 0.000826 -0.109586 -6.334326 +v 0.000825 -0.398073 5.896140 +v 0.152918 1.000000 -5.815708 +v 0.152917 1.000000 -1.971130 +v 0.940448 1.168419 -1.971128 +v 1.620345 1.168419 -5.815706 +v 0.152918 1.168419 -5.815708 +v 0.152917 1.168419 -1.971130 +v 0.921118 1.091883 -1.050430 +v 0.921118 1.091883 1.516050 +v 0.080533 1.091883 -1.050432 +v 0.080533 1.091883 1.516048 +v 0.613003 -0.553430 5.546911 +v 0.963691 -0.559956 2.248834 +v 0.613003 -0.396857 5.546912 +v 0.963691 -0.070362 2.248835 +v 1.499370 -0.994317 3.966028 +v 1.850058 -0.997914 0.667950 +v 1.499370 -0.908021 3.966029 +v 1.850058 -0.728071 0.667951 +v 1.601022 0.760960 -6.334324 +v 1.601021 0.129454 -6.334325 +v 0.263955 0.760960 -6.334325 +v 0.263955 0.129454 -6.334325 +v 1.334809 0.760960 -7.515329 +v 1.334809 0.129455 -7.515330 +v 0.530168 0.760960 -7.515330 +v 0.530168 0.129455 -7.515330 +v 1.192720 0.649445 -7.515329 +v 1.192720 0.240971 -7.515330 +v 0.672258 0.649445 -7.515330 +v 0.672258 0.240971 -7.515330 +v 1.192719 0.649444 -6.524630 +v 1.192719 0.240970 -6.524631 +v 0.672257 0.649444 -6.524631 +v 0.672257 0.240970 -6.524631 +v 3.851026 0.431116 -1.883326 +v 3.851026 0.946662 -1.883325 +v 4.592170 0.946662 -6.074241 +v 4.592169 0.431116 -6.074242 +v 4.995714 0.561404 -1.918362 +v 4.995714 1.016394 -1.918360 +v 5.736857 1.016394 -6.109276 +v 5.736857 0.561404 -6.109277 +v 3.975454 0.471731 -2.162156 +v 3.975454 0.919244 -2.162155 +v 4.618796 0.919244 -5.800034 +v 4.618795 0.471730 -5.800035 +v 4.969088 0.584825 -2.192568 +v 4.969088 0.979775 -2.192567 +v 5.612430 0.979775 -5.830446 +v 5.612429 0.584825 -5.830447 +v 0.864214 -0.673890 3.184381 +v 0.864213 0.489129 3.184384 +v 0.864213 -0.018552 3.184383 +v 0.000825 0.489129 3.184382 +v 0.000825 -0.673890 3.184381 +v 0.850955 -0.557858 3.309075 +v 0.850955 -0.175321 3.309076 +v 1.737321 -0.996758 1.728192 +v 1.737321 -0.785920 1.728193 +v -1.864151 -1.219172 -5.532511 +v -0.575869 -0.666304 5.896140 +v -0.940448 1.000000 -1.971128 +v -1.620345 1.000000 -5.815706 +v -1.864152 1.000000 -6.334323 +v -0.575869 -0.129842 5.896143 +v -5.440438 -1.462153 -5.818601 +v -4.896782 -1.462153 -2.744413 +v -1.000825 -0.677484 1.899605 +v -5.440438 -1.246362 -5.818600 +v -1.000825 0.852342 1.899608 +v -4.896782 -1.246362 -2.744412 +v -1.160660 -0.450871 -2.356325 +v -1.704316 -0.450871 -5.430513 +v -1.000825 -0.351920 -1.293797 +v -1.000825 1.000000 -1.293794 +v -1.160660 -0.877888 -2.356326 +v -1.704316 -0.877888 -5.430514 +v -1.000825 -1.219172 -1.452514 +v -1.000825 1.000000 -1.452511 +v -1.000825 -0.351920 1.759410 +v -1.000825 1.000000 1.759413 +v -9.097919 1.221145 -6.212147 +v -8.356775 1.221145 -2.021231 +v -1.864151 -0.109586 -6.334325 +v -0.575869 -0.398073 5.896141 +v -9.097919 0.943958 -6.212148 +v -8.356775 0.943958 -2.021233 +v -1.061916 0.113661 -1.797961 +v -1.000825 0.161258 1.899606 +v -1.000825 0.324040 -1.293795 +v -1.803060 0.113661 -5.988876 +v -1.000825 -0.109586 -1.452513 +v -1.061916 0.776753 -1.797960 +v -1.803061 0.776753 -5.988875 +v -1.000825 0.324040 1.759412 +v -0.000825 -1.219172 -5.532512 +v -0.000825 -0.666304 5.896139 +v -0.000826 1.000000 -6.334325 +v -0.000825 -0.129842 5.896140 +v -0.000825 0.852342 1.899606 +v -0.000825 -0.677484 1.899604 +v -0.000825 -0.351920 -1.293797 +v -0.000825 1.000000 -1.293796 +v -0.000825 1.000000 -1.452513 +v -0.000825 -1.219172 -1.452515 +v -0.000825 -0.351920 1.759409 +v -0.000825 1.000000 1.759411 +v -0.000826 -0.109586 -6.334326 +v -0.000825 -0.398073 5.896140 +v -0.152918 1.000000 -5.815708 +v -0.152917 1.000000 -1.971130 +v -0.940448 1.168419 -1.971128 +v -1.620345 1.168419 -5.815706 +v -0.152918 1.168419 -5.815708 +v -0.152917 1.168419 -1.971130 +v -0.921118 1.091883 -1.050430 +v -0.921118 1.091883 1.516050 +v -0.080533 1.091883 -1.050432 +v -0.080533 1.091883 1.516048 +v -0.613003 -0.553430 5.546911 +v -0.963691 -0.559956 2.248834 +v -0.613003 -0.396857 5.546912 +v -0.963691 -0.070362 2.248835 +v -1.499370 -0.994317 3.966028 +v -1.850058 -0.997914 0.667950 +v -1.499370 -0.908021 3.966029 +v -1.850058 -0.728071 0.667951 +v -1.601022 0.760960 -6.334324 +v -1.601021 0.129454 -6.334325 +v -0.263955 0.760960 -6.334325 +v -0.263955 0.129454 -6.334325 +v -1.334809 0.760960 -7.515329 +v -1.334809 0.129455 -7.515330 +v -0.530168 0.760960 -7.515330 +v -0.530168 0.129455 -7.515330 +v -1.192720 0.649445 -7.515329 +v -1.192720 0.240971 -7.515330 +v -0.672258 0.649445 -7.515330 +v -0.672258 0.240971 -7.515330 +v -1.192719 0.649444 -6.524630 +v -1.192719 0.240970 -6.524631 +v -0.672257 0.649444 -6.524631 +v -0.672257 0.240970 -6.524631 +v -3.851026 0.431116 -1.883326 +v -3.851026 0.946662 -1.883325 +v -4.592170 0.946662 -6.074241 +v -4.592169 0.431116 -6.074242 +v -4.995714 0.561404 -1.918362 +v -4.995714 1.016394 -1.918360 +v -5.736857 1.016394 -6.109276 +v -5.736857 0.561404 -6.109277 +v -3.975454 0.471731 -2.162156 +v -3.975454 0.919244 -2.162155 +v -4.618796 0.919244 -5.800034 +v -4.618795 0.471730 -5.800035 +v -4.969088 0.584825 -2.192568 +v -4.969088 0.979775 -2.192567 +v -5.612430 0.979775 -5.830446 +v -5.612429 0.584825 -5.830447 +v -0.864214 -0.673890 3.184381 +v -0.864213 0.489129 3.184384 +v -0.864213 -0.018552 3.184383 +v -0.000825 0.489129 3.184382 +v -0.000825 -0.673890 3.184381 +v -0.850955 -0.557858 3.309075 +v -0.850955 -0.175321 3.309076 +v -1.737321 -0.996758 1.728192 +v -1.737321 -0.785920 1.728193 +vt 0.135351 -0.558072 +vt 0.003035 -0.363507 +vt 0.092282 -0.976844 +vt -0.081322 0.947351 +vt 0.100058 1.958891 +vt 0.050091 1.852185 +vt -0.092752 1.055565 +vt -0.251711 1.059474 +vt 0.075587 0.041384 +vt -0.086008 0.279003 +vt -0.086212 0.249830 +vt -0.276044 1.968137 +vt -0.246101 1.859467 +vt 0.009828 1.911388 +vt -0.133014 1.114769 +vt 0.413322 1.261595 +vt 0.299103 0.624605 +vt 1.243955 0.407183 +vt 0.515404 1.111487 +vt 1.358173 1.044173 +vt -0.081553 0.914324 +vt 0.080042 0.676706 +vt 0.401185 0.474498 +vt 1.295541 0.331328 +vt 0.365315 1.568841 +vt 0.299111 1.575740 +vt 0.143401 0.707357 +vt 0.629403 1.011947 +vt 0.449192 0.167251 +vt 1.409760 0.968317 +vt 0.986264 1.738667 +vt 1.573373 1.877873 +vt 1.417663 1.009490 +vt 0.237182 -0.196235 +vt 0.721785 1.030226 +vt 0.830554 0.870285 +vt 0.877494 1.898608 +vt 1.351399 1.106930 +vt 0.183935 0.557301 +vt 1.507109 1.975312 +vt 0.241636 0.439088 +vt 0.114297 -0.045011 +vt 0.140593 1.808834 +vt -0.015118 0.940452 +vt 0.156405 -1.071134 +vt 0.164119 -0.998223 +vt 0.040336 -1.068281 +vt 0.104459 -1.162571 +vt -0.165787 1.882802 +vt -0.014821 1.660811 +vt -0.287852 0.283965 +vt -0.293374 0.366508 +vt -0.289630 0.900550 +vt 0.035337 -0.191272 +vt 0.247348 0.172213 +vt 0.253300 1.021193 +vt -0.283166 0.952313 +vt -0.283398 0.919286 +vt 0.039792 0.444050 +vt 0.314806 -0.339851 +vt 0.112962 -0.334889 +vt -0.288056 0.254793 +vt -0.023788 -0.973990 +vt -0.155922 -0.359599 +vt 0.220528 -1.165425 +vt 0.108710 -0.748730 +vt -0.286364 1.918670 +vt -0.291973 1.118678 +vt -0.119962 0.896379 +vt -0.123707 0.362337 +vt 0.162891 -0.598569 +vt 0.467532 -0.853353 +vt 0.201549 -1.053262 +vt 0.161663 -0.198915 +vt 0.267667 -0.752638 +vt 0.278705 -0.371021 +vt 0.526390 -0.542053 +vt 0.483821 -0.479457 +vt 0.488162 -0.883689 +vt 0.500110 -0.105561 +vt 0.564618 -0.200418 +vt -0.110331 2.127229 +vt 0.040636 1.905238 +vt -0.010786 1.578087 +vt 0.104092 1.876168 +vt 0.255058 1.654176 +vt -0.054992 2.087323 +vt 0.203048 1.901245 +vt 0.052081 2.123235 +vt 0.042658 1.943733 +vt -0.056437 1.881175 +vt 0.147710 1.941151 +vt 0.050060 2.084741 +vt 0.146264 1.735002 +vt 0.041212 1.737584 +vt 0.048615 1.878591 +vt 0.663065 1.872485 +vt 0.786311 1.691257 +vt 0.507355 1.004102 +vt 0.630601 0.822874 +vt 0.955144 1.689498 +vt 0.860727 1.828333 +vt 0.725565 1.074543 +vt 0.819981 0.935708 +vt 0.674594 1.805657 +vt 0.539432 1.051867 +vt 0.646413 0.894554 +vt 0.781576 1.648344 +vt 0.240127 -0.712141 +vn 0.994400 0.000000 0.105700 +vn 0.000000 1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.984700 0.000000 0.174100 +vn 0.211800 0.976600 0.037500 +vn -0.103300 0.000000 -0.994600 +vn 0.103300 -0.000000 0.994600 +vn 0.911400 0.378700 0.161200 +vn -0.157300 -0.987200 -0.027800 +vn 0.113700 -0.993300 0.020100 +vn 0.030600 -0.000000 0.999500 +vn -0.061100 0.998100 -0.010800 +vn -0.030600 0.000000 -0.999500 +vn -0.000000 -0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn -0.755400 0.655300 0.000000 +vn 0.000000 -1.000000 0.000000 +vn -0.000000 -0.180000 0.983700 +vn 0.000000 -0.395500 -0.918500 +vn -0.000000 0.688500 0.725200 +vn 0.000000 -0.585700 -0.810500 +vn -0.000000 0.974900 0.222500 +vn -0.000000 -1.000000 0.002800 +vn -1.000000 0.000000 -0.000000 +vn -0.000000 0.935500 0.353200 +vn 0.755400 0.655300 0.000000 +vn 0.000000 0.935500 -0.353200 +vn 0.673800 0.724900 0.143400 +vn 0.872300 -0.000000 0.489100 +vn -0.872300 0.000000 -0.489100 +vn -0.518300 -0.853500 -0.054200 +vn -0.975500 0.000000 -0.219900 +vn 0.975500 0.000000 -0.219900 +vn -0.913200 0.000000 -0.407500 +vn -0.436900 0.896200 -0.077300 +vn -0.995300 -0.000000 0.096600 +vn -0.297300 -0.953400 -0.052600 +vn 0.473900 -0.876600 0.083800 +vn 0.913200 0.000000 0.407500 +vn 0.342200 0.937700 0.060500 +vn 0.995300 -0.000000 -0.096600 +vn -0.519200 -0.853000 -0.054300 +vn 0.722400 0.676400 0.143800 +vn -0.994400 0.000000 0.105700 +vn -0.984700 0.000000 0.174100 +vn -0.211800 0.976600 0.037500 +vn 0.103300 0.000000 -0.994600 +vn -0.103300 -0.000000 0.994600 +vn -0.911400 0.378700 0.161200 +vn 0.157300 -0.987200 -0.027800 +vn -0.113700 -0.993300 0.020100 +vn -0.030600 -0.000000 0.999500 +vn 0.061100 0.998100 -0.010800 +vn 0.030600 0.000000 -0.999500 +vn -0.691900 0.713200 0.112500 +vn -0.872300 -0.000000 0.489100 +vn 0.872300 0.000000 -0.489100 +vn 0.518300 -0.853500 -0.054200 +vn 0.913200 0.000000 -0.407500 +vn 0.436900 0.896200 -0.077300 +vn 0.995300 0.000000 0.096600 +vn 0.297300 -0.953300 -0.052600 +vn -0.473900 -0.876600 0.083800 +vn -0.913200 -0.000000 0.407500 +vn -0.342200 0.937700 0.060500 +vn -0.995300 -0.000000 -0.096600 +vn 0.519200 -0.853000 -0.054300 +vn -0.714800 0.690100 0.113700 +vn 0.974400 0.089700 0.206200 +vn 0.870400 0.288400 0.399100 +vn 0.691900 0.713200 0.112500 +vn -0.518000 -0.853700 -0.053400 +vn -0.519700 -0.852700 -0.053600 +vn 0.714800 0.690100 0.113700 +vn -0.974400 0.089700 0.206200 +vn -0.870400 0.288400 0.399100 +vn -0.673800 0.724900 0.143400 +vn 0.518000 -0.853700 -0.053400 +vn 0.297300 -0.953400 -0.052600 +vn 0.519700 -0.852700 -0.053600 +vn -0.722400 0.676400 0.143800 +vn -0.000000 0.962300 0.272000 +usemtl Material.001 +s off +f 103/1/1 102/2/1 6/3/1 +f 20/4/2 5/5/2 4/6/2 +f 20/4/2 3/7/2 52/8/2 +f 36/9/3 22/10/3 11/11/3 +f 39/12/2 51/13/2 4/6/2 +f 4/6/4 54/14/4 53/15/4 +f 14/16/5 13/17/5 12/18/5 +f 18/19/6 14/16/6 10/20/6 +f 20/4/3 16/21/3 31/22/3 +f 17/23/7 8/24/7 12/18/7 +f 25/25/4 32/26/4 29/27/4 +f 10/20/4 12/18/4 8/24/4 +f 1/28/8 18/19/8 17/23/8 +f 19/29/4 17/23/4 13/17/4 +f 25/25/4 14/16/4 18/19/4 +f 18/19/9 7/30/9 8/24/9 +f 92/31/10 27/32/10 28/33/10 +f 16/21/3 22/10/3 36/9/3 +f 31/22/3 36/9/3 21/34/3 +f 90/35/11 89/36/11 28/33/11 +f 91/37/12 90/35/12 24/38/12 +f 33/39/4 13/17/4 14/16/4 +f 23/40/4 24/38/4 28/33/4 +f 33/39/3 31/22/3 15/41/3 +f 21/34/3 36/9/3 30/42/3 +f 5/5/4 35/43/4 32/26/4 +f 5/5/4 20/4/4 34/44/4 +f 33/39/4 29/27/4 34/44/4 +f 91/37/13 23/40/13 27/32/13 +f 103/1/1 26/45/1 63/46/1 +f 26/45/14 50/47/14 38/48/14 +f 39/12/15 71/49/15 72/50/15 +f 48/51/16 60/52/16 59/53/16 +f 15/41/17 21/34/17 47/54/17 +f 19/29/17 46/55/17 37/56/17 +f 39/12/2 45/57/2 52/8/2 +f 20/4/2 45/57/2 44/58/2 +f 19/29/18 15/41/18 43/59/18 +f 9/60/19 42/61/19 47/54/19 +f 22/10/20 48/51/20 41/62/20 +f 25/25/21 1/28/21 37/56/21 +f 6/3/14 40/63/14 50/47/14 +f 104/64/22 40/63/22 6/3/22 +f 2/65/23 38/48/23 105/66/23 +f 55/67/2 56/68/2 53/15/2 +f 3/7/14 53/15/14 56/68/14 +f 51/13/15 55/67/15 54/14/15 +f 52/8/24 56/68/24 55/67/24 +f 57/69/2 59/53/2 60/52/2 +f 48/51/25 22/10/25 58/70/25 +f 16/21/26 57/69/26 58/70/26 +f 16/21/27 44/58/27 59/53/27 +f 107/71/28 63/46/28 67/72/28 +f 26/45/1 2/65/1 61/73/1 +f 9/60/1 30/42/1 64/74/1 +f 101/75/1 9/60/1 62/76/1 +f 108/77/1 109/78/1 67/72/1 +f 61/73/29 65/79/29 67/72/29 +f 62/76/30 64/74/30 68/80/30 +f 62/76/31 66/81/31 108/77/31 +f 71/49/32 75/82/32 76/83/32 +f 25/25/15 49/84/15 72/50/15 +f 5/5/15 69/85/15 71/49/15 +f 25/25/15 70/86/15 69/85/15 +f 76/83/15 75/82/15 79/87/15 +f 72/50/17 76/83/17 74/88/17 +f 71/49/2 69/85/2 73/89/2 +f 70/86/33 74/88/33 73/89/33 +f 80/90/3 79/87/3 83/91/3 +f 76/83/15 80/90/15 78/92/15 +f 75/82/15 73/89/15 77/93/15 +f 74/88/15 78/92/15 77/93/15 +f 82/94/15 84/95/15 83/91/15 +f 80/90/2 84/95/2 82/94/2 +f 77/93/17 81/96/17 83/91/17 +f 77/93/24 78/92/24 82/94/24 +f 35/43/13 87/97/13 88/98/13 +f 35/43/12 34/44/12 86/99/12 +f 34/44/11 29/27/11 85/100/11 +f 32/26/10 88/98/10 85/100/10 +f 92/31/34 100/101/34 99/102/34 +f 90/35/35 91/37/35 99/102/35 +f 89/36/36 90/35/36 98/103/36 +f 89/36/37 97/104/37 100/101/37 +f 95/105/13 99/102/13 100/101/13 +f 95/105/12 94/106/12 98/103/12 +f 94/106/11 93/107/11 97/104/11 +f 96/108/10 100/101/10 97/104/10 +f 88/98/38 96/108/38 93/107/38 +f 86/99/39 85/100/39 93/107/39 +f 87/97/40 86/99/40 94/106/40 +f 87/97/41 95/105/41 96/108/41 +f 106/109/42 108/77/42 65/79/42 +f 66/81/1 68/80/1 109/78/1 +f 101/75/1 106/109/1 61/73/1 +f 64/74/43 107/71/43 109/78/43 +f 101/75/23 105/66/23 42/61/23 +f 103/1/1 107/71/1 64/74/1 +f 30/42/1 11/11/1 102/2/1 +f 212/1/44 135/45/44 115/3/44 +f 129/4/2 112/7/2 113/6/2 +f 161/8/2 112/7/2 129/4/2 +f 145/9/24 139/42/24 120/11/24 +f 113/6/2 160/13/2 148/12/2 +f 162/15/45 163/14/45 113/6/45 +f 123/16/46 119/20/46 121/18/46 +f 127/19/47 116/30/47 119/20/47 +f 140/22/24 125/21/24 129/4/24 +f 121/18/48 117/24/48 126/23/48 +f 138/27/45 141/26/45 134/25/45 +f 117/24/45 121/18/45 119/20/45 +f 126/23/49 127/19/49 110/28/49 +f 122/17/45 126/23/45 128/29/45 +f 127/19/45 123/16/45 134/25/45 +f 117/24/50 116/30/50 127/19/50 +f 137/33/51 136/32/51 201/31/51 +f 145/9/24 131/10/24 125/21/24 +f 130/34/24 145/9/24 140/22/24 +f 199/35/52 133/38/52 137/33/52 +f 200/37/53 132/40/53 133/38/53 +f 123/16/45 122/17/45 142/39/45 +f 137/33/45 133/38/45 132/40/45 +f 124/41/24 140/22/24 142/39/24 +f 130/34/24 118/60/24 139/42/24 +f 141/26/45 144/43/45 114/5/45 +f 114/5/45 144/43/45 143/44/45 +f 143/44/45 138/27/45 142/39/45 +f 136/32/54 132/40/54 200/37/54 +f 212/1/44 216/71/44 172/46/44 +f 147/48/14 159/47/14 135/45/14 +f 181/50/15 180/49/15 148/12/15 +f 168/53/26 169/52/26 157/51/26 +f 124/41/17 152/59/17 156/54/17 +f 146/56/17 155/55/17 128/29/17 +f 148/12/2 160/13/2 161/8/2 +f 129/4/2 125/21/2 153/58/2 +f 155/55/18 152/59/18 124/41/18 +f 130/34/19 156/54/19 151/61/19 +f 131/10/20 120/11/20 150/62/20 +f 134/25/21 158/84/21 146/56/21 +f 159/47/14 149/63/14 115/3/14 +f 115/3/22 149/63/22 213/64/22 +f 214/66/23 147/48/23 111/65/23 +f 162/15/2 165/68/2 164/67/2 +f 165/68/14 162/15/14 112/7/14 +f 163/14/15 164/67/15 160/13/15 +f 164/67/3 165/68/3 161/8/3 +f 166/69/2 167/70/2 169/52/2 +f 157/51/25 169/52/25 167/70/25 +f 167/70/16 166/69/16 125/21/16 +f 125/21/27 166/69/27 168/53/27 +f 216/71/55 218/78/55 176/72/55 +f 135/45/44 172/46/44 170/73/44 +f 118/60/44 171/76/44 173/74/44 +f 210/75/44 215/109/44 171/76/44 +f 217/77/44 174/79/44 176/72/44 +f 176/72/56 174/79/56 170/73/56 +f 171/76/57 175/81/57 177/80/57 +f 217/77/58 175/81/58 171/76/58 +f 185/83/33 184/82/33 180/49/33 +f 134/25/15 179/86/15 181/50/15 +f 180/49/15 178/85/15 114/5/15 +f 178/85/15 179/86/15 134/25/15 +f 189/90/15 188/87/15 184/82/15 +f 183/88/17 185/83/17 181/50/17 +f 180/49/2 184/82/2 182/89/2 +f 182/89/32 183/88/32 179/86/32 +f 189/90/24 193/95/24 192/91/24 +f 187/92/15 189/90/15 185/83/15 +f 184/82/15 188/87/15 186/93/15 +f 186/93/15 187/92/15 183/88/15 +f 192/91/15 193/95/15 191/94/15 +f 191/94/2 193/95/2 189/90/2 +f 192/91/17 190/96/17 186/93/17 +f 186/93/3 190/96/3 191/94/3 +f 197/98/54 196/97/54 144/43/54 +f 144/43/53 196/97/53 195/99/53 +f 143/44/52 195/99/52 194/100/52 +f 194/100/51 197/98/51 141/26/51 +f 208/102/59 209/101/59 201/31/59 +f 199/35/60 207/103/60 208/102/60 +f 198/36/61 206/104/61 207/103/61 +f 209/101/62 206/104/62 198/36/62 +f 209/101/54 208/102/54 204/105/54 +f 204/105/53 208/102/53 207/103/53 +f 203/106/52 207/103/52 206/104/52 +f 206/104/51 209/101/51 205/108/51 +f 202/107/63 205/108/63 197/98/63 +f 195/99/64 203/106/64 202/107/64 +f 196/97/65 204/105/65 203/106/65 +f 205/108/66 204/105/66 196/97/66 +f 174/79/67 217/77/67 215/109/67 +f 175/81/44 217/77/44 218/78/44 +f 170/73/44 215/109/44 210/75/44 +f 173/74/68 177/80/68 218/78/68 +f 151/61/23 214/66/23 210/75/23 +f 173/74/44 216/71/44 212/1/44 +f 139/42/44 212/1/44 211/2/44 +f 26/45/1 103/1/1 6/3/1 +f 3/7/2 20/4/2 4/6/2 +f 45/57/2 20/4/2 52/8/2 +f 30/42/3 36/9/3 11/11/3 +f 5/5/2 39/12/2 4/6/2 +f 3/7/4 4/6/4 53/15/4 +f 10/20/5 14/16/5 12/18/5 +f 7/30/6 18/19/6 10/20/6 +f 33/39/3 20/4/3 31/22/3 +f 13/17/7 17/23/7 12/18/7 +f 33/39/4 25/25/4 29/27/4 +f 7/30/4 10/20/4 8/24/4 +f 19/29/69 1/28/69 17/23/69 +f 33/39/4 19/29/4 13/17/4 +f 1/28/70 25/25/70 18/19/70 +f 17/23/9 18/19/9 8/24/9 +f 89/36/10 92/31/10 28/33/10 +f 31/22/3 16/21/3 36/9/3 +f 15/41/3 31/22/3 21/34/3 +f 24/38/11 90/35/11 28/33/11 +f 23/40/12 91/37/12 24/38/12 +f 25/25/4 33/39/4 14/16/4 +f 27/32/4 23/40/4 28/33/4 +f 19/29/3 33/39/3 15/41/3 +f 9/60/3 21/34/3 30/42/3 +f 25/25/4 5/5/4 32/26/4 +f 35/43/4 5/5/4 34/44/4 +f 20/4/4 33/39/4 34/44/4 +f 92/31/13 91/37/13 27/32/13 +f 107/71/1 103/1/1 63/46/1 +f 2/65/14 26/45/14 38/48/14 +f 49/84/15 39/12/15 72/50/15 +f 44/58/16 48/51/16 59/53/16 +f 43/59/17 15/41/17 47/54/17 +f 1/28/17 19/29/17 37/56/17 +f 51/13/2 39/12/2 52/8/2 +f 16/21/2 20/4/2 44/58/2 +f 46/55/18 19/29/18 43/59/18 +f 21/34/19 9/60/19 47/54/19 +f 11/11/20 22/10/20 41/62/20 +f 49/84/21 25/25/21 37/56/21 +f 26/45/14 6/3/14 50/47/14 +f 102/2/22 104/64/22 6/3/22 +f 101/75/23 2/65/23 105/66/23 +f 54/14/2 55/67/2 53/15/2 +f 52/8/14 3/7/14 56/68/14 +f 4/6/15 51/13/15 54/14/15 +f 51/13/24 52/8/24 55/67/24 +f 58/70/2 57/69/2 60/52/2 +f 60/52/25 48/51/25 58/70/25 +f 22/10/26 16/21/26 58/70/26 +f 57/69/27 16/21/27 59/53/27 +f 109/78/71 107/71/71 67/72/71 +f 63/46/1 26/45/1 61/73/1 +f 62/76/1 9/60/1 64/74/1 +f 106/109/1 101/75/1 62/76/1 +f 65/79/1 108/77/1 67/72/1 +f 63/46/29 61/73/29 67/72/29 +f 66/81/30 62/76/30 68/80/30 +f 106/109/72 62/76/72 108/77/72 +f 72/50/32 71/49/32 76/83/32 +f 70/86/15 25/25/15 72/50/15 +f 39/12/15 5/5/15 71/49/15 +f 5/5/15 25/25/15 69/85/15 +f 80/90/15 76/83/15 79/87/15 +f 70/86/17 72/50/17 74/88/17 +f 75/82/2 71/49/2 73/89/2 +f 69/85/33 70/86/33 73/89/33 +f 84/95/3 80/90/3 83/91/3 +f 74/88/15 76/83/15 78/92/15 +f 79/87/15 75/82/15 77/93/15 +f 73/89/15 74/88/15 77/93/15 +f 81/96/15 82/94/15 83/91/15 +f 78/92/2 80/90/2 82/94/2 +f 79/87/17 77/93/17 83/91/17 +f 81/96/24 77/93/24 82/94/24 +f 32/26/13 35/43/13 88/98/13 +f 87/97/12 35/43/12 86/99/12 +f 86/99/11 34/44/11 85/100/11 +f 29/27/10 32/26/10 85/100/10 +f 91/37/34 92/31/34 99/102/34 +f 98/103/35 90/35/35 99/102/35 +f 97/104/36 89/36/36 98/103/36 +f 92/31/37 89/36/37 100/101/37 +f 96/108/13 95/105/13 100/101/13 +f 99/102/12 95/105/12 98/103/12 +f 98/103/11 94/106/11 97/104/11 +f 93/107/10 96/108/10 97/104/10 +f 85/100/38 88/98/38 93/107/38 +f 94/106/39 86/99/39 93/107/39 +f 95/105/40 87/97/40 94/106/40 +f 88/98/41 87/97/41 96/108/41 +f 61/73/73 106/109/73 65/79/73 +f 108/77/1 66/81/1 109/78/1 +f 2/65/1 101/75/1 61/73/1 +f 68/80/74 64/74/74 109/78/74 +f 9/60/23 101/75/23 42/61/23 +f 30/42/1 103/1/1 64/74/1 +f 103/1/1 30/42/1 102/2/1 +f 211/2/44 212/1/44 115/3/44 +f 114/5/2 129/4/2 113/6/2 +f 154/57/2 161/8/2 129/4/2 +f 131/10/24 145/9/24 120/11/24 +f 114/5/2 113/6/2 148/12/2 +f 112/7/45 162/15/45 113/6/45 +f 122/17/46 123/16/46 121/18/46 +f 123/16/47 127/19/47 119/20/47 +f 142/39/24 140/22/24 129/4/24 +f 122/17/48 121/18/48 126/23/48 +f 142/39/45 138/27/45 134/25/45 +f 116/30/45 117/24/45 119/20/45 +f 128/29/75 126/23/75 110/28/75 +f 142/39/45 122/17/45 128/29/45 +f 110/28/76 127/19/76 134/25/76 +f 126/23/50 117/24/50 127/19/50 +f 198/36/51 137/33/51 201/31/51 +f 140/22/24 145/9/24 125/21/24 +f 124/41/24 130/34/24 140/22/24 +f 198/36/52 199/35/52 137/33/52 +f 199/35/53 200/37/53 133/38/53 +f 134/25/45 123/16/45 142/39/45 +f 136/32/45 137/33/45 132/40/45 +f 128/29/24 124/41/24 142/39/24 +f 145/9/24 130/34/24 139/42/24 +f 134/25/45 141/26/45 114/5/45 +f 129/4/45 114/5/45 143/44/45 +f 129/4/45 143/44/45 142/39/45 +f 201/31/54 136/32/54 200/37/54 +f 135/45/44 212/1/44 172/46/44 +f 111/65/14 147/48/14 135/45/14 +f 158/84/15 181/50/15 148/12/15 +f 153/58/26 168/53/26 157/51/26 +f 130/34/17 124/41/17 156/54/17 +f 110/28/17 146/56/17 128/29/17 +f 154/57/2 148/12/2 161/8/2 +f 154/57/2 129/4/2 153/58/2 +f 128/29/18 155/55/18 124/41/18 +f 118/60/19 130/34/19 151/61/19 +f 157/51/20 131/10/20 150/62/20 +f 110/28/21 134/25/21 146/56/21 +f 135/45/14 159/47/14 115/3/14 +f 211/2/22 115/3/22 213/64/22 +f 210/75/23 214/66/23 111/65/23 +f 163/14/2 162/15/2 164/67/2 +f 161/8/14 165/68/14 112/7/14 +f 113/6/15 163/14/15 160/13/15 +f 160/13/3 164/67/3 161/8/3 +f 168/53/2 166/69/2 169/52/2 +f 131/10/25 157/51/25 167/70/25 +f 131/10/16 167/70/16 125/21/16 +f 153/58/27 125/21/27 168/53/27 +f 172/46/77 216/71/77 176/72/77 +f 111/65/44 135/45/44 170/73/44 +f 139/42/44 118/60/44 173/74/44 +f 118/60/44 210/75/44 171/76/44 +f 218/78/44 217/77/44 176/72/44 +f 172/46/56 176/72/56 170/73/56 +f 173/74/57 171/76/57 177/80/57 +f 215/109/78 217/77/78 171/76/78 +f 181/50/33 185/83/33 180/49/33 +f 158/84/15 134/25/15 181/50/15 +f 148/12/15 180/49/15 114/5/15 +f 114/5/15 178/85/15 134/25/15 +f 185/83/15 189/90/15 184/82/15 +f 179/86/17 183/88/17 181/50/17 +f 178/85/2 180/49/2 182/89/2 +f 178/85/32 182/89/32 179/86/32 +f 188/87/24 189/90/24 192/91/24 +f 183/88/15 187/92/15 185/83/15 +f 182/89/15 184/82/15 186/93/15 +f 182/89/15 186/93/15 183/88/15 +f 190/96/15 192/91/15 191/94/15 +f 187/92/2 191/94/2 189/90/2 +f 188/87/17 192/91/17 186/93/17 +f 187/92/3 186/93/3 191/94/3 +f 141/26/54 197/98/54 144/43/54 +f 143/44/53 144/43/53 195/99/53 +f 138/27/52 143/44/52 194/100/52 +f 138/27/51 194/100/51 141/26/51 +f 200/37/59 208/102/59 201/31/59 +f 200/37/60 199/35/60 208/102/60 +f 199/35/61 198/36/61 207/103/61 +f 201/31/79 209/101/79 198/36/79 +f 205/108/54 209/101/54 204/105/54 +f 203/106/53 204/105/53 207/103/53 +f 202/107/52 203/106/52 206/104/52 +f 202/107/51 206/104/51 205/108/51 +f 194/100/63 202/107/63 197/98/63 +f 194/100/64 195/99/64 202/107/64 +f 195/99/65 196/97/65 203/106/65 +f 197/98/66 205/108/66 196/97/66 +f 170/73/80 174/79/80 215/109/80 +f 177/80/44 175/81/44 218/78/44 +f 111/65/44 170/73/44 210/75/44 +f 216/71/81 173/74/81 218/78/81 +f 118/60/23 151/61/23 210/75/23 +f 139/42/44 173/74/44 212/1/44 +f 120/11/44 139/42/44 211/2/44 +usemtl Material.003 +f 41/62/82 104/64/82 102/2/82 +f 211/2/82 213/64/82 150/62/82 +f 11/11/82 41/62/82 102/2/82 +f 120/11/82 211/2/82 150/62/82 diff --git a/pbr-mtl.md b/pbr-mtl.md new file mode 100644 index 00000000..b5856216 --- /dev/null +++ b/pbr-mtl.md @@ -0,0 +1,29 @@ +## PBR material extension. + +The spec can be found in either + +https://benhouston3d.com/blog/extended-wavefront-obj-mtl-for-pbr/ + +or Internet Archive: https://web.archive.org/web/20230210121526/http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + +* Kd/map_Kd (base/diffuse) // reuse +* Ks/map_Ks (specular) // reuse +* d or Tr (opacity) // reuse +* map_d/map_Tr (opacitymap) // reuse +* Tf (translucency) // reuse +* bump/-bm (bump map) // reuse +* disp (displacement map) // reuse + +PBR material parameters as defined by the Disney PBR. + +* Pr/map_Pr (roughness) // new +* Pm/map_Pm (metallic) // new +* Ps/map_Ps (sheen) // new +* Pc (clearcoat thickness) // new +* Pcr (clearcoat roughness) // new +* Ke/map_Ke (emissive) // new +* aniso (anisotropy) // new +* anisor (anisotropy rotation) // new +* norm (normal map) // new + +EoL. diff --git a/premake4.lua b/premake4.lua index ad020a66..cd5b2952 100644 --- a/premake4.lua +++ b/premake4.lua @@ -1,9 +1,5 @@ -lib_sources = { - "tiny_obj_loader.cc" -} - sources = { - "test.cc", + "loader_example.cc", } -- premake4.lua @@ -20,14 +16,14 @@ solution "TinyObjLoaderSolution" project "tinyobjloader" kind "ConsoleApp" language "C++" - files { lib_sources, sources } + files { sources } configuration "Debug" defines { "DEBUG" } -- -DDEBUG flags { "Symbols" } - targetname "test_tinyobjloader_debug" + targetname "loader_example_debug" configuration "Release" -- defines { "NDEBUG" } -- -NDEBUG flags { "Symbols", "Optimize" } - targetname "test_tinyobjloader" + targetname "loader_example" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d3ba7cf3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] + +requires = [ + # NOTE: setuptools_scm>=8 is not supported in py3.6 cibuildwheel env. + # so use older setuptools_scm for a while + #"setuptools>=64", + #"setuptools_scm>=8", + "setuptools>=45", + "setuptools_scm[toml]<8", + "wheel", + "pybind11>=2.10.0", +] +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 140 + +[project] +name = "tinyobjloader" + +# version: Use setuptools_scm +dynamic = ["version", "classifiers", "authors", "description"] + + +readme = {file = "README.md", content-type = "text/markdown"} + +# Project URLs in pyproject.toml is not mature. +# so write it to setup.py +# https://github.com/pypa/packaging-problems/issues/606 +# +# [project.urils] + + +[tool.setuptools_scm] +# setuptools_scm>=8 +#version_file = "python/_version.py" + +# setuptools_scm<8 +write_to = "python/_version.py" diff --git a/python/LICENSE b/python/LICENSE new file mode 100644 index 00000000..3af18aba --- /dev/null +++ b/python/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2012-2019 Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/python/Makefile b/python/Makefile new file mode 100644 index 00000000..ede9c2d2 --- /dev/null +++ b/python/Makefile @@ -0,0 +1,7 @@ +all: + cd .. && python -m pip install . + +t: + python sample.py + +.PHONY: t diff --git a/python/README.md b/python/README.md new file mode 100644 index 00000000..8f9aa5d2 --- /dev/null +++ b/python/README.md @@ -0,0 +1,87 @@ +# tinyobjloader, Wavefront .obj loader + +`tinyobjloader` is a python wrapper for C++ wavefront .obj loader. +`tinyobjloader` is rather fast and feature rich than other pure python version of .obj loader. + +## Requirements + +* python 3.6+ + +## Install + +You can install `tinyobjloader` with pip. + +``` +$ pip install tinyobjloader +``` + +## Quick tutorial + +```py +import sys +import tinyobjloader + +# Create reader. +reader = tinyobjloader.ObjReader() + +filename = "cornellbox.obj" + +# Load .obj(and .mtl) using default configuration +ret = reader.ParseFromFile(filename) + +if ret == False: + print("Warn:", reader.Warning()) + pint("Err:", reader.Error()) + print("Failed to load : ", filename) + + sys.exit(-1) + +if reader.Warning(): + print("Warn:", reader.Warning()) + +attrib = reader.GetAttrib() +print("attrib.vertices = ", len(attrib.vertices)) +print("attrib.normals = ", len(attrib.normals)) +print("attrib.texcoords = ", len(attrib.texcoords)) + +materials = reader.GetMaterials() +print("Num materials: ", len(materials)) +for m in materials: + print(m.name) + print(m.diffuse) + +shapes = reader.GetShapes() +print("Num shapes: ", len(shapes)) +for shape in shapes: + print(shape.name) + print("num_indices = {}".format(len(shape.mesh.indices))) + +``` + +## More detailed usage + +Please take a look at `python/sample.py` file in tinyobjloader git repo. + +https://github.com/syoyo/tinyobjloader/blob/master/python/sample.py + +## How to build + +Using `cibuildwheel` is a recommended way to build a python module. +See $tinyobjloader/azure-pipelines.yml for details. + +### Developer build + +Assume pip is installed. + +``` +$ git clone https://github.com/tinyobjloader/tinyobjloader +$ cd tinyobjloader +$ python -m pip install . +``` + +## License + +MIT(tinyobjloader) and ISC(mapbox earcut) license. + +## TODO + * [ ] Writer saver diff --git a/python/bindings.cc b/python/bindings.cc new file mode 100644 index 00000000..e7e6c951 --- /dev/null +++ b/python/bindings.cc @@ -0,0 +1,248 @@ +#include +#include +#include +#include + +// Use double precision for better python integration. +#define TINYOBJLOADER_USE_DOUBLE + +// define some helper functions for pybind11 +#define TINY_OBJ_LOADER_PYTHON_BINDING +#include "../tiny_obj_loader.h" + +namespace py = pybind11; + +using namespace tinyobj; + +PYBIND11_MODULE(tinyobjloader, tobj_module) +{ + tobj_module.doc() = "Python bindings for TinyObjLoader."; + + // register struct + py::class_(tobj_module, "ObjReaderConfig") + .def(py::init<>()) + .def_readwrite("triangulate", &ObjReaderConfig::triangulate); + + // py::init<>() for default constructor + py::class_(tobj_module, "ObjReader") + .def(py::init<>()) + .def("ParseFromFile", &ObjReader::ParseFromFile, py::arg("filename"), py::arg("option") = ObjReaderConfig()) + .def("ParseFromString", &ObjReader::ParseFromString, py::arg("obj_text"), py::arg("mtl_text"), py::arg("option") = ObjReaderConfig()) + .def("Valid", &ObjReader::Valid) + .def("GetAttrib", &ObjReader::GetAttrib) + .def("GetShapes", &ObjReader::GetShapes) + .def("GetMaterials", &ObjReader::GetMaterials) + .def("Warning", &ObjReader::Warning) + .def("Error", &ObjReader::Error); + + py::class_(tobj_module, "attrib_t") + .def(py::init<>()) + .def_readonly("vertices", &attrib_t::vertices) + .def_readonly("vertex_weights", &attrib_t::vertex_weights) + .def_readonly("skin_weights", &attrib_t::skin_weights) + .def_readonly("normals", &attrib_t::normals) + .def_readonly("texcoords", &attrib_t::texcoords) + .def_readonly("colors", &attrib_t::colors) + .def("numpy_vertices", [] (attrib_t &instance) { + auto ret = py::array_t(instance.vertices.size()); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.vertices.data(), instance.vertices.size() * sizeof(real_t)); + return ret; + }) + .def("numpy_vertex_weights", [] (attrib_t &instance) { + auto ret = py::array_t(instance.vertex_weights.size()); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.vertex_weights.data(), instance.vertex_weights.size() * sizeof(real_t)); + return ret; + }) + .def("numpy_normals", [] (attrib_t &instance) { + auto ret = py::array_t(instance.normals.size()); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.normals.data(), instance.normals.size() * sizeof(real_t)); + return ret; + }) + .def("numpy_texcoords", [] (attrib_t &instance) { + auto ret = py::array_t(instance.texcoords.size()); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.texcoords.data(), instance.texcoords.size() * sizeof(real_t)); + return ret; + }) + .def("numpy_colors", [] (attrib_t &instance) { + auto ret = py::array_t(instance.colors.size()); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.colors.data(), instance.colors.size() * sizeof(real_t)); + return ret; + }) + ; + + py::class_(tobj_module, "shape_t") + .def(py::init<>()) + .def_readwrite("name", &shape_t::name) + .def_readwrite("mesh", &shape_t::mesh) + .def_readwrite("lines", &shape_t::lines) + .def_readwrite("points", &shape_t::points); + + py::class_(tobj_module, "index_t") + .def(py::init<>()) + .def_readwrite("vertex_index", &index_t::vertex_index) + .def_readwrite("normal_index", &index_t::normal_index) + .def_readwrite("texcoord_index", &index_t::texcoord_index) + ; + + // NOTE(syoyo): It looks it is rather difficult to expose assignment by array index to + // python world for array variable. + // For example following python scripting does not work well. + // + // print(mat.diffuse) + // >>> [0.1, 0.2, 0.3] + // mat.diffuse[1] = 1.0 + // print(mat.diffuse) + // >>> [0.1, 0.2, 0.3] # No modification + // + // https://github.com/pybind/pybind11/issues/1134 + // + // so, we need to update array variable like this: + // + // diffuse = mat.diffuse + // diffuse[1] = 1.0 + // mat.diffuse = diffuse + // + py::class_(tobj_module, "material_t") + .def(py::init<>()) + .def_readwrite("name", &material_t::name) + .def_property("ambient", &material_t::GetAmbient, &material_t::SetAmbient) + .def_property("diffuse", &material_t::GetDiffuse, &material_t::SetDiffuse) + .def_property("specular", &material_t::GetSpecular, &material_t::SetSpecular) + .def_property("transmittance", &material_t::GetTransmittance, &material_t::SetTransmittance) + .def_readwrite("shininess", &material_t::shininess) + .def_readwrite("ior", &material_t::ior) + .def_readwrite("dissolve", &material_t::dissolve) + .def_readwrite("illum", &material_t::illum) + .def_readwrite("ambient_texname", &material_t::ambient_texname) + .def_readwrite("diffuse_texname", &material_t::diffuse_texname) + .def_readwrite("specular_texname", &material_t::specular_texname) + .def_readwrite("specular_highlight_texname", &material_t::specular_highlight_texname) + .def_readwrite("bump_texname", &material_t::bump_texname) + .def_readwrite("displacement_texname", &material_t::displacement_texname) + .def_readwrite("alpha_texname", &material_t::alpha_texname) + .def_readwrite("reflection_texname", &material_t::reflection_texname) + // TODO(syoyo): Expose texture parameter + // PBR + .def_readwrite("roughness", &material_t::roughness) + .def_readwrite("metallic", &material_t::metallic) + .def_readwrite("sheen", &material_t::sheen) + .def_readwrite("clearcoat_thickness", &material_t::clearcoat_thickness) + .def_readwrite("clearcoat_roughness", &material_t::clearcoat_roughness) + .def_readwrite("anisotropy", &material_t::anisotropy) + .def_readwrite("anisotropy_rotation", &material_t::anisotropy_rotation) + + .def_readwrite("roughness_texname", &material_t::roughness_texname) + .def_readwrite("metallic_texname", &material_t::metallic_texname) + .def_readwrite("sheen_texname", &material_t::sheen_texname) + .def_readwrite("emissive_texname", &material_t::emissive_texname) + .def_readwrite("normal_texname", &material_t::normal_texname) + + .def("GetCustomParameter", &material_t::GetCustomParameter) + ; + + py::class_(tobj_module, "mesh_t", py::buffer_protocol()) + .def(py::init<>()) + .def_readonly("num_face_vertices", &mesh_t::num_face_vertices) + .def("numpy_num_face_vertices", [] (mesh_t &instance) { + auto ret = py::array_t(instance.num_face_vertices.size()); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.num_face_vertices.data(), instance.num_face_vertices.size() * sizeof(unsigned char)); + return ret; + }) + .def("vertex_indices", [](mesh_t &self) { + // NOTE: we cannot use py::buffer_info and py:buffer as a return type. + // py::memoriview is not suited for vertex indices usecase, since indices data may be used after + // deleting C++ mesh_t object in Python world. + // + // So create a dedicated Python object(std::vector) + + std::vector indices; + indices.resize(self.indices.size()); + for (size_t i = 0; i < self.indices.size(); i++) { + indices[i] = self.indices[i].vertex_index; + } + + return indices; + }) + .def("normal_indices", [](mesh_t &self) { + + std::vector indices; + indices.resize(self.indices.size()); + for (size_t i = 0; i < self.indices.size(); i++) { + indices[i] = self.indices[i].normal_index; + } + + return indices; + }) + .def("texcoord_indices", [](mesh_t &self) { + + std::vector indices; + indices.resize(self.indices.size()); + for (size_t i = 0; i < self.indices.size(); i++) { + indices[i] = self.indices[i].texcoord_index; + } + + return indices; + }) + .def_readonly("indices", &mesh_t::indices) + .def("numpy_indices", [] (mesh_t &instance) { + // Flatten indexes. index_t is composed of 3 ints(vertex_index, normal_index, texcoord_index). + // numpy_indices = [0, -1, -1, 1, -1, -1, ...] + // C++11 or later should pack POD struct tightly and does not reorder variables, + // so we can memcpy to copy data. + // Still, we check the size of struct and byte offsets of each variable just for sure. + static_assert(sizeof(index_t) == 12, "sizeof(index_t) must be 12"); + static_assert(offsetof(index_t, vertex_index) == 0, "offsetof(index_t, vertex_index) must be 0"); + static_assert(offsetof(index_t, normal_index) == 4, "offsetof(index_t, normal_index) must be 4"); + static_assert(offsetof(index_t, texcoord_index) == 8, "offsetof(index_t, texcoord_index) must be 8"); + + auto ret = py::array_t(instance.indices.size() * 3); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.indices.data(), instance.indices.size() * 3 * sizeof(int)); + return ret; + }) + .def_readonly("material_ids", &mesh_t::material_ids) + .def("numpy_material_ids", [] (mesh_t &instance) { + auto ret = py::array_t(instance.material_ids.size()); + py::buffer_info buf = ret.request(); + memcpy(buf.ptr, instance.material_ids.data(), instance.material_ids.size() * sizeof(int)); + return ret; + }); + + py::class_(tobj_module, "lines_t") + .def(py::init<>()) + .def_readonly("indices", &lines_t::indices) + .def_readonly("num_line_vertices", &lines_t::num_line_vertices) + ; + + py::class_(tobj_module, "points_t") + .def(py::init<>()) + .def_readonly("indices", &points_t::indices) + ; + + py::class_(tobj_module, "joint_and_weight_t") + .def(py::init<>()) + .def_readonly("joint_id", &joint_and_weight_t::joint_id, "Joint index(NOTE: Joint info is provided externally, not from .obj") + .def_readonly("weight", &joint_and_weight_t::weight, "Weight value(NOTE: weight is not normalized)") + ; + + py::class_(tobj_module, "skin_weight_t") + .def(py::init<>()) + .def_readonly("vertex_id", &skin_weight_t::vertex_id) + .def_readonly("weightValues", &skin_weight_t::weightValues) + ; + + py::class_(tobj_module, "tag_t") + .def(py::init<>()) + .def_readonly("name", &tag_t::name) + .def_readonly("intValues", &tag_t::intValues) + .def_readonly("floatValues", &tag_t::floatValues) + .def_readonly("stringValues", &tag_t::stringValues) + ; +} + diff --git a/python/sample.py b/python/sample.py new file mode 100644 index 00000000..45c97612 --- /dev/null +++ b/python/sample.py @@ -0,0 +1,138 @@ +import sys +import tinyobjloader + +is_numpy_available = False +try: + import numpy + + is_numpy_available = True +except: + print( + "NumPy not installed. Do not use numpy_*** API. If you encounter slow performance, see a performance tips for non-numpy API https://github.com/tinyobjloader/tinyobjloader/issues/275" + ) + +filename = "../models/cornell_box.obj" + +if len(sys.argv) > 1: + filename = sys.argv[1] + + +reader = tinyobjloader.ObjReader() + +# Load .obj(and .mtl) using default configuration +ret = reader.ParseFromFile(filename) + +# Optionally you can set custom `config` +# config = tinyobj.ObjReaderConfig() +# config.triangulate = False +# ret = reader.ParseFromFile(filename, config) + +if ret == False: + print("Failed to load : ", filename) + print("Warn:", reader.Warning()) + print("Err:", reader.Error()) + sys.exit(-1) + +if reader.Warning(): + print("Warn:", reader.Warning()) + +attrib = reader.GetAttrib() +print("len(attrib.vertices) = ", len(attrib.vertices)) +print("len(attrib.vertex_weights) = ", len(attrib.vertex_weights)) +print("len(attrib.normals) = ", len(attrib.normals)) +print("len(attrib.texcoords) = ", len(attrib.texcoords)) +print("len(attrib.colors) = ", len(attrib.colors)) +print("len(attrib.skin_weights) = ", len(attrib.skin_weights)) + +# vertex data must be `xyzxyzxyz...` +assert len(attrib.vertices) % 3 == 0 + +# normal data must be `xyzxyzxyz...` +assert len(attrib.normals) % 3 == 0 + +# texcoords data must be `uvuvuv...` +assert len(attrib.texcoords) % 2 == 0 + +# colors data must be `rgbrgbrgb...` +assert len(attrib.texcoords) % 3 == 0 + +# Performance note +# (direct?) array access through member variable is quite slow. +# https://github.com/tinyobjloader/tinyobjloader/issues/275#issuecomment-753465833 +# +# We encourage first copy(?) varible to Python world: +# +# vertices = attrib.vertices +# +# for i in range(...) +# v = vertices[i] +# +# Or please consider using numpy_*** interface(e.g. numpy_vertices()) + +for i, v in enumerate(attrib.vertices): + print("v[{}] = {}".format(i, v)) + +# vw is filled with 1.0 if [w] component is not present in `v` line in .obj +for i, w in enumerate(attrib.vertex_weights): + print("vweight[{}] = {}".format(i, w)) + +for i, v in enumerate(attrib.normals): + print("vn[{}] = {}".format(i, v)) + +for i, v in enumerate(attrib.texcoords): + print("vt[{}] = {}".format(i, v)) + +for i, v in enumerate(attrib.colors): + print("vcol[{}] = {}".format(i, v)) + +if len(attrib.skin_weights): + print("num skin weights", len(attrib.skin_weights)) + + for i, skin in enumerate(attrib.skin_weights): + print("skin_weight[{}]".format(i)) + print(" vertex_id = ", skin.vertex_id) + print(" len(weights) = ", len(skin.weightValues)) + for k, w in enumerate(skin.weightValues): + print(" [{}] joint_id: {}, weight: {}".format(k, w.joint_id, w.weight)) + +if is_numpy_available: + print("numpy_v = {}".format(attrib.numpy_vertices())) + print("numpy_vn = {}".format(attrib.numpy_normals())) + print("numpy_vt = {}".format(attrib.numpy_texcoords())) + print("numpy_vcol = {}".format(attrib.numpy_colors())) + +materials = reader.GetMaterials() +print("Num materials: ", len(materials)) +for m in materials: + print(m.name) + print(m.diffuse) + print(m.diffuse_texname) + # Partial update(array indexing) does not work + # m.diffuse[1] = 1.0 + + # Update with full object assignment works + m.diffuse = [1, 2, 3] + print(m.diffuse) + + # print(m.shininess) + # print(m.illum) + +shapes = reader.GetShapes() +print("Num shapes: ", len(shapes)) +for shape in shapes: + print(shape.name) + print("len(num_indices) = {}".format(len(shape.mesh.indices))) + for i, idx in enumerate(shape.mesh.indices): + print("[{}] v_idx {}".format(i, idx.vertex_index)) + print("[{}] vn_idx {}".format(i, idx.normal_index)) + print("[{}] vt_idx {}".format(i, idx.texcoord_index)) + print("material_ids = {}".format(shape.mesh.material_ids)) + + # faster access to indices + a = shape.mesh.vertex_indices() + print("vertex_indices", shape.mesh.vertex_indices()) + + if is_numpy_available: + print("numpy_indices = {}".format(shape.mesh.numpy_indices())) + print("numpy_num_face_vertices = {}".format(shape.mesh.numpy_num_face_vertices())) + print("numpy_material_ids = {}".format(shape.mesh.numpy_material_ids())) diff --git a/python/tiny_obj_loader.cc b/python/tiny_obj_loader.cc new file mode 100644 index 00000000..821542e7 --- /dev/null +++ b/python/tiny_obj_loader.cc @@ -0,0 +1,9 @@ +// Use double precision for better python integration. +// Need also define this in `binding.cc`(and all compilation units) +#define TINYOBJLOADER_USE_DOUBLE + +// Use robust triangulation by using Mapbox earcut. +#define TINYOBJLOADER_USE_MAPBOX_EARCUT + +#define TINYOBJLOADER_IMPLEMENTATION +#include "../tiny_obj_loader.h" diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..cb950871 --- /dev/null +++ b/setup.py @@ -0,0 +1,69 @@ +# Adapted from https://github.com/pybind/python_example/blob/master/setup.py +import sys + +#from pybind11 import get_cmake_dir +# Available at setup time due to pyproject.toml +from pybind11.setup_helpers import Pybind11Extension#, build_ext +from setuptools import setup + +try: + # try to read setuptools_scm generated _version.py + from .python import _version +except: + __version__ = "2.0.0rc10" + +with open("README.md", "r", encoding="utf8") as fh: + long_description = fh.read() + +# The main interface is through Pybind11Extension. +# * You can add cxx_std=11/14/17, and then build_ext can be removed. +# * You can set include_pybind11=false to add the include directory yourself, +# say from a submodule. +# +# Note: +# Sort input source files if you glob sources to ensure bit-for-bit +# reproducible builds (https://github.com/pybind/python_example/pull/53) + +ext_modules = [ + Pybind11Extension("tinyobjloader", + sorted(["python/bindings.cc", "python/tiny_obj_loader.cc"]), + # Example: passing in the version to the compiled code + define_macros = [('VERSION_INFO', __version__)], + cxx_std=11, + ), +] + +setup( + name="tinyobjloader", + packages=['python'], + #version=__version__, + author="Syoyo Fujita", + author_email="syoyo@lighttransport.com", + url="https://github.com/tinyobjloader/tinyobjloader", + #project_urls={ + # "Issue Tracker": "https://github.com/tinyobjloader/tinyobjloader/issues", + #}, + description="Tiny but powerful Wavefront OBJ loader", + long_description=long_description, + long_description_content_type='text/markdown', + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Intended Audience :: Manufacturing", + "Topic :: Artistic Software", + "Topic :: Multimedia :: Graphics :: 3D Modeling", + "Topic :: Scientific/Engineering :: Visualization", + "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: ISC License (ISCL)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + ], + ext_modules=ext_modules, + #extras_require={"test": "pytest"}, + # Currently, build_ext only provides an optional "highest supported C++ + # level" feature, but in the future it may provide more features. + # cmdclass={"build_ext": build_ext}, + #zip_safe=False, + #python_requires=">=3.6", +) diff --git a/test.cc b/test.cc deleted file mode 100644 index 82b6178c..00000000 --- a/test.cc +++ /dev/null @@ -1,191 +0,0 @@ -#include "tiny_obj_loader.h" - -#include -#include -#include -#include -#include -#include - -static void PrintInfo(const std::vector& shapes) -{ - std::cout << "# of shapes : " << shapes.size() << std::endl; - - for (size_t i = 0; i < shapes.size(); i++) { - printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); - printf("shape[%ld].indices: %ld\n", i, shapes[i].mesh.indices.size()); - assert((shapes[i].mesh.indices.size() % 3) == 0); - for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { - printf(" idx[%ld] = %d\n", f, shapes[i].mesh.indices[f]); - } - - printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); - assert((shapes[i].mesh.positions.size() % 3) == 0); - for (size_t v = 0; v < shapes[i].mesh.positions.size() / 3; v++) { - printf(" v[%ld] = (%f, %f, %f)\n", v, - shapes[i].mesh.positions[3*v+0], - shapes[i].mesh.positions[3*v+1], - shapes[i].mesh.positions[3*v+2]); - } - - printf("shape[%ld].material.name = %s\n", i, shapes[i].material.name.c_str()); - printf(" material.Ka = (%f, %f ,%f)\n", shapes[i].material.ambient[0], shapes[i].material.ambient[1], shapes[i].material.ambient[2]); - printf(" material.Kd = (%f, %f ,%f)\n", shapes[i].material.diffuse[0], shapes[i].material.diffuse[1], shapes[i].material.diffuse[2]); - printf(" material.Ks = (%f, %f ,%f)\n", shapes[i].material.specular[0], shapes[i].material.specular[1], shapes[i].material.specular[2]); - printf(" material.Tr = (%f, %f ,%f)\n", shapes[i].material.transmittance[0], shapes[i].material.transmittance[1], shapes[i].material.transmittance[2]); - printf(" material.Ke = (%f, %f ,%f)\n", shapes[i].material.emission[0], shapes[i].material.emission[1], shapes[i].material.emission[2]); - printf(" material.Ns = %f\n", shapes[i].material.shininess); - printf(" material.Ni = %f\n", shapes[i].material.ior); - printf(" material.dissolve = %f\n", shapes[i].material.dissolve); - printf(" material.illum = %d\n", shapes[i].material.illum); - printf(" material.map_Ka = %s\n", shapes[i].material.ambient_texname.c_str()); - printf(" material.map_Kd = %s\n", shapes[i].material.diffuse_texname.c_str()); - printf(" material.map_Ks = %s\n", shapes[i].material.specular_texname.c_str()); - printf(" material.map_Ns = %s\n", shapes[i].material.normal_texname.c_str()); - std::map::const_iterator it(shapes[i].material.unknown_parameter.begin()); - std::map::const_iterator itEnd(shapes[i].material.unknown_parameter.end()); - for (; it != itEnd; it++) { - printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); - } - printf("\n"); - } -} - -static bool -TestLoadObj( - const char* filename, - const char* basepath = NULL) -{ - std::cout << "Loading " << filename << std::endl; - - std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, filename, basepath); - - if (!err.empty()) { - std::cerr << err << std::endl; - return false; - } - - PrintInfo(shapes); - - return true; -} - - -static bool -TestStreamLoadObj() -{ - std::cout << "Stream Loading " << std::endl; - - std::stringstream objStream; - objStream - << "mtllib cube.mtl\n" - "\n" - "v 0.000000 2.000000 2.000000\n" - "v 0.000000 0.000000 2.000000\n" - "v 2.000000 0.000000 2.000000\n" - "v 2.000000 2.000000 2.000000\n" - "v 0.000000 2.000000 0.000000\n" - "v 0.000000 0.000000 0.000000\n" - "v 2.000000 0.000000 0.000000\n" - "v 2.000000 2.000000 0.000000\n" - "# 8 vertices\n" - "\n" - "g front cube\n" - "usemtl white\n" - "f 1 2 3 4\n" - "g back cube\n" - "# expects white material\n" - "f 8 7 6 5\n" - "g right cube\n" - "usemtl red\n" - "f 4 3 7 8\n" - "g top cube\n" - "usemtl white\n" - "f 5 1 4 8\n" - "g left cube\n" - "usemtl green\n" - "f 5 6 2 1\n" - "g bottom cube\n" - "usemtl white\n" - "f 2 6 7 3\n" - "# 6 elements"; - -std::string matStream( - "newmtl white\n" - "Ka 0 0 0\n" - "Kd 1 1 1\n" - "Ks 0 0 0\n" - "\n" - "newmtl red\n" - "Ka 0 0 0\n" - "Kd 1 0 0\n" - "Ks 0 0 0\n" - "\n" - "newmtl green\n" - "Ka 0 0 0\n" - "Kd 0 1 0\n" - "Ks 0 0 0\n" - "\n" - "newmtl blue\n" - "Ka 0 0 0\n" - "Kd 0 0 1\n" - "Ks 0 0 0\n" - "\n" - "newmtl light\n" - "Ka 20 20 20\n" - "Kd 1 1 1\n" - "Ks 0 0 0"); - - using namespace tinyobj; - class MaterialStringStreamReader: - public MaterialReader - { - public: - MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {} - virtual ~MaterialStringStreamReader() {} - virtual std::string operator() ( - const std::string& matId, - std::map& matMap) - { - return LoadMtl(matMap, m_matSStream); - } - - private: - std::stringstream m_matSStream; - }; - - MaterialStringStreamReader matSSReader(matStream); - std::vector shapes; - std::string err = tinyobj::LoadObj(shapes, objStream, matSSReader); - - if (!err.empty()) { - std::cerr << err << std::endl; - return false; - } - - PrintInfo(shapes); - - return true; -} - -int -main( - int argc, - char **argv) -{ - - if (argc > 1) { - const char* basepath = NULL; - if (argc > 2) { - basepath = argv[2]; - } - assert(true == TestLoadObj(argv[1], basepath)); - } else { - assert(true == TestLoadObj("cornell_box.obj")); - assert(true == TestLoadObj("cube.obj")); - assert(true == TestStreamLoadObj()); - } - - return 0; -} diff --git a/tests/LICENSE.acutest.txt b/tests/LICENSE.acutest.txt new file mode 100644 index 00000000..97309201 --- /dev/null +++ b/tests/LICENSE.acutest.txt @@ -0,0 +1,22 @@ + +# The MIT License (MIT) + +Copyright © 2013-2017 Martin Mitáš + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the “Software”), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 00000000..83d297d2 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,17 @@ +.PHONY: clean + +CXX ?= clang++ +CXXFLAGS ?= -g -O1 +EXTRA_CXXFLAGS ?= -std=c++03 -fsanitize=address + +tester: tester.cc ../tiny_obj_loader.h + $(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) -o tester tester.cc + +all: tester + +check: tester + ./tester + +clean: + rm -rf tester + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 00000000..1b0b43d1 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,37 @@ +# Build&Test + +## Use makefile + + $ make check + +## Use ninja + kuroga + +Assume + +* ninja 1.4+ +* python 2.6+ + +Are installed. + +### Linux/MacOSX + + $ python kuroga.py config-posix.py + $ ninja + +### Windows + +Visual Studio 2013 is required to build tester. + +On Windows console. + + > python kuroga.py config-msvc.py + > vcbuild.bat + + +Or on msys2 bash, + + $ python kuroga.py config-msvc.py + $ cmd //c vcbuild.bat + + + diff --git a/tests/acutest.h b/tests/acutest.h new file mode 100644 index 00000000..29a51c33 --- /dev/null +++ b/tests/acutest.h @@ -0,0 +1,1514 @@ +/* + * Acutest -- Another C/C++ Unit Test facility + * + * + * Copyright (c) 2013-2019 Martin Mitas + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef ACUTEST_H__ +#define ACUTEST_H__ + + +/************************ + *** Public interface *** + ************************/ + +/* By default, "acutest.h" provides the main program entry point (function + * main()). However, if the test suite is composed of multiple source files + * which include "acutest.h", then this causes a problem of multiple main() + * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all + * compilation units but one. + */ + +/* Macro to specify list of unit tests in the suite. + * The unit test implementation MUST provide list of unit tests it implements + * with this macro: + * + * TEST_LIST = { + * { "test1_name", test1_func_ptr }, + * { "test2_name", test2_func_ptr }, + * ... + * { 0 } + * }; + * + * The list specifies names of each test (must be unique) and pointer to + * a function implementing it. The function does not take any arguments + * and has no return values, i.e. every test function has to be compatible + * with this prototype: + * + * void test_func(void); + */ +#define TEST_LIST const struct test__ test_list__[] + + +/* Macros for testing whether an unit test succeeds or fails. These macros + * can be used arbitrarily in functions implementing the unit tests. + * + * If any condition fails throughout execution of a test, the test fails. + * + * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows + * also to specify an error message to print out if the condition fails. + * (It expects printf-like format string and its parameters). The macros + * return non-zero (condition passes) or 0 (condition fails). + * + * That can be useful when more conditions should be checked only if some + * preceding condition passes, as illustrated in this code snippet: + * + * SomeStruct* ptr = allocate_some_struct(); + * if(TEST_CHECK(ptr != NULL)) { + * TEST_CHECK(ptr->member1 < 100); + * TEST_CHECK(ptr->member2 > 200); + * } + */ +#define TEST_CHECK_(cond,...) test_check__((cond), __FILE__, __LINE__, __VA_ARGS__) +#define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond) + +#ifdef __cplusplus +/* Macros to verify that the code (the 1st argument) throws exception of given + * type (the 2nd argument). (Note these macros are only available in C++.) + * + * TEST_EXCEPTION_ is like TEST_EXCEPTION but accepts custom printf-like + * message. + * + * For example: + * + * TEST_EXCEPTION(function_that_throw(), ExpectedExceptionType); + * + * If the function_that_throw() throws ExpectedExceptionType, the check passes. + * If the function throws anything incompatible with ExpectedExceptionType + * (or if it does not thrown an exception at all), the check fails. + */ +#define TEST_EXCEPTION(code, exctype) \ + do { \ + bool exc_ok__ = false; \ + const char *msg__ = NULL; \ + try { \ + code; \ + msg__ = "No exception thrown."; \ + } catch(exctype const&) { \ + exc_ok__= true; \ + } catch(...) { \ + msg__ = "Unexpected exception thrown."; \ + } \ + test_check__(exc_ok__, __FILE__, __LINE__, #code " throws " #exctype); \ + if(msg__ != NULL) \ + test_message__("%s", msg__); \ + } while(0) +#define TEST_EXCEPTION_(code, exctype, ...) \ + do { \ + bool exc_ok__ = false; \ + const char *msg__ = NULL; \ + try { \ + code; \ + msg__ = "No exception thrown."; \ + } catch(exctype const&) { \ + exc_ok__= true; \ + } catch(...) { \ + msg__ = "Unexpected exception thrown."; \ + } \ + test_check__(exc_ok__, __FILE__, __LINE__, __VA_ARGS__); \ + if(msg__ != NULL) \ + test_message__("%s", msg__); \ + } while(0) +#endif /* #ifdef __cplusplus */ + + +/* Sometimes it is useful to split execution of more complex unit tests to some + * smaller parts and associate those parts with some names. + * + * This is especially handy if the given unit test is implemented as a loop + * over some vector of multiple testing inputs. Using these macros allow to use + * sort of subtitle for each iteration of the loop (e.g. outputting the input + * itself or a name associated to it), so that if any TEST_CHECK condition + * fails in the loop, it can be easily seen which iteration triggers the + * failure, without the need to manually output the iteration-specific data in + * every single TEST_CHECK inside the loop body. + * + * TEST_CASE allows to specify only single string as the name of the case, + * TEST_CASE_ provides all the power of printf-like string formatting. + * + * Note that the test cases cannot be nested. Starting a new test case ends + * implicitly the previous one. To end the test case explicitly (e.g. to end + * the last test case after exiting the loop), you may use TEST_CASE(NULL). + */ +#define TEST_CASE_(...) test_case__(__VA_ARGS__) +#define TEST_CASE(name) test_case__("%s", name); + + +/* printf-like macro for outputting an extra information about a failure. + * + * Intended use is to output some computed output versus the expected value, + * e.g. like this: + * + * if(!TEST_CHECK(produced == expected)) { + * TEST_MSG("Expected: %d", expected); + * TEST_MSG("Produced: %d", produced); + * } + * + * Note the message is only written down if the most recent use of any checking + * macro (like e.g. TEST_CHECK or TEST_EXCEPTION) in the current test failed. + * This means the above is equivalent to just this: + * + * TEST_CHECK(produced == expected); + * TEST_MSG("Expected: %d", expected); + * TEST_MSG("Produced: %d", produced); + * + * The macro can deal with multi-line output fairly well. It also automatically + * adds a final new-line if there is none present. + */ +#define TEST_MSG(...) test_message__(__VA_ARGS__) + + +/* Maximal output per TEST_MSG call. Longer messages are cut. + * You may define another limit prior including "acutest.h" + */ +#ifndef TEST_MSG_MAXSIZE + #define TEST_MSG_MAXSIZE 1024 +#endif + + +/* Macro for dumping a block of memory. + * + * Its inteded use is very similar to what TEST_MSG is for, but instead of + * generating any printf-like message, this is for dumping raw block of a + * memory in a hexadecimal form: + * + * TEST_CHECK(size_produced == size_expected && memcmp(addr_produced, addr_expected, size_produced) == 0); + * TEST_DUMP("Expected:", addr_expected, size_expected); + * TEST_DUMP("Produced:", addr_produced, size_produced); + */ +#define TEST_DUMP(title, addr, size) test_dump__(title, addr, size) + +/* Maximal output per TEST_DUMP call (in bytes to dump). Longer blocks are cut. + * You may define another limit prior including "acutest.h" + */ +#ifndef TEST_DUMP_MAXSIZE + #define TEST_DUMP_MAXSIZE 1024 +#endif + + +/********************** + *** Implementation *** + **********************/ + +/* The unit test files should not rely on anything below. */ + +#include +#include +#include +#include +#include + +#if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) + #define ACUTEST_UNIX__ 1 + #include + #include + #include + #include + #include + #include + + #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC + #define ACUTEST_HAS_POSIX_TIMER__ 1 + #endif +#endif + +#if defined(__gnu_linux__) + #define ACUTEST_LINUX__ 1 + #include + #include +#endif + +#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define ACUTEST_WIN__ 1 + #include + #include +#endif + +#ifdef __cplusplus + #include +#endif + + +/* Note our global private identifiers end with '__' to mitigate risk of clash + * with the unit tests implementation. */ + + +#ifdef __cplusplus + extern "C" { +#endif + + +struct test__ { + const char* name; + void (*func)(void); +}; + +extern const struct test__ test_list__[]; + +int test_check__(int cond, const char* file, int line, const char* fmt, ...); +void test_case__(const char* fmt, ...); +void test_message__(const char* fmt, ...); +void test_dump__(const char* title, const void* addr, size_t size); + + +#ifndef TEST_NO_MAIN + +static char* test_argv0__ = NULL; +static size_t test_list_size__ = 0; +static const struct test__** tests__ = NULL; +static char* test_flags__ = NULL; +static size_t test_count__ = 0; +static int test_no_exec__ = -1; +static int test_no_summary__ = 0; +static int test_tap__ = 0; +static int test_skip_mode__ = 0; +static int test_worker__ = 0; +static int test_worker_index__ = 0; +static int test_cond_failed__ = 0; + +static int test_stat_failed_units__ = 0; +static int test_stat_run_units__ = 0; + +static const struct test__* test_current_unit__ = NULL; +static int test_current_index__ = 0; +static char test_case_name__[64] = ""; +static int test_current_already_logged__ = 0; +static int test_case_current_already_logged__ = 0; +static int test_verbose_level__ = 2; +static int test_current_failures__ = 0; +static int test_colorize__ = 0; +static int test_timer__ = 0; + +#if defined ACUTEST_WIN__ + static LARGE_INTEGER test_timer_freq__; + static LARGE_INTEGER test_timer_start__; + static LARGE_INTEGER test_timer_end__; + + static void + test_timer_init__(void) + { + QueryPerformanceFrequency(&test_timer_freq__); + } + + static void + test_timer_get_time__(LARGE_INTEGER* ts) + { + QueryPerformanceCounter(ts); + } + + static void + test_timer_print_diff__(void) + { + double duration = test_timer_end__.QuadPart - test_timer_start__.QuadPart; + duration /= test_timer_freq__.QuadPart; + printf("%.6lf secs", duration); + } +#elif defined ACUTEST_HAS_POSIX_TIMER__ + static clockid_t test_timer_id__; + struct timespec test_timer_start__; + struct timespec test_timer_end__; + + static void + test_timer_init__(void) + { + if(test_timer__ == 1) + #ifdef CLOCK_MONOTONIC_RAW + /* linux specific; not subject of NTP adjustements or adjtime() */ + test_timer_id__ = CLOCK_MONOTONIC_RAW; + #else + test_timer_id__ = CLOCK_MONOTONIC; + #endif + else if(test_timer__ == 2) + test_timer_id__ = CLOCK_PROCESS_CPUTIME_ID; + } + + static void + test_timer_get_time__(struct timespec* ts) + { + clock_gettime(test_timer_id__, ts); + } + + static void + test_timer_print_diff__(void) + { + double duration = ((double) test_timer_end__.tv_sec + + (double) test_timer_end__.tv_nsec * 10e-9) + - + ((double) test_timer_start__.tv_sec + + (double) test_timer_start__.tv_nsec * 10e-9); + printf("%.6lf secs", duration); + } +#else + static int test_timer_start__; + static int test_timer_end__; + + void + test_timer_init__(void) + {} + + static void + test_timer_get_time__(int* ts) + { + (void) ts; + } + + static void + test_timer_print_diff__(void) + {} +#endif + +#define TEST_COLOR_DEFAULT__ 0 +#define TEST_COLOR_GREEN__ 1 +#define TEST_COLOR_RED__ 2 +#define TEST_COLOR_DEFAULT_INTENSIVE__ 3 +#define TEST_COLOR_GREEN_INTENSIVE__ 4 +#define TEST_COLOR_RED_INTENSIVE__ 5 + +static int +test_print_in_color__(int color, const char* fmt, ...) +{ + va_list args; + char buffer[256]; + int n; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + buffer[sizeof(buffer)-1] = '\0'; + + if(!test_colorize__) { + return printf("%s", buffer); + } + +#if defined ACUTEST_UNIX__ + { + const char* col_str; + switch(color) { + case TEST_COLOR_GREEN__: col_str = "\033[0;32m"; break; + case TEST_COLOR_RED__: col_str = "\033[0;31m"; break; + case TEST_COLOR_GREEN_INTENSIVE__: col_str = "\033[1;32m"; break; + case TEST_COLOR_RED_INTENSIVE__: col_str = "\033[1;31m"; break; + case TEST_COLOR_DEFAULT_INTENSIVE__: col_str = "\033[1m"; break; + default: col_str = "\033[0m"; break; + } + printf("%s", col_str); + n = printf("%s", buffer); + printf("\033[0m"); + return n; + } +#elif defined ACUTEST_WIN__ + { + HANDLE h; + CONSOLE_SCREEN_BUFFER_INFO info; + WORD attr; + + h = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(h, &info); + + switch(color) { + case TEST_COLOR_GREEN__: attr = FOREGROUND_GREEN; break; + case TEST_COLOR_RED__: attr = FOREGROUND_RED; break; + case TEST_COLOR_GREEN_INTENSIVE__: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; + case TEST_COLOR_RED_INTENSIVE__: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; + case TEST_COLOR_DEFAULT_INTENSIVE__: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; + default: attr = 0; break; + } + if(attr != 0) + SetConsoleTextAttribute(h, attr); + n = printf("%s", buffer); + SetConsoleTextAttribute(h, info.wAttributes); + return n; + } +#else + n = printf("%s", buffer); + return n; +#endif +} + +static void +test_begin_test_line__(const struct test__* test) +{ + if(!test_tap__) { + if(test_verbose_level__ >= 3) { + test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n", test->name); + test_current_already_logged__++; + } else if(test_verbose_level__ >= 1) { + int n; + char spaces[48]; + + n = test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s... ", test->name); + memset(spaces, ' ', sizeof(spaces)); + if(n < (int) sizeof(spaces)) + printf("%.*s", (int) sizeof(spaces) - n, spaces); + } else { + test_current_already_logged__ = 1; + } + } +} + +static void +test_finish_test_line__(int result) +{ + if(test_tap__) { + const char* str = (result == 0) ? "ok" : "not ok"; + + printf("%s %u - %s\n", str, test_current_index__ + 1, test_current_unit__->name); + + if(result == 0 && test_timer__) { + printf("# Duration: "); + test_timer_print_diff__(); + printf("\n"); + } + } else { + int color = (result == 0) ? TEST_COLOR_GREEN_INTENSIVE__ : TEST_COLOR_RED_INTENSIVE__; + const char* str = (result == 0) ? "OK" : "FAILED"; + printf("[ "); + test_print_in_color__(color, str); + printf(" ]"); + + if(result == 0 && test_timer__) { + printf(" "); + test_timer_print_diff__(); + } + + printf("\n"); + } +} + +static void +test_line_indent__(int level) +{ + static const char spaces[] = " "; + int n = level * 2; + + if(test_tap__ && n > 0) { + n--; + printf("#"); + } + + while(n > 16) { + printf("%s", spaces); + n -= 16; + } + printf("%.*s", n, spaces); +} + +int +test_check__(int cond, const char* file, int line, const char* fmt, ...) +{ + const char *result_str; + int result_color; + int verbose_level; + + if(cond) { + result_str = "ok"; + result_color = TEST_COLOR_GREEN__; + verbose_level = 3; + } else { + if(!test_current_already_logged__ && test_current_unit__ != NULL) + test_finish_test_line__(-1); + + result_str = "failed"; + result_color = TEST_COLOR_RED__; + verbose_level = 2; + test_current_failures__++; + test_current_already_logged__++; + } + + if(test_verbose_level__ >= verbose_level) { + va_list args; + + if(!test_case_current_already_logged__ && test_case_name__[0]) { + test_line_indent__(1); + test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); + test_current_already_logged__++; + test_case_current_already_logged__++; + } + + test_line_indent__(test_case_name__[0] ? 2 : 1); + if(file != NULL) { + if(test_verbose_level__ < 3) { +#ifdef ACUTEST_WIN__ + const char* lastsep1 = strrchr(file, '\\'); + const char* lastsep2 = strrchr(file, '/'); + if(lastsep1 == NULL) + lastsep1 = file-1; + if(lastsep2 == NULL) + lastsep2 = file-1; + file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1; +#else + const char* lastsep = strrchr(file, '/'); + if(lastsep != NULL) + file = lastsep+1; +#endif + } + printf("%s:%d: Check ", file, line); + } + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + printf("... "); + test_print_in_color__(result_color, result_str); + printf("\n"); + test_current_already_logged__++; + } + + test_cond_failed__ = (cond == 0); + return !test_cond_failed__; +} + +void +test_case__(const char* fmt, ...) +{ + va_list args; + + if(test_verbose_level__ < 2) + return; + + if(test_case_name__[0]) { + test_case_current_already_logged__ = 0; + test_case_name__[0] = '\0'; + } + + if(fmt == NULL) + return; + + va_start(args, fmt); + vsnprintf(test_case_name__, sizeof(test_case_name__) - 1, fmt, args); + va_end(args); + test_case_name__[sizeof(test_case_name__) - 1] = '\0'; + + if(test_verbose_level__ >= 3) { + test_line_indent__(1); + test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); + test_current_already_logged__++; + test_case_current_already_logged__++; + } +} + +void +test_message__(const char* fmt, ...) +{ + char buffer[TEST_MSG_MAXSIZE]; + char* line_beg; + char* line_end; + va_list args; + + if(test_verbose_level__ < 2) + return; + + /* We allow extra message only when something is already wrong in the + * current test. */ + if(test_current_unit__ == NULL || !test_cond_failed__) + return; + + va_start(args, fmt); + vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args); + va_end(args); + buffer[TEST_MSG_MAXSIZE-1] = '\0'; + + line_beg = buffer; + while(1) { + line_end = strchr(line_beg, '\n'); + if(line_end == NULL) + break; + test_line_indent__(test_case_name__[0] ? 3 : 2); + printf("%.*s\n", (int)(line_end - line_beg), line_beg); + line_beg = line_end + 1; + } + if(line_beg[0] != '\0') { + test_line_indent__(test_case_name__[0] ? 3 : 2); + printf("%s\n", line_beg); + } +} + +void +test_dump__(const char* title, const void* addr, size_t size) +{ + static const size_t BYTES_PER_LINE = 16; + size_t line_beg; + size_t truncate = 0; + + if(test_verbose_level__ < 2) + return; + + /* We allow extra message only when something is already wrong in the + * current test. */ + if(test_current_unit__ == NULL || !test_cond_failed__) + return; + + if(size > TEST_DUMP_MAXSIZE) { + truncate = size - TEST_DUMP_MAXSIZE; + size = TEST_DUMP_MAXSIZE; + } + + test_line_indent__(test_case_name__[0] ? 3 : 2); + printf((title[strlen(title)-1] == ':') ? "%s\n" : "%s:\n", title); + + for(line_beg = 0; line_beg < size; line_beg += BYTES_PER_LINE) { + size_t line_end = line_beg + BYTES_PER_LINE; + size_t off; + + test_line_indent__(test_case_name__[0] ? 4 : 3); + printf("%08x: ", line_beg); + for(off = line_beg; off < line_end; off++) { + if(off < size) + printf(" %02x", ((unsigned char*)addr)[off]); + else + printf(" "); + } + + printf(" "); + for(off = line_beg; off < line_end; off++) { + unsigned char byte = ((unsigned char*)addr)[off]; + if(off < size) + printf("%c", (iscntrl(byte) ? '.' : byte)); + else + break; + } + + printf("\n"); + } + + if(truncate > 0) { + test_line_indent__(test_case_name__[0] ? 4 : 3); + printf(" ... (and more %u bytes)\n", (unsigned) truncate); + } +} + +static void +test_list_names__(void) +{ + const struct test__* test; + + printf("Unit tests:\n"); + for(test = &test_list__[0]; test->func != NULL; test++) + printf(" %s\n", test->name); +} + +static void +test_remember__(int i) +{ + if(test_flags__[i]) + return; + else + test_flags__[i] = 1; + + tests__[test_count__] = &test_list__[i]; + test_count__++; +} + +static int +test_name_contains_word__(const char* name, const char* pattern) +{ + static const char word_delim[] = " \t-_."; + const char* substr; + size_t pattern_len; + int starts_on_word_boundary; + int ends_on_word_boundary; + + pattern_len = strlen(pattern); + + substr = strstr(name, pattern); + while(substr != NULL) { + starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL); + ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL); + + if(starts_on_word_boundary && ends_on_word_boundary) + return 1; + + substr = strstr(substr+1, pattern); + } + + return 0; +} + +static int +test_lookup__(const char* pattern) +{ + int i; + int n = 0; + + /* Try exact match. */ + for(i = 0; i < (int) test_list_size__; i++) { + if(strcmp(test_list__[i].name, pattern) == 0) { + test_remember__(i); + n++; + break; + } + } + if(n > 0) + return n; + + /* Try word match. */ + for(i = 0; i < (int) test_list_size__; i++) { + if(test_name_contains_word__(test_list__[i].name, pattern)) { + test_remember__(i); + n++; + } + } + if(n > 0) + return n; + + /* Try relaxed match. */ + for(i = 0; i < (int) test_list_size__; i++) { + if(strstr(test_list__[i].name, pattern) != NULL) { + test_remember__(i); + n++; + } + } + + return n; +} + + +/* Called if anything goes bad in Acutest, or if the unit test ends in other + * way then by normal returning from its function (e.g. exception or some + * abnormal child process termination). */ +static void +test_error__(const char* fmt, ...) +{ + va_list args; + + if(test_verbose_level__ == 0) + return; + + if(test_verbose_level__ <= 2 && !test_current_already_logged__ && test_current_unit__ != NULL) { + if(test_tap__) { + test_finish_test_line__(-1); + } else { + printf("[ "); + test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED"); + printf(" ]\n"); + } + } + + if(test_verbose_level__ >= 2) { + test_line_indent__(1); + if(test_verbose_level__ >= 3) + test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "ERROR: "); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); + } + + if(test_verbose_level__ >= 3) { + printf("\n"); + } +} + +/* Call directly the given test unit function. */ +static int +test_do_run__(const struct test__* test, int index) +{ + test_current_unit__ = test; + test_current_index__ = index; + test_current_failures__ = 0; + test_current_already_logged__ = 0; + test_cond_failed__ = 0; + + test_timer_init__(); + + test_begin_test_line__(test); + +#ifdef __cplusplus + try { +#endif + + /* This is good to do for case the test unit e.g. crashes. */ + fflush(stdout); + fflush(stderr); + + test_timer_get_time__(&test_timer_start__); + test->func(); + test_timer_get_time__(&test_timer_end__); + + if(test_verbose_level__ >= 3) { + test_line_indent__(1); + if(test_current_failures__ == 0) { + test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS: "); + printf("All conditions have passed.\n"); + + if(test_timer__) { + test_line_indent__(1); + printf("Duration: "); + test_timer_print_diff__(); + printf("\n"); + } + } else { + test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED: "); + printf("%d condition%s %s failed.\n", + test_current_failures__, + (test_current_failures__ == 1) ? "" : "s", + (test_current_failures__ == 1) ? "has" : "have"); + } + printf("\n"); + } else if(test_verbose_level__ >= 1 && test_current_failures__ == 0) { + test_finish_test_line__(0); + } + + test_case__(NULL); + test_current_unit__ = NULL; + return (test_current_failures__ == 0) ? 0 : -1; + +#ifdef __cplusplus + } catch(std::exception& e) { + const char* what = e.what(); + if(what != NULL) + test_error__("Threw std::exception: %s", what); + else + test_error__("Threw std::exception"); + return -1; + } catch(...) { + test_error__("Threw an exception"); + return -1; + } +#endif +} + +/* Trigger the unit test. If possible (and not suppressed) it starts a child + * process who calls test_do_run__(), otherwise it calls test_do_run__() + * directly. */ +static void +test_run__(const struct test__* test, int index) +{ + int failed = 1; + + test_current_unit__ = test; + test_current_already_logged__ = 0; + + if(!test_no_exec__) { + +#if defined(ACUTEST_UNIX__) + + pid_t pid; + int exit_code; + + /* Make sure the child starts with empty I/O buffers. */ + fflush(stdout); + fflush(stderr); + + pid = fork(); + if(pid == (pid_t)-1) { + test_error__("Cannot fork. %s [%d]", strerror(errno), errno); + failed = 1; + } else if(pid == 0) { + /* Child: Do the test. */ + failed = (test_do_run__(test, index) != 0); + exit(failed ? 1 : 0); + } else { + /* Parent: Wait until child terminates and analyze its exit code. */ + waitpid(pid, &exit_code, 0); + if(WIFEXITED(exit_code)) { + switch(WEXITSTATUS(exit_code)) { + case 0: failed = 0; break; /* test has passed. */ + case 1: /* noop */ break; /* "normal" failure. */ + default: test_error__("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); + } + } else if(WIFSIGNALED(exit_code)) { + char tmp[32]; + const char* signame; + switch(WTERMSIG(exit_code)) { + case SIGINT: signame = "SIGINT"; break; + case SIGHUP: signame = "SIGHUP"; break; + case SIGQUIT: signame = "SIGQUIT"; break; + case SIGABRT: signame = "SIGABRT"; break; + case SIGKILL: signame = "SIGKILL"; break; + case SIGSEGV: signame = "SIGSEGV"; break; + case SIGILL: signame = "SIGILL"; break; + case SIGTERM: signame = "SIGTERM"; break; + default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; + } + test_error__("Test interrupted by %s", signame); + } else { + test_error__("Test ended in an unexpected way [%d]", exit_code); + } + } + +#elif defined(ACUTEST_WIN__) + + char buffer[512] = {0}; + STARTUPINFOA startupInfo; + PROCESS_INFORMATION processInfo; + DWORD exitCode; + + /* Windows has no fork(). So we propagate all info into the child + * through a command line arguments. */ + _snprintf(buffer, sizeof(buffer)-1, + "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"", + test_argv0__, index, test_timer__ ? "--timer" : "", + test_tap__ ? "--tap" : "", test_verbose_level__, + test_colorize__ ? "always" : "never", + test->name); + memset(&startupInfo, 0, sizeof(startupInfo)); + startupInfo.cb = sizeof(STARTUPINFO); + if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { + WaitForSingleObject(processInfo.hProcess, INFINITE); + GetExitCodeProcess(processInfo.hProcess, &exitCode); + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + failed = (exitCode != 0); + } else { + test_error__("Cannot create unit test subprocess [%ld].", GetLastError()); + failed = 1; + } + +#else + + /* A platform where we don't know how to run child process. */ + failed = (test_do_run__(test, index) != 0); + +#endif + + } else { + /* Child processes suppressed through --no-exec. */ + failed = (test_do_run__(test, index) != 0); + } + + test_current_unit__ = NULL; + + test_stat_run_units__++; + if(failed) + test_stat_failed_units__++; +} + +#if defined(ACUTEST_WIN__) +/* Callback for SEH events. */ +static LONG CALLBACK +test_exception_filter__(EXCEPTION_POINTERS *ptrs) +{ + test_error__("Unhandled SEH exception %08lx at %p.", + ptrs->ExceptionRecord->ExceptionCode, + ptrs->ExceptionRecord->ExceptionAddress); + fflush(stdout); + fflush(stderr); + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + + +#define TEST_CMDLINE_OPTFLAG_OPTIONALARG__ 0x0001 +#define TEST_CMDLINE_OPTFLAG_REQUIREDARG__ 0x0002 + +#define TEST_CMDLINE_OPTID_NONE__ 0 +#define TEST_CMDLINE_OPTID_UNKNOWN__ (-0x7fffffff + 0) +#define TEST_CMDLINE_OPTID_MISSINGARG__ (-0x7fffffff + 1) +#define TEST_CMDLINE_OPTID_BOGUSARG__ (-0x7fffffff + 2) + +typedef struct TEST_CMDLINE_OPTION__ { + char shortname; + const char* longname; + int id; + unsigned flags; +} TEST_CMDLINE_OPTION__; + +static int +test_cmdline_handle_short_opt_group__(const TEST_CMDLINE_OPTION__* options, + const char* arggroup, + int (*callback)(int /*optval*/, const char* /*arg*/)) +{ + const TEST_CMDLINE_OPTION__* opt; + int i; + int ret = 0; + + for(i = 0; arggroup[i] != '\0'; i++) { + for(opt = options; opt->id != 0; opt++) { + if(arggroup[i] == opt->shortname) + break; + } + + if(opt->id != 0 && !(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { + ret = callback(opt->id, NULL); + } else { + /* Unknown option. */ + char badoptname[3]; + badoptname[0] = '-'; + badoptname[1] = arggroup[i]; + badoptname[2] = '\0'; + ret = callback((opt->id != 0 ? TEST_CMDLINE_OPTID_MISSINGARG__ : TEST_CMDLINE_OPTID_UNKNOWN__), + badoptname); + } + + if(ret != 0) + break; + } + + return ret; +} + +#define TEST_CMDLINE_AUXBUF_SIZE__ 32 + +static int +test_cmdline_read__(const TEST_CMDLINE_OPTION__* options, int argc, char** argv, + int (*callback)(int /*optval*/, const char* /*arg*/)) +{ + + const TEST_CMDLINE_OPTION__* opt; + char auxbuf[TEST_CMDLINE_AUXBUF_SIZE__+1]; + int after_doubledash = 0; + int i = 1; + int ret = 0; + + auxbuf[TEST_CMDLINE_AUXBUF_SIZE__] = '\0'; + + while(i < argc) { + if(after_doubledash || strcmp(argv[i], "-") == 0) { + /* Non-option argument. */ + ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); + } else if(strcmp(argv[i], "--") == 0) { + /* End of options. All the remaining members are non-option arguments. */ + after_doubledash = 1; + } else if(argv[i][0] != '-') { + /* Non-option argument. */ + ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); + } else { + for(opt = options; opt->id != 0; opt++) { + if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) { + size_t len = strlen(opt->longname); + if(strncmp(argv[i]+2, opt->longname, len) == 0) { + /* Regular long option. */ + if(argv[i][2+len] == '\0') { + /* with no argument provided. */ + if(!(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) + ret = callback(opt->id, NULL); + else + ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); + break; + } else if(argv[i][2+len] == '=') { + /* with an argument provided. */ + if(opt->flags & (TEST_CMDLINE_OPTFLAG_OPTIONALARG__ | TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { + ret = callback(opt->id, argv[i]+2+len+1); + } else { + sprintf(auxbuf, "--%s", opt->longname); + ret = callback(TEST_CMDLINE_OPTID_BOGUSARG__, auxbuf); + } + break; + } else { + continue; + } + } + } else if(opt->shortname != '\0' && argv[i][0] == '-') { + if(argv[i][1] == opt->shortname) { + /* Regular short option. */ + if(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__) { + if(argv[i][2] != '\0') + ret = callback(opt->id, argv[i]+2); + else if(i+1 < argc) + ret = callback(opt->id, argv[++i]); + else + ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); + break; + } else { + ret = callback(opt->id, NULL); + + /* There might be more (argument-less) short options + * grouped together. */ + if(ret == 0 && argv[i][2] != '\0') + ret = test_cmdline_handle_short_opt_group__(options, argv[i]+2, callback); + break; + } + } + } + } + + if(opt->id == 0) { /* still not handled? */ + if(argv[i][0] != '-') { + /* Non-option argument. */ + ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); + } else { + /* Unknown option. */ + char* badoptname = argv[i]; + + if(strncmp(badoptname, "--", 2) == 0) { + /* Strip any argument from the long option. */ + char* assignement = strchr(badoptname, '='); + if(assignement != NULL) { + size_t len = assignement - badoptname; + if(len > TEST_CMDLINE_AUXBUF_SIZE__) + len = TEST_CMDLINE_AUXBUF_SIZE__; + strncpy(auxbuf, badoptname, len); + auxbuf[len] = '\0'; + badoptname = auxbuf; + } + } + + ret = callback(TEST_CMDLINE_OPTID_UNKNOWN__, badoptname); + } + } + } + + if(ret != 0) + return ret; + i++; + } + + return ret; +} + +static void +test_help__(void) +{ + printf("Usage: %s [options] [test...]\n", test_argv0__); + printf("\n"); + printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); + printf("tests in the suite but those listed. By default, if no tests are specified\n"); + printf("on the command line, all unit tests in the suite are run.\n"); + printf("\n"); + printf("Options:\n"); + printf(" -s, --skip Execute all unit tests but the listed ones\n"); + printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n"); + printf(" (WHEN is one of 'auto', 'always', 'never')\n"); +#if defined ACUTEST_WIN__ + printf(" -t, --timer Measure test duration\n"); +#elif defined ACUTEST_HAS_POSIX_TIMER__ + printf(" -t, --timer Measure test duration (real time)\n"); + printf(" --timer=TIMER Measure test duration, using given timer\n"); + printf(" (TIMER is one of 'real', 'cpu')\n"); +#endif + printf(" -E, --no-exec Same as --exec=never\n"); + printf(" --no-summary Suppress printing of test results summary\n"); + printf(" --tap Produce TAP-compliant output\n"); + printf(" (See https://testanything.org/)\n"); + printf(" -l, --list List unit tests in the suite and exit\n"); + printf(" -v, --verbose Make output more verbose\n"); + printf(" --verbose=LEVEL Set verbose level to LEVEL:\n"); + printf(" 0 ... Be silent\n"); + printf(" 1 ... Output one line per test (and summary)\n"); + printf(" 2 ... As 1 and failed conditions (this is default)\n"); + printf(" 3 ... As 1 and all conditions (and extended summary)\n"); + printf(" --color[=WHEN] Enable colorized output\n"); + printf(" (WHEN is one of 'auto', 'always', 'never')\n"); + printf(" --no-color Same as --color=never\n"); + printf(" -h, --help Display this help and exit\n"); + + if(test_list_size__ < 16) { + printf("\n"); + test_list_names__(); + } +} + +static const TEST_CMDLINE_OPTION__ test_cmdline_options__[] = { + { 's', "skip", 's', 0 }, + { 0, "exec", 'e', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, + { 'E', "no-exec", 'E', 0 }, +#if defined ACUTEST_WIN__ + { 't', "timer", 't', 0 }, +#elif defined ACUTEST_HAS_POSIX_TIMER__ + { 't', "timer", 't', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, +#endif + { 0, "no-summary", 'S', 0 }, + { 0, "tap", 'T', 0 }, + { 'l', "list", 'l', 0 }, + { 'v', "verbose", 'v', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, + { 0, "color", 'c', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, + { 0, "no-color", 'C', 0 }, + { 'h', "help", 'h', 0 }, + { 0, "worker", 'w', TEST_CMDLINE_OPTFLAG_REQUIREDARG__ }, /* internal */ + { 0, NULL, 0, 0 } +}; + +static int +test_cmdline_callback__(int id, const char* arg) +{ + switch(id) { + case 's': + test_skip_mode__ = 1; + break; + + case 'e': + if(arg == NULL || strcmp(arg, "always") == 0) { + test_no_exec__ = 0; + } else if(strcmp(arg, "never") == 0) { + test_no_exec__ = 1; + } else if(strcmp(arg, "auto") == 0) { + /*noop*/ + } else { + fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", test_argv0__, arg); + fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); + exit(2); + } + break; + + case 'E': + test_no_exec__ = 1; + break; + + case 't': +#if defined ACUTEST_WIN__ || defined ACUTEST_HAS_POSIX_TIMER__ + if(arg == NULL || strcmp(arg, "real") == 0) { + test_timer__ = 1; + #ifndef ACUTEST_WIN__ + } else if(strcmp(arg, "cpu") == 0) { + test_timer__ = 2; + #endif + } else { + fprintf(stderr, "%s: Unrecognized argument '%s' for option --timer.\n", test_argv0__, arg); + fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); + exit(2); + } +#endif + break; + + case 'S': + test_no_summary__ = 1; + break; + + case 'T': + test_tap__ = 1; + break; + + case 'l': + test_list_names__(); + exit(0); + + case 'v': + test_verbose_level__ = (arg != NULL ? atoi(arg) : test_verbose_level__+1); + break; + + case 'c': + if(arg == NULL || strcmp(arg, "always") == 0) { + test_colorize__ = 1; + } else if(strcmp(arg, "never") == 0) { + test_colorize__ = 0; + } else if(strcmp(arg, "auto") == 0) { + /*noop*/ + } else { + fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", test_argv0__, arg); + fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); + exit(2); + } + break; + + case 'C': + test_colorize__ = 0; + break; + + case 'h': + test_help__(); + exit(0); + + case 'w': + test_worker__ = 1; + test_worker_index__ = atoi(arg); + break; + + case 0: + if(test_lookup__(arg) == 0) { + fprintf(stderr, "%s: Unrecognized unit test '%s'\n", test_argv0__, arg); + fprintf(stderr, "Try '%s --list' for list of unit tests.\n", test_argv0__); + exit(2); + } + break; + + case TEST_CMDLINE_OPTID_UNKNOWN__: + fprintf(stderr, "Unrecognized command line option '%s'.\n", arg); + fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); + exit(2); + + case TEST_CMDLINE_OPTID_MISSINGARG__: + fprintf(stderr, "The command line option '%s' requires an argument.\n", arg); + fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); + exit(2); + + case TEST_CMDLINE_OPTID_BOGUSARG__: + fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg); + fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); + exit(2); + } + + return 0; +} + + +#ifdef ACUTEST_LINUX__ +static int +test_is_tracer_present__(void) +{ + char buf[256+32+1]; + int tracer_present = 0; + int fd; + ssize_t n_read; + + fd = open("/proc/self/status", O_RDONLY); + if(fd == -1) + return 0; + + n_read = read(fd, buf, sizeof(buf)-1); + while(n_read > 0) { + static const char pattern[] = "TracerPid:"; + const char* field; + + buf[n_read] = '\0'; + field = strstr(buf, pattern); + if(field != NULL && field < buf + sizeof(buf) - 32) { + pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); + tracer_present = (tracer_pid != 0); + break; + } + + if(n_read == sizeof(buf)-1) { + memmove(buf, buf + sizeof(buf)-1 - 32, 32); + n_read = read(fd, buf+32, sizeof(buf)-1-32); + if(n_read > 0) + n_read += 32; + } + } + + close(fd); + return tracer_present; +} +#endif + +int +main(int argc, char** argv) +{ + int i; + test_argv0__ = argv[0]; + +#if defined ACUTEST_UNIX__ + test_colorize__ = isatty(STDOUT_FILENO); +#elif defined ACUTEST_WIN__ + #if defined __BORLANDC__ + test_colorize__ = isatty(_fileno(stdout)); + #else + test_colorize__ = _isatty(_fileno(stdout)); + #endif +#else + test_colorize__ = 0; +#endif + + /* Count all test units */ + test_list_size__ = 0; + for(i = 0; test_list__[i].func != NULL; i++) + test_list_size__++; + + tests__ = (const struct test__**) malloc(sizeof(const struct test__*) * test_list_size__); + test_flags__ = (char*) malloc(sizeof(char) * test_list_size__); + if(tests__ == NULL || test_flags__ == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(2); + } + memset((void*) test_flags__, 0, sizeof(char) * test_list_size__); + + /* Parse options */ + test_cmdline_read__(test_cmdline_options__, argc, argv, test_cmdline_callback__); + +#if defined(ACUTEST_WIN__) + SetUnhandledExceptionFilter(test_exception_filter__); +#endif + + /* By default, we want to run all tests. */ + if(test_count__ == 0) { + for(i = 0; test_list__[i].func != NULL; i++) + tests__[i] = &test_list__[i]; + test_count__ = test_list_size__; + } + + /* Guess whether we want to run unit tests as child processes. */ + if(test_no_exec__ < 0) { + test_no_exec__ = 0; + + if(test_count__ <= 1) { + test_no_exec__ = 1; + } else { +#ifdef ACUTEST_WIN__ + if(IsDebuggerPresent()) + test_no_exec__ = 1; +#endif +#ifdef ACUTEST_LINUX__ + if(test_is_tracer_present__()) + test_no_exec__ = 1; +#endif + } + } + + if(test_tap__) { + /* TAP requires we know test result ("ok", "not ok") before we output + * anything about the test, and this gets problematic for larger verbose + * levels. */ + if(test_verbose_level__ > 2) + test_verbose_level__ = 2; + + /* TAP harness should provide some summary. */ + test_no_summary__ = 1; + + if(!test_worker__) + printf("1..%d\n", (int) test_count__); + } + + /* Run the tests */ + if(!test_skip_mode__) { + /* Run the listed tests. */ + for(i = 0; i < (int) test_count__; i++) + test_run__(tests__[i], test_worker_index__ + i); + } else { + /* Run all tests except those listed. */ + int index = test_worker_index__; + for(i = 0; test_list__[i].func != NULL; i++) { + if(!test_flags__[i]) + test_run__(&test_list__[i], index++); + } + } + + /* Write a summary */ + if(!test_no_summary__ && test_verbose_level__ >= 1) { + if(test_verbose_level__ >= 3) { + test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Summary:\n"); + + printf(" Count of all unit tests: %4d\n", (int) test_list_size__); + printf(" Count of run unit tests: %4d\n", test_stat_run_units__); + printf(" Count of failed unit tests: %4d\n", test_stat_failed_units__); + printf(" Count of skipped unit tests: %4d\n", (int) test_list_size__ - test_stat_run_units__); + } + + if(test_stat_failed_units__ == 0) { + test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS:"); + printf(" All unit tests have passed.\n"); + } else { + test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED:"); + printf(" %d of %d unit tests %s failed.\n", + test_stat_failed_units__, test_stat_run_units__, + (test_stat_failed_units__ == 1) ? "has" : "have"); + } + + if(test_verbose_level__ >= 3) + printf("\n"); + } + + free((void*) tests__); + free((void*) test_flags__); + + return (test_stat_failed_units__ == 0) ? 0 : 1; +} + + +#endif /* #ifndef TEST_NO_MAIN */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif + + +#endif /* #ifndef ACUTEST_H__ */ diff --git a/tests/assets/issue-244.mtl b/tests/assets/issue-244.mtl new file mode 100644 index 00000000..63c46a43 --- /dev/null +++ b/tests/assets/issue-244.mtl @@ -0,0 +1,11 @@ +newmtl None +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl None1 +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + + diff --git a/tests/config-msvc.py b/tests/config-msvc.py new file mode 100644 index 00000000..a7771def --- /dev/null +++ b/tests/config-msvc.py @@ -0,0 +1,52 @@ +exe = "tester.exe" + +toolchain = "msvc" + +# optional +link_pool_depth = 1 + +# optional +builddir = { + "gnu" : "build" + , "msvc" : "build" + , "clang" : "build" + } + +includes = { + "gnu" : [ "-I." ] + , "msvc" : [ "/I." ] + , "clang" : [ "-I." ] + } + +defines = { + "gnu" : [ "-DEXAMPLE=1" ] + , "msvc" : [ "/DEXAMPLE=1" ] + , "clang" : [ "-DEXAMPLE=1" ] + } + +cflags = { + "gnu" : [ "-O2", "-g" ] + , "msvc" : [ "/O2" ] + , "clang" : [ "-O2", "-g" ] + } + +cxxflags = { + "gnu" : [ "-O2", "-g" ] + , "msvc" : [ "/O2", "/W4", "/EHsc"] + , "clang" : [ "-O2", "-g", "-fsanitize=address" ] + } + +ldflags = { + "gnu" : [ ] + , "msvc" : [ ] + , "clang" : [ "-fsanitize=address" ] + } + +# optionsl +cxx_files = [ "tester.cc" ] +c_files = [ ] + +# You can register your own toolchain through register_toolchain function +def register_toolchain(ninja): + pass + diff --git a/tests/config-posix.py b/tests/config-posix.py new file mode 100644 index 00000000..29cc4d55 --- /dev/null +++ b/tests/config-posix.py @@ -0,0 +1,53 @@ +exe = "tester" + +# "gnu" or "clang" +toolchain = "gnu" + +# optional +link_pool_depth = 1 + +# optional +builddir = { + "gnu" : "build" + , "msvc" : "build" + , "clang" : "build" + } + +includes = { + "gnu" : [ "-I." ] + , "msvc" : [ "/I." ] + , "clang" : [ "-I." ] + } + +defines = { + "gnu" : [ ] + , "msvc" : [ ] + , "clang" : [ ] + } + +cflags = { + "gnu" : [ "-O2", "-g" ] + , "msvc" : [ "/O2" ] + , "clang" : [ "-O2", "-g" ] + } + +# Warn as much as possible: http://qiita.com/MitsutakaTakeda/items/6b9966f890cc9b944d75 +cxxflags = { + "gnu" : [ "-O2", "-g", "-pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused", "-fsanitize=address" ] + , "msvc" : [ "/O2", "/W4" ] + , "clang" : [ "-O2", "-g", "-Werror -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic", "-fsanitize=address" ] + } + +ldflags = { + "gnu" : [ "-fsanitize=address" ] + , "msvc" : [ ] + , "clang" : [ "-fsanitize=address" ] + } + +cxx_files = [ "tester.cc" ] +c_files = [ ] + +# You can register your own toolchain through register_toolchain function +def register_toolchain(ninja): + pass + diff --git a/tests/issue-177.mtl b/tests/issue-177.mtl new file mode 100644 index 00000000..d3a1c7a6 --- /dev/null +++ b/tests/issue-177.mtl @@ -0,0 +1,24 @@ +newmtl white +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 + +newmtl red +Ka 0 0 0 +Kd 1 0 0 +Ks 0 0 0 + +newmtl green +Ka 0 0 0 +Kd 0 1 0 +Ks 0 0 0 + +newmtl blue +Ka 0 0 0 +Kd 0 0 1 +Ks 0 0 0 + +newmtl light +Ka 20 20 20 +Kd 1 1 1 +Ks 0 0 0 diff --git a/tests/issue-177.obj b/tests/issue-177.obj new file mode 100644 index 00000000..0d0ac684 --- /dev/null +++ b/tests/issue-177.obj @@ -0,0 +1,31 @@ +mtllib issue-177.mtl + +v 0.000000 2.000000 2.000000 +v 0.000000 0.000000 2.000000 +v 2.000000 0.000000 2.000000 +v 2.000000 2.000000 2.000000 +v 0.000000 2.000000 0.000000 +v 0.000000 0.000000 0.000000 +v 2.000000 0.000000 0.000000 +v 2.000000 2.000000 0.000000 +# 8 vertices + +g front cube +usemtl white +f 1 2 3 4 +g back cube +# expects white material +f 8 7 6 5 +g right cube +usemtl red +f 4 3 7 8 +g top cube +usemtl white +f 5 1 4 8 +g left cube +usemtl green +f 5 6 2 1 +g bottom cube +usemtl white +f 2 6 7 3 +# 6 elements diff --git a/tests/kuroga.py b/tests/kuroga.py new file mode 100755 index 00000000..56d3f863 --- /dev/null +++ b/tests/kuroga.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python + +# +# Kuroga, single python file meta-build system for ninja +# https://github.com/lighttransport/kuroga +# +# Requirements: python 2.6 or 2.7 +# +# Usage: $ python kuroga.py input.py +# + +import imp +import re +import textwrap +import glob +import os +import sys + +# gcc preset +def add_gnu_rule(ninja): + ninja.rule('gnucxx', description='CXX $out', + command='$gnucxx -MMD -MF $out.d $gnudefines $gnuincludes $gnucxxflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('gnucc', description='CC $out', + command='$gnucc -MMD -MF $out.d $gnudefines $gnuincludes $gnucflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('gnulink', description='LINK $out', pool='link_pool', + command='$gnuld -o $out $in $libs $gnuldflags') + ninja.rule('gnuar', description='AR $out', pool='link_pool', + command='$gnuar rsc $out $in') + ninja.rule('gnustamp', description='STAMP $out', command='touch $out') + ninja.newline() + + ninja.variable('gnucxx', 'g++') + ninja.variable('gnucc', 'gcc') + ninja.variable('gnuld', '$gnucxx') + ninja.variable('gnuar', 'ar') + ninja.newline() + +# clang preset +def add_clang_rule(ninja): + ninja.rule('clangcxx', description='CXX $out', + command='$clangcxx -MMD -MF $out.d $clangdefines $clangincludes $clangcxxflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('clangcc', description='CC $out', + command='$clangcc -MMD -MF $out.d $clangdefines $clangincludes $clangcflags -c $in -o $out', + depfile='$out.d', deps='gcc') + ninja.rule('clanglink', description='LINK $out', pool='link_pool', + command='$clangld -o $out $in $libs $clangldflags') + ninja.rule('clangar', description='AR $out', pool='link_pool', + command='$clangar rsc $out $in') + ninja.rule('clangstamp', description='STAMP $out', command='touch $out') + ninja.newline() + + ninja.variable('clangcxx', 'clang++') + ninja.variable('clangcc', 'clang') + ninja.variable('clangld', '$clangcxx') + ninja.variable('clangar', 'ar') + ninja.newline() + +# msvc preset +def add_msvc_rule(ninja): + ninja.rule('msvccxx', description='CXX $out', + command='$msvccxx /TP /showIncludes $msvcdefines $msvcincludes $msvccxxflags -c $in /Fo$out', + depfile='$out.d', deps='msvc') + ninja.rule('msvccc', description='CC $out', + command='$msvccc /TC /showIncludes $msvcdefines $msvcincludes $msvccflags -c $in /Fo$out', + depfile='$out.d', deps='msvc') + ninja.rule('msvclink', description='LINK $out', pool='link_pool', + command='$msvcld $msvcldflags $in $libs /OUT:$out') + ninja.rule('msvcar', description='AR $out', pool='link_pool', + command='$msvcar $in /OUT:$out') + #ninja.rule('msvcstamp', description='STAMP $out', command='touch $out') + ninja.newline() + + ninja.variable('msvccxx', 'cl.exe') + ninja.variable('msvccc', 'cl.exe') + ninja.variable('msvcld', 'link.exe') + ninja.variable('msvcar', 'lib.exe') + ninja.newline() + +# -- from ninja_syntax.py -- +def escape_path(word): + return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') + +class Writer(object): + def __init__(self, output, width=78): + self.output = output + self.width = width + + def newline(self): + self.output.write('\n') + + def comment(self, text, has_path=False): + for line in textwrap.wrap(text, self.width - 2, break_long_words=False, + break_on_hyphens=False): + self.output.write('# ' + line + '\n') + + def variable(self, key, value, indent=0): + if value is None: + return + if isinstance(value, list): + value = ' '.join(filter(None, value)) # Filter out empty strings. + self._line('%s = %s' % (key, value), indent) + + def pool(self, name, depth): + self._line('pool %s' % name) + self.variable('depth', depth, indent=1) + + def rule(self, name, command, description=None, depfile=None, + generator=False, pool=None, restat=False, rspfile=None, + rspfile_content=None, deps=None): + self._line('rule %s' % name) + self.variable('command', command, indent=1) + if description: + self.variable('description', description, indent=1) + if depfile: + self.variable('depfile', depfile, indent=1) + if generator: + self.variable('generator', '1', indent=1) + if pool: + self.variable('pool', pool, indent=1) + if restat: + self.variable('restat', '1', indent=1) + if rspfile: + self.variable('rspfile', rspfile, indent=1) + if rspfile_content: + self.variable('rspfile_content', rspfile_content, indent=1) + if deps: + self.variable('deps', deps, indent=1) + + def build(self, outputs, rule, inputs=None, implicit=None, order_only=None, + variables=None): + outputs = as_list(outputs) + out_outputs = [escape_path(x) for x in outputs] + all_inputs = [escape_path(x) for x in as_list(inputs)] + + if implicit: + implicit = [escape_path(x) for x in as_list(implicit)] + all_inputs.append('|') + all_inputs.extend(implicit) + if order_only: + order_only = [escape_path(x) for x in as_list(order_only)] + all_inputs.append('||') + all_inputs.extend(order_only) + + self._line('build %s: %s' % (' '.join(out_outputs), + ' '.join([rule] + all_inputs))) + + if variables: + if isinstance(variables, dict): + iterator = iter(variables.items()) + else: + iterator = iter(variables) + + for key, val in iterator: + self.variable(key, val, indent=1) + + return outputs + + def include(self, path): + self._line('include %s' % path) + + def subninja(self, path): + self._line('subninja %s' % path) + + def default(self, paths): + self._line('default %s' % ' '.join(as_list(paths))) + + def _count_dollars_before_index(self, s, i): + """Returns the number of '$' characters right in front of s[i].""" + dollar_count = 0 + dollar_index = i - 1 + while dollar_index > 0 and s[dollar_index] == '$': + dollar_count += 1 + dollar_index -= 1 + return dollar_count + + def _line(self, text, indent=0): + """Write 'text' word-wrapped at self.width characters.""" + leading_space = ' ' * indent + while len(leading_space) + len(text) > self.width: + # The text is too wide; wrap if possible. + + # Find the rightmost space that would obey our width constraint and + # that's not an escaped space. + available_space = self.width - len(leading_space) - len(' $') + space = available_space + while True: + space = text.rfind(' ', 0, space) + if (space < 0 or + self._count_dollars_before_index(text, space) % 2 == 0): + break + + if space < 0: + # No such space; just use the first unescaped space we can find. + space = available_space - 1 + while True: + space = text.find(' ', space + 1) + if (space < 0 or + self._count_dollars_before_index(text, space) % 2 == 0): + break + if space < 0: + # Give up on breaking. + break + + self.output.write(leading_space + text[0:space] + ' $\n') + text = text[space+1:] + + # Subsequent lines are continuations, so indent them. + leading_space = ' ' * (indent+2) + + self.output.write(leading_space + text + '\n') + + def close(self): + self.output.close() + + +def as_list(input): + if input is None: + return [] + if isinstance(input, list): + return input + return [input] + +# -- end from ninja_syntax.py -- + +def gen(ninja, toolchain, config): + + ninja.variable('ninja_required_version', '1.4') + ninja.newline() + + if hasattr(config, "builddir"): + builddir = config.builddir[toolchain] + ninja.variable(toolchain + 'builddir', builddir) + else: + builddir = '' + + ninja.variable(toolchain + 'defines', config.defines[toolchain] or []) + ninja.variable(toolchain + 'includes', config.includes[toolchain] or []) + ninja.variable(toolchain + 'cflags', config.cflags[toolchain] or []) + ninja.variable(toolchain + 'cxxflags', config.cxxflags[toolchain] or []) + ninja.variable(toolchain + 'ldflags', config.ldflags[toolchain] or []) + ninja.newline() + + if hasattr(config, "link_pool_depth"): + ninja.pool('link_pool', depth=config.link_pool_depth) + else: + ninja.pool('link_pool', depth=4) + ninja.newline() + + # Add default toolchain(gnu, clang and msvc) + add_gnu_rule(ninja) + add_clang_rule(ninja) + add_msvc_rule(ninja) + + obj_files = [] + + cc = toolchain + 'cc' + cxx = toolchain + 'cxx' + link = toolchain + 'link' + ar = toolchain + 'ar' + + if hasattr(config, "cxx_files"): + for src in config.cxx_files: + srcfile = src + obj = os.path.splitext(srcfile)[0] + '.o' + obj = os.path.join(builddir, obj); + obj_files.append(obj) + ninja.build(obj, cxx, srcfile) + ninja.newline() + + if hasattr(config, "c_files"): + for src in config.c_files: + srcfile = src + obj = os.path.splitext(srcfile)[0] + '.o' + obj = os.path.join(builddir, obj); + obj_files.append(obj) + ninja.build(obj, cc, srcfile) + ninja.newline() + + targetlist = [] + if hasattr(config, "exe"): + ninja.build(config.exe, link, obj_files) + targetlist.append(config.exe) + + if hasattr(config, "staticlib"): + ninja.build(config.staticlib, ar, obj_files) + targetlist.append(config.staticlib) + + ninja.build('all', 'phony', targetlist) + ninja.newline() + + ninja.default('all') + +def main(): + if len(sys.argv) < 2: + print("Usage: python kuroga.py config.py") + sys.exit(1) + + config = imp.load_source("config", sys.argv[1]) + + f = open('build.ninja', 'w') + ninja = Writer(f) + + if hasattr(config, "register_toolchain"): + config.register_toolchain(ninja) + + gen(ninja, config.toolchain, config) + f.close() + +main() diff --git a/tests/tester.cc b/tests/tester.cc new file mode 100644 index 00000000..b5c4d0db --- /dev/null +++ b/tests/tester.cc @@ -0,0 +1,1636 @@ +#define TINYOBJLOADER_IMPLEMENTATION +#include "../tiny_obj_loader.h" + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wmissing-declarations" +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wswitch-default" +#endif + + +#include "acutest.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#include +#include +#include +#include +#include +#include + +template +static bool FloatEquals(const T& a, const T& b) { + // Edit eps value as you wish. + const T eps = std::numeric_limits::epsilon() * static_cast(100); + + const T abs_diff = std::abs(a - b); + + if (abs_diff < eps) { + return true; + } else { + return false; + } +} + +static void PrintInfo(const tinyobj::attrib_t& attrib, + const std::vector& shapes, + const std::vector& materials, + bool triangulate = true) { + std::cout << "# of vertices : " << (attrib.vertices.size() / 3) << std::endl; + std::cout << "# of normals : " << (attrib.normals.size() / 3) << std::endl; + std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2) + << std::endl; + + std::cout << "# of shapes : " << shapes.size() << std::endl; + std::cout << "# of materials : " << materials.size() << std::endl; + + for (size_t v = 0; v < attrib.vertices.size() / 3; v++) { + printf(" v[%ld] = (%f, %f, %f)\n", v, + static_cast(attrib.vertices[3 * v + 0]), + static_cast(attrib.vertices[3 * v + 1]), + static_cast(attrib.vertices[3 * v + 2])); + } + + for (size_t v = 0; v < attrib.normals.size() / 3; v++) { + printf(" n[%ld] = (%f, %f, %f)\n", v, + static_cast(attrib.normals[3 * v + 0]), + static_cast(attrib.normals[3 * v + 1]), + static_cast(attrib.normals[3 * v + 2])); + } + + for (size_t v = 0; v < attrib.texcoords.size() / 2; v++) { + printf(" uv[%ld] = (%f, %f)\n", v, + static_cast(attrib.texcoords[2 * v + 0]), + static_cast(attrib.texcoords[2 * v + 1])); + } + + for (size_t i = 0; i < shapes.size(); i++) { + printf("shape[%ld].name = %s\n", i, shapes[i].name.c_str()); + printf("Size of shape[%ld].indices: %ld\n", i, + shapes[i].mesh.indices.size()); + + if (triangulate) { + printf("Size of shape[%ld].material_ids: %ld\n", i, + shapes[i].mesh.material_ids.size()); + assert((shapes[i].mesh.indices.size() % 3) == 0); + for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { + tinyobj::index_t i0 = shapes[i].mesh.indices[3 * f + 0]; + tinyobj::index_t i1 = shapes[i].mesh.indices[3 * f + 1]; + tinyobj::index_t i2 = shapes[i].mesh.indices[3 * f + 2]; + printf(" idx[%ld] = %d/%d/%d, %d/%d/%d, %d/%d/%d. mat_id = %d\n", f, + i0.vertex_index, i0.normal_index, i0.texcoord_index, + i1.vertex_index, i1.normal_index, i1.texcoord_index, + i2.vertex_index, i2.normal_index, i2.texcoord_index, + shapes[i].mesh.material_ids[f]); + } + } else { + for (size_t f = 0; f < shapes[i].mesh.indices.size(); f++) { + tinyobj::index_t idx = shapes[i].mesh.indices[f]; + printf(" idx[%ld] = %d/%d/%d\n", f, idx.vertex_index, idx.normal_index, + idx.texcoord_index); + } + + printf("Size of shape[%ld].material_ids: %ld\n", i, + shapes[i].mesh.material_ids.size()); + assert(shapes[i].mesh.material_ids.size() == + shapes[i].mesh.num_face_vertices.size()); + for (size_t m = 0; m < shapes[i].mesh.material_ids.size(); m++) { + printf(" material_id[%ld] = %d\n", m, shapes[i].mesh.material_ids[m]); + } + } + + printf("shape[%ld].num_faces: %ld\n", i, + shapes[i].mesh.num_face_vertices.size()); + for (size_t v = 0; v < shapes[i].mesh.num_face_vertices.size(); v++) { + printf(" num_vertices[%ld] = %ld\n", v, + static_cast(shapes[i].mesh.num_face_vertices[v])); + } + + // printf("shape[%ld].vertices: %ld\n", i, shapes[i].mesh.positions.size()); + // assert((shapes[i].mesh.positions.size() % 3) == 0); + // for (size_t v = 0; v < shapes[i].mesh.positions.size() / 3; v++) { + // printf(" v[%ld] = (%f, %f, %f)\n", v, + // static_cast(shapes[i].mesh.positions[3*v+0]), + // static_cast(shapes[i].mesh.positions[3*v+1]), + // static_cast(shapes[i].mesh.positions[3*v+2])); + //} + + printf("shape[%ld].num_tags: %ld\n", i, shapes[i].mesh.tags.size()); + for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { + printf(" tag[%ld] = %s ", t, shapes[i].mesh.tags[t].name.c_str()); + printf(" ints: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) { + printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); + if (j < (shapes[i].mesh.tags[t].intValues.size() - 1)) { + printf(", "); + } + } + printf("]"); + + printf(" floats: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) { + printf("%f", static_cast( + shapes[i].mesh.tags[t].floatValues[j])); + if (j < (shapes[i].mesh.tags[t].floatValues.size() - 1)) { + printf(", "); + } + } + printf("]"); + + printf(" strings: ["); + for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size() - 1)) { + printf(", "); + } + } + printf("]"); + printf("\n"); + } + } + + for (size_t i = 0; i < materials.size(); i++) { + printf("material[%ld].name = %s\n", i, materials[i].name.c_str()); + printf(" material.Ka = (%f, %f ,%f)\n", + static_cast(materials[i].ambient[0]), + static_cast(materials[i].ambient[1]), + static_cast(materials[i].ambient[2])); + printf(" material.Kd = (%f, %f ,%f)\n", + static_cast(materials[i].diffuse[0]), + static_cast(materials[i].diffuse[1]), + static_cast(materials[i].diffuse[2])); + printf(" material.Ks = (%f, %f ,%f)\n", + static_cast(materials[i].specular[0]), + static_cast(materials[i].specular[1]), + static_cast(materials[i].specular[2])); + printf(" material.Tr = (%f, %f ,%f)\n", + static_cast(materials[i].transmittance[0]), + static_cast(materials[i].transmittance[1]), + static_cast(materials[i].transmittance[2])); + printf(" material.Ke = (%f, %f ,%f)\n", + static_cast(materials[i].emission[0]), + static_cast(materials[i].emission[1]), + static_cast(materials[i].emission[2])); + printf(" material.Ns = %f\n", + static_cast(materials[i].shininess)); + printf(" material.Ni = %f\n", static_cast(materials[i].ior)); + printf(" material.dissolve = %f\n", + static_cast(materials[i].dissolve)); + printf(" material.illum = %d\n", materials[i].illum); + printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); + printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); + printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); + printf(" material.map_Ns = %s\n", + materials[i].specular_highlight_texname.c_str()); + printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); + printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); + printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); + printf(" material.refl = %s\n", materials[i].reflection_texname.c_str()); + std::map::const_iterator it( + materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd( + materials[i].unknown_parameter.end()); + + for (; it != itEnd; it++) { + printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); + } + printf("\n"); + } +} + +static bool TestLoadObj(const char* filename, const char* basepath = NULL, + bool triangulate = true) { + std::cout << "Loading " << filename << std::endl; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + filename, basepath, triangulate); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + if (!ret) { + printf("Failed to load/parse .obj.\n"); + return false; + } + + PrintInfo(attrib, shapes, materials, triangulate); + + return true; +} + +static bool TestLoadObjFromPreopenedFile(const char* filename, + const char* basepath = NULL, + bool readMaterials = true, + bool triangulate = true) { + std::string fullFilename = std::string(basepath) + filename; + std::cout << "Loading " << fullFilename << std::endl; + + std::ifstream fileStream(fullFilename.c_str()); + + if (!fileStream) { + std::cerr << "Could not find specified file: " << fullFilename << std::endl; + return false; + } + + tinyobj::MaterialStreamReader materialStreamReader(fileStream); + tinyobj::MaterialStreamReader* materialReader = + readMaterials ? &materialStreamReader : NULL; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &fileStream, materialReader); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + if (!ret) { + printf("Failed to load/parse .obj.\n"); + return false; + } + + std::cout << "Loaded material count: " << materials.size() << "\n"; + + return true; +} + +static bool TestStreamLoadObj() { + std::cout << "Stream Loading " << std::endl; + + std::stringstream objStream; + objStream << "mtllib cube.mtl\n" + "\n" + "v 0.000000 2.000000 2.000000\n" + "v 0.000000 0.000000 2.000000\n" + "v 2.000000 0.000000 2.000000\n" + "v 2.000000 2.000000 2.000000\n" + "v 0.000000 2.000000 0.000000\n" + "v 0.000000 0.000000 0.000000\n" + "v 2.000000 0.000000 0.000000\n" + "v 2.000000 2.000000 0.000000\n" + "# 8 vertices\n" + "\n" + "g front cube\n" + "usemtl white\n" + "f 1 2 3 4\n" + "g back cube\n" + "# expects white material\n" + "f 8 7 6 5\n" + "g right cube\n" + "usemtl red\n" + "f 4 3 7 8\n" + "g top cube\n" + "usemtl white\n" + "f 5 1 4 8\n" + "g left cube\n" + "usemtl green\n" + "f 5 6 2 1\n" + "g bottom cube\n" + "usemtl white\n" + "f 2 6 7 3\n" + "# 6 elements"; + + std::string matStream( + "newmtl white\n" + "Ka 0 0 0\n" + "Kd 1 1 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl red\n" + "Ka 0 0 0\n" + "Kd 1 0 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl green\n" + "Ka 0 0 0\n" + "Kd 0 1 0\n" + "Ks 0 0 0\n" + "\n" + "newmtl blue\n" + "Ka 0 0 0\n" + "Kd 0 0 1\n" + "Ks 0 0 0\n" + "\n" + "newmtl light\n" + "Ka 20 20 20\n" + "Kd 1 1 1\n" + "Ks 0 0 0"); + + using namespace tinyobj; + class MaterialStringStreamReader : public MaterialReader { + public: + MaterialStringStreamReader(const std::string& matSStream) + : m_matSStream(matSStream) {} + virtual ~MaterialStringStreamReader() {} + virtual bool operator()(const std::string& matId, + std::vector* materials, + std::map* matMap, + std::string* warn, std::string* err) { + (void)matId; + (void)warn; + (void)err; + std::string warning; + std::string error_msg; + LoadMtl(matMap, materials, &m_matSStream, &warning, &error_msg); + return true; + } + + private: + std::stringstream m_matSStream; + }; + + MaterialStringStreamReader matSSReader(matStream); + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + &objStream, &matSSReader); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + if (!ret) { + return false; + } + + PrintInfo(attrib, shapes, materials); + + return true; +} + +const char* gMtlBasePath = "../models/"; + +void test_cornell_box() { + TEST_CHECK(true == TestLoadObj("../models/cornell_box.obj", gMtlBasePath)); +} + +void test_catmark_torus_creases0() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/catmark_torus_creases0.obj", + gMtlBasePath, /*triangulate*/ false); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(8 == shapes[0].mesh.tags.size()); +} + +void test_pbr() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/pbr-mat-ext.obj", gMtlBasePath, + /*triangulate*/ false); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(FloatEquals(0.2f, materials[0].roughness)); + TEST_CHECK(FloatEquals(0.3f, materials[0].metallic)); + TEST_CHECK(FloatEquals(0.4f, materials[0].sheen)); + TEST_CHECK(FloatEquals(0.5f, materials[0].clearcoat_thickness)); + TEST_CHECK(FloatEquals(0.6f, materials[0].clearcoat_roughness)); + TEST_CHECK(FloatEquals(0.7f, materials[0].anisotropy)); + TEST_CHECK(FloatEquals(0.8f, materials[0].anisotropy_rotation)); + TEST_CHECK(0 == materials[0].roughness_texname.compare("roughness.tex")); + TEST_CHECK(0 == materials[0].metallic_texname.compare("metallic.tex")); + TEST_CHECK(0 == materials[0].sheen_texname.compare("sheen.tex")); + TEST_CHECK(0 == materials[0].emissive_texname.compare("emissive.tex")); + TEST_CHECK(0 == materials[0].normal_texname.compare("normalmap.tex")); +} + +void test_stream_load() { TEST_CHECK(true == TestStreamLoadObj()); } + +void test_stream_load_from_file_skipping_materials() { + TEST_CHECK(true == TestLoadObjFromPreopenedFile( + "../models/pbr-mat-ext.obj", gMtlBasePath, + /*readMaterials*/ false, /*triangulate*/ false)); +} + +void test_stream_load_from_file_with_materials() { + TEST_CHECK(true == TestLoadObjFromPreopenedFile( + "../models/pbr-mat-ext.obj", gMtlBasePath, + /*readMaterials*/ true, /*triangulate*/ false)); +} + +void test_trailing_whitespace_in_mtl_issue92() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-92.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(0 == materials[0].diffuse_texname.compare("tmp.png")); +} + +void test_transmittance_filter_issue95() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-95.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(FloatEquals(0.1f, materials[0].transmittance[0])); + TEST_CHECK(FloatEquals(0.2f, materials[0].transmittance[1])); + TEST_CHECK(FloatEquals(0.3f, materials[0].transmittance[2])); +} + +void test_transmittance_filter_Tf_issue95() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-95-2.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(FloatEquals(0.1f, materials[0].transmittance[0])); + TEST_CHECK(FloatEquals(0.2f, materials[0].transmittance[1])); + TEST_CHECK(FloatEquals(0.3f, materials[0].transmittance[2])); +} + +void test_transmittance_filter_Kt_issue95() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-95.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(FloatEquals(0.1f, materials[0].transmittance[0])); + TEST_CHECK(FloatEquals(0.2f, materials[0].transmittance[1])); + TEST_CHECK(FloatEquals(0.3f, materials[0].transmittance[2])); +} + +void test_usemtl_at_last_line_issue104() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/usemtl-issue-104.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); +} + +void test_texture_opts_issue85() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/texture-options-issue-85.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(3 == materials.size()); + TEST_CHECK(0 == materials[0].name.compare("default")); + TEST_CHECK(0 == materials[1].name.compare("bm2")); + TEST_CHECK(0 == materials[2].name.compare("bm3")); + TEST_CHECK(true == materials[0].ambient_texopt.clamp); + TEST_CHECK(FloatEquals(0.1f, materials[0].diffuse_texopt.origin_offset[0])); + TEST_CHECK(FloatEquals(0.0f, materials[0].diffuse_texopt.origin_offset[1])); + TEST_CHECK(FloatEquals(0.0f, materials[0].diffuse_texopt.origin_offset[2])); + TEST_CHECK(FloatEquals(0.1f, materials[0].specular_texopt.scale[0])); + TEST_CHECK(FloatEquals(0.2f, materials[0].specular_texopt.scale[1])); + TEST_CHECK(FloatEquals(1.0f, materials[0].specular_texopt.scale[2])); + TEST_CHECK( + FloatEquals(0.1f, materials[0].specular_highlight_texopt.turbulence[0])); + TEST_CHECK( + FloatEquals(0.2f, materials[0].specular_highlight_texopt.turbulence[1])); + TEST_CHECK( + FloatEquals(0.3f, materials[0].specular_highlight_texopt.turbulence[2])); + TEST_CHECK(FloatEquals(3.0f, materials[0].bump_texopt.bump_multiplier)); + + TEST_CHECK( + FloatEquals(0.1f, materials[1].specular_highlight_texopt.brightness)); + TEST_CHECK( + FloatEquals(0.3f, materials[1].specular_highlight_texopt.contrast)); + TEST_CHECK('r' == materials[1].bump_texopt.imfchan); + + TEST_CHECK(tinyobj::TEXTURE_TYPE_SPHERE == materials[2].diffuse_texopt.type); + TEST_CHECK(tinyobj::TEXTURE_TYPE_CUBE_TOP == + materials[2].specular_texopt.type); + TEST_CHECK(tinyobj::TEXTURE_TYPE_CUBE_BOTTOM == + materials[2].specular_highlight_texopt.type); + TEST_CHECK(tinyobj::TEXTURE_TYPE_CUBE_LEFT == + materials[2].ambient_texopt.type); + TEST_CHECK(tinyobj::TEXTURE_TYPE_CUBE_RIGHT == + materials[2].alpha_texopt.type); + TEST_CHECK(tinyobj::TEXTURE_TYPE_CUBE_FRONT == materials[2].bump_texopt.type); + TEST_CHECK(tinyobj::TEXTURE_TYPE_CUBE_BACK == + materials[2].displacement_texopt.type); +} + +void test_mtllib_multiple_filenames_issue112() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/mtllib-multiple-files-issue-112.obj", + gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == materials.size()); +} + +void test_tr_and_d_issue43() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/tr-and-d-issue-43.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(2 == materials.size()); + + TEST_CHECK(FloatEquals(0.75f, materials[0].dissolve)); + TEST_CHECK(FloatEquals(0.75f, materials[1].dissolve)); +} + +void test_refl() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/refl.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + PrintInfo(attrib, shapes, materials); + + TEST_CHECK(true == ret); + TEST_CHECK(5 == materials.size()); + + TEST_CHECK(materials[0].reflection_texname.compare("reflection.tga") == 0); +} + +void test_map_Bump() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/map-bump.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + PrintInfo(attrib, shapes, materials); + + TEST_CHECK(true == ret); + TEST_CHECK(2 == materials.size()); + + TEST_CHECK(materials[0].bump_texname.compare("bump.jpg") == 0); +} + +void test_g_ignored_issue138() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-138.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + PrintInfo(attrib, shapes, materials); + + TEST_CHECK(true == ret); + TEST_CHECK(2 == shapes.size()); + TEST_CHECK(2 == materials.size()); +} + +void test_vertex_col_ext_issue144() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/cube-vertexcol.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + // PrintInfo(attrib, shapes, materials); + + TEST_CHECK(true == ret); + TEST_CHECK((8 * 3) == attrib.colors.size()); + + TEST_CHECK(FloatEquals(0.0f, attrib.colors[3 * 0 + 0])); + TEST_CHECK(FloatEquals(0.0f, attrib.colors[3 * 0 + 1])); + TEST_CHECK(FloatEquals(0.0f, attrib.colors[3 * 0 + 2])); + + TEST_CHECK(FloatEquals(0.0f, attrib.colors[3 * 1 + 0])); + TEST_CHECK(FloatEquals(0.0f, attrib.colors[3 * 1 + 1])); + TEST_CHECK(FloatEquals(1.0f, attrib.colors[3 * 1 + 2])); + + TEST_CHECK(FloatEquals(1.0f, attrib.colors[3 * 4 + 0])); + + TEST_CHECK(FloatEquals(1.0f, attrib.colors[3 * 7 + 0])); + TEST_CHECK(FloatEquals(1.0f, attrib.colors[3 * 7 + 1])); + TEST_CHECK(FloatEquals(1.0f, attrib.colors[3 * 7 + 2])); +} + +void test_norm_texopts() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/norm-texopt.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(FloatEquals(3.0f, materials[0].normal_texopt.bump_multiplier)); +} + +void test_zero_face_idx_value_issue140() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-140-zero-face-idx.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(false == ret); + TEST_CHECK(!err.empty()); +} + +void test_invalid_relative_vertex_index() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/invalid-relative-vertex-index.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(false == ret); + TEST_CHECK(!err.empty()); +} + +void test_invalid_texture_vertex_index() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/invalid-relative-texture-vertex-index.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(false == ret); + TEST_CHECK(!err.empty()); +} + +void test_texture_name_whitespace_issue145() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/texture-filename-with-whitespace.obj", + gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(err.empty()); + TEST_CHECK(2 < materials.size()); + + TEST_CHECK(0 == materials[0].diffuse_texname.compare("texture 01.png")); + TEST_CHECK(0 == materials[1].bump_texname.compare("bump 01.png")); + TEST_CHECK(FloatEquals(2.0f, materials[1].bump_texopt.bump_multiplier)); +} + +void test_smoothing_group_issue162() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-162-smoothing-group.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(2 == shapes.size()); + + TEST_CHECK(2 == shapes[0].mesh.smoothing_group_ids.size()); + TEST_CHECK(1 == shapes[0].mesh.smoothing_group_ids[0]); + TEST_CHECK(1 == shapes[0].mesh.smoothing_group_ids[1]); + + TEST_CHECK(10 == shapes[1].mesh.smoothing_group_ids.size()); + TEST_CHECK(0 == shapes[1].mesh.smoothing_group_ids[0]); + TEST_CHECK(0 == shapes[1].mesh.smoothing_group_ids[1]); + TEST_CHECK(3 == shapes[1].mesh.smoothing_group_ids[2]); + TEST_CHECK(3 == shapes[1].mesh.smoothing_group_ids[3]); + TEST_CHECK(4 == shapes[1].mesh.smoothing_group_ids[4]); + TEST_CHECK(4 == shapes[1].mesh.smoothing_group_ids[5]); + TEST_CHECK(0 == shapes[1].mesh.smoothing_group_ids[6]); + TEST_CHECK(0 == shapes[1].mesh.smoothing_group_ids[7]); + TEST_CHECK(6 == shapes[1].mesh.smoothing_group_ids[8]); + TEST_CHECK(6 == shapes[1].mesh.smoothing_group_ids[9]); +} + +void test_invalid_face_definition() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/invalid-face-definition.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(0 == shapes[0].mesh.indices.size()); +} + +void test_Empty_mtl_basedir_issue177() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + + // A case where the user explicitly provides an empty string + // Win32 specific? + const char* userBaseDir = ""; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "issue-177.obj", userBaseDir); + + // if mtl loading fails, we get an warning message here + ret &= warn.empty(); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); +} + +void test_line_primitive() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/line-prim.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(8 == shapes[0].lines.indices.size()); + TEST_CHECK(2 == shapes[0].lines.num_line_vertices.size()); +} + +void test_points_primitive() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/points-prim.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(8 == shapes[0].points.indices.size()); +} + +void test_multiple_group_names() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/cube.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(6 == shapes.size()); + TEST_CHECK(0 == shapes[0].name.compare("front cube")); + TEST_CHECK(0 == shapes[1].name.compare("back cube")); // multiple whitespaces + // are aggregated as + // single white space. +} + +void test_initialize_all_texopts() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/cornell_box.obj", gMtlBasePath, false); + + TEST_CHECK(ret == true); + TEST_CHECK(0 < materials.size()); + +#define TEST_CHECK_DEFAULT_TEXOPT(texopt) \ + TEST_CHECK(tinyobj::TEXTURE_TYPE_NONE == texopt.type); \ + TEST_CHECK(0.0 == texopt.brightness); \ + TEST_CHECK(1.0 == texopt.contrast); \ + TEST_CHECK(false == texopt.clamp); \ + TEST_CHECK(true == texopt.blendu); \ + TEST_CHECK(true == texopt.blendv); \ + TEST_CHECK(1.0 == texopt.bump_multiplier); \ + for (int j = 0; j < 3; j++) { \ + TEST_CHECK(0.0 == texopt.origin_offset[j]); \ + TEST_CHECK(1.0 == texopt.scale[j]); \ + TEST_CHECK(0.0 == texopt.turbulence[j]); \ + } + for (size_t i = 0; i < materials.size(); i++) { + TEST_CHECK_DEFAULT_TEXOPT(materials[i].ambient_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].diffuse_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].specular_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].specular_highlight_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].bump_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].displacement_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].alpha_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].reflection_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].roughness_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].metallic_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].sheen_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].emissive_texopt); + TEST_CHECK_DEFAULT_TEXOPT(materials[i].normal_texopt); + } +#undef TEST_CHECK_DEFAULT_TEXOPT +} + +void test_colorspace_issue184() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/colorspace-issue-184.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(0 == materials[0].diffuse_texopt.colorspace.compare("sRGB")); + TEST_CHECK(0 == materials[0].specular_texopt.colorspace.size()); + TEST_CHECK(0 == materials[0].bump_texopt.colorspace.compare("linear")); +} + +void test_leading_decimal_dots_issue201() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/leading-decimal-dot-issue-201.obj", + gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(FloatEquals(0.8e-1f, attrib.vertices[0])); + TEST_CHECK(FloatEquals(-.7e+2f, attrib.vertices[1])); + TEST_CHECK(FloatEquals(.575869f, attrib.vertices[3])); + TEST_CHECK(FloatEquals(-.666304f, attrib.vertices[4])); + TEST_CHECK(FloatEquals(.940448f, attrib.vertices[6])); +} + +void test_mtl_default_search_path_v2_API_issue208() { + tinyobj::ObjReader reader; + + bool ret = reader.ParseFromFile("../models/cornell_box.obj"); + + std::cout << "WARN: " << reader.Warning() << "\n"; + + TEST_CHECK(ret == true); + TEST_CHECK(reader.Warning().empty()); +} + +void test_leading_zero_in_exponent_notation_issue210() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/leading-zero-in-exponent-notation-issue-210.obj", + gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(FloatEquals(0.8e-001f, attrib.vertices[0])); + TEST_CHECK(FloatEquals(-.7e+02f, attrib.vertices[1])); + + std::cout << "exp " << 0.8e-01 << std::endl; + std::cout << "bora " << attrib.vertices[0] << std::endl; +} + +void test_usemtl_then_o_issue235() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/issue-235-usemtl-then-o.obj", + gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(2 == shapes.size()); + TEST_CHECK(2 == materials.size()); + TEST_CHECK(4 == shapes[1].mesh.indices[0].vertex_index); +} + +void test_mtl_searchpaths_issue244() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + // .mtl is located at ./assets/issue-244.mtl +#if _WIN32 + std::string search_paths("../;../models;./assets"); +#else + std::string search_paths("../:../models:./assets"); +#endif + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/issue-244-mtl-searchpaths.obj", + search_paths.c_str()); + + TEST_CHECK(warn.empty()); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(2 == shapes.size()); + TEST_CHECK(2 == materials.size()); + TEST_CHECK(4 == shapes[1].mesh.indices[0].vertex_index); +} + +void test_usemtl_whitespace_issue246() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/issue-246-usemtl-whitespace.obj", + gMtlBasePath); + + TEST_CHECK(warn.empty()); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + TEST_CHECK(1 == materials.size()); + TEST_CHECK(0 == shapes[0].mesh.material_ids[0]); +} + +void test_texres_texopt_issue248() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/issue-248-texres-texopt.obj", + gMtlBasePath); + + TEST_CHECK(warn.empty()); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 < materials.size()); + TEST_CHECK(512 == materials[0].diffuse_texopt.texture_resolution); + TEST_CHECK("input.jpg" == materials[0].diffuse_texname); +} + +void test_mtl_filename_with_whitespace_issue46() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/mtl filename with whitespace issue46.obj", + gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + TEST_CHECK(true == ret); + TEST_CHECK(1 == materials.size()); + TEST_CHECK("green" == materials[0].name); +} + +void test_face_missing_issue295() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/issue-295-trianguation-failure.obj", + gMtlBasePath, /* triangualte */true); + + TEST_CHECK(warn.empty()); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(1 == shapes.size()); + + // 14 quad faces are triangulated into 28 triangles. + TEST_CHECK(28 == shapes[0].mesh.num_face_vertices.size()); + TEST_CHECK(28 == shapes[0].mesh.smoothing_group_ids.size()); + TEST_CHECK(28 == shapes[0].mesh.material_ids.size()); + TEST_CHECK((3 * 28) == shapes[0].mesh.indices.size()); // 28 triangle faces x 3 +} + +void test_comment_issue389() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj( + &attrib, &shapes, &materials, &warn, &err, + "../models/issue-389-comment.obj", + gMtlBasePath, /* triangualte */false); + + TEST_CHECK(warn.empty()); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); +} + +void test_default_kd_for_multiple_materials_issue391() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/issue-391.obj", gMtlBasePath); + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + const tinyobj::real_t kGrey[] = {0.6, 0.6, 0.6}; + const tinyobj::real_t kRed[] = {1.0, 0.0, 0.0}; + + TEST_CHECK(true == ret); + TEST_CHECK(2 == materials.size()); + for (size_t i = 0; i < materials.size(); ++i) { + const tinyobj::material_t& material = materials[i]; + if (material.name == "has_map") { + for (int i = 0; i < 3; ++i) TEST_CHECK(material.diffuse[i] == kGrey[i]); + } else if (material.name == "has_kd") { + for (int i = 0; i < 3; ++i) TEST_CHECK(material.diffuse[i] == kRed[i]); + } else { + std::cerr << "Unexpected material found!" << std::endl; + TEST_CHECK(false); + } + } +} + +void test_removeUtf8Bom() { + // Basic input with BOM + std::string withBOM = "\xEF\xBB\xBFhello world"; + TEST_CHECK(tinyobj::removeUtf8Bom(withBOM) == "hello world"); + + // Input without BOM + std::string noBOM = "hello world"; + TEST_CHECK(tinyobj::removeUtf8Bom(noBOM) == "hello world"); + + // Leaves short string unchanged + std::string shortStr = "\xEF"; + TEST_CHECK(tinyobj::removeUtf8Bom(shortStr) == shortStr); + + std::string shortStr2 = "\xEF\xBB"; + TEST_CHECK(tinyobj::removeUtf8Bom(shortStr2) == shortStr2); + + // BOM only returns empty string + std::string justBom = "\xEF\xBB\xBF"; + TEST_CHECK(tinyobj::removeUtf8Bom(justBom) == ""); + + // Empty string + std::string emptyStr = ""; + TEST_CHECK(tinyobj::removeUtf8Bom(emptyStr) == ""); +} + +void test_loadObj_with_BOM() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string warn; + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, + "../models/cube_w_BOM.obj", gMtlBasePath); + + if (!warn.empty()) { + std::cout << "WARN: " << warn << std::endl; + } + + if (!err.empty()) { + std::cerr << "ERR: " << err << std::endl; + } + + TEST_CHECK(true == ret); + TEST_CHECK(6 == shapes.size()); + TEST_CHECK(0 == shapes[0].name.compare("front cube")); + TEST_CHECK(0 == shapes[1].name.compare("back cube")); // multiple whitespaces + // are aggregated as + // single white space. +} + + +// Fuzzer test. +// Just check if it does not crash. +// Disable by default since Windows filesystem can't create filename of afl +// testdata +#if 0 + +void test_afl000000", "[AFL]() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "./afl/id:000000,sig:11,src:000000,op:havoc,rep:128", gMtlBasePath); + + TEST_CHECK(true == ret); +} + +void test_afl000001", "[AFL]() { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "./afl/id:000001,sig:11,src:000000,op:havoc,rep:64", gMtlBasePath); + + TEST_CHECK(true == ret); +} +#endif + +#if 0 +int +main( + int argc, + char **argv) +{ + if (argc > 1) { + const char* basepath = NULL; + if (argc > 2) { + basepath = argv[2]; + } + assert(true == TestLoadObj(argv[1], basepath)); + } else { + //assert(true == TestLoadObj("cornell_box.obj")); + //assert(true == TestLoadObj("cube.obj")); + assert(true == TestStreamLoadObj()); + assert(true == TestLoadObj("catmark_torus_creases0.obj", NULL, false)); + } + + return 0; +} +#endif + +TEST_LIST = { + {"cornell_box", test_cornell_box}, + {"catmark_torus_creases0", test_catmark_torus_creases0}, + {"pbr", test_pbr}, + {"stream_load", test_stream_load}, + {"stream_load_from_file_skipping_materials", + test_stream_load_from_file_skipping_materials}, + {"stream_load_from_file_with_materials", + test_stream_load_from_file_with_materials}, + {"trailing_whitespace_in_mtl_issue92", + test_trailing_whitespace_in_mtl_issue92}, + {"transmittance_filter_issue95", test_transmittance_filter_issue95}, + {"transmittance_filter_Tf_issue95", test_transmittance_filter_Tf_issue95}, + {"transmittance_filter_Kt_issue95", test_transmittance_filter_Kt_issue95}, + {"usemtl_at_last_line_issue104", test_usemtl_at_last_line_issue104}, + {"texture_opts_issue85", test_texture_opts_issue85}, + {"mtllib_multiple_filenames_issue112", + test_mtllib_multiple_filenames_issue112}, + {"tr_and_d_issue43", test_tr_and_d_issue43}, + {"refl", test_refl}, + {"map_bump", test_map_Bump}, + {"g_ignored_issue138", test_g_ignored_issue138}, + {"vertex_col_ext_issue144", test_vertex_col_ext_issue144}, + {"norm_texopts", test_norm_texopts}, + {"zero_face_idx_value_issue140", test_zero_face_idx_value_issue140}, + {"texture_name_whitespace_issue145", test_texture_name_whitespace_issue145}, + {"smoothing_group_issue162", test_smoothing_group_issue162}, + {"invalid_face_definition", test_invalid_face_definition}, + {"Empty_mtl_basedir_issue177", test_Empty_mtl_basedir_issue177}, + {"line_primitive", test_line_primitive}, + {"points_primitive", test_points_primitive}, + {"multiple_group_names", test_multiple_group_names}, + {"initialize_all_texopts", test_initialize_all_texopts}, + {"colorspace_issue184", test_colorspace_issue184}, + {"leading_decimal_dots_issue201", test_leading_decimal_dots_issue201}, + {"mtl_default_search_path_v2_API_issue208", + test_mtl_default_search_path_v2_API_issue208}, + {"leading_zero_in_exponent_notation_issue210", + test_leading_zero_in_exponent_notation_issue210}, + {"usemtl_then_o_issue235", + test_usemtl_then_o_issue235}, + {"mtl_searchpaths_issue244", + test_mtl_searchpaths_issue244}, + {"usemtl_whitespece_issue246", + test_usemtl_whitespace_issue246}, + {"texres_texopt_issue248", + test_texres_texopt_issue248}, + {"test_mtl_filename_with_whitespace_issue46", + test_mtl_filename_with_whitespace_issue46}, + {"test_face_missing_issue295", + test_face_missing_issue295}, + {"test_comment_issue389", + test_comment_issue389}, + {"test_invalid_relative_vertex_index", + test_invalid_relative_vertex_index}, + {"test_invalid_texture_vertex_index", + test_invalid_texture_vertex_index}, + {"default_kd_for_multiple_materials_issue391", + test_default_kd_for_multiple_materials_issue391}, + {"test_removeUtf8Bom", test_removeUtf8Bom}, + {"test_loadObj_with_BOM", test_loadObj_with_BOM}, + {NULL, NULL}}; diff --git a/tests/vcbuild.bat b/tests/vcbuild.bat new file mode 100644 index 00000000..e8646735 --- /dev/null +++ b/tests/vcbuild.bat @@ -0,0 +1,4 @@ +chcp 437 +python kuroga.py config-msvc.py +call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86_amd64 +ninja diff --git a/tiny_obj_loader.cc b/tiny_obj_loader.cc old mode 100755 new mode 100644 index 5c283795..e57d0444 --- a/tiny_obj_loader.cc +++ b/tiny_obj_loader.cc @@ -1,729 +1,2 @@ -// -// Copyright 2012-2013, Syoyo Fujita. -// -// Licensed under 2-clause BSD liecense. -// - -// -// version 0.9.6: Support Ni(index of refraction) mtl parameter. -// Parse transmittance material parameter correctly. -// version 0.9.5: Parse multiple group name. -// Add support of specifying the base path to load material file. -// version 0.9.4: Initial suupport of group tag(g) -// version 0.9.3: Fix parsing triple 'x/y/z' -// version 0.9.2: Add more .mtl load support -// version 0.9.1: Add initial .mtl load support -// version 0.9.0: Initial -// - - -#include -#include -#include - -#include -#include -#include -#include -#include - +#define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" - -namespace tinyobj { - -struct vertex_index { - int v_idx, vt_idx, vn_idx; - vertex_index() {}; - vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {}; - vertex_index(int vidx, int vtidx, int vnidx) : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}; - -}; -// for std::map -static inline bool operator<(const vertex_index& a, const vertex_index& b) -{ - if (a.v_idx != b.v_idx) return (a.v_idx < b.v_idx); - if (a.vn_idx != b.vn_idx) return (a.vn_idx < b.vn_idx); - if (a.vt_idx != b.vt_idx) return (a.vt_idx < b.vt_idx); - - return false; -} - -struct obj_shape { - std::vector v; - std::vector vn; - std::vector vt; -}; - -static inline bool isSpace(const char c) { - return (c == ' ') || (c == '\t'); -} - -static inline bool isNewLine(const char c) { - return (c == '\r') || (c == '\n') || (c == '\0'); -} - -// Make index zero-base, and also support relative index. -static inline int fixIndex(int idx, int n) -{ - int i; - - if (idx > 0) { - i = idx - 1; - } else if (idx == 0) { - i = 0; - } else { // negative value = relative - i = n + idx; - } - return i; -} - -static inline std::string parseString(const char*& token) -{ - std::string s; - int b = strspn(token, " \t"); - int e = strcspn(token, " \t\r"); - s = std::string(&token[b], &token[e]); - - token += (e - b); - return s; -} - -static inline int parseInt(const char*& token) -{ - token += strspn(token, " \t"); - int i = atoi(token); - token += strcspn(token, " \t\r"); - return i; -} - -static inline float parseFloat(const char*& token) -{ - token += strspn(token, " \t"); - float f = (float)atof(token); - token += strcspn(token, " \t\r"); - return f; -} - -static inline void parseFloat2( - float& x, float& y, - const char*& token) -{ - x = parseFloat(token); - y = parseFloat(token); -} - -static inline void parseFloat3( - float& x, float& y, float& z, - const char*& token) -{ - x = parseFloat(token); - y = parseFloat(token); - z = parseFloat(token); -} - - -// Parse triples: i, i/j/k, i//k, i/j -static vertex_index parseTriple( - const char* &token, - int vsize, - int vnsize, - int vtsize) -{ - vertex_index vi(-1); - - vi.v_idx = fixIndex(atoi(token), vsize); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { - return vi; - } - token++; - - // i//k - if (token[0] == '/') { - token++; - vi.vn_idx = fixIndex(atoi(token), vnsize); - token += strcspn(token, "/ \t\r"); - return vi; - } - - // i/j/k or i/j - vi.vt_idx = fixIndex(atoi(token), vtsize); - token += strcspn(token, "/ \t\r"); - if (token[0] != '/') { - return vi; - } - - // i/j/k - token++; // skip '/' - vi.vn_idx = fixIndex(atoi(token), vnsize); - token += strcspn(token, "/ \t\r"); - return vi; -} - -static unsigned int -updateVertex( - std::map& vertexCache, - std::vector& positions, - std::vector& normals, - std::vector& texcoords, - const std::vector& in_positions, - const std::vector& in_normals, - const std::vector& in_texcoords, - const vertex_index& i) -{ - const std::map::iterator it = vertexCache.find(i); - - if (it != vertexCache.end()) { - // found cache - return it->second; - } - - assert(in_positions.size() > (unsigned int) (3*i.v_idx+2)); - - positions.push_back(in_positions[3*i.v_idx+0]); - positions.push_back(in_positions[3*i.v_idx+1]); - positions.push_back(in_positions[3*i.v_idx+2]); - - if (i.vn_idx >= 0) { - normals.push_back(in_normals[3*i.vn_idx+0]); - normals.push_back(in_normals[3*i.vn_idx+1]); - normals.push_back(in_normals[3*i.vn_idx+2]); - } - - if (i.vt_idx >= 0) { - texcoords.push_back(in_texcoords[2*i.vt_idx+0]); - texcoords.push_back(in_texcoords[2*i.vt_idx+1]); - } - - unsigned int idx = positions.size() / 3 - 1; - vertexCache[i] = idx; - - return idx; -} - -void InitMaterial(material_t& material) { - material.name = ""; - material.ambient_texname = ""; - material.diffuse_texname = ""; - material.specular_texname = ""; - material.normal_texname = ""; - for (int i = 0; i < 3; i ++) { - material.ambient[i] = 0.f; - material.diffuse[i] = 0.f; - material.specular[i] = 0.f; - material.transmittance[i] = 0.f; - material.emission[i] = 0.f; - } - material.illum = 0; - material.dissolve = 1.f; - material.shininess = 1.f; - material.ior = 1.f; - material.unknown_parameter.clear(); -} - -static bool -exportFaceGroupToShape( - shape_t& shape, - std::map vertexCache, - const std::vector &in_positions, - const std::vector &in_normals, - const std::vector &in_texcoords, - const std::vector >& faceGroup, - const int material, - const std::string &name, - bool clearCache) -{ - if (faceGroup.empty()) { - return false; - } - - size_t offset; - - offset = shape.mesh.indices.size(); - - // Flatten vertices and indices - for (size_t i = 0; i < faceGroup.size(); i++) { - const std::vector& face = faceGroup[i]; - - vertex_index i0 = face[0]; - vertex_index i1(-1); - vertex_index i2 = face[1]; - - size_t npolys = face.size(); - - // Polygon -> triangle fan conversion - for (size_t k = 2; k < npolys; k++) { - i1 = i2; - i2 = face[k]; - - unsigned int v0 = updateVertex(vertexCache, shape.mesh.positions, shape.mesh.normals, shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); - unsigned int v1 = updateVertex(vertexCache, shape.mesh.positions, shape.mesh.normals, shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); - unsigned int v2 = updateVertex(vertexCache, shape.mesh.positions, shape.mesh.normals, shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); - - shape.mesh.indices.push_back(v0); - shape.mesh.indices.push_back(v1); - shape.mesh.indices.push_back(v2); - } - - } - - // - // Construct shape. - // - shape.submeshes.push_back(std::pair(offset, shape.mesh.indices.size()-offset)); - - shape.name = name; - - shape.materials.push_back(material); - - if (clearCache) - vertexCache.clear(); - - return true; - -} - -std::string LoadMtl ( - std::map& material_map, - std::vector& materials, - std::istream& inStream) -{ - material_map.clear(); - std::stringstream err; - - material_t material; - - int maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. - while (inStream.peek() != -1) { - inStream.getline(&buf[0], maxchars); - - std::string linebuf(&buf[0]); - - // Trim newline '\r\n' or '\n' - if (linebuf.size() > 0) { - if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); - } - if (linebuf.size() > 0) { - if (linebuf[linebuf.size()-1] == '\r') linebuf.erase(linebuf.size()-1); - } - - // Skip if empty line. - if (linebuf.empty()) { - continue; - } - - // Skip leading space. - const char* token = linebuf.c_str(); - token += strspn(token, " \t"); - - assert(token); - if (token[0] == '\0') continue; // empty line - - if (token[0] == '#') continue; // comment line - - // new mtl - if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { - // flush previous material. - if (!material.name.empty()) - { - material_map.insert(std::pair(material.name, materials.size())); - materials.push_back(material); - } - - // initial temporary material - InitMaterial(material); - - // set new mtl name - char namebuf[4096]; - token += 7; - sscanf(token, "%s", namebuf); - material.name = namebuf; - continue; - } - - // ambient - if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.ambient[0] = r; - material.ambient[1] = g; - material.ambient[2] = b; - continue; - } - - // diffuse - if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.diffuse[0] = r; - material.diffuse[1] = g; - material.diffuse[2] = b; - continue; - } - - // specular - if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.specular[0] = r; - material.specular[1] = g; - material.specular[2] = b; - continue; - } - - // transmittance - if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.transmittance[0] = r; - material.transmittance[1] = g; - material.transmittance[2] = b; - continue; - } - - // ior(index of refraction) - if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { - token += 2; - material.ior = parseFloat(token); - continue; - } - - // emission - if(token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { - token += 2; - float r, g, b; - parseFloat3(r, g, b, token); - material.emission[0] = r; - material.emission[1] = g; - material.emission[2] = b; - continue; - } - - // shininess - if(token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { - token += 2; - material.shininess = parseFloat(token); - continue; - } - - // illum model - if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { - token += 6; - material.illum = parseInt(token); - continue; - } - - // dissolve - if ((token[0] == 'd' && isSpace(token[1]))) { - token += 1; - material.dissolve = parseFloat(token); - continue; - } - if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { - token += 2; - material.dissolve = parseFloat(token); - continue; - } - - // ambient texture - if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { - token += 7; - material.ambient_texname = token; - continue; - } - - // diffuse texture - if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { - token += 7; - material.diffuse_texname = token; - continue; - } - - // specular texture - if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { - token += 7; - material.specular_texname = token; - continue; - } - - // normal texture - if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { - token += 7; - material.normal_texname = token; - continue; - } - - // unknown parameter - const char* _space = strchr(token, ' '); - if(!_space) { - _space = strchr(token, '\t'); - } - if(_space) { - int len = _space - token; - std::string key(token, len); - std::string value = _space + 1; - material.unknown_parameter.insert(std::pair(key, value)); - } - } - // flush last material. - material_map.insert(std::pair(material.name, materials.size())); - materials.push_back(material); - - return err.str(); -} - -std::string MaterialFileReader::operator() ( - const std::string& matId, - std::vector& materials, - std::map& matMap) -{ - std::string filepath; - - if (!m_mtlBasePath.empty()) { - filepath = std::string(m_mtlBasePath) + matId; - } else { - filepath = matId; - } - - std::ifstream matIStream(filepath.c_str()); - return LoadMtl(matMap, materials, matIStream); -} - -std::string -LoadObj( - std::vector& shapes, - std::vector& materials, // [output] - const char* filename, - const char* mtl_basepath) -{ - - shapes.clear(); - - std::stringstream err; - - std::ifstream ifs(filename); - if (!ifs) { - err << "Cannot open file [" << filename << "]" << std::endl; - return err.str(); - } - - std::string basePath; - if (mtl_basepath) { - basePath = mtl_basepath; - } - MaterialFileReader matFileReader( basePath ); - - return LoadObj(shapes, materials, ifs, matFileReader); -} - -std::string LoadObj( - std::vector& shapes, - std::vector& materials, // [output] - std::istream& inStream, - MaterialReader& readMatFn) -{ - std::stringstream err; - - std::vector v; - std::vector vn; - std::vector vt; - std::vector > faceGroup; - std::string name; - - // material - std::map material_map; - std::map vertexCache; - int material = -1; - - shape_t shape; - - int maxchars = 8192; // Alloc enough size. - std::vector buf(maxchars); // Alloc enough size. - while (inStream.peek() != -1) { - inStream.getline(&buf[0], maxchars); - - std::string linebuf(&buf[0]); - - // Trim newline '\r\n' or '\n' - if (linebuf.size() > 0) { - if (linebuf[linebuf.size()-1] == '\n') linebuf.erase(linebuf.size()-1); - } - if (linebuf.size() > 0) { - if (linebuf[linebuf.size()-1] == '\r') linebuf.erase(linebuf.size()-1); - } - - // Skip if empty line. - if (linebuf.empty()) { - continue; - } - - // Skip leading space. - const char* token = linebuf.c_str(); - token += strspn(token, " \t"); - - assert(token); - if (token[0] == '\0') continue; // empty line - - if (token[0] == '#') continue; // comment line - - // vertex - if (token[0] == 'v' && isSpace((token[1]))) { - token += 2; - float x, y, z; - parseFloat3(x, y, z, token); - v.push_back(x); - v.push_back(y); - v.push_back(z); - continue; - } - - // normal - if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { - token += 3; - float x, y, z; - parseFloat3(x, y, z, token); - vn.push_back(x); - vn.push_back(y); - vn.push_back(z); - continue; - } - - // texcoord - if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { - token += 3; - float x, y; - parseFloat2(x, y, token); - vt.push_back(x); - vt.push_back(y); - continue; - } - - // face - if (token[0] == 'f' && isSpace((token[1]))) { - token += 2; - token += strspn(token, " \t"); - - std::vector face; - while (!isNewLine(token[0])) { - vertex_index vi = parseTriple(token, v.size() / 3, vn.size() / 3, vt.size() / 2); - face.push_back(vi); - int n = strspn(token, " \t\r"); - token += n; - } - - faceGroup.push_back(face); - - continue; - } - - // use mtl - if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { - - char namebuf[4096]; - token += 7; - sscanf(token, "%s", namebuf); - - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, false); - faceGroup.clear(); - - if (material_map.find(namebuf) != material_map.end()) { - material = material_map[namebuf]; - } else { - // { error!! material not found } - material = -1; - } - - continue; - - } - - // load mtl - if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { - char namebuf[4096]; - token += 7; - sscanf(token, "%s", namebuf); - - std::string err_mtl = readMatFn(namebuf, materials, material_map); - if (!err_mtl.empty()) { - faceGroup.clear(); // for safety - return err_mtl; - } - - continue; - } - - // group name - if (token[0] == 'g' && isSpace((token[1]))) { - - // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); - if (ret) { - shapes.push_back(shape); - } - - shape = shape_t(); - - material = -1; - faceGroup.clear(); - - std::vector names; - while (!isNewLine(token[0])) { - std::string str = parseString(token); - names.push_back(str); - token += strspn(token, " \t\r"); // skip tag - } - - assert(names.size() > 0); - - // names[0] must be 'g', so skipt 0th element. - if (names.size() > 1) { - name = names[1]; - } else { - name = ""; - } - - continue; - } - - // object name - if (token[0] == 'o' && isSpace((token[1]))) { - - // flush previous face group. - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); - if (ret) { - shapes.push_back(shape); - } - - material = -1; - faceGroup.clear(); - shape = shape_t(); - - // @todo { multiple object name? } - char namebuf[4096]; - token += 2; - sscanf(token, "%s", namebuf); - name = std::string(namebuf); - - - continue; - } - - // Ignore unknown command. - } - - bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, material, name, true); - if (ret) { - shapes.push_back(shape); - } - faceGroup.clear(); // for safety - - return err.str(); -} - - -} diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index a832aa53..a927864e 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -1,108 +1,3517 @@ +/* +The MIT License (MIT) + +Copyright (c) 2012-Present, Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// +// version 2.0.0 : Add new object oriented API. 1.x API is still provided. +// * Add python binding. +// * Support line primitive. +// * Support points primitive. +// * Support multiple search path for .mtl(v1 API). +// * Support vertex skinning weight `vw`(as an tinyobj +// extension). Note that this differs vertex weight([w] +// component in `v` line) +// * Support escaped whitespece in mtllib +// * Add robust triangulation using Mapbox +// earcut(TINYOBJLOADER_USE_MAPBOX_EARCUT). +// version 1.4.0 : Modifed ParseTextureNameAndOption API +// version 1.3.1 : Make ParseTextureNameAndOption API public +// version 1.3.0 : Separate warning and error message(breaking API of LoadObj) +// version 1.2.3 : Added color space extension('-colorspace') to tex opts. +// version 1.2.2 : Parse multiple group names. +// version 1.2.1 : Added initial support for line('l') primitive(PR #178) +// version 1.2.0 : Hardened implementation(#175) +// version 1.1.1 : Support smoothing groups(#162) +// version 1.1.0 : Support parsing vertex color(#144) +// version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) +// version 1.0.7 : Support multiple tex options(#126) +// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) +// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) +// version 1.0.4 : Support multiple filenames for 'mtllib'(#112) +// version 1.0.3 : Support parsing texture options(#85) +// version 1.0.2 : Improve parsing speed by about a factor of 2 for large +// files(#105) +// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) +// version 1.0.0 : Change data structure. Change license from BSD to MIT. // -// Copyright 2012-2013, Syoyo Fujita. + // -// Licensed under 2-clause BSD liecense. +// Use this in *one* .cc +// #define TINYOBJLOADER_IMPLEMENTATION +// #include "tiny_obj_loader.h" // -#ifndef _TINY_OBJ_LOADER_H -#define _TINY_OBJ_LOADER_H +#ifndef TINY_OBJ_LOADER_H_ +#define TINY_OBJ_LOADER_H_ + +#include #include #include -#include namespace tinyobj { -typedef struct -{ - std::string name; - - float ambient[3]; - float diffuse[3]; - float specular[3]; - float transmittance[3]; - float emission[3]; - float shininess; - float ior; // index of refraction - float dissolve; // 1 == opaque; 0 == fully transparent - // illumination model (see http://www.fileformat.info/format/material/) - int illum; - - std::string ambient_texname; - std::string diffuse_texname; - std::string specular_texname; - std::string normal_texname; - std::map unknown_parameter; -} material_t; - -typedef struct -{ - std::vector positions; - std::vector normals; - std::vector texcoords; - std::vector indices; -} mesh_t; - -typedef struct -{ - std::string name; - mesh_t mesh; - std::vector< std::pair > submeshes; - std::vector< int > materials; -} shape_t; - -class MaterialReader -{ -public: - MaterialReader(){} - virtual ~MaterialReader(){} - - virtual std::string operator() ( - const std::string& matId, - std::vector& materials, - std::map& matMap) = 0; -}; - -class MaterialFileReader: - public MaterialReader -{ - public: - MaterialFileReader(const std::string& mtl_basepath): m_mtlBasePath(mtl_basepath) {} - virtual ~MaterialFileReader() {} - virtual std::string operator() ( - const std::string& matId, - std::vector& materials, - std::map& matMap); - - private: - std::string m_mtlBasePath; +// TODO(syoyo): Better C++11 detection for older compiler +#if __cplusplus > 199711L +#define TINYOBJ_OVERRIDE override +#else +#define TINYOBJ_OVERRIDE +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + +#pragma clang diagnostic ignored "-Wpadded" + +#endif + +// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... +// +// -blendu on | off # set horizontal texture blending +// (default on) +// -blendv on | off # set vertical texture blending +// (default on) +// -boost real_value # boost mip-map sharpness +// -mm base_value gain_value # modify texture map values (default +// 0 1) +// # base_value = brightness, +// gain_value = contrast +// -o u [v [w]] # Origin offset (default +// 0 0 0) +// -s u [v [w]] # Scale (default +// 1 1 1) +// -t u [v [w]] # Turbulence (default +// 0 0 0) +// -texres resolution # texture resolution to create +// -clamp on | off # only render texels in the clamped +// 0-1 range (default off) +// # When unclamped, textures are +// repeated across a surface, +// # when clamped, only texels which +// fall within the 0-1 +// # range are rendered. +// -bm mult_value # bump multiplier (for bump maps +// only) +// +// -imfchan r | g | b | m | l | z # specifies which channel of the file +// is used to +// # create a scalar or bump texture. +// r:red, g:green, +// # b:blue, m:matte, l:luminance, +// z:z-depth.. +// # (the default for bump is 'l' and +// for decal is 'm') +// bump -imfchan r bumpmap.tga # says to use the red channel of +// bumpmap.tga as the bumpmap +// +// For reflection maps... +// +// -type sphere # specifies a sphere for a "refl" +// reflection map +// -type cube_top | cube_bottom | # when using a cube map, the texture +// file for each +// cube_front | cube_back | # side of the cube is specified +// separately +// cube_left | cube_right +// +// TinyObjLoader extension. +// +// -colorspace SPACE # Color space of the texture. e.g. +// 'sRGB` or 'linear' +// + +#ifdef TINYOBJLOADER_USE_DOUBLE +//#pragma message "using double" +typedef double real_t; +#else +//#pragma message "using float" +typedef float real_t; +#endif + +typedef enum { + TEXTURE_TYPE_NONE, // default + TEXTURE_TYPE_SPHERE, + TEXTURE_TYPE_CUBE_TOP, + TEXTURE_TYPE_CUBE_BOTTOM, + TEXTURE_TYPE_CUBE_FRONT, + TEXTURE_TYPE_CUBE_BACK, + TEXTURE_TYPE_CUBE_LEFT, + TEXTURE_TYPE_CUBE_RIGHT +} texture_type_t; + +struct texture_option_t { + texture_type_t type; // -type (default TEXTURE_TYPE_NONE) + real_t sharpness; // -boost (default 1.0?) + real_t brightness; // base_value in -mm option (default 0) + real_t contrast; // gain_value in -mm option (default 1) + real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0) + real_t scale[3]; // -s u [v [w]] (default 1 1 1) + real_t turbulence[3]; // -t u [v [w]] (default 0 0 0) + int texture_resolution; // -texres resolution (No default value in the spec. + // We'll use -1) + bool clamp; // -clamp (default false) + char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') + bool blendu; // -blendu (default on) + bool blendv; // -blendv (default on) + real_t bump_multiplier; // -bm (for bump maps only, default 1.0) + + // extension + std::string colorspace; // Explicitly specify color space of stored texel + // value. Usually `sRGB` or `linear` (default empty). +}; + +struct material_t { + std::string name; + + real_t ambient[3]; + real_t diffuse[3]; + real_t specular[3]; + real_t transmittance[3]; + real_t emission[3]; + real_t shininess; + real_t ior; // index of refraction + real_t dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + int dummy; // Suppress padding warning. + + std::string ambient_texname; // map_Ka. For ambient or ambient occlusion. + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, map_Bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + std::string reflection_texname; // refl + + texture_option_t ambient_texopt; + texture_option_t diffuse_texopt; + texture_option_t specular_texopt; + texture_option_t specular_highlight_texopt; + texture_option_t bump_texopt; + texture_option_t displacement_texopt; + texture_option_t alpha_texopt; + texture_option_t reflection_texopt; + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + real_t roughness; // [0, 1] default 0 + real_t metallic; // [0, 1] default 0 + real_t sheen; // [0, 1] default 0 + real_t clearcoat_thickness; // [0, 1] default 0 + real_t clearcoat_roughness; // [0, 1] default 0 + real_t anisotropy; // aniso. [0, 1] default 0 + real_t anisotropy_rotation; // anisor. [0, 1] default 0 + real_t pad0; + std::string roughness_texname; // map_Pr + std::string metallic_texname; // map_Pm + std::string sheen_texname; // map_Ps + std::string emissive_texname; // map_Ke + std::string normal_texname; // norm. For normal mapping. + + texture_option_t roughness_texopt; + texture_option_t metallic_texopt; + texture_option_t sheen_texopt; + texture_option_t emissive_texopt; + texture_option_t normal_texopt; + + int pad2; + + std::map unknown_parameter; + +#ifdef TINY_OBJ_LOADER_PYTHON_BINDING + // For pybind11 + std::array GetDiffuse() { + std::array values; + values[0] = double(diffuse[0]); + values[1] = double(diffuse[1]); + values[2] = double(diffuse[2]); + + return values; + } + + std::array GetSpecular() { + std::array values; + values[0] = double(specular[0]); + values[1] = double(specular[1]); + values[2] = double(specular[2]); + + return values; + } + + std::array GetTransmittance() { + std::array values; + values[0] = double(transmittance[0]); + values[1] = double(transmittance[1]); + values[2] = double(transmittance[2]); + + return values; + } + + std::array GetEmission() { + std::array values; + values[0] = double(emission[0]); + values[1] = double(emission[1]); + values[2] = double(emission[2]); + + return values; + } + + std::array GetAmbient() { + std::array values; + values[0] = double(ambient[0]); + values[1] = double(ambient[1]); + values[2] = double(ambient[2]); + + return values; + } + + void SetDiffuse(std::array &a) { + diffuse[0] = real_t(a[0]); + diffuse[1] = real_t(a[1]); + diffuse[2] = real_t(a[2]); + } + + void SetAmbient(std::array &a) { + ambient[0] = real_t(a[0]); + ambient[1] = real_t(a[1]); + ambient[2] = real_t(a[2]); + } + + void SetSpecular(std::array &a) { + specular[0] = real_t(a[0]); + specular[1] = real_t(a[1]); + specular[2] = real_t(a[2]); + } + + void SetTransmittance(std::array &a) { + transmittance[0] = real_t(a[0]); + transmittance[1] = real_t(a[1]); + transmittance[2] = real_t(a[2]); + } + + std::string GetCustomParameter(const std::string &key) { + std::map::const_iterator it = + unknown_parameter.find(key); + + if (it != unknown_parameter.end()) { + return it->second; + } + return std::string(); + } + +#endif +}; + +struct tag_t { + std::string name; + + std::vector intValues; + std::vector floatValues; + std::vector stringValues; +}; + +struct joint_and_weight_t { + int joint_id; + real_t weight; +}; + +struct skin_weight_t { + int vertex_id; // Corresponding vertex index in `attrib_t::vertices`. + // Compared to `index_t`, this index must be positive and + // start with 0(does not allow relative indexing) + std::vector weightValues; +}; + +// Index struct to support different indices for vtx/normal/texcoord. +// -1 means not used. +struct index_t { + int vertex_index; + int normal_index; + int texcoord_index; +}; + +struct mesh_t { + std::vector indices; + std::vector + num_face_vertices; // The number of vertices per + // face. 3 = triangle, 4 = quad, ... + std::vector material_ids; // per-face material ID + std::vector smoothing_group_ids; // per-face smoothing group + // ID(0 = off. positive value + // = group id) + std::vector tags; // SubD tag +}; + +// struct path_t { +// std::vector indices; // pairs of indices for lines +//}; + +struct lines_t { + // Linear flattened indices. + std::vector indices; // indices for vertices(poly lines) + std::vector num_line_vertices; // The number of vertices per line. +}; + +struct points_t { + std::vector indices; // indices for points +}; + +struct shape_t { + std::string name; + mesh_t mesh; + lines_t lines; + points_t points; +}; + +// Vertex attributes +struct attrib_t { + std::vector vertices; // 'v'(xyz) + + // For backward compatibility, we store vertex weight in separate array. + std::vector vertex_weights; // 'v'(w) + std::vector normals; // 'vn' + std::vector texcoords; // 'vt'(uv) + + // For backward compatibility, we store texture coordinate 'w' in separate + // array. + std::vector texcoord_ws; // 'vt'(w) + std::vector colors; // extension: vertex colors + + // + // TinyObj extension. + // + + // NOTE(syoyo): array index is based on the appearance order. + // To get a corresponding skin weight for a specific vertex id `vid`, + // Need to reconstruct a look up table: `skin_weight_t::vertex_id` == `vid` + // (e.g. using std::map, std::unordered_map) + std::vector skin_weights; + + attrib_t() {} + + // + // For pybind11 + // + const std::vector &GetVertices() const { return vertices; } + + const std::vector &GetVertexWeights() const { return vertex_weights; } +}; + +struct callback_t { + // W is optional and set to 1 if there is no `w` item in `v` line + void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w); + void (*vertex_color_cb)(void *user_data, real_t x, real_t y, real_t z, + real_t r, real_t g, real_t b, bool has_color); + void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z); + + // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in + // `vt` line. + void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z); + + // called per 'f' line. num_indices is the number of face indices(e.g. 3 for + // triangle, 4 for quad) + // 0 will be passed for undefined index in index_t members. + void (*index_cb)(void *user_data, index_t *indices, int num_indices); + // `name` material name, `material_id` = the array index of material_t[]. -1 + // if + // a material not found in .mtl + void (*usemtl_cb)(void *user_data, const char *name, int material_id); + // `materials` = parsed material data. + void (*mtllib_cb)(void *user_data, const material_t *materials, + int num_materials); + // There may be multiple group names + void (*group_cb)(void *user_data, const char **names, int num_names); + void (*object_cb)(void *user_data, const char *name); + + callback_t() + : vertex_cb(NULL), + vertex_color_cb(NULL), + normal_cb(NULL), + texcoord_cb(NULL), + index_cb(NULL), + usemtl_cb(NULL), + mtllib_cb(NULL), + group_cb(NULL), + object_cb(NULL) {} +}; + +class MaterialReader { + public: + MaterialReader() {} + virtual ~MaterialReader(); + + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) = 0; +}; + +/// +/// Read .mtl from a file. +/// +class MaterialFileReader : public MaterialReader { + public: + // Path could contain separator(';' in Windows, ':' in Posix) + explicit MaterialFileReader(const std::string &mtl_basedir) + : m_mtlBaseDir(mtl_basedir) {} + virtual ~MaterialFileReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::string m_mtlBaseDir; +}; + +/// +/// Read .mtl from a stream. +/// +class MaterialStreamReader : public MaterialReader { + public: + explicit MaterialStreamReader(std::istream &inStream) + : m_inStream(inStream) {} + virtual ~MaterialStreamReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::istream &m_inStream; +}; + +// v2 API +struct ObjReaderConfig { + bool triangulate; // triangulate polygon? + + // Currently not used. + // "simple" or empty: Create triangle fan + // "earcut": Use the algorithm based on Ear clipping + std::string triangulation_method; + + /// Parse vertex color. + /// If vertex color is not present, its filled with default value. + /// false = no vertex color + /// This will increase memory of parsed .obj + bool vertex_color; + + /// + /// Search path to .mtl file. + /// Default = "" = search from the same directory of .obj file. + /// Valid only when loading .obj from a file. + /// + std::string mtl_search_path; + + ObjReaderConfig() + : triangulate(true), triangulation_method("simple"), vertex_color(true) {} +}; + +/// +/// Wavefront .obj reader class(v2 API) +/// +class ObjReader { + public: + ObjReader() : valid_(false) {} + + /// + /// Load .obj and .mtl from a file. + /// + /// @param[in] filename wavefront .obj filename + /// @param[in] config Reader configuration + /// + bool ParseFromFile(const std::string &filename, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// Parse .obj from a text string. + /// Need to supply .mtl text string by `mtl_text`. + /// This function ignores `mtllib` line in .obj text. + /// + /// @param[in] obj_text wavefront .obj filename + /// @param[in] mtl_text wavefront .mtl filename + /// @param[in] config Reader configuration + /// + bool ParseFromString(const std::string &obj_text, const std::string &mtl_text, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// .obj was loaded or parsed correctly. + /// + bool Valid() const { return valid_; } + + const attrib_t &GetAttrib() const { return attrib_; } + + const std::vector &GetShapes() const { return shapes_; } + + const std::vector &GetMaterials() const { return materials_; } + + /// + /// Warning message(may be filled after `Load` or `Parse`) + /// + const std::string &Warning() const { return warning_; } + + /// + /// Error message(filled when `Load` or `Parse` failed) + /// + const std::string &Error() const { return error_; } + + private: + bool valid_; + + attrib_t attrib_; + std::vector shapes_; + std::vector materials_; + + std::string warning_; + std::string error_; }; +/// ==>>========= Legacy v1 API ============================================= + /// Loads .obj from a file. +/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data /// 'shapes' will be filled with parsed shape data -/// The function returns error string. -/// Returns empty string when loading .obj success. -/// 'mtl_basepath' is optional, and used for base path for .mtl file. -std::string LoadObj( - std::vector& shapes, // [output] - std::vector& materials, // [output] - const char* filename, - const char* mtl_basepath = NULL); - -/// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve +/// Returns true when loading .obj become success. +/// Returns warning message into `warn`, and error message into `err` +/// 'mtl_basedir' is optional, and used for base directory for .mtl file. +/// In default(`NULL'), .mtl file is searched from an application's working +/// directory. +/// 'triangulate' is optional, and used whether triangulate polygon face in .obj +/// or not. +/// Option 'default_vcols_fallback' specifies whether vertex colors should +/// always be defined, even if no colors are given (fallback to white). +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, const char *filename, + const char *mtl_basedir = NULL, bool triangulate = true, + bool default_vcols_fallback = true); + +/// Loads .obj from a file with custom user callback. +/// .mtl is loaded as usual and parsed material_t data will be passed to +/// `callback.mtllib_cb`. +/// Returns true when loading .obj/.mtl become success. +/// Returns warning message into `warn`, and error message into `err` +/// See `examples/callback_api/` for how to use this function. +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data = NULL, + MaterialReader *readMatFn = NULL, + std::string *warn = NULL, std::string *err = NULL); + +/// Loads object from a std::istream, uses `readMatFn` to retrieve /// std::istream for materials. -/// Returns empty string when loading .obj success. -std::string LoadObj( - std::vector& shapes, // [output] - std::vector& materials, // [output] - std::istream& inStream, - MaterialReader& readMatFn); +/// Returns true when loading .obj become success. +/// Returns warning and error message into `err` +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn = NULL, bool triangulate = true, + bool default_vcols_fallback = true); /// Loads materials into std::map -/// Returns an empty string if successful -std::string LoadMtl ( - std::map& material_map, - std::vector& materials, - std::istream& inStream); +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning, std::string *err); + +/// +/// Parse texture name and texture option for custom texture parameter through +/// material::unknown_parameter +/// +/// @param[out] texname Parsed texture name +/// @param[out] texopt Parsed texopt +/// @param[in] linebuf Input string +/// +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf); + +/// =<<========== Legacy v1 API ============================================= + +} // namespace tinyobj + +#endif // TINY_OBJ_LOADER_H_ + +#ifdef TINYOBJLOADER_IMPLEMENTATION +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + +#ifdef TINYOBJLOADER_DONOT_INCLUDE_MAPBOX_EARCUT +// Assume earcut.hpp is included outside of tiny_obj_loader.h +#else + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include + +#include "mapbox/earcut.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#endif // TINYOBJLOADER_USE_MAPBOX_EARCUT + +namespace tinyobj { + +MaterialReader::~MaterialReader() {} + +struct vertex_index_t { + int v_idx, vt_idx, vn_idx; + vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} + vertex_index_t(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} +}; + +// Internal data structure for face representation +// index + smoothing group. +struct face_t { + unsigned int + smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. + int pad_; + std::vector vertex_indices; // face vertex indices. + + face_t() : smoothing_group_id(0), pad_(0) {} +}; + +// Internal data structure for line representation +struct __line_t { + // l v1/vt1 v2/vt2 ... + // In the specification, line primitrive does not have normal index, but + // TinyObjLoader allow it + std::vector vertex_indices; +}; + +// Internal data structure for points representation +struct __points_t { + // p v1 v2 ... + // In the specification, point primitrive does not have normal index and + // texture coord index, but TinyObjLoader allow it. + std::vector vertex_indices; +}; + +struct tag_sizes { + tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} + int num_ints; + int num_reals; + int num_strings; +}; + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +// +// Manages group of primitives(face, line, points, ...) +struct PrimGroup { + std::vector faceGroup; + std::vector<__line_t> lineGroup; + std::vector<__points_t> pointsGroup; + + void clear() { + faceGroup.clear(); + lineGroup.clear(); + pointsGroup.clear(); + } + + bool IsEmpty() const { + return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); + } + + // TODO(syoyo): bspline, surface, ... +}; + +// See +// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf +static std::istream &safeGetline(std::istream &is, std::string &t) { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf *sb = is.rdbuf(); + + if (se) { + for (;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if (sb->sgetc() == '\n') sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if (t.empty()) is.setstate(std::ios::eofbit); + return is; + default: + t += static_cast(c); + } + } + } + + return is; +} + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +template +static inline std::string toString(const T &t) { + std::stringstream ss; + ss << t; + return ss.str(); +} + +static inline std::string removeUtf8Bom(const std::string& input) { + // UTF-8 BOM = 0xEF,0xBB,0xBF + if (input.size() >= 3 && + static_cast(input[0]) == 0xEF && + static_cast(input[1]) == 0xBB && + static_cast(input[2]) == 0xBF) { + return input.substr(3); // Skip BOM + } + return input; +} + +struct warning_context { + std::string *warn; + size_t line_number; +}; + +// Make index zero-base, and also support relative index. +static inline bool fixIndex(int idx, int n, int *ret, bool allow_zero, + const warning_context &context) { + if (!ret) { + return false; + } + + if (idx > 0) { + (*ret) = idx - 1; + return true; + } + + if (idx == 0) { + // zero is not allowed according to the spec. + if (context.warn) { + (*context.warn) += + "A zero value index found (will have a value of -1 for normal and " + "tex indices. Line " + + toString(context.line_number) + ").\n"; + } + + (*ret) = idx - 1; + return allow_zero; + } + + if (idx < 0) { + (*ret) = n + idx; // negative value = relative + if ((*ret) < 0) { + return false; // invalid relative index + } + return true; + } + + return false; // never reach here. +} + +static inline std::string parseString(const char **token) { + std::string s; + (*token) += strspn((*token), " \t"); + size_t e = strcspn((*token), " \t\r"); + s = std::string((*token), &(*token)[e]); + (*token) += e; + return s; +} + +static inline int parseInt(const char **token) { + (*token) += strspn((*token), " \t"); + int i = atoi((*token)); + (*token) += strcspn((*token), " \t\r"); + return i; +} + +// Tries to parse a floating point number located at s. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// The following situations triggers a failure: +// - s >= s_end. +// - parse failure. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) { + if (s >= s_end) { + return false; + } + + double mantissa = 0.0; + // This exponent is base 2 rather than 10. + // However the exponent we parse is supposed to be one of ten, + // thus we must take care to convert the exponent/and or the + // mantissa to a * 2^E, where a is the mantissa and E is the + // exponent. + // To get the final double we will use ldexp, it requires the + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // How many characters were read in a loop. + int read = 0; + // Tells whether a loop terminated due to reaching s_end. + bool end_not_reached = false; + bool leading_decimal_dots = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + if ((curr != s_end) && (*curr == '.')) { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else if (*curr == '.') { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + if (!leading_decimal_dots) { + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // We must make sure we actually got something. + if (read == 0) goto fail; + } + + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + static const double pow_lut[] = { + 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, + }; + const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; + + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * + (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') { + curr++; + // Figure out if a sign is present and if it is. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // To avoid annoying MSVC's min/max macro definiton, + // Use hardcoded int max value + if (exponent > + (2147483647 / 10)) { // 2147483647 = std::numeric_limits::max() + // Integer overflow + goto fail; + } + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble: + *result = (sign == '+' ? 1 : -1) * + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) + : mantissa); + return true; +fail: + return false; +} + +static inline real_t parseReal(const char **token, double default_value = 0.0) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val = default_value; + tryParseDouble((*token), end, &val); + real_t f = static_cast(val); + (*token) = end; + return f; +} + +static inline bool parseReal(const char **token, real_t *out) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val; + bool ret = tryParseDouble((*token), end, &val); + if (ret) { + real_t f = static_cast(val); + (*out) = f; + } + (*token) = end; + return ret; +} + +static inline void parseReal2(real_t *x, real_t *y, const char **token, + const double default_x = 0.0, + const double default_y = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); +} + +static inline void parseReal3(real_t *x, real_t *y, real_t *z, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); +} + +#if 0 // not used +static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0, + const double default_w = 1.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + (*w) = parseReal(token, default_w); +} +#endif + +// Extension: parse vertex with colors(6 items) +// Return 3: xyz, 4: xyzw, 6: xyzrgb +// `r`: red(case 6) or [w](case 4) +static inline int parseVertexWithColor(real_t *x, real_t *y, real_t *z, + real_t *r, real_t *g, real_t *b, + const char **token, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + // TODO: Check error + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + + // - 4 components(x, y, z, w) ot 6 components + bool has_r = parseReal(token, r); + + if (!has_r) { + (*r) = (*g) = (*b) = 1.0; + return 3; + } + + bool has_g = parseReal(token, g); + + if (!has_g) { + (*g) = (*b) = 1.0; + return 4; + } + + bool has_b = parseReal(token, b); + + if (!has_b) { + (*r) = (*g) = (*b) = 1.0; + return 3; // treated as xyz + } + + return 6; +} + +static inline bool parseOnOff(const char **token, bool default_value = true) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + + bool ret = default_value; + if ((0 == strncmp((*token), "on", 2))) { + ret = true; + } else if ((0 == strncmp((*token), "off", 3))) { + ret = false; + } + + (*token) = end; + return ret; +} + +static inline texture_type_t parseTextureType( + const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + texture_type_t ty = default_value; + + if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) { + ty = TEXTURE_TYPE_CUBE_TOP; + } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) { + ty = TEXTURE_TYPE_CUBE_BOTTOM; + } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) { + ty = TEXTURE_TYPE_CUBE_LEFT; + } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) { + ty = TEXTURE_TYPE_CUBE_RIGHT; + } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) { + ty = TEXTURE_TYPE_CUBE_FRONT; + } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) { + ty = TEXTURE_TYPE_CUBE_BACK; + } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) { + ty = TEXTURE_TYPE_SPHERE; + } + + (*token) = end; + return ty; +} + +static tag_sizes parseTagTriple(const char **token) { + tag_sizes ts; + + (*token) += strspn((*token), " \t"); + ts.num_ints = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + + (*token)++; // Skip '/' + + (*token) += strspn((*token), " \t"); + ts.num_reals = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + (*token)++; // Skip '/' + + ts.num_strings = parseInt(token); + + return ts; +} + +// Parse triples with index offsets: i, i/j/k, i//k, i/j +static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, + vertex_index_t *ret, const warning_context &context) { + if (!ret) { + return false; + } + + vertex_index_t vi(-1); + + if (!fixIndex(atoi((*token)), vsize, &vi.v_idx, false, context)) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + if (!fixIndex(atoi((*token)), vnsize, &vi.vn_idx, true, context)) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + (*ret) = vi; + return true; + } + + // i/j/k or i/j + if (!fixIndex(atoi((*token)), vtsize, &vi.vt_idx, true, context)) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + + // i/j/k + (*token)++; // skip '/' + if (!fixIndex(atoi((*token)), vnsize, &vi.vn_idx, true, context)) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + + (*ret) = vi; + + return true; +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index_t parseRawTriple(const char **token) { + vertex_index_t vi(static_cast(0)); // 0 is an invalid index in OBJ + + vi.v_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; +} + +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf) { + // @todo { write more robust lexer and parser. } + bool found_texname = false; + std::string texture_name; + + const char *token = linebuf; // Assume line ends with NULL + + while (!IS_NEW_LINE((*token))) { + token += strspn(token, " \t"); // skip space + if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendu = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendv = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->clamp = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->sharpness = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { + token += 4; + texopt->bump_multiplier = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), + &(texopt->origin_offset[2]), &token); + } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), + &token, 1.0, 1.0, 1.0); + } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), + &(texopt->turbulence[2]), &token); + } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { + token += 5; + texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); + } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) { + token += 7; + // TODO(syoyo): Check if arg is int type. + texopt->texture_resolution = parseInt(&token); + } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) { + token += 9; + token += strspn(token, " \t"); + const char *end = token + strcspn(token, " \t\r"); + if ((end - token) == 1) { // Assume one char for -imfchan + texopt->imfchan = (*token); + } + token = end; + } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { + token += 4; + parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); + } else if ((0 == strncmp(token, "-colorspace", 11)) && + IS_SPACE((token[11]))) { + token += 12; + texopt->colorspace = parseString(&token); + } else { +// Assume texture filename +#if 0 + size_t len = strcspn(token, " \t\r"); // untile next space + texture_name = std::string(token, token + len); + token += len; + + token += strspn(token, " \t"); // skip space +#else + // Read filename until line end to parse filename containing whitespace + // TODO(syoyo): Support parsing texture option flag after the filename. + texture_name = std::string(token); + token += texture_name.length(); +#endif + + found_texname = true; + } + } + + if (found_texname) { + (*texname) = texture_name; + return true; + } else { + return false; + } +} + +static void InitTexOpt(texture_option_t *texopt, const bool is_bump) { + if (is_bump) { + texopt->imfchan = 'l'; + } else { + texopt->imfchan = 'm'; + } + texopt->bump_multiplier = static_cast(1.0); + texopt->clamp = false; + texopt->blendu = true; + texopt->blendv = true; + texopt->sharpness = static_cast(1.0); + texopt->brightness = static_cast(0.0); + texopt->contrast = static_cast(1.0); + texopt->origin_offset[0] = static_cast(0.0); + texopt->origin_offset[1] = static_cast(0.0); + texopt->origin_offset[2] = static_cast(0.0); + texopt->scale[0] = static_cast(1.0); + texopt->scale[1] = static_cast(1.0); + texopt->scale[2] = static_cast(1.0); + texopt->turbulence[0] = static_cast(0.0); + texopt->turbulence[1] = static_cast(0.0); + texopt->turbulence[2] = static_cast(0.0); + texopt->texture_resolution = -1; + texopt->type = TEXTURE_TYPE_NONE; } -#endif // _TINY_OBJ_LOADER_H +static void InitMaterial(material_t *material) { + InitTexOpt(&material->ambient_texopt, /* is_bump */ false); + InitTexOpt(&material->diffuse_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false); + InitTexOpt(&material->bump_texopt, /* is_bump */ true); + InitTexOpt(&material->displacement_texopt, /* is_bump */ false); + InitTexOpt(&material->alpha_texopt, /* is_bump */ false); + InitTexOpt(&material->reflection_texopt, /* is_bump */ false); + InitTexOpt(&material->roughness_texopt, /* is_bump */ false); + InitTexOpt(&material->metallic_texopt, /* is_bump */ false); + InitTexOpt(&material->sheen_texopt, /* is_bump */ false); + InitTexOpt(&material->emissive_texopt, /* is_bump */ false); + InitTexOpt(&material->normal_texopt, + /* is_bump */ false); // @fixme { is_bump will be true? } + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->reflection_texname = ""; + material->alpha_texname = ""; + for (int i = 0; i < 3; i++) { + material->ambient[i] = static_cast(0.0); + material->diffuse[i] = static_cast(0.0); + material->specular[i] = static_cast(0.0); + material->transmittance[i] = static_cast(0.0); + material->emission[i] = static_cast(0.0); + } + material->illum = 0; + material->dissolve = static_cast(1.0); + material->shininess = static_cast(1.0); + material->ior = static_cast(1.0); + + material->roughness = static_cast(0.0); + material->metallic = static_cast(0.0); + material->sheen = static_cast(0.0); + material->clearcoat_thickness = static_cast(0.0); + material->clearcoat_roughness = static_cast(0.0); + material->anisotropy_rotation = static_cast(0.0); + material->anisotropy = static_cast(0.0); + material->roughness_texname = ""; + material->metallic_texname = ""; + material->sheen_texname = ""; + material->emissive_texname = ""; + material->normal_texname = ""; + + material->unknown_parameter.clear(); +} + +// code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html +template +static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) { + int i, j, c = 0; + for (i = 0, j = nvert - 1; i < nvert; j = i++) { + if (((verty[i] > testy) != (verty[j] > testy)) && + (testx < + (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + + vertx[i])) + c = !c; + } + return c; +} + +struct TinyObjPoint { + real_t x, y, z; + TinyObjPoint() : x(0), y(0), z(0) {} + TinyObjPoint(real_t x_, real_t y_, real_t z_) : x(x_), y(y_), z(z_) {} +}; + +inline TinyObjPoint cross(const TinyObjPoint &v1, const TinyObjPoint &v2) { + return TinyObjPoint(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x); +} + +inline real_t dot(const TinyObjPoint &v1, const TinyObjPoint &v2) { + return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); +} + +inline real_t GetLength(TinyObjPoint &e) { + return std::sqrt(e.x * e.x + e.y * e.y + e.z * e.z); +} + +inline TinyObjPoint Normalize(TinyObjPoint e) { + real_t inv_length = real_t(1) / GetLength(e); + return TinyObjPoint(e.x * inv_length, e.y * inv_length, e.z * inv_length); +} + +inline TinyObjPoint WorldToLocal(const TinyObjPoint &a, const TinyObjPoint &u, + const TinyObjPoint &v, const TinyObjPoint &w) { + return TinyObjPoint(dot(a, u), dot(a, v), dot(a, w)); +} + +// TODO(syoyo): refactor function. +static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, + const std::vector &tags, + const int material_id, const std::string &name, + bool triangulate, const std::vector &v, + std::string *warn) { + if (prim_group.IsEmpty()) { + return false; + } + + shape->name = name; + + // polygon + if (!prim_group.faceGroup.empty()) { + // Flatten vertices and indices + for (size_t i = 0; i < prim_group.faceGroup.size(); i++) { + const face_t &face = prim_group.faceGroup[i]; + + size_t npolys = face.vertex_indices.size(); + + if (npolys < 3) { + // Face must have 3+ vertices. + if (warn) { + (*warn) += "Degenerated face found\n."; + } + continue; + } + + if (triangulate && npolys != 3) { + if (npolys == 4) { + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1 = face.vertex_indices[1]; + vertex_index_t i2 = face.vertex_indices[2]; + vertex_index_t i3 = face.vertex_indices[3]; + + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + size_t vi3 = size_t(i3.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size()) || ((3 * vi3 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + if (warn) { + (*warn) += "Face with invalid vertex index found.\n"; + } + continue; + } + + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t v3x = v[vi3 * 3 + 0]; + real_t v3y = v[vi3 * 3 + 1]; + real_t v3z = v[vi3 * 3 + 2]; + + // There are two candidates to split the quad into two triangles. + // + // Choose the shortest edge. + // TODO: Is it better to determine the edge to split by calculating + // the area of each triangle? + // + // +---+ + // |\ | + // | \ | + // | \| + // +---+ + // + // +---+ + // | /| + // | / | + // |/ | + // +---+ + + real_t e02x = v2x - v0x; + real_t e02y = v2y - v0y; + real_t e02z = v2z - v0z; + real_t e13x = v3x - v1x; + real_t e13y = v3y - v1y; + real_t e13z = v3z - v1z; + + real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z; + real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z; + + index_t idx0, idx1, idx2, idx3; + + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + idx3.vertex_index = i3.v_idx; + idx3.normal_index = i3.vn_idx; + idx3.texcoord_index = i3.vt_idx; + + if (sqr02 < sqr13) { + // [0, 1, 2], [0, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } else { + // [0, 1, 3], [1, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx3); + + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } + + // Two triangle faces + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.num_face_vertices.push_back(3); + + shape->mesh.material_ids.push_back(material_id); + shape->mesh.material_ids.push_back(material_id); + + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + + } else { +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i0_2 = i0; + + // TMW change: Find the normal axis of the polygon using Newell's + // method + TinyObjPoint n; + for (size_t k = 0; k < npolys; ++k) { + i0 = face.vertex_indices[k % npolys]; + size_t vi0 = size_t(i0.v_idx); + + size_t j = (k + 1) % npolys; + i0_2 = face.vertex_indices[j]; + size_t vi0_2 = size_t(i0_2.v_idx); + + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + + real_t v0x_2 = v[vi0_2 * 3 + 0]; + real_t v0y_2 = v[vi0_2 * 3 + 1]; + real_t v0z_2 = v[vi0_2 * 3 + 2]; + + const TinyObjPoint point1(v0x, v0y, v0z); + const TinyObjPoint point2(v0x_2, v0y_2, v0z_2); + + TinyObjPoint a(point1.x - point2.x, point1.y - point2.y, + point1.z - point2.z); + TinyObjPoint b(point1.x + point2.x, point1.y + point2.y, + point1.z + point2.z); + + n.x += (a.y * b.z); + n.y += (a.z * b.x); + n.z += (a.x * b.y); + } + real_t length_n = GetLength(n); + // Check if zero length normal + if (length_n <= 0) { + continue; + } + // Negative is to flip the normal to the correct direction + real_t inv_length = -real_t(1.0) / length_n; + n.x *= inv_length; + n.y *= inv_length; + n.z *= inv_length; + + TinyObjPoint axis_w, axis_v, axis_u; + axis_w = n; + TinyObjPoint a; + if (std::fabs(axis_w.x) > real_t(0.9999999)) { + a = TinyObjPoint(0, 1, 0); + } else { + a = TinyObjPoint(1, 0, 0); + } + axis_v = Normalize(cross(axis_w, a)); + axis_u = cross(axis_w, axis_v); + using Point = std::array; + + // first polyline define the main polygon. + // following polylines define holes(not used in tinyobj). + std::vector > polygon; + + std::vector polyline; + + // TMW change: Find best normal and project v0x and v0y to those + // coordinates, instead of picking a plane aligned with an axis (which + // can flip polygons). + + // Fill polygon data(facevarying vertices). + for (size_t k = 0; k < npolys; k++) { + i0 = face.vertex_indices[k]; + size_t vi0 = size_t(i0.v_idx); + + assert(((3 * vi0 + 2) < v.size())); + + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + + TinyObjPoint polypoint(v0x, v0y, v0z); + TinyObjPoint loc = WorldToLocal(polypoint, axis_u, axis_v, axis_w); + + polyline.push_back({loc.x, loc.y}); + } + + polygon.push_back(polyline); + std::vector indices = mapbox::earcut(polygon); + // => result = 3 * faces, clockwise + + assert(indices.size() % 3 == 0); + + // Reconstruct vertex_index_t + for (size_t k = 0; k < indices.size() / 3; k++) { + { + index_t idx0, idx1, idx2; + idx0.vertex_index = face.vertex_indices[indices[3 * k + 0]].v_idx; + idx0.normal_index = + face.vertex_indices[indices[3 * k + 0]].vn_idx; + idx0.texcoord_index = + face.vertex_indices[indices[3 * k + 0]].vt_idx; + idx1.vertex_index = face.vertex_indices[indices[3 * k + 1]].v_idx; + idx1.normal_index = + face.vertex_indices[indices[3 * k + 1]].vn_idx; + idx1.texcoord_index = + face.vertex_indices[indices[3 * k + 1]].vt_idx; + idx2.vertex_index = face.vertex_indices[indices[3 * k + 2]].v_idx; + idx2.normal_index = + face.vertex_indices[indices[3 * k + 2]].vn_idx; + idx2.texcoord_index = + face.vertex_indices[indices[3 * k + 2]].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } + +#else // Built-in ear clipping triangulation + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1(-1); + vertex_index_t i2 = face.vertex_indices[1]; + + // find the two axes to work in + size_t axes[2] = {1, 2}; + for (size_t k = 0; k < npolys; ++k) { + i0 = face.vertex_indices[(k + 0) % npolys]; + i1 = face.vertex_indices[(k + 1) % npolys]; + i2 = face.vertex_indices[(k + 2) % npolys]; + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + continue; + } + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t e0x = v1x - v0x; + real_t e0y = v1y - v0y; + real_t e0z = v1z - v0z; + real_t e1x = v2x - v1x; + real_t e1y = v2y - v1y; + real_t e1z = v2z - v1z; + real_t cx = std::fabs(e0y * e1z - e0z * e1y); + real_t cy = std::fabs(e0z * e1x - e0x * e1z); + real_t cz = std::fabs(e0x * e1y - e0y * e1x); + const real_t epsilon = std::numeric_limits::epsilon(); + // std::cout << "cx " << cx << ", cy " << cy << ", cz " << cz << + // "\n"; + if (cx > epsilon || cy > epsilon || cz > epsilon) { + // std::cout << "corner\n"; + // found a corner + if (cx > cy && cx > cz) { + // std::cout << "pattern0\n"; + } else { + // std::cout << "axes[0] = 0\n"; + axes[0] = 0; + if (cz > cx && cz > cy) { + // std::cout << "axes[1] = 1\n"; + axes[1] = 1; + } + } + break; + } + } + + face_t remainingFace = face; // copy + size_t guess_vert = 0; + vertex_index_t ind[3]; + real_t vx[3]; + real_t vy[3]; + + // How many iterations can we do without decreasing the remaining + // vertices. + size_t remainingIterations = face.vertex_indices.size(); + size_t previousRemainingVertices = + remainingFace.vertex_indices.size(); + + while (remainingFace.vertex_indices.size() > 3 && + remainingIterations > 0) { + // std::cout << "remainingIterations " << remainingIterations << + // "\n"; + + npolys = remainingFace.vertex_indices.size(); + if (guess_vert >= npolys) { + guess_vert -= npolys; + } + + if (previousRemainingVertices != npolys) { + // The number of remaining vertices decreased. Reset counters. + previousRemainingVertices = npolys; + remainingIterations = npolys; + } else { + // We didn't consume a vertex on previous iteration, reduce the + // available iterations. + remainingIterations--; + } + + for (size_t k = 0; k < 3; k++) { + ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys]; + size_t vi = size_t(ind[k].v_idx); + if (((vi * 3 + axes[0]) >= v.size()) || + ((vi * 3 + axes[1]) >= v.size())) { + // ??? + vx[k] = static_cast(0.0); + vy[k] = static_cast(0.0); + } else { + vx[k] = v[vi * 3 + axes[0]]; + vy[k] = v[vi * 3 + axes[1]]; + } + } + + // + // area is calculated per face + // + real_t e0x = vx[1] - vx[0]; + real_t e0y = vy[1] - vy[0]; + real_t e1x = vx[2] - vx[1]; + real_t e1y = vy[2] - vy[1]; + real_t cross = e0x * e1y - e0y * e1x; + // std::cout << "axes = " << axes[0] << ", " << axes[1] << "\n"; + // std::cout << "e0x, e0y, e1x, e1y " << e0x << ", " << e0y << ", " + // << e1x << ", " << e1y << "\n"; + + real_t area = + (vx[0] * vy[1] - vy[0] * vx[1]) * static_cast(0.5); + // std::cout << "cross " << cross << ", area " << area << "\n"; + // if an internal angle + if (cross * area < static_cast(0.0)) { + // std::cout << "internal \n"; + guess_vert += 1; + // std::cout << "guess vert : " << guess_vert << "\n"; + continue; + } + + // check all other verts in case they are inside this triangle + bool overlap = false; + for (size_t otherVert = 3; otherVert < npolys; ++otherVert) { + size_t idx = (guess_vert + otherVert) % npolys; + + if (idx >= remainingFace.vertex_indices.size()) { + // std::cout << "???0\n"; + // ??? + continue; + } + + size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx); + + if (((ovi * 3 + axes[0]) >= v.size()) || + ((ovi * 3 + axes[1]) >= v.size())) { + // std::cout << "???1\n"; + // ??? + continue; + } + real_t tx = v[ovi * 3 + axes[0]]; + real_t ty = v[ovi * 3 + axes[1]]; + if (pnpoly(3, vx, vy, tx, ty)) { + // std::cout << "overlap\n"; + overlap = true; + break; + } + } + + if (overlap) { + // std::cout << "overlap2\n"; + guess_vert += 1; + continue; + } + + // this triangle is an ear + { + index_t idx0, idx1, idx2; + idx0.vertex_index = ind[0].v_idx; + idx0.normal_index = ind[0].vn_idx; + idx0.texcoord_index = ind[0].vt_idx; + idx1.vertex_index = ind[1].v_idx; + idx1.normal_index = ind[1].vn_idx; + idx1.texcoord_index = ind[1].vt_idx; + idx2.vertex_index = ind[2].v_idx; + idx2.normal_index = ind[2].vn_idx; + idx2.texcoord_index = ind[2].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + + // remove v1 from the list + size_t removed_vert_index = (guess_vert + 1) % npolys; + while (removed_vert_index + 1 < npolys) { + remainingFace.vertex_indices[removed_vert_index] = + remainingFace.vertex_indices[removed_vert_index + 1]; + removed_vert_index += 1; + } + remainingFace.vertex_indices.pop_back(); + } + + // std::cout << "remainingFace.vi.size = " << + // remainingFace.vertex_indices.size() << "\n"; + if (remainingFace.vertex_indices.size() == 3) { + i0 = remainingFace.vertex_indices[0]; + i1 = remainingFace.vertex_indices[1]; + i2 = remainingFace.vertex_indices[2]; + { + index_t idx0, idx1, idx2; + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } +#endif + } // npolys + } else { + for (size_t k = 0; k < npolys; k++) { + index_t idx; + idx.vertex_index = face.vertex_indices[k].v_idx; + idx.normal_index = face.vertex_indices[k].vn_idx; + idx.texcoord_index = face.vertex_indices[k].vt_idx; + shape->mesh.indices.push_back(idx); + } + + shape->mesh.num_face_vertices.push_back( + static_cast(npolys)); + shape->mesh.material_ids.push_back(material_id); // per face + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); // per face + } + } + + shape->mesh.tags = tags; + } + + // line + if (!prim_group.lineGroup.empty()) { + // Flatten indices + for (size_t i = 0; i < prim_group.lineGroup.size(); i++) { + for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->lines.indices.push_back(idx); + } + + shape->lines.num_line_vertices.push_back( + int(prim_group.lineGroup[i].vertex_indices.size())); + } + } + + // points + if (!prim_group.pointsGroup.empty()) { + // Flatten & convert indices + for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) { + for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->points.indices.push_back(idx); + } + } + } + + return true; +} + +// Split a string with specified delimiter character and escape character. +// https://rosettacode.org/wiki/Tokenize_a_string_with_escaping#C.2B.2B +static void SplitString(const std::string &s, char delim, char escape, + std::vector &elems) { + std::string token; + + bool escaping = false; + for (size_t i = 0; i < s.size(); ++i) { + char ch = s[i]; + if (escaping) { + escaping = false; + } else if (ch == escape) { + escaping = true; + continue; + } else if (ch == delim) { + if (!token.empty()) { + elems.push_back(token); + } + token.clear(); + continue; + } + token += ch; + } + + elems.push_back(token); +} + +static std::string JoinPath(const std::string &dir, + const std::string &filename) { + if (dir.empty()) { + return filename; + } else { + // check '/' + char lastChar = *dir.rbegin(); + if (lastChar != '/') { + return dir + std::string("/") + filename; + } else { + return dir + filename; + } + } +} + +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning, std::string *err) { + (void)err; + + // Create a default material anyway. + material_t material; + InitMaterial(&material); + + // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification. + bool has_d = false; + bool has_tr = false; + + // has_kd is used to set a default diffuse value when map_Kd is present + // and Kd is not. + bool has_kd = false; + + std::stringstream warn_ss; + + size_t line_no = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + line_no++; + + // Trim trailing whitespace. + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + if (line_no == 1) { + linebuf = removeUtf8Bom(linebuf); + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); + } + + // initial temporary material + InitMaterial(&material); + + has_d = false; + has_tr = false; + has_kd = false; + + // set new mtl name + token += 7; + { + std::string namebuf = parseString(&token); + // TODO: empty name check? + if (namebuf.empty()) { + if (warning) { + (*warning) += "empty material name in `newmtl`\n"; + } + } + material.name = namebuf; + } + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + // diffuse + if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + has_kd = true; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + // transmittance + if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || + (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseReal(&token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + // shininess + if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseReal(&token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + // dissolve + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseReal(&token); + + if (has_tr) { + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } + has_d = true; + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + if (has_d) { + // `d` wins. Ignore `Tr` value. + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } else { + // We invert value of Tr(assume Tr is in range [0, 1]) + // NOTE: Interpretation of Tr is application(exporter) dependent. For + // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) + material.dissolve = static_cast(1.0) - parseReal(&token); + } + has_tr = true; + continue; + } + + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseReal(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseReal(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseReal(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseReal(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseReal(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseReal(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseReal(&token); + continue; + } + + // ambient or ambient occlusion texture + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.ambient_texname), + &(material.ambient_texopt), token); + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.diffuse_texname), + &(material.diffuse_texopt), token); + + // Set a decent diffuse default value if a diffuse texture is specified + // without a matching Kd value. + if (!has_kd) { + material.diffuse[0] = static_cast(0.6); + material.diffuse[1] = static_cast(0.6); + material.diffuse[2] = static_cast(0.6); + } + + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_texname), + &(material.specular_texopt), token); + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_highlight_texname), + &(material.specular_highlight_texopt), token); + continue; + } + + // bump texture + if (((0 == strncmp(token, "map_bump", 8)) || + (0 == strncmp(token, "map_Bump", 8))) && + IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = token; + ParseTextureNameAndOption(&(material.alpha_texname), + &(material.alpha_texopt), token); + continue; + } + + // displacement texture + if (((0 == strncmp(token, "map_disp", 8)) || + (0 == strncmp(token, "map_Disp", 8))) && + IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.displacement_texname), + &(material.displacement_texopt), token); + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.displacement_texname), + &(material.displacement_texopt), token); + continue; + } + + // reflection map + if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.reflection_texname), + &(material.reflection_texopt), token); + continue; + } + + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.roughness_texname), + &(material.roughness_texopt), token); + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.metallic_texname), + &(material.metallic_texopt), token); + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.sheen_texname), + &(material.sheen_texopt), token); + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.emissive_texname), + &(material.emissive_texopt), token); + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.normal_texname), + &(material.normal_texopt), token); + continue; + } + + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, static_cast(len)); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } + } + // flush last material. + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); + + if (warning) { + (*warning) = warn_ss.str(); + } +} + +bool MaterialFileReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *warn, std::string *err) { + if (!m_mtlBaseDir.empty()) { +#ifdef _WIN32 + char sep = ';'; +#else + char sep = ':'; +#endif + + // https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g + std::vector paths; + std::istringstream f(m_mtlBaseDir); + + std::string s; + while (getline(f, s, sep)) { + paths.push_back(s); + } + + for (size_t i = 0; i < paths.size(); i++) { + std::string filepath = JoinPath(paths[i], matId); + + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + } + + std::stringstream ss; + ss << "Material file [ " << matId + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + + } else { + std::string filepath = matId; + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + + std::stringstream ss; + ss << "Material file [ " << filepath + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + + return false; + } +} + +bool MaterialStreamReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *warn, std::string *err) { + (void)err; + (void)matId; + if (!m_inStream) { + std::stringstream ss; + ss << "Material stream in error state. \n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + } + + LoadMtl(matMap, materials, &m_inStream, warn, err); + + return true; +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, const char *filename, const char *mtl_basedir, + bool triangulate, bool default_vcols_fallback) { + attrib->vertices.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->colors.clear(); + shapes->clear(); + + std::stringstream errss; + + std::ifstream ifs(filename); + if (!ifs) { + errss << "Cannot open file [" << filename << "]\n"; + if (err) { + (*err) = errss.str(); + } + return false; + } + + std::string baseDir = mtl_basedir ? mtl_basedir : ""; + if (!baseDir.empty()) { +#ifndef _WIN32 + const char dirsep = '/'; +#else + const char dirsep = '\\'; +#endif + if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; + } + MaterialFileReader matFileReader(baseDir); + + return LoadObj(attrib, shapes, materials, warn, err, &ifs, &matFileReader, + triangulate, default_vcols_fallback); +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn /*= NULL*/, bool triangulate, + bool default_vcols_fallback) { + std::stringstream errss; + + std::vector v; + std::vector vertex_weights; // optional [w] component in `v` + std::vector vn; + std::vector vt; + std::vector vc; + std::vector vw; // tinyobj extension: vertex skin weights + std::vector tags; + PrimGroup prim_group; + std::string name; + + // material + std::set material_filenames; + std::map material_map; + int material = -1; + + // smoothing group id + unsigned int current_smoothing_id = + 0; // Initial value. 0 means no smoothing. + + int greatest_v_idx = -1; + int greatest_vn_idx = -1; + int greatest_vt_idx = -1; + + shape_t shape; + + bool found_all_colors = true; // check if all 'v' line has color info + + size_t line_num = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + + line_num++; + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + if (line_num == 1) { + linebuf = removeUtf8Bom(linebuf); + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + real_t x, y, z; + real_t r, g, b; + + int num_components = parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); + found_all_colors &= (num_components == 6); + + v.push_back(x); + v.push_back(y); + v.push_back(z); + + vertex_weights.push_back( + r); // r = w, and initialized to 1.0 when `w` component is not found. + + if ((num_components == 6) || default_vcols_fallback) { + vc.push_back(r); + vc.push_back(g); + vc.push_back(b); + } + + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + vn.push_back(x); + vn.push_back(y); + vn.push_back(z); + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y; + parseReal2(&x, &y, &token); + vt.push_back(x); + vt.push_back(y); + continue; + } + + // skin weight. tinyobj extension + if (token[0] == 'v' && token[1] == 'w' && IS_SPACE((token[2]))) { + token += 3; + + // vw ... + // example: + // vw 0 0 0.25 1 0.25 2 0.5 + + // TODO(syoyo): Add syntax check + int vid = 0; + vid = parseInt(&token); + + skin_weight_t sw; + + sw.vertex_id = vid; + + while (!IS_NEW_LINE(token[0]) && token[0] != '#') { + real_t j, w; + // joint_id should not be negative, weight may be negative + // TODO(syoyo): # of elements check + parseReal2(&j, &w, &token, -1.0); + + if (j < static_cast(0)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `vw' line. joint_id is negative. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + joint_and_weight_t jw; + + jw.joint_id = int(j); + jw.weight = w; + + sw.weightValues.push_back(jw); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + vw.push_back(sw); + } + + warning_context context; + context.warn = warn; + context.line_number = line_num; + + // line + if (token[0] == 'l' && IS_SPACE((token[1]))) { + token += 2; + + __line_t line; + + while (!IS_NEW_LINE(token[0]) && token[0] != '#') { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi, context)) { + if (err) { + (*err) += + "Failed to parse `l' line (e.g. a zero value for vertex index. " + "Line " + + toString(line_num) + ").\n"; + } + return false; + } + + line.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.lineGroup.push_back(line); + + continue; + } + + // points + if (token[0] == 'p' && IS_SPACE((token[1]))) { + token += 2; + + __points_t pts; + + while (!IS_NEW_LINE(token[0]) && token[0] != '#') { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi, context)) { + if (err) { + (*err) += + "Failed to parse `p' line (e.g. a zero value for vertex index. " + "Line " + + toString(line_num) + ").\n"; + } + return false; + } + + pts.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.pointsGroup.push_back(pts); + + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + face_t face; + + face.smoothing_group_id = current_smoothing_id; + face.vertex_indices.reserve(3); + + while (!IS_NEW_LINE(token[0]) && token[0] != '#') { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi, context)) { + if (err) { + (*err) += + "Failed to parse `f' line (e.g. a zero value for vertex index " + "or invalid relative vertex index). Line " + + toString(line_num) + ").\n"; + } + return false; + } + + greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx; + greatest_vn_idx = + greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx; + greatest_vt_idx = + greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx; + + face.vertex_indices.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + // replace with emplace_back + std::move on C++11 + prim_group.faceGroup.push_back(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6))) { + token += 6; + std::string namebuf = parseString(&token); + + int newMaterialId = -1; + std::map::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { error!! material not found } + if (warn) { + (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n"; + } + } + + if (newMaterialId != material) { + // Create per-face material. Thus we don't add `shape` to `shapes` at + // this time. + // just clear `faceGroup` after `exportGroupsToShape()` call. + exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + prim_group.faceGroup.clear(); + material = newMaterialId; + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + std::stringstream ss; + ss << "Looks like empty filename for mtllib. Use default " + "material (line " + << line_num << ".)\n"; + + (*warn) += ss.str(); + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), materials, + &material_map, &warn_mtl, &err_mtl); + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0) { + shapes->push_back(shape); + } + + shape = shape_t(); + + // material = -1; + prim_group.clear(); + + std::vector names; + + while (!IS_NEW_LINE(token[0]) && token[0] != '#') { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + // names[0] must be 'g' + + if (names.size() < 2) { + // 'g' with empty names + if (warn) { + std::stringstream ss; + ss << "Empty group name. line: " << line_num << "\n"; + (*warn) += ss.str(); + name = ""; + } + } else { + std::stringstream ss; + ss << names[1]; + + // tinyobjloader does not support multiple groups for a primitive. + // Currently we concatinate multiple group names with a space to get + // single group name. + + for (size_t i = 2; i < names.size(); i++) { + ss << " " << names[i]; + } + + name = ss.str(); + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 || + shape.points.indices.size() > 0) { + shapes->push_back(shape); + } + + // material = -1; + prim_group.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + token += 2; + std::stringstream ss; + ss << token; + name = ss.str(); + + continue; + } + + if (token[0] == 't' && IS_SPACE(token[1])) { + const int max_tag_nums = 8192; // FIXME(syoyo): Parameterize. + tag_t tag; + + token += 2; + + tag.name = parseString(&token); + + tag_sizes ts = parseTagTriple(&token); + + if (ts.num_ints < 0) { + ts.num_ints = 0; + } + if (ts.num_ints > max_tag_nums) { + ts.num_ints = max_tag_nums; + } + + if (ts.num_reals < 0) { + ts.num_reals = 0; + } + if (ts.num_reals > max_tag_nums) { + ts.num_reals = max_tag_nums; + } + + if (ts.num_strings < 0) { + ts.num_strings = 0; + } + if (ts.num_strings > max_tag_nums) { + ts.num_strings = max_tag_nums; + } + + tag.intValues.resize(static_cast(ts.num_ints)); + + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = parseInt(&token); + } + + tag.floatValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + tag.stringValues[i] = parseString(&token); + } + + tags.push_back(tag); + + continue; + } + + if (token[0] == 's' && IS_SPACE(token[1])) { + // smoothing group id + token += 2; + + // skip space. + token += strspn(token, " \t"); // skip space + + if (token[0] == '\0') { + continue; + } + + if (token[0] == '\r' || token[1] == '\n') { + continue; + } + + if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' && + token[2] == 'f') { + current_smoothing_id = 0; + } else { + // assume number + int smGroupId = parseInt(&token); + if (smGroupId < 0) { + // parse error. force set to 0. + // FIXME(syoyo): Report warning. + current_smoothing_id = 0; + } else { + current_smoothing_id = static_cast(smGroupId); + } + } + + continue; + } // smoothing group id + + // Ignore unknown command. + } + + // not all vertices have colors, no default colors desired? -> clear colors + if (!found_all_colors && !default_vcols_fallback) { + vc.clear(); + } + + if (greatest_v_idx >= static_cast(v.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vn_idx >= static_cast(vn.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex normal indices out of bounds (line " << line_num + << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vt_idx >= static_cast(vt.size() / 2)) { + if (warn) { + std::stringstream ss; + ss << "Vertex texcoord indices out of bounds (line " << line_num + << ".)\n\n"; + (*warn) += ss.str(); + } + } + + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + // exportGroupsToShape return false when `usemtl` is called in the last + // line. + // we also add `shape` to `shapes` when `shape.mesh` has already some + // faces(indices) + if (ret || shape.mesh.indices + .size()) { // FIXME(syoyo): Support other prims(e.g. lines) + shapes->push_back(shape); + } + prim_group.clear(); // for safety + + if (err) { + (*err) += errss.str(); + } + + attrib->vertices.swap(v); + attrib->vertex_weights.swap(vertex_weights); + attrib->normals.swap(vn); + attrib->texcoords.swap(vt); + attrib->texcoord_ws.swap(vt); + attrib->colors.swap(vc); + attrib->skin_weights.swap(vw); + + return true; +} + +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data /*= NULL*/, + MaterialReader *readMatFn /*= NULL*/, + std::string *warn, /* = NULL*/ + std::string *err /*= NULL*/) { + std::stringstream errss; + + // material + std::set material_filenames; + std::map material_map; + int material_id = -1; // -1 = invalid + + std::vector indices; + std::vector materials; + std::vector names; + names.reserve(2); + std::vector names_out; + + std::string linebuf; + while (inStream.peek() != -1) { + safeGetline(inStream, linebuf); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + real_t x, y, z; + real_t r, g, b; + + int num_components = parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); + if (callback.vertex_cb) { + callback.vertex_cb(user_data, x, y, z, r); // r=w is optional + } + if (callback.vertex_color_cb) { + bool found_color = (num_components == 6); + callback.vertex_color_cb(user_data, x, y, z, r, g, b, found_color); + } + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + if (callback.normal_cb) { + callback.normal_cb(user_data, x, y, z); + } + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; // y and z are optional. default = 0.0 + parseReal3(&x, &y, &z, &token); + if (callback.texcoord_cb) { + callback.texcoord_cb(user_data, x, y, z); + } + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + indices.clear(); + while (!IS_NEW_LINE(token[0]) && token[0] != '#') { + vertex_index_t vi = parseRawTriple(&token); + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + indices.push_back(idx); + size_t n = strspn(token, " \t\r"); + token += n; + } + + if (callback.index_cb && indices.size() > 0) { + callback.index_cb(user_data, &indices.at(0), + static_cast(indices.size())); + } + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + std::stringstream ss; + ss << token; + std::string namebuf = ss.str(); + + int newMaterialId = -1; + std::map::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { warn!! material not found } + if (warn && (!callback.usemtl_cb)) { + (*warn) += "material [ " + namebuf + " ] not found in .mtl\n"; + } + } + + if (newMaterialId != material_id) { + material_id = newMaterialId; + } + + if (callback.usemtl_cb) { + callback.usemtl_cb(user_data, namebuf.c_str(), material_id); + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + (*warn) += + "Looks like empty filename for mtllib. Use default " + "material. \n"; + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, + &material_map, &warn_mtl, &err_mtl); + + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; // This should be warn message. + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } else { + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + names.clear(); + + while (!IS_NEW_LINE(token[0]) && token[0] != '#') { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + if (callback.group_cb) { + if (names.size() > 1) { + // create const char* array. + names_out.resize(names.size() - 1); + for (size_t j = 0; j < names_out.size(); j++) { + names_out[j] = names[j + 1].c_str(); + } + callback.group_cb(user_data, &names_out.at(0), + static_cast(names_out.size())); + + } else { + callback.group_cb(user_data, NULL, 0); + } + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + token += 2; + + std::stringstream ss; + ss << token; + std::string object_name = ss.str(); + + if (callback.object_cb) { + callback.object_cb(user_data, object_name.c_str()); + } + + continue; + } + +#if 0 // @todo + if (token[0] == 't' && IS_SPACE(token[1])) { + tag_t tag; + + token += 2; + std::stringstream ss; + ss << token; + tag.name = ss.str(); + + token += tag.name.size() + 1; + + tag_sizes ts = parseTagTriple(&token); + + tag.intValues.resize(static_cast(ts.num_ints)); + + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.floatValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + std::stringstream ss; + ss << token; + tag.stringValues[i] = ss.str(); + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } +#endif + + // Ignore unknown command. + } + + if (err) { + (*err) += errss.str(); + } + + return true; +} + +bool ObjReader::ParseFromFile(const std::string &filename, + const ObjReaderConfig &config) { + std::string mtl_search_path; + + if (config.mtl_search_path.empty()) { + // + // split at last '/'(for unixish system) or '\\'(for windows) to get + // the base directory of .obj file + // + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) { + mtl_search_path = filename.substr(0, pos); + } + } else { + mtl_search_path = config.mtl_search_path; + } + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + filename.c_str(), mtl_search_path.c_str(), + config.triangulate, config.vertex_color); + + return valid_; +} + +bool ObjReader::ParseFromString(const std::string &obj_text, + const std::string &mtl_text, + const ObjReaderConfig &config) { + std::stringbuf obj_buf(obj_text); + std::stringbuf mtl_buf(mtl_text); + + std::istream obj_ifs(&obj_buf); + std::istream mtl_ifs(&mtl_buf); + + MaterialStreamReader mtl_ss(mtl_ifs); + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color); + + return valid_; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} // namespace tinyobj + +#endif diff --git a/tinyobjloader-config.cmake.in b/tinyobjloader-config.cmake.in new file mode 100644 index 00000000..91f01b0e --- /dev/null +++ b/tinyobjloader-config.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +set(TINYOBJLOADER_VERSION "@TINYOBJLOADER_VERSION@") + +set_and_check(TINYOBJLOADER_INCLUDE_DIRS "@PACKAGE_TINYOBJLOADER_INCLUDE_DIR@") +set_and_check(TINYOBJLOADER_LIBRARY_DIRS "@PACKAGE_TINYOBJLOADER_LIBRARY_DIR@") +set(TINYOBJLOADER_LIBRARIES @LIBRARY_NAME@) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") diff --git a/tinyobjloader.pc.in b/tinyobjloader.pc.in new file mode 100644 index 00000000..048a2873 --- /dev/null +++ b/tinyobjloader.pc.in @@ -0,0 +1,15 @@ +# Generated by CMake @CMAKE_VERSION@ for @PROJECT_NAME@. Any changes to this +# file will be overwritten by the next CMake run. The input file was +# tinyobjloader.pc.in. + +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@TINYOBJLOADER_LIBRARY_DIR@ +includedir=${prefix}/@TINYOBJLOADER_INCLUDE_DIR@ + +Name: @PROJECT_NAME@ +Description: Tiny but powerful single file wavefront obj loader +URL: https://syoyo.github.io/tinyobjloader/ +Version: @TINYOBJLOADER_VERSION@ +Libs: -L${libdir} -l@LIBRARY_NAME@ +Cflags: -I${includedir} diff --git a/tools/windows/premake5.exe b/tools/windows/premake5.exe new file mode 100644 index 00000000..51c05a80 Binary files /dev/null and b/tools/windows/premake5.exe differ diff --git a/vcsetup.bat b/vcsetup.bat new file mode 100644 index 00000000..921c1e9e --- /dev/null +++ b/vcsetup.bat @@ -0,0 +1 @@ +.\\tools\\windows\\premake5.exe vs2013 diff --git a/wercker.yml b/wercker.yml deleted file mode 100644 index 3c1583c1..00000000 --- a/wercker.yml +++ /dev/null @@ -1,12 +0,0 @@ -box: rioki/gcc-cpp@0.0.1 -build: - steps: - # Execute a custom script step. - - script: - name: build - code: | - git clone https://github.com/syoyo/orebuildenv.git - chmod +x ./orebuildenv/build/linux/bin/premake4 - ./orebuildenv/build/linux/bin/premake4 gmake - make - ./test_tinyobjloader