diff --git a/.gitignore b/.gitignore index 99968094..727f6f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,18 @@ waveletNoiseTile.bin waveletNoiseTileDouble.bin build doc +tensorflow/mantaGen/datasets +scenes/results +scenes/frames + +# macos +.DS_Store + +# vscode +\.vscode + +# Python +venv +__pycache__ +.ipynb_checkpoints +# *.ipynb diff --git a/CMakeLists.txt b/CMakeLists.txt index e933a975..c20803a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # # MantaFlow fluid solver framework # -# Copyright 2011-2015 Tobias Pfaff, Nils Thuerey +# Copyright 2011-2020 Tobias Pfaff, Nils Thuerey # # This program is free software, distributed under the terms of the # Apache License, Version 2.0 @@ -13,39 +13,40 @@ project (MantaFlow) cmake_minimum_required(VERSION 2.8) -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/tools/cmake/") -SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) -SET(VERBOSE 1) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/tools/cmake/") +set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) +set(VERBOSE 1) -SET(MANTAVERSION "0.12") +set(MANTAVERSION "0.13") #****************************************************************************** # Default paths # - CMake's path finder is completely useless for Qt5 + Python on Win64 # - allow override from command line on OsX, eg use "cmake .. -DCMAKE_PREFIX_PATH=/Users/someone/qt5.2.1/5.2.1/clang_64/ -IF(WIN32) - SET(WIN_QT_PATH "C:/Qt/5.9/msvc2013_64_opengl") # qt5/win64 - SET(WIN_PYTHON_PATH "C:/Python36") # choose python version with PYTHON_VERSION above - SET(CMAKE_LIBRARY_PATH "C:/Program Files (x86)/Windows Kits/8.0/Lib/win8/um/x64") - SET(CMAKE_PREFIX_PATH ${WIN_QT_PATH}) -ENDIF() +if(WIN32) + set(WIN_QT_PATH "C:/Qt/5.9/msvc2013_64_opengl") # qt5/win64 + set(WIN_PYTHON_PATH "C:/Python36") # choose python version with PYTHON_VERSION above + set(CMAKE_LIBRARY_PATH "C:/Program Files (x86)/Windows Kits/8.0/Lib/win8/um/x64") + set(CMAKE_PREFIX_PATH ${WIN_QT_PATH}) +endif() -IF(APPLE) - IF(NOT CMAKE_PREFIX_PATH) - SET(CMAKE_PREFIX_PATH "/usr/local/opt/qt5/") # mac/homebrew (version independent) - #SET(CMAKE_PREFIX_PATH "/home/myname/qt/5.5/clang_64") # other... - ENDIF() -ENDIF() +if(APPLE) + if(NOT CMAKE_PREFIX_PATH) + set(CMAKE_PREFIX_PATH "/usr/local/opt/qt5/") # mac/homebrew (version independent) + #set(CMAKE_PREFIX_PATH "/home/myname/qt/5.5/clang_64") # other... + #set(CMAKE_PREFIX_PATH "/Users/Cesar/Qt/5.15.0/clang_64/") + endif() +endif() #****************************************************************************** # setup default params -IF(NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE "Release") -ELSE() +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +else() MESSAGE("Build-type: '${CMAKE_BUILD_TYPE}'") -ENDIF() +endif() # compilation versions OPTION(DEBUG "Enable debug compilation" OFF) @@ -65,6 +66,7 @@ OPTION(NUMPY "Compile with numpy integration?" OFF) # compile with openVDB support OPTION(OPENVDB "Exporting OpenVDB files" OFF) +OPTION(OPENVDB_BLOSC "Enable OpenVDB Blosc compression" OFF) # further options for blender integration OPTION(BLENDER "Compile for Blender integration" OFF) @@ -74,42 +76,45 @@ OPTION(BUILD_STATIC "Enable building static library" OFF) # special option to compile manta without any python, disables python glue code (while trying to keep as much of the rest as possible) # this also means: static lib output , instead of executable OPTION(NOPYTHON "Compile without python support (limited functionality!)" OFF) -IF(NOPYTHON) - SET(BUILD_STATIC ON) -ENDIF() +if(NOPYTHON) + set(BUILD_STATIC ON) +endif() # check consistency of MT options -SET(MT OFF) -SET(MT_TYPE "NONE") -if (TBB) - SET (MT_TYPE "TBB") - SET (MT ON) +set(MT OFF) +set(MT_TYPE "NONE") +if(TBB) + set(MT_TYPE "TBB") + set(MT ON) endif() -if (OPENMP) - SET (MT_TYPE "OPENMP") - SET (MT ON) +if(OPENMP) + set(MT_TYPE "OPENMP") + set(MT ON) endif() -if (TBB AND OPENMP) +if(TBB AND OPENMP) message(FATAL_ERROR "Cannot activate both OPENMP and TBB") endif() # make sure debug settings match... -IF(NOT DEBUG) - IF(${CMAKE_BUILD_TYPE} STREQUAL "Debug") - SET(DEBUG 1) - ENDIF() -ENDIF() +if(NOT DEBUG) + if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + set(DEBUG 1) + endif() +endif() add_definitions ( -DMANTAVERSION="${MANTAVERSION}" ) -IF(DEBUG) - SET(CMAKE_BUILD_TYPE "Debug") +if(DEBUG) + set(CMAKE_BUILD_TYPE "Debug") add_definitions ( -DDEBUG=1 ) -ENDIF() +endif() -IF(BLENDER) +if(BLENDER) add_definitions ( -DBLENDER=1 ) -ENDIF() +endif() +if(BLENDER AND GUI) + message(FATAL_ERROR "Cannot activate both BLENDER and GUI") +endif() # translate option into python version string if(NOT PYTHON_VERSION) @@ -118,6 +123,9 @@ else() set(PYTHON_VER_ID ${PYTHON_VERSION}) endif() +if(OPENVDB AND NOT TBB) + message(FATAL_ERROR "Cannot activate OPENVDB without TBB") +endif() MESSAGE(STATUS "") MESSAGE(STATUS "Options - " @@ -138,7 +146,7 @@ MESSAGE(STATUS "") # Pre-processor # compile prep -SET(SOURCES +set(SOURCES source/preprocessor/main.cpp source/preprocessor/code.cpp source/preprocessor/tokenize.cpp @@ -149,21 +157,21 @@ SET(SOURCES source/preprocessor/codegen_kernel.cpp ) add_executable(prep ${SOURCES}) -if (NOT WIN32) +if(NOT WIN32) set_target_properties(prep PROPERTIES COMPILE_FLAGS "-Wall -O2") endif() #****************************************************************************** # Setup main project -SET(F_LIBS "" ) -SET(F_LIB_PATHS) -SET(F_LINKADD "") # additional linker flags, not a list +set(F_LIBS "" ) +set(F_LIB_PATHS) +set(F_LINKADD "") # additional linker flags, not a list set(PP_PATH "pp") -SET(SILENT_SOURCES) +set(SILENT_SOURCES) # need pre-processing -SET(PP_SOURCES +set(PP_SOURCES source/general.cpp source/fluidsolver.cpp source/conjugategrad.cpp @@ -176,9 +184,12 @@ SET(PP_SOURCES source/mesh.cpp source/particle.cpp source/movingobs.cpp + source/fileio/ioutil.cpp source/fileio/iogrids.cpp source/fileio/iomeshes.cpp source/fileio/ioparticles.cpp + source/fileio/iovdb.cpp + source/fileio/mantaio.cpp source/noisefield.cpp source/kernel.cpp source/vortexsheet.cpp @@ -197,6 +208,7 @@ SET(PP_SOURCES source/plugin/meshplugins.cpp source/plugin/pressure.cpp source/plugin/ptsplugins.cpp + source/plugin/secondaryparticles.cpp source/plugin/surfaceturbulence.cpp source/plugin/vortexplugins.cpp source/plugin/waveletturbulence.cpp @@ -205,7 +217,7 @@ SET(PP_SOURCES source/test.cpp ) -SET(PP_HEADERS +set(PP_HEADERS source/general.h source/commonkernels.h source/conjugategrad.h @@ -241,7 +253,7 @@ set(NOPP_SOURCES source/util/simpleimage.cpp ) -SET(NOPP_HEADERS +set(NOPP_HEADERS source/pwrapper/pythonInclude.h source/pwrapper/pclass.h source/pwrapper/registry.h @@ -256,7 +268,7 @@ SET(NOPP_HEADERS source/util/solvana.h ) -if (GUI) +if(GUI) # need QT preprocessor set(QT_HEADERS source/gui/mainwindow.h @@ -282,7 +294,7 @@ if (GUI) endif() # include dirs -SET(INCLUDE_PATHS +set(INCLUDE_PATHS ${CMAKE_CURRENT_BINARY_DIR}/${PP_PATH}/source ${CMAKE_CURRENT_BINARY_DIR}/${PP_PATH}/source/util ${CMAKE_CURRENT_BINARY_DIR}/${PP_PATH}/source/fileio @@ -292,7 +304,7 @@ SET(INCLUDE_PATHS ) # reduced version without python -if (NOPYTHON) +if(NOPYTHON) # replace some of the source lists set(NOPP_SOURCES source/nopython/pclass.cpp @@ -301,7 +313,7 @@ if (NOPYTHON) source/util/simpleimage.cpp ) # update paths to use nopython versions - SET(INCLUDE_PATHS + set(INCLUDE_PATHS ${CMAKE_CURRENT_BINARY_DIR}/${PP_PATH}/source ${CMAKE_CURRENT_BINARY_DIR}/${PP_PATH}/source/util ${CMAKE_CURRENT_BINARY_DIR}/${PP_PATH}/source/fileio @@ -321,13 +333,11 @@ if(MT) # Intel TBB add_definitions( -DTBB=1 ) - if (DEBUG) + if(DEBUG) add_definitions( -DTBB_USE_DEBUG=1 ) - list(APPEND F_LIBS tbb) - else() - list(APPEND F_LIBS tbb) endif() - if (WIN32) + list(APPEND F_LIBS tbb) + if(WIN32) find_package(TBB REQUIRED) list(APPEND INCLUDE_PATHS ${TBB_INCLUDE_DIRS}) list(APPEND F_LIB_PATHS ${TBB_LIBRARY_DIRS}) @@ -340,11 +350,11 @@ if(MT) # OpenMP add_definitions( -DOPENMP=1 ) - if (WIN32) + if(WIN32) add_definitions( /openmp) else() add_definitions(-fopenmp) - SET(F_LINKADD "${F_LINKADD} -fopenmp ") + set(F_LINKADD "${F_LINKADD} -fopenmp ") endif() endif() endif() @@ -352,7 +362,7 @@ endif() #****************************************************************************** # optional - openvdb support -IF(OPENVDB) +if(OPENVDB) add_definitions(-DOPENVDB=1) message("Note - mantaflow assumes OpenVDB is installed in '/openVDB'. For windows you can find an example zip package on the mantaflow homepage under downloads.") @@ -372,7 +382,7 @@ IF(OPENVDB) list(APPEND F_LIBS ${OPENVDB_LIBRARY}) add_definitions(${OPENVDB_DEFINITIONS}) else() - if (WIN32) + if(WIN32) message(FATAL_ERROR "Cannot find OpenVDB") else() message("Warning - OpenVDB not found, trying default paths") @@ -382,7 +392,7 @@ IF(OPENVDB) endif() # for windos try houdini, otherwise use openExr directly - if (WIN32) + if(WIN32) find_package(Houdini REQUIRED) if(HOUDINI_FOUND) message("Found Houdini") @@ -394,7 +404,11 @@ IF(OPENVDB) else() # for open exr - should be in the default path, add includes if necessary endif() -ENDIF() +endif() + +if(OPENVDB_BLOSC) + add_definitions(-DOPENVDB_BLOSC=1) +endif() #****************************************************************************** # add a target to generate API documentation with Doxygen @@ -432,15 +446,15 @@ endif(DOXYGEN_FOUND) # Python # note - if configuration fails, comment out the auto block below, and manually set the following two paths: -#set(PYTHON_INCLUDE_DIRS "PYTHON_PATH/include") -#set(PYTHON_LIBRARY "PYTHON_PATH/libs/pythonX.Y.lib/so") +set(PYTHON_INCLUDE_DIRS "/Users/Cesar/anaconda3/include/python3.7m/") +set(PYTHON_LIBRARIES "/Users/Cesar/anaconda3/lib/libpython3.7m.dylib") # auto block -if (WIN32) +if(WIN32) # convenience override for windows, typically the auto find doesnt work, so fall back to WIN_PYTHON_PATH find_package(PythonLibs ${PYTHON_VER_ID} QUIET) if(NOT PYTHONLIBS_FOUND) set(PYTHON_INCLUDE_DIRS "${WIN_PYTHON_PATH}/include") - set(PYTHON_LIBRARY "${WIN_PYTHON_PATH}/libs/python${PYTHON_VER_ID}.lib") + set(PYTHON_LIBRARIES "${WIN_PYTHON_PATH}/libs/python${PYTHON_VER_ID}.lib") endif() else() # try to configure from python itself first @@ -492,7 +506,7 @@ endif() #****************************************************************************** # Z lib compression -if (1) +if(1) # default: build from own sources set(ZLIB_SRC adler32.c compress.c crc32.c deflate.c gzclose.c gzlib.c gzread.c gzwrite.c inflate.c infback.c inftrees.c inffast.c trees.c uncompr.c zutil.c) @@ -502,19 +516,20 @@ if (1) set(ZLIB_ADDFLAGS "-Dverbose=-1") if(NOT WIN32) - set(ZLIB_ADDFLAGS "-Wno-implicit-function-declaration -Dverbose=-1") + # otherwise we get warnings that we could only fix by upgrading zlib to a version > 1.2.8 + set(ZLIB_ADDFLAGS "-Wno-implicit-function-declaration -Wno-shift-negative-value -Dverbose=-1") endif() set_source_files_properties(${SILENT_SOURCES} PROPERTIES COMPILE_FLAGS "${ZLIB_ADDFLAGS}") list(APPEND INCLUDE_PATHS dependencies/zlib-1.2.8) endif() -if (0) +if(0) # try to locate and use system libs include(FindZLIB) list(APPEND INCLUDE_PATHS ${ZLIB_INCLUDE_DIR}) list(APPEND F_LIBS ${ZLIB_LIBRARIES}) endif() -if (0) +if(0) # disable add_definitions(-DNO_ZLIB=1) endif() @@ -539,14 +554,14 @@ endif() #****************************************************************************** # generate git repository info , currently only for unix systems -IF(NOT WIN32) +if(NOT WIN32) set(GITINFO "${CMAKE_CURRENT_BINARY_DIR}/${PP_PATH}/source/gitinfo.h") MESSAGE(STATUS "Git info target header ${GITINFO}") add_custom_command(OUTPUT ${GITINFO} COMMAND python${PYTHON_VER_ID} "${CMAKE_CURRENT_SOURCE_DIR}/tools/getGitVersion.py" "${GITINFO}" DEPENDS ${PP_SOURCES} ${PP_HEADERS} ${NOPP_SOURCES} ${NOPP_HEADERS} ${QT_SOURCES} ${QT_HEADERS} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) -ENDIF() +endif() #****************************************************************************** # apply preprocessor @@ -557,7 +572,7 @@ set(PP_REGCPP) set(PP_REGS) set(PP_PREPD "0") set(PREPPED_SOURCES) -if (PREPDEBUG) +if(PREPDEBUG) set(PP_PREPD "1") endif() FOREACH(it ${PP_SOURCES} ${PP_HEADERS}) @@ -568,14 +583,14 @@ FOREACH(it ${PP_SOURCES} ${PP_HEADERS}) string(REPLACE "source/" "" INFILE ${it}) set(OUTFILES "${CURPP}") - if ("${CUREXT}" STREQUAL ".h" OR "${CUREXT}" STREQUAL ".py") - IF(NOT NOPYTHON) + if("${CUREXT}" STREQUAL ".h" OR "${CUREXT}" STREQUAL ".py") + if(NOT NOPYTHON) list(APPEND PP_REGS "${CURPP}.reg") list(APPEND PP_REGCPP "${CURPP}.reg.cpp") - ENDIF() + endif() set_source_files_properties("${CURPP}.reg.cpp" OBJECT_DEPENDS "${CURPP}") list(APPEND OUTFILES "${CURPP}.reg") - ENDIF() + endif() # preprocessor add_custom_command(OUTPUT ${OUTFILES} @@ -590,7 +605,7 @@ ENDFOREACH(it) list(APPEND SOURCES ${PREPPED_SOURCES}) # link reg files , for python glue code -IF(NOT NOPYTHON) +if(NOT NOPYTHON) add_custom_command(OUTPUT ${PP_REGCPP} COMMAND prep link ${PP_REGS} DEPENDS prep ${PP_REGS} @@ -605,7 +620,7 @@ IF(NOT NOPYTHON) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Ensuring registration functions are not removed by compiler") list(APPEND SOURCES ${PP_REGISTER}) -ENDIF() +endif() #****************************************************************************** # QT for GUI @@ -622,14 +637,13 @@ if(GUI) cmake_policy(SET CMP0020 NEW) find_package(Qt5Core QUIET) - if (Qt5Core_FOUND) + if(Qt5Core_FOUND) message("Using Qt5") find_package(Qt5Widgets REQUIRED) find_package(Qt5OpenGL REQUIRED) qt5_wrap_cpp(MOC_OUTFILES ${QT_REMAP} ) qt5_add_resources(QT_RES resources/res.qrc ) - add_definitions(-DGUI=1) add_definitions(${Qt5Widgets_DEFINITIONS}) list(APPEND INCLUDE_PATHS ${Qt5Widgets_INCLUDE_DIRS} ${Qt5OpenGL_INCLUDE_DIRS}) list(APPEND F_LIBS ${Qt5Widgets_LIBRARIES} ${Qt5OpenGL_LIBRARIES}) @@ -647,9 +661,9 @@ if(GUI) list(APPEND SOURCES ${MOC_OUTFILES} ${QT_RES}) endif() - if (APPLE) + if(APPLE) # mac opengl framework - SET(F_LINKADD "${F_LINKADD} -framework OpenGL ") + set(F_LINKADD "${F_LINKADD} -framework OpenGL ") else() find_package(OpenGL REQUIRED) list(APPEND F_LIBS ${OPENGL_LIBRARIES}) @@ -659,18 +673,18 @@ endif() #****************************************************************************** # setup executable -SET(EXECCMD manta) +set(EXECCMD manta) -SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${F_LINKADD} ") -SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${F_LINKADD} ") +set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${F_LINKADD} ") +set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${F_LINKADD} ") include_directories( ${INCLUDE_PATHS}) link_directories( ${F_LIB_PATHS} ) -IF(NOT NOPYTHON) +if(NOT NOPYTHON) # build regular executable - if (WIN32) + if(WIN32) # make nice folders for Visual Studio set_source_files_properties(${PP_SOURCES} ${PP_HEADERS} ${NOPP_HEADERS} PROPERTIES HEADER_FILE_ONLY TRUE) add_executable(${EXECCMD} ${SOURCES} ${PP_SOURCES} ${PP_HEADERS}) @@ -681,65 +695,74 @@ IF(NOT NOPYTHON) target_link_libraries( ${EXECCMD} ${F_LIBS} ) # fix for "el capitan" TBB and changed dynamic library env vars, lookup of TBB can cause problems without @rpath - IF(APPLE) + if(APPLE) EXEC_PROGRAM(uname ARGS -v OUTPUT_VARIABLE OSX_VERSION) STRING(REGEX MATCH "[0-9]+" OSX_VERSION ${OSX_VERSION}) - IF (OSX_VERSION GREATER 14) + if(OSX_VERSION GREATER 14) ADD_CUSTOM_COMMAND(TARGET manta POST_BUILD COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change libtbb.dylib @rpath/libtbb.dylib $ ) - ENDIF() - ENDIF() + endif() + endif() -ELSEIF(BUILD_STATIC) +elseif(BUILD_STATIC) # only build static library in nopython mode - SET(EXECCMD manta_static) + set(EXECCMD manta_static) # only build static library, e.g. for nopython mode ADD_LIBRARY (core OBJECT ${SOURCES} ${GITINFO}) ADD_LIBRARY (${EXECCMD} STATIC $) -ENDIF() +endif() # gcc compiler flags -IF(NOT WIN32) - IF(DEBUG) +if(NOT WIN32) + if(DEBUG) # stricter: no optimizations and inlining set_target_properties(${EXECMD} PROPERTIES COMPILE_FLAGS " -O0 -fno-inline -Wall ") - ELSE() + else() # non-debug, optimized version set_target_properties(${EXECMD} PROPERTIES COMPILE_FLAGS " -O3 -Wall ") - ENDIF() -ENDIF() + endif() +endif() -# use c++11 for all pre-processed c++ sources -# TODO, enable by default +# if supported, use c++14 for all pre-processed c++ sources include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++14" COMPILER_SUPPORTS_CXX14) CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) -SET(CPP11_FLAG " ") -if(COMPILER_SUPPORTS_CXX11) - SET(CPP11_FLAG " -std=c++11 ") - set_source_files_properties(${PREPPED_SOURCES} ${PP_REGCPP} PROPERTIES COMPILE_FLAGS ${CPP11_FLAG}) - add_definitions(-DMANTA_WITHCPP11=1) +set(CXX_STANDARD_FLAG " ") +if(COMPILER_SUPPORTS_CXX14) + set(CXX_STANDARD_FLAG " -std=c++14 ") +elseif(COMPILER_SUPPORTS_CXX11) + set(CXX_STANDARD_FLAG " -std=c++11 ") elseif(COMPILER_SUPPORTS_CXX0X) - SET(CPP11_FLAG " -std=c++0x ") - set_source_files_properties(${PREPPED_SOURCES} ${PP_REGCPP} PROPERTIES COMPILE_FLAGS ${CPP11_FLAG}) - add_definitions(-DMANTA_WITHCPP11=1) + set(CXX_STANDARD_FLAG " -std=c++0x ") +endif() +set_source_files_properties(${PREPPED_SOURCES} ${PP_REGCPP} PROPERTIES COMPILE_FLAGS ${CXX_STANDARD_FLAG}) + +# sanity check for openvdb support +if(OPENVDB AND NOT COMPILER_SUPPORTS_CXX14) + message(FATAL_ERROR "Compilation with OpenVDB requires a compiler with C++14 support") endif() # necessary for newer LLVM clang versions, to prevent abundance of "Namify" template warnings... for compatibility disable unknown warnings -IF(APPLE) - set_source_files_properties(${PP_REGCPP} ${PREPPED_SOURCES} ${NOPP_SOURCES} PROPERTIES COMPILE_FLAGS " -Wno-undefined-var-template -Wno-unknown-warning-option ${CPP11_FLAG}" ) +if(APPLE) + set_source_files_properties(${PP_REGCPP} ${PREPPED_SOURCES} ${NOPP_SOURCES} PROPERTIES COMPILE_FLAGS " -Wno-undefined-var-template -Wno-unknown-warning-option ${CXX_STANDARD_FLAG}" ) if(GUI) - set_source_files_properties(${MOC_OUTFILES} PROPERTIES COMPILE_FLAGS " -Wno-undefined-var-template -Wno-unknown-warning-option ${CPP11_FLAG}" ) - ENDIF() -ENDIF() + set_source_files_properties(${MOC_OUTFILES} PROPERTIES COMPILE_FLAGS " -Wno-undefined-var-template -Wno-unknown-warning-option ${CXX_STANDARD_FLAG}" ) + endif() +endif() + +# thread support for linux +if(NOT APPLE AND NOT WIN32) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") +endif() # python debugging -IF(DEBUG_PYTHON_WITH_RELEASE) +if(DEBUG_PYTHON_WITH_RELEASE) add_definitions(-DDEBUG_PYTHON_WITH_RELEASE=1) -ENDIF() +endif() # MSVC compiler settings -IF(WIN32) +if(WIN32) # get rid of some MSVC warnings add_definitions( /wd4018 /wd4146 /wd4800 ) @@ -754,7 +777,7 @@ IF(WIN32) # disable warnings for unsecure functions add_definitions( /D "_CRT_SECURE_NO_WARNINGS" ) - if (BLENDER) + if(BLENDER) set(CMAKE_CXX_FLAGS_DEBUG "/MTd /Od /Ob0 /D_DEBUG /D PLATFORM_WINDOWS /Zi /RTC1 " ) set(CMAKE_CXX_FLAGS_MINSIZEREL "/MT /O1 /Ob1 /D NDEBUG /D PLATFORM_WINDOWS " ) set(CMAKE_CXX_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG /D PLATFORM_WINDOWS " ) @@ -763,7 +786,7 @@ IF(WIN32) # enable when using Qt creator: #add_definitions(/FS) -ENDIF() +endif() # print debug summary # MESSAGE(STATUS "DEBUG Flag-Summary - Includes: '${INCLUDE_PATHS}' | Libs: '${F_LIBS}' | LibPaths: '${F_LIB_PATHS}' ") diff --git a/README.md b/README.md index 323603e6..7339579d 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ Mantaflow is an open-source framework targeted at fluid simulation research in C Its parallelized C++ solver core, python scene definition interface and plugin system allow for quickly prototyping and testing new algorithms. In addition, it provides a toolbox of examples for deep learning experiments with fluids. E.g., it contains examples -how to build convolutional neural network setups in conjunction with the [tensorflow framework](https://www.tensorflow.org). +how to build convolutional neural network setups in conjunction with [tensorflow](https://www.tensorflow.org). -For more information on how to install, run and code with Mantaflow, please head over to our home page at -[http://mantaflow.com](http://mantaflow.com) +For deep learning with fully differentiable fluid simulations, please check out our [phiflow framework](https://github.com/tum-pbs/PhiFlow). -![mantaflow logo](resources/mantaflow-logo1.png) +More information on how to install, run and code with Mantaflow, please head over to our home page at +[http://mantaflow.com](http://mantaflow.com) diff --git a/scenes/fire.py b/scenes/fire.py index 7df7e2f5..8f50a34f 100644 --- a/scenes/fire.py +++ b/scenes/fire.py @@ -12,8 +12,8 @@ s = Solver(name='main', gridSize = gs, dim=dim) # buoyancy parameters -smokeDensity = -0.001 # alpha -smokeTempDiff = 0.1 # beta +smokeDensity = -0.001 +smokeTempDiff = 0.1 # set time step range s.frameLength = 1.2 # length of one frame (in "world time") @@ -47,9 +47,12 @@ noise.valOffset = 0.75 noise.timeAnim = 0.2 -# needs positive gravity because of addHeatBuoyancy2() gravity = vec3(0,-0.0981,0) +# vorticity global is applied to all cells, vorticity flames only to ones with fuel +vortGlobal = 0.1 +vortFlames = 0.5 + # initialize domain with boundary bWidth=1 flags.initDomain( boundaryWidth=bWidth ) @@ -90,7 +93,10 @@ if doOpen: resetOutflow( flags=flags, real=density ) - vorticityConfinement( vel=vel, flags=flags, strength=0.1 ) + # Apply global and fuel-based flame vorticity + flame.copyFrom(fuel) + flame.multConst(vortFlames) # temporarily misuse flame grid + vorticityConfinement( vel=vel, flags=flags, strength=vortGlobal, strengthCell=flame) addBuoyancy( flags=flags, density=density, vel=vel, gravity=(gravity*smokeDensity ) ) addBuoyancy( flags=flags, density=heat, vel=vel, gravity=(gravity*smokeTempDiff) ) diff --git a/scenes/flip05_nbflip.py b/scenes/flip05_nbflip.py index a3df1eeb..056250cb 100644 --- a/scenes/flip05_nbflip.py +++ b/scenes/flip05_nbflip.py @@ -153,5 +153,9 @@ adjustNumber( parts=pp, vel=vel, flags=flags, minParticles=1*minParticles, maxParticles=2*minParticles, phi=phi ) s.step() - - + + # optionally save some of the simulation objects to an OpenVDB file (requires compilation with -DOPENVDB=1) + if 0: + # note: when saving pdata fields, they must be accompanied by and listed before their parent pp + objects = [flags, phiParts, phi, pressure, vel, pVel, pp] + save( name='fluid_data_%04d.vdb' % s.frame, objects=objects ) diff --git a/scenes/plume_2d.py b/scenes/plume_2d.py index a904a59c..6c82f273 100644 --- a/scenes/plume_2d.py +++ b/scenes/plume_2d.py @@ -36,7 +36,10 @@ if t<300: source.applyToGrid(grid=density, value=1) - + + # optionally, dissolve smoke + #dissolveSmoke(flags=flags, density=density, speed=4) + advectSemiLagrange(flags=flags, vel=vel, grid=density, order=2) advectSemiLagrange(flags=flags, vel=vel, grid=vel, order=2) resetOutflow(flags=flags,real=density) diff --git a/scenes/view_particles_in_notebook.ipynb b/scenes/view_particles_in_notebook.ipynb new file mode 100644 index 00000000..45413390 --- /dev/null +++ b/scenes/view_particles_in_notebook.ipynb @@ -0,0 +1,108 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import k3d\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "Output()", + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "b32e24084a764b3f960f19125f2df785" + } + }, + "metadata": {} + } + ], + "source": [ + "positions = np.loadtxt('frames/particles-frame-0.nptxt').astype(np.float32)\n", + "plot = k3d.plot()\n", + "points = k3d.points(positions, point_size=0.4, shader='3dSpecular')\n", + "plot += points\n", + "plot.display()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": "Output()", + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "7abf44e35b4c430aba7bf0c6d17fba96" + } + }, + "metadata": {} + } + ], + "source": [ + "# View 50 frames at a time\n", + "\n", + "start = 0\n", + "stop = 49\n", + "\n", + "if start > 0:\n", + " offset = start\n", + " start = 0\n", + " stop = stop - offset\n", + "else:\n", + " offset = 0\n", + "\n", + "positions = {\n", + " str(t):np.loadtxt('frames/particles-frame-%d.nptxt' % (t+offset)).astype(np.float32) for t in range(start, stop, 1)\n", + "}\n", + "plot = k3d.plot(name='points', time=float(start))\n", + "plt_points = k3d.points(positions=positions, point_size=0.4, shader='3dSpecular', color=0xD0D3D4, opacity=0.5)\n", + "plot += plt_points\n", + "plot.display()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.7.6 64-bit ('anaconda3': conda)", + "language": "python", + "name": "python37664bitanaconda3condab615bb15fcd4464bb2000e7d9e446dfd" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6-final" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file diff --git a/scenes/view_particles_in_notebook.py b/scenes/view_particles_in_notebook.py new file mode 100644 index 00000000..54e95dc9 --- /dev/null +++ b/scenes/view_particles_in_notebook.py @@ -0,0 +1,81 @@ +# +# NUMPY file format test +# +from manta import * + +# solver params +dim = 3 +# dim = 2 +particleNumber = 2 +res = 64 +gs = vec3(res,res,res) + +if (dim==2): + gs.z=1 + particleNumber = 3 # use more particles in 2d + +s = Solver(name='main', gridSize = gs, dim=dim) +s.timestep = 0.5 + +# prepare grids and particles +flags = s.create(FlagGrid) +flags2 = s.create(FlagGrid) +vel = s.create(MACGrid) +vel2 = s.create(MACGrid) +velOld = s.create(MACGrid) +pressure = s.create(RealGrid) +pressure2 = s.create(RealGrid) +tmpVec3 = s.create(VecGrid) +pp = s.create(BasicParticleSystem) +# add velocity data to particles +pVel = pp.create(PdataVec3) +# apic part +mass = s.create(MACGrid) +pCx = pp.create(PdataVec3) +pCy = pp.create(PdataVec3) +pCz = pp.create(PdataVec3) + +# scene setup +flags.initDomain(boundaryWidth=0) +# enable one of the following +# fluidbox = Box(parent=s, p0=gs*vec3(0,0,0), p1=gs*vec3(0.4,0.6,1)) # breaking dam +fluidbox = Box(parent=s, p0=gs*vec3(0.4,0.72,0.4), p1=gs*vec3(0.6,0.92,0.6)) # centered falling block +phiInit = fluidbox.computeLevelset() +flags.updateFromLevelset(phiInit) +# phiInit is not needed from now on! + +# note, there's no resamplig here, so we need _LOTS_ of particles... +sampleFlagsWithParticles(flags=flags, parts=pp, discretization=particleNumber, randomness=0.2) + +#main loop +for t in range(200): + mantaMsg('\nFrame %i, simulation time %f' % (s.frame, s.timeTotal)) + + # Affine Particle in Cell (APIC) method + pp.advectInGrid(flags=flags, vel=vel, integrationMode=IntRK4, deleteInObstacle=False) + apicMapPartsToMAC(flags=flags, vel=vel, parts=pp, partVel=pVel, cpx=pCx, cpy=pCy, cpz=pCz, mass=mass) + extrapolateMACFromWeight(vel=vel , distance=2, weight=tmpVec3) + markFluidCells(parts=pp, flags=flags) + + addGravity(flags=flags, vel=vel, gravity=(0,-0.002,0)) + + # pressure solve + setWallBcs(flags=flags, vel=vel) + solvePressure(flags=flags, vel=vel, pressure=pressure) + setWallBcs(flags=flags, vel=vel) + + # we dont have any levelset, ie no extrapolation, so make sure the velocities are valid + extrapolateMACSimple(flags=flags, vel=vel) + + # APIC velocity update + apicMapMACGridToParts(partVel=pVel, cpx=pCx, cpy=pCy, cpz=pCz, parts=pp, vel=vel, flags=flags) + + s.step() + + file_name = './frames/particles-frame-%d.nptxt' % s.frame + pp.save(file_name) + + + + + diff --git a/scenes/waveletTurbulenceObs.py b/scenes/waveletTurbulenceObs.py index 47ca4b0a..d0f46cc4 100644 --- a/scenes/waveletTurbulenceObs.py +++ b/scenes/waveletTurbulenceObs.py @@ -104,11 +104,12 @@ tempFlag = sm.create(FlagGrid) # wavelet turbulence noise field -xl_wltnoise = NoiseField( parent=xl, loadFromFile=True) -# scale according to lowres sim , smaller numbers mean larger vortices -# note - this noise is parented to xl solver, thus will automatically rescale -xl_wltnoise.posScale = vec3( int(1.0*gs.x) ) * 0.5 -xl_wltnoise.timeAnim = 0.1 +if(upres>0): + xl_wltnoise = NoiseField( parent=xl, loadFromFile=True) + # scale according to lowres sim , smaller numbers mean larger vortices + # note - this noise is parented to xl solver, thus will automatically rescale + xl_wltnoise.posScale = vec3( int(1.0*gs.x) ) * 0.5 + xl_wltnoise.timeAnim = 0.1 # setup user interface if (GUI): diff --git a/source/conjugategrad.cpp b/source/conjugategrad.cpp index 6da90d41..e9d0a5e9 100644 --- a/source/conjugategrad.cpp +++ b/source/conjugategrad.cpp @@ -392,8 +392,8 @@ PYTHON() void cgSolveDiffusion(const FlagGrid& flags, GridBase& grid, debMsg("FluidSolver::solveDiffusion iterations:"<getIterations()<<", res:"<getSigma(), CG_DEBUGLEVEL); } else - if( (grid.getType() & GridBase::TypeVec3) || (grid.getType() & GridBase::TypeVec3) ) - { + if( (grid.getType() & GridBase::TypeVec3) || (grid.getType() & GridBase::TypeMAC) ) + { Grid& vec = ((Grid&) grid); Grid u(parent); diff --git a/source/fileio/iogrids.cpp b/source/fileio/iogrids.cpp index dd06fd82..6197bb9d 100644 --- a/source/fileio/iogrids.cpp +++ b/source/fileio/iogrids.cpp @@ -22,10 +22,6 @@ extern "C" { } #endif -#if OPENVDB==1 -#include "openvdb/openvdb.h" -#endif - #include "cnpy.h" #include "mantaio.h" #include "grid.h" @@ -231,49 +227,80 @@ static int unifyGridType(int type) { //***************************************************************************** template -void writeGridTxt(const string& name, Grid* grid) { +int writeGridTxt(const string& name, Grid* grid) { debMsg( "writing grid " << grid->getName() << " to text file " << name ,1); ofstream ofs(name.c_str()); if (!ofs.good()) errMsg("writeGridTxt: can't open file " << name); + return 0; FOR_IJK(*grid) { ofs << Vec3i(i,j,k) <<" = "<< (*grid)(i,j,k) <<"\n"; } ofs.close(); + return 1; +} + +int writeGridsTxt(const string& name, std::vector* grids) { + errMsg("writeGridsTxt: writing multiple grids to one .txt file not supported yet"); + return 0; +} + +int readGridsTxt(const string& name, std::vector* grids) { + errMsg("readGridsTxt: writing multiple grids from one .txt file not supported yet"); + return 0; } template -void writeGridRaw(const string& name, Grid* grid) { +int writeGridRaw(const string& name, Grid* grid) { debMsg( "writing grid " << grid->getName() << " to raw file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) errMsg("writeGridRaw: can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { + errMsg("writeGridRaw: can't open file " << name); + return 0; + } + gzwrite(gzf, &((*grid)[0]), sizeof(T)*grid->getSizeX()*grid->getSizeY()*grid->getSizeZ()); - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif } template -void readGridRaw(const string& name, Grid* grid) { +int readGridRaw(const string& name, Grid* grid) { debMsg( "reading grid " << grid->getName() << " from raw file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "rb"); - if (!gzf) errMsg("readGridRaw: can't open file " << name); - + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb"); + if (!gzf) { + errMsg("readGridRaw: can't open file " << name); + return 0; + } + IndexInt bytes = sizeof(T)*grid->getSizeX()*grid->getSizeY()*grid->getSizeZ(); IndexInt readBytes = gzread(gzf, &((*grid)[0]), bytes); assertMsg(bytes==readBytes, "can't read raw file, stream length does not match, "<* grids) { + errMsg("writeGridsRaw: writing multiple grids to one .raw file not supported yet"); + return 0; +} + +int readGridsRaw(const string& name, std::vector* grids) { + errMsg("readGridsRaw: reading multiple grids from one .raw file not supported yet"); + return 0; +} + //! legacy headers for reading old files typedef struct { int dimX, dimY, dimZ; @@ -296,7 +323,7 @@ typedef struct { void getUniFileSize(const string& name, int& x, int& y, int& z, int* t, std::string* info) { x = y = z = 0; # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "rb"); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb"); if (gzf) { char ID[5]={0,0,0,0,0}; gzread(gzf, ID, 4); @@ -356,7 +383,7 @@ PYTHON() void printUniFileInfoString(const string& name) { // actual read/write functions template -void writeGridUni(const string& name, Grid* grid) { +int writeGridUni(const string& name, Grid* grid) { debMsg( "Writing grid " << grid->getName() << " to uni file " << name ,1); # if NO_ZLIB!=1 @@ -378,11 +405,16 @@ void writeGridUni(const string& name, Grid* grid) { head.elementType = 1; else if (grid->getType() & GridBase::TypeVec3) head.elementType = 2; - else + else { errMsg("writeGridUni: unknown element type"); + return 0; + } - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) errMsg("writeGridUni: can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { + errMsg("writeGridUni: can't open file " << name); + return 0; + } gzwrite(gzf, ID, 4); # if FLOATINGPOINT_PRECISION!=1 @@ -395,21 +427,25 @@ void writeGridUni(const string& name, Grid* grid) { gzwrite(gzf, &head, sizeof(UniHeader)); gzwrite(gzf, ptr, sizeof(T)*head.dimX*head.dimY*head.dimZ); # endif - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif }; template -void readGridUni(const string& name, Grid* grid) { +int readGridUni(const string& name, Grid* grid) { debMsg( "Reading grid " << grid->getName() << " from uni file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "rb"); - if (!gzf) errMsg("readGridUni: can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb"); + if (!gzf) { + errMsg("readGridUni: can't open file " << name); + return 0; + } char ID[5]={0,0,0,0,0}; gzread(gzf, ID, 4); @@ -425,7 +461,7 @@ void readGridUni(const string& name, Grid* grid) { gzseek(gzf, numEl, SEEK_CUR); // actual grid read gzread(gzf, &((*grid)[0]), sizeof(T)*numEl); - } + } else if (!strcmp(ID, "MNT1")) { // legacy file format 2 UniLegacyHeader2 head; @@ -449,7 +485,7 @@ void readGridUni(const string& name, Grid* grid) { assertMsg (head.bytesPerElement == sizeof(T), "grid element size doesn't match "<< head.bytesPerElement <<" vs "<< sizeof(T) ); gzread(gzf, &((*grid)[0]), sizeof(T)*head.dimX*head.dimY*head.dimZ); # endif - } + } else if (!strcmp(ID, "MNT3")) { // current file format UniHeader head; @@ -467,17 +503,40 @@ void readGridUni(const string& name, Grid* grid) { # endif } else { errMsg( "readGridUni: Unknown header '"<* grids) { + errMsg("writeGridsUni: writing multiple grids to one .uni file not supported yet"); + return 0; +} + +int readGridsUni(const string& name, std::vector* grids) { + errMsg("readGridsUni: reading multiple grids from one .uni file not supported yet"); + return 0; +} + template -void writeGridVol(const string& name, Grid* grid) { +int writeGridVol(const string& name, Grid* grid) { debMsg( "writing grid " << grid->getName() << " to vol file " << name ,1); errMsg("writeGridVol: Type not yet supported!"); + return 0; +} + +int writeGridsVol(const string& name, std::vector* grids) { + errMsg("writeGridsVol: writing multiple grids to one .vol file not supported yet"); + return 0; +} + +int readGridsVol(const string& name, std::vector* grids) { + errMsg("readGridsVol: reading multiple grids from one .vol file not supported yet"); + return 0; } struct volHeader { @@ -490,7 +549,7 @@ struct volHeader { }; template <> -void writeGridVol(const string& name, Grid* grid) { +int writeGridVol(const string& name, Grid* grid) { debMsg( "writing real grid " << grid->getName() << " to vol file " << name ,1); volHeader header; @@ -509,7 +568,7 @@ void writeGridVol(const string& name, Grid* grid) { FILE* fp = fopen( name.c_str(), "wb" ); if (fp == NULL) { errMsg("writeGridVol: Cannot open '" << name << "'"); - return; + return 0; } fwrite( &header, sizeof(volHeader), 1, fp ); @@ -524,26 +583,26 @@ void writeGridVol(const string& name, Grid* grid) { fwrite( &value, sizeof(float), 1, fp ); } # endif - - fclose(fp); + return ( !fclose(fp) ); }; template -void readGridVol(const string& name, Grid* grid) { +int readGridVol(const string& name, Grid* grid) { debMsg( "writing grid " << grid->getName() << " to vol file " << name ,1); errMsg("readGridVol: Type not yet supported!"); + return 0; } template <> -void readGridVol(const string& name, Grid* grid) { +int readGridVol(const string& name, Grid* grid) { debMsg( "reading real grid " << grid->getName() << " from vol file " << name ,1); volHeader header; FILE* fp = fopen( name.c_str(), "rb" ); if (fp == NULL) { errMsg("readGridVol: Cannot open '" << name << "'"); - return; + return 0; } // note, only very basic file format checks here! @@ -551,18 +610,18 @@ void readGridVol(const string& name, Grid* grid) { if( header.dimX != grid->getSizeX() || header.dimY != grid->getSizeY() || header.dimZ != grid->getSizeZ()) errMsg( "grid dim doesn't match, "<< Vec3(header.dimX,header.dimY,header.dimZ)<<" vs "<< grid->getSize() ); # if FLOATINGPOINT_PRECISION!=1 errMsg("readGridVol: Double precision not yet supported"); + return 0; # else const unsigned int s = sizeof(float)*header.dimX*header.dimY*header.dimZ; assertMsg( fread( &((*grid)[0]), 1, s, fp) == s, "can't read file, no / not enough data"); # endif - - fclose(fp); + return ( !fclose(fp) ); }; // 4d grids IO template -void writeGrid4dUni(const string& name, Grid4d* grid) { +int writeGrid4dUni(const string& name, Grid4d* grid) { debMsg( "writing grid4d " << grid->getName() << " to uni file " << name ,1); # if NO_ZLIB!=1 @@ -586,11 +645,16 @@ void writeGrid4dUni(const string& name, Grid4d* grid) { head.elementType = 2; else if (grid->getType() & Grid4dBase::TypeVec4) head.elementType = 2; - else + else { errMsg("writeGrid4dUni: unknown element type"); + return 0; + } - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) errMsg("writeGrid4dUni: can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { + errMsg("writeGrid4dUni: can't open file " << name); + return 0; + } gzwrite(gzf, ID, 4); # if FLOATINGPOINT_PRECISION!=1 @@ -605,16 +669,17 @@ void writeGrid4dUni(const string& name, Grid4d* grid) { gzwrite(gzf, ptr, sizeof(T)*head.dimX*head.dimY*head.dimZ* 1); } # endif - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif }; //! note, reading 4d uni grids is slightly more complicated than 3d ones //! as it optionally supports sliced reading template -void readGrid4dUni(const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle ) +int readGrid4dUni(const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle ) { if(grid) debMsg( "reading grid " << grid->getName() << " from uni file " << name ,1); if(slice) debMsg( "reading slice " << slice->getName() << ",t="<* grid, int readTslice, Grid4d* grid, int readTslice, Grid4dgetStrideT()* 1); // quick and dirty... - return; + return 1; } if( (!strcmp(ID, "M4T2")) || (!strcmp(ID, "M4T3")) ) { @@ -692,6 +760,7 @@ void readGrid4dUni(const string& name, Grid4d* grid, int readTslice, Grid4d* grid, int readTslice, Grid4d -void writeGrid4dRaw(const string& name, Grid4d* grid) { +int writeGrid4dRaw(const string& name, Grid4d* grid) { debMsg( "writing grid4d " << grid->getName() << " to raw file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) errMsg("writeGrid4dRaw: can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { + errMsg("writeGrid4dRaw: can't open file " << name); + return 0; + } gzwrite(gzf, &((*grid)[0]), sizeof(T)*grid->getSizeX()*grid->getSizeY()*grid->getSizeZ()*grid->getSizeT()); - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif } template -void readGrid4dRaw(const string& name, Grid4d* grid) { +int readGrid4dRaw(const string& name, Grid4d* grid) { debMsg( "reading grid4d " << grid->getName() << " from raw file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "rb"); - if (!gzf) errMsg("readGrid4dRaw: can't open file " << name); - + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb"); + if (!gzf) { + errMsg("readGrid4dRaw: can't open file " << name); + return 0; + } IndexInt bytes = sizeof(T)*grid->getSizeX()*grid->getSizeY()*grid->getSizeZ()*grid->getSizeT(); IndexInt readBytes = gzread(gzf, &((*grid)[0]), bytes); assertMsg(bytes==readBytes, "can't read raw file, stream length does not match, "< -void writeGridVDB(const string& name, Grid* grid) { - debMsg("Writing grid " << grid->getName() << " to vdb file " << name << " not yet supported!", 1); -} - -template -void readGridVDB(const string& name, Grid* grid) { - debMsg("Reading grid " << grid->getName() << " from vdb file " << name << " not yet supported!", 1); -} - -template <> -void writeGridVDB(const string& name, Grid* grid) { - debMsg("Writing real grid " << grid->getName() << " to vdb file " << name, 1); - - // Create an empty floating-point grid with background value 0. - openvdb::initialize(); - openvdb::FloatGrid::Ptr gridVDB = openvdb::FloatGrid::create(); - gridVDB->setTransform( openvdb::math::Transform::createLinearTransform( 1./grid->getSizeX() )); //voxel size - - // Get an accessor for coordinate-based access to voxels. - openvdb::FloatGrid::Accessor accessor = gridVDB->getAccessor(); - - // Identify the grid as a level set. - gridVDB->setGridClass(openvdb::GRID_FOG_VOLUME); - - // Name the grid "density". - gridVDB->setName( grid->getName() ); - - openvdb::io::File file(name); - - FOR_IJK(*grid) { - openvdb::Coord xyz(i, j, k); - accessor.setValue(xyz, (*grid)(i, j, k)); - } - - // Add the grid pointer to a container. - openvdb::GridPtrVec gridsVDB; - gridsVDB.push_back(gridVDB); - - // Write out the contents of the container. - file.write(gridsVDB); - file.close(); -}; - -template <> -void readGridVDB(const string& name, Grid* grid) { - debMsg("Reading real grid " << grid->getName() << " from vdb file " << name, 1); - - openvdb::initialize(); - openvdb::io::File file(name); - file.open(); - - openvdb::GridBase::Ptr baseGrid; - for (openvdb::io::File::NameIterator nameIter = file.beginName(); nameIter != file.endName(); ++nameIter) - { - #ifndef BLENDER - // Read in only the grid we are interested in. - if (nameIter.gridName() == grid->getName()) { - baseGrid = file.readGrid(nameIter.gridName()); - } else { - debMsg("skipping grid " << nameIter.gridName(), 1); - } - #else - // For Blender, skip name check and pick first grid from loop - baseGrid = file.readGrid(nameIter.gridName()); - break; - #endif - } - file.close(); - openvdb::FloatGrid::Ptr gridVDB = openvdb::gridPtrCast(baseGrid); - - openvdb::FloatGrid::Accessor accessor = gridVDB->getAccessor(); - - FOR_IJK(*grid) { - openvdb::Coord xyz(i, j, k); - float v = accessor.getValue(xyz); - (*grid)(i, j, k) = v; - } -}; - -template <> -void writeGridVDB(const string& name, Grid* grid) { - debMsg("Writing vec3 grid " << grid->getName() << " to vdb file " << name, 1); - - openvdb::initialize(); - openvdb::Vec3SGrid::Ptr gridVDB = openvdb::Vec3SGrid::create(); - // note , warning - velocity content currently not scaled... - gridVDB->setTransform( openvdb::math::Transform::createLinearTransform( 1./grid->getSizeX() )); //voxel size - openvdb::Vec3SGrid::Accessor accessor = gridVDB->getAccessor(); - - // MAC or regular vec grid? - if(grid->getType() & GridBase::TypeMAC) gridVDB->setGridClass(openvdb::GRID_STAGGERED); - else gridVDB->setGridClass(openvdb::GRID_UNKNOWN); - - gridVDB->setName( grid->getName() ); - - openvdb::io::File file(name); - FOR_IJK(*grid) { - openvdb::Coord xyz(i, j, k); - Vec3 v = (*grid)(i, j, k); - openvdb::Vec3f vo( (float)v[0] , (float)v[1] , (float)v[2] ); - accessor.setValue(xyz, vo); - } - - openvdb::GridPtrVec gridsVDB; - gridsVDB.push_back(gridVDB); - - file.write(gridsVDB); - file.close(); -}; - -template <> -void readGridVDB(const string& name, Grid* grid) { - debMsg("Reading vec3 grid " << grid->getName() << " from vdb file " << name, 1); - - openvdb::initialize(); - openvdb::io::File file(name); - file.open(); - - openvdb::GridBase::Ptr baseGrid; - for (openvdb::io::File::NameIterator nameIter = file.beginName(); nameIter != file.endName(); ++nameIter) - { - #ifndef BLENDER - // Read in only the grid we are interested in. - if (nameIter.gridName() == grid->getName()) { - baseGrid = file.readGrid(nameIter.gridName()); - } else { - debMsg("skipping grid " << nameIter.gridName(), 1); - } - #else - // For Blender, skip name check and pick first grid from loop - baseGrid = file.readGrid(nameIter.gridName()); - break; - #endif - } - file.close(); - openvdb::Vec3SGrid::Ptr gridVDB = openvdb::gridPtrCast(baseGrid); - - openvdb::Vec3SGrid::Accessor accessor = gridVDB->getAccessor(); - - FOR_IJK(*grid) { - openvdb::Coord xyz(i, j, k); - openvdb::Vec3f v = accessor.getValue(xyz); - (*grid)(i, j, k).x = (float)v[0]; - (*grid)(i, j, k).y = (float)v[1]; - (*grid)(i, j, k).z = (float)v[2]; - } -}; - -#endif // OPENVDB==1 - - //***************************************************************************** // npz file support (warning - read works, but write generates uncompressed npz; i.e. not recommended for large volumes) template -void writeGridNumpy(const string& name, Grid* grid) { +int writeGridNumpy(const string& name, Grid* grid) { # if NO_ZLIB==1 debMsg( "file format not supported without zlib" ,1); - return; + return 0; # endif # if FLOATINGPOINT_PRECISION!=1 errMsg("writeGridNumpy: Double precision not yet supported"); + return 0; # endif // find suffix to differentiate between npy <-> npz , TODO: check for actual "npy" string @@ -939,8 +860,10 @@ void writeGridNumpy(const string& name, Grid* grid) { uDim = 1; else if (grid->getType() & GridBase::TypeVec3 || grid->getType() & GridBase::TypeMAC) uDim = 3; - else + else { errMsg("writeGridNumpy: unknown element type"); + return 0; + } const std::vector shape = {static_cast(grid->getSizeZ()), static_cast(grid->getSizeY()), static_cast(grid->getSizeX()), uDim}; @@ -957,16 +880,18 @@ void writeGridNumpy(const string& name, Grid* grid) { } else { cnpy::npy_save(name, &grid[0], shape, "w"); } + return 1; }; template -void readGridNumpy(const string& name, Grid* grid) { +int readGridNumpy(const string& name, Grid* grid) { # if NO_ZLIB==1 debMsg( "file format not supported without zlib" ,1); - return; + return 0; # endif # if FLOATINGPOINT_PRECISION!=1 errMsg("readGridNumpy: Double precision not yet supported"); + return 0; # endif // find suffix to differentiate between npy <-> npz @@ -995,8 +920,10 @@ void readGridNumpy(const string& name, Grid* grid) { uDim = 1; else if (grid->getType() & GridBase::TypeVec3 || grid->getType() & GridBase::TypeMAC) uDim = 3; - else + else { errMsg("readGridNumpy: unknown element type"); + return 0; + } assertMsg (gridArr.shape[3] == uDim, "grid data dim doesn't match, " << gridArr.shape[3] << " vs " << uDim ); if (grid->getType() & GridBase::TypeVec3 || grid->getType() & GridBase::TypeMAC) { @@ -1008,8 +935,19 @@ void readGridNumpy(const string& name, Grid* grid) { // copy back, TODO: beautify... memcpy(&((*grid)[0]), gridArr.data(), sizeof(T) * grid->getSizeX() * grid->getSizeY() * grid->getSizeZ() ); + return 1; }; +int writeGridsNumpy(const string& name, std::vector* grids) { + errMsg("writeGridsNumpy: writing multiple grids to one .npz file not supported yet"); + return 0; +} + +int readGridsNumpy(const string& name, std::vector* grids) { + errMsg("readGridsNumpy: reading multiple grids from one .npz file not supported yet"); + return 0; +} + // adopted from getUniFileSize void getNpzFileSize(const string& name, int& x, int& y, int& z, int* t = NULL, std::string* info = NULL) { x = y = z = 0; @@ -1040,8 +978,7 @@ PYTHON() Vec3 getNpzFileSize(const string& name) { //***************************************************************************** // helper functions - -void quantizeReal(Real& v, const Real step) { +void quantizeReal(Real& v, const Real step) { int q = int(v / step + step*0.5); double qd = q * (double)step; v = (Real)qd; @@ -1056,67 +993,51 @@ KERNEL(idx) void knQuantizeVec3(Grid& grid, Real step) { } PYTHON() void quantizeGridVec3(Grid& grid, Real step) { knQuantizeVec3(grid,step); } - - // explicit instantiation -template void writeGridRaw (const string& name, Grid* grid); -template void writeGridRaw(const string& name, Grid* grid); -template void writeGridRaw(const string& name, Grid* grid); -template void writeGridUni (const string& name, Grid* grid); -template void writeGridUni(const string& name, Grid* grid); -template void writeGridUni(const string& name, Grid* grid); -template void writeGridVol (const string& name, Grid* grid); -template void writeGridVol(const string& name, Grid* grid); -template void writeGridTxt (const string& name, Grid* grid); -template void writeGridTxt(const string& name, Grid* grid); -template void writeGridTxt(const string& name, Grid* grid); - -template void readGridRaw (const string& name, Grid* grid); -template void readGridRaw (const string& name, Grid* grid); -template void readGridRaw (const string& name, Grid* grid); -template void readGridUni (const string& name, Grid* grid); -template void readGridUni (const string& name, Grid* grid); -template void readGridUni (const string& name, Grid* grid); -template void readGridVol (const string& name, Grid* grid); -template void readGridVol (const string& name, Grid* grid); - -template void readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); -template void readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); -template void readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); -template void readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); -template void writeGrid4dUni (const string& name, Grid4d* grid); -template void writeGrid4dUni(const string& name, Grid4d* grid); -template void writeGrid4dUni(const string& name, Grid4d* grid); -template void writeGrid4dUni(const string& name, Grid4d* grid); - -template void readGrid4dRaw (const string& name, Grid4d* grid); -template void readGrid4dRaw (const string& name, Grid4d* grid); -template void readGrid4dRaw (const string& name, Grid4d* grid); -template void readGrid4dRaw (const string& name, Grid4d* grid); -template void writeGrid4dRaw (const string& name, Grid4d* grid); -template void writeGrid4dRaw(const string& name, Grid4d* grid); -template void writeGrid4dRaw(const string& name, Grid4d* grid); -template void writeGrid4dRaw(const string& name, Grid4d* grid); - -template void writeGridNumpy (const string& name, Grid* grid); -template void writeGridNumpy(const string& name, Grid* grid); -template void writeGridNumpy(const string& name, Grid* grid); -template void readGridNumpy (const string& name, Grid* grid); -template void readGridNumpy (const string& name, Grid* grid); -template void readGridNumpy (const string& name, Grid* grid); - -#if OPENVDB==1 -template void writeGridVDB(const string& name, Grid* grid); -template void writeGridVDB(const string& name, Grid* grid); -template void writeGridVDB(const string& name, Grid* grid); - -template void readGridVDB(const string& name, Grid* grid); -template void readGridVDB(const string& name, Grid* grid); -template void readGridVDB(const string& name, Grid* grid); -#endif // OPENVDB==1 +template int writeGridRaw (const string& name, Grid* grid); +template int writeGridRaw(const string& name, Grid* grid); +template int writeGridRaw(const string& name, Grid* grid); +template int writeGridUni (const string& name, Grid* grid); +template int writeGridUni(const string& name, Grid* grid); +template int writeGridUni(const string& name, Grid* grid); +template int writeGridVol (const string& name, Grid* grid); +template int writeGridVol(const string& name, Grid* grid); +template int writeGridTxt (const string& name, Grid* grid); +template int writeGridTxt(const string& name, Grid* grid); +template int writeGridTxt(const string& name, Grid* grid); + +template int readGridRaw (const string& name, Grid* grid); +template int readGridRaw (const string& name, Grid* grid); +template int readGridRaw (const string& name, Grid* grid); +template int readGridUni (const string& name, Grid* grid); +template int readGridUni (const string& name, Grid* grid); +template int readGridUni (const string& name, Grid* grid); +template int readGridVol (const string& name, Grid* grid); +template int readGridVol (const string& name, Grid* grid); + +template int readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); +template int readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); +template int readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); +template int readGrid4dUni (const string& name, Grid4d* grid, int readTslice, Grid4d* slice, void** fileHandle); +template int writeGrid4dUni (const string& name, Grid4d* grid); +template int writeGrid4dUni(const string& name, Grid4d* grid); +template int writeGrid4dUni(const string& name, Grid4d* grid); +template int writeGrid4dUni(const string& name, Grid4d* grid); + +template int readGrid4dRaw (const string& name, Grid4d* grid); +template int readGrid4dRaw (const string& name, Grid4d* grid); +template int readGrid4dRaw (const string& name, Grid4d* grid); +template int readGrid4dRaw (const string& name, Grid4d* grid); +template int writeGrid4dRaw (const string& name, Grid4d* grid); +template int writeGrid4dRaw(const string& name, Grid4d* grid); +template int writeGrid4dRaw(const string& name, Grid4d* grid); +template int writeGrid4dRaw(const string& name, Grid4d* grid); + +template int writeGridNumpy (const string& name, Grid* grid); +template int writeGridNumpy(const string& name, Grid* grid); +template int writeGridNumpy(const string& name, Grid* grid); +template int readGridNumpy (const string& name, Grid* grid); +template int readGridNumpy (const string& name, Grid* grid); +template int readGridNumpy (const string& name, Grid* grid); } //namespace - -namespace Manta { - -} diff --git a/source/fileio/iomeshes.cpp b/source/fileio/iomeshes.cpp index ec968df9..ab443373 100644 --- a/source/fileio/iomeshes.cpp +++ b/source/fileio/iomeshes.cpp @@ -122,20 +122,24 @@ void mdataReadConvert(gzFile& gzf, MeshDataImpl& mdata, void* ptr, i // mesh data //***************************************************************************** -void readBobjFile(const string& name, Mesh* mesh, bool append) { +int readBobjFile(const string& name, Mesh* mesh, bool append) { debMsg( "reading mesh file " << name ,1); if (!append) mesh->clear(); - else + else { errMsg("readBobj: append not yet implemented!"); + return 0; + } # if NO_ZLIB!=1 const Real dx = mesh->getParent()->getDx(); const Vec3 gs = toVec3( mesh->getParent()->getGridSize() ); - gzFile gzf = gzopen(name.c_str(), "rb1"); // do some compression - if (!gzf) + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb1"); // do some compression + if (!gzf) { errMsg("readBobj: unable to open file"); + return 0; + } // read vertices int num = 0; @@ -145,7 +149,7 @@ void readBobjFile(const string& name, Mesh* mesh, bool append) { for (int i=0; i pos; gzread(gzf, &pos.value[0], sizeof(float)*3); - mesh->nodes(i).pos = toVec3(pos); + mesh->nodes(i).pos = toVec3(pos); // convert to grid space mesh->nodes(i).pos /= dx; @@ -158,7 +162,7 @@ void readBobjFile(const string& name, Mesh* mesh, bool append) { for (int i=0; i pos; gzread(gzf, &pos.value[0], sizeof(float)*3); - mesh->nodes(i).normal = toVec3(pos); + mesh->nodes(i).normal = toVec3(pos); } // read tris @@ -171,24 +175,27 @@ void readBobjFile(const string& name, Mesh* mesh, bool append) { gzread(gzf, &trip, sizeof(int)); mesh->tris(t).c[j] = trip; } - } + } // note - vortex sheet info ignored for now... (see writeBobj) - gzclose( gzf ); debMsg( "read mesh , triangles "<numTris()<<", vertices "<numNodes()<<" ",1 ); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif } -void writeBobjFile(const string& name, Mesh* mesh) { +int writeBobjFile(const string& name, Mesh* mesh) { debMsg( "writing mesh file " << name ,1); # if NO_ZLIB!=1 const Real dx = mesh->getParent()->getDx(); const Vec3i gs = mesh->getParent()->getGridSize(); - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { errMsg("writeBobj: unable to open file"); + return 0; + } // write vertices int numVerts = mesh->numNodes(); @@ -249,7 +256,7 @@ void writeBobjFile(const string& name, Mesh* mesh) { float dens = 0; if (triPerVertex[point]>0) dens = density[point] / triPerVertex[point]; - gzwrite(gzf, &dens, sizeof(float)); + gzwrite(gzf, &dens, sizeof(float)); } } @@ -261,21 +268,24 @@ void writeBobjFile(const string& name, Mesh* mesh) { // averaged smoke densities for(int point=0; pointnodes(point).flags & Mesh::NfMarked) ? 1: 0; - gzwrite(gzf, &alpha, sizeof(float)); + gzwrite(gzf, &alpha, sizeof(float)); } } - gzclose( gzf ); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif } -void readObjFile(const std::string& name, Mesh* mesh, bool append) { +int readObjFile(const std::string& name, Mesh* mesh, bool append) { ifstream ifs (name.c_str()); - if (!ifs.good()) + if (!ifs.good()) { errMsg("can't open file '" + name + "'"); + return 0; + } if (!append) mesh->clear(); @@ -291,11 +301,13 @@ void readObjFile(const std::string& name, Mesh* mesh, bool append) { continue; } if (id == "vt") { - // tex coord, ignore + // tex coord, ignore } else if (id == "vn") { // normals - if (!mesh->numNodes()) + if (!mesh->numNodes()) { errMsg("invalid amount of nodes"); + return 0; + } Node n = mesh->nodes(cnt); ifs >> n.normal.x >> n.normal.y >> n.normal.z; cnt++; @@ -317,8 +329,10 @@ void readObjFile(const std::string& name, Mesh* mesh, bool append) { if (face.find('/') != string::npos) face = face.substr(0, face.find('/')); // ignore other indices int idx = atoi(face.c_str()) - 1; - if (idx < 0) + if (idx < 0) { errMsg("invalid face encountered"); + return 0; + } idx += nodebase; t.c[i] = idx; } @@ -329,17 +343,20 @@ void readObjFile(const std::string& name, Mesh* mesh, bool append) { // kill rest of line getline(ifs, id); } - ifs.close(); + ifs.close(); + return 1; } // write regular .obj file, in line with bobj.gz output (but only verts & tris for now) -void writeObjFile(const string& name, Mesh* mesh) { +int writeObjFile(const string& name, Mesh* mesh) { const Real dx = mesh->getParent()->getDx(); const Vec3i gs = mesh->getParent()->getGridSize(); ofstream ofs(name.c_str()); - if (!ofs.good()) + if (!ofs.good()) { errMsg("writeObjFile: can't open file " << name); + return 0; + } ofs << "o MantaMesh\n"; @@ -367,15 +384,19 @@ void writeObjFile(const string& name, Mesh* mesh) { } ofs.close(); + return 1; } template -void readMdataUni(const std::string& name, MeshDataImpl* mdata ) { +int readMdataUni(const std::string& name, MeshDataImpl* mdata ) { debMsg( "reading mesh data " << mdata->getName() << " from uni file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "rb"); - if (!gzf) errMsg("can't open file " << name ); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb"); + if (!gzf) { + errMsg("can't open file " << name ); + return 0; + } char ID[5]={0,0,0,0,0}; gzread(gzf, ID, 4); @@ -383,6 +404,8 @@ void readMdataUni(const std::string& name, MeshDataImpl* mdata ) { if (!strcmp(ID, "MD01")) { UniMeshHeader head; assertMsg (gzread(gzf, &head, sizeof(UniMeshHeader)) == sizeof(UniMeshHeader), "can't read file, no header present"); + mdata->resize(head.dim); + assertMsg (head.dim == mdata->size() , "mdata size doesn't match"); # if FLOATINGPOINT_PRECISION!=1 MeshDataImpl temp(mdata->getParent()); @@ -395,14 +418,15 @@ void readMdataUni(const std::string& name, MeshDataImpl* mdata ) { assertMsg(bytes==readBytes, "can't read uni file, stream length does not match, "< -void writeMdataUni(const std::string& name, MeshDataImpl* mdata ) { +int writeMdataUni(const std::string& name, MeshDataImpl* mdata ) { debMsg( "writing mesh data " << mdata->getName() << " to uni file " << name ,1); # if NO_ZLIB!=1 @@ -415,8 +439,11 @@ void writeMdataUni(const std::string& name, MeshDataImpl* mdata ) { MuTime stamp; head.timestamp = stamp.time; - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) errMsg("can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { + errMsg("can't open file " << name); + return 0; + } gzwrite(gzf, ID, 4); # if FLOATINGPOINT_PRECISION!=1 @@ -428,19 +455,20 @@ void writeMdataUni(const std::string& name, MeshDataImpl* mdata ) { gzwrite(gzf, &head, sizeof(UniMeshHeader)); gzwrite(gzf, &(mdata->get(0)), sizeof(T)*head.dim); # endif - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif }; // explicit instantiation -template void writeMdataUni (const std::string& name, MeshDataImpl* mdata ); -template void writeMdataUni(const std::string& name, MeshDataImpl* mdata ); -template void writeMdataUni(const std::string& name, MeshDataImpl* mdata ); -template void readMdataUni (const std::string& name, MeshDataImpl* mdata ); -template void readMdataUni (const std::string& name, MeshDataImpl* mdata ); -template void readMdataUni (const std::string& name, MeshDataImpl* mdata ); +template int writeMdataUni (const std::string& name, MeshDataImpl* mdata); +template int writeMdataUni(const std::string& name, MeshDataImpl* mdata); +template int writeMdataUni(const std::string& name, MeshDataImpl* mdata); +template int readMdataUni (const std::string& name, MeshDataImpl* mdata); +template int readMdataUni (const std::string& name, MeshDataImpl* mdata); +template int readMdataUni (const std::string& name, MeshDataImpl* mdata); } //namespace diff --git a/source/fileio/ioparticles.cpp b/source/fileio/ioparticles.cpp index dc653186..2842c5cb 100644 --- a/source/fileio/ioparticles.cpp +++ b/source/fileio/ioparticles.cpp @@ -59,7 +59,7 @@ template <> void pdataConvertWrite( gzFile& gzf, ParticleDataImpl& pdata, void* ptr, UniPartHeader& head) { gzwrite(gzf, &head, sizeof(UniPartHeader)); gzwrite(gzf, &pdata[0], sizeof(int)*head.dim); -} +} template <> void pdataConvertWrite( gzFile& gzf, ParticleDataImpl& pdata, void* ptr, UniPartHeader& head) { head.bytesPerElement = sizeof(float); @@ -69,7 +69,7 @@ void pdataConvertWrite( gzFile& gzf, ParticleDataImpl& pdata, void* ptr, *ptrf = (float)pdata[i]; } gzwrite(gzf, ptr, sizeof(float)* head.dim); -} +} template <> void pdataConvertWrite( gzFile& gzf, ParticleDataImpl& pdata, void* ptr, UniPartHeader& head) { head.bytesPerElement = sizeof(Vector3D); @@ -102,7 +102,7 @@ void pdataReadConvert(gzFile& gzf, ParticleDataImpl& pdata, void float* ptrf = (float*)ptr; for(int i=0; i @@ -114,7 +114,7 @@ void pdataReadConvert(gzFile& gzf, ParticleDataImpl& pdata, void* pt Vec3 v; for(int c=0; c<3; ++c) { v[c] = double(*ptrf); ptrf++; } pdata[i] = v; - } + } } @@ -127,7 +127,7 @@ void pdataReadConvert(gzFile& gzf, ParticleDataImpl& pdata, void* pt static const int PartSysSize = sizeof(Vector3D)+sizeof(int); -void writeParticlesUni(const std::string& name, const BasicParticleSystem* parts ) { +int writeParticlesUni(const std::string& name, const BasicParticleSystem* parts ) { debMsg( "writing particles " << parts->getName() << " to uni file " << name ,1); # if NO_ZLIB!=1 @@ -144,8 +144,11 @@ void writeParticlesUni(const std::string& name, const BasicParticleSystem* parts MuTime stamp; head.timestamp = stamp.time; - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) errMsg("can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { + errMsg("can't open file " << name); + return 0; + } gzwrite(gzf, ID, 4); # if FLOATINGPOINT_PRECISION!=1 @@ -162,24 +165,29 @@ void writeParticlesUni(const std::string& name, const BasicParticleSystem* parts gzwrite(gzf, &head, sizeof(UniPartHeader)); gzwrite(gzf, &((*parts)[0]), PartSysSize*head.dim); # endif - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif }; -void readParticlesUni(const std::string& name, BasicParticleSystem* parts ) { +int readParticlesUni(const std::string& name, BasicParticleSystem* parts ) { debMsg( "reading particles " << parts->getName() << " from uni file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "rb"); - if (!gzf) errMsg("can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb"); + if (!gzf) { + errMsg("can't open file " << name); + return 0; + } char ID[5]={0,0,0,0,0}; gzread(gzf, ID, 4); if (!strcmp(ID, "PB01")) { errMsg("particle uni file format v01 not supported anymore"); + return 0; } else if (!strcmp(ID, "PB02")) { // current file format UniPartHeader head; @@ -207,14 +215,15 @@ void readParticlesUni(const std::string& name, BasicParticleSystem* parts ) { parts->transformPositions( Vec3i(head.dimX,head.dimY,head.dimZ), parts->getParent()->getGridSize() ); } - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif }; template -void writePdataUni(const std::string& name, ParticleDataImpl* pdata ) { +int writePdataUni(const std::string& name, ParticleDataImpl* pdata ) { debMsg( "writing particle data " << pdata->getName() << " to uni file " << name ,1); # if NO_ZLIB!=1 @@ -231,8 +240,11 @@ void writePdataUni(const std::string& name, ParticleDataImpl* pdata ) { MuTime stamp; head.timestamp = stamp.time; - gzFile gzf = gzopen(name.c_str(), "wb1"); // do some compression - if (!gzf) errMsg("can't open file " << name); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); // do some compression + if (!gzf) { + errMsg("can't open file " << name); + return 0; + } gzwrite(gzf, ID, 4); # if FLOATINGPOINT_PRECISION!=1 @@ -244,20 +256,24 @@ void writePdataUni(const std::string& name, ParticleDataImpl* pdata ) { gzwrite(gzf, &head, sizeof(UniPartHeader)); gzwrite(gzf, &(pdata->get(0)), sizeof(T)*head.dim); # endif - gzclose(gzf); + return (gzclose(gzf) == Z_OK); # else debMsg( "file format not supported without zlib" ,1); + return 0; # endif }; template -void readPdataUni(const std::string& name, ParticleDataImpl* pdata ) { +int readPdataUni(const std::string& name, ParticleDataImpl* pdata ) { debMsg( "reading particle data " << pdata->getName() << " from uni file " << name ,1); # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "rb"); - if (!gzf) errMsg("can't open file " << name ); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "rb"); + if (!gzf) { + errMsg("can't open file " << name ); + return 0; + } char ID[5]={0,0,0,0,0}; gzread(gzf, ID, 4); @@ -265,6 +281,8 @@ void readPdataUni(const std::string& name, ParticleDataImpl* pdata ) { if (!strcmp(ID, "PD01")) { UniPartHeader head; assertMsg (gzread(gzf, &head, sizeof(UniPartHeader)) == sizeof(UniPartHeader), "can't read file, no header present"); + pdata->resize(head.dim); + assertMsg (head.dim == pdata->size() , "pdata size doesn't match"); # if FLOATINGPOINT_PRECISION!=1 ParticleDataImpl temp(pdata->getParent()); @@ -277,21 +295,19 @@ void readPdataUni(const std::string& name, ParticleDataImpl* pdata ) { assertMsg(bytes==readBytes, "can't read uni file, stream length does not match, "< (const std::string& name, ParticleDataImpl* pdata ); -template void writePdataUni(const std::string& name, ParticleDataImpl* pdata ); -template void writePdataUni(const std::string& name, ParticleDataImpl* pdata ); -template void readPdataUni (const std::string& name, ParticleDataImpl* pdata ); -template void readPdataUni (const std::string& name, ParticleDataImpl* pdata ); -template void readPdataUni (const std::string& name, ParticleDataImpl* pdata ); +template int writePdataUni (const std::string& name, ParticleDataImpl* pdata); +template int writePdataUni(const std::string& name, ParticleDataImpl* pdata); +template int writePdataUni(const std::string& name, ParticleDataImpl* pdata); +template int readPdataUni (const std::string& name, ParticleDataImpl* pdata); +template int readPdataUni (const std::string& name, ParticleDataImpl* pdata); +template int readPdataUni (const std::string& name, ParticleDataImpl* pdata); } //namespace diff --git a/source/fileio/ioutil.cpp b/source/fileio/ioutil.cpp new file mode 100644 index 00000000..35527560 --- /dev/null +++ b/source/fileio/ioutil.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * + * MantaFlow fluid solver framework + * Copyright 2011-2020 Tobias Pfaff, Nils Thuerey + * + * This program is free software, distributed under the terms of the + * Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Helper functions to handle file IO + * + ******************************************************************************/ + +#include "mantaio.h" + +#if OPENVDB==1 +# include "openvdb/openvdb.h" +#endif + +#if NO_ZLIB != 1 +extern "C" { +# include +} + +#if defined(WIN32) || defined(_WIN32) +# include +# include +#endif + +using namespace std; + +namespace Manta { + +#if defined(WIN32) || defined(_WIN32) +static wstring stringToWstring(const char *str) +{ + const int length_wc = MultiByteToWideChar(CP_UTF8, 0, str, strlen(str), NULL, 0); + wstring strWide(length_wc, 0); + MultiByteToWideChar(CP_UTF8, 0, str, strlen(str), &strWide[0], length_wc); + return strWide; +} +#endif // WIN32==1 + +void *safeGzopen(const char *filename, const char *mode) +{ + gzFile gzfile; + +# if defined(WIN32) || defined(_WIN32) + wstring filenameWide = stringToWstring(filename); + gzfile = gzopen_w(filenameWide.c_str(), mode); +# else + gzfile = gzopen(filename, mode); +# endif + + return gzfile; +} +#endif // NO_ZLIB != 1 + +# if defined(OPENVDB) +// Convert from OpenVDB value to Manta value. +template +void convertFrom(S& in, T* out) { + errMsg("OpenVDB convertFrom Warning: Unsupported type conversion"); +} + +template<> +void convertFrom(int& in, int* out) { + (*out) = in; +} + +template<> +void convertFrom(float& in, Real* out) { + (*out) = (Real) in; +} + +template<> +void convertFrom(openvdb::Vec3s& in, Vec3* out) { + (*out).x = in.x(); + (*out).y = in.y(); + (*out).z = in.z(); +} + +// Convert to OpenVDB value from Manta value. +template +void convertTo(S* out, T& in) { + errMsg("OpenVDB convertTo Warning: Unsupported type conversion"); +} + +template<> +void convertTo(int* out, int& in) { + (*out) = in; +} + +template<> +void convertTo(float* out, Real& in) { + (*out) = (float) in; +} + +template<> +void convertTo(openvdb::Vec3s* out, Vec3& in) { + (*out).x() = in.x; + (*out).y() = in.y; + (*out).z() = in.z; +} +#endif // OPENVDB==1 + +} // namespace diff --git a/source/fileio/iovdb.cpp b/source/fileio/iovdb.cpp new file mode 100644 index 00000000..7bfd9f51 --- /dev/null +++ b/source/fileio/iovdb.cpp @@ -0,0 +1,505 @@ +/****************************************************************************** + * + * MantaFlow fluid solver framework + * Copyright 2020 Sebastian Barschkis, Nils Thuerey + * + * This program is free software, distributed under the terms of the + * Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Loading and writing grids and particles from and to OpenVDB files. + * + ******************************************************************************/ + +#include +#include +#include +#include + +#include "mantaio.h" +#include "grid.h" +#include "vector4d.h" +#include "grid4d.h" +#include "particle.h" + +#if OPENVDB==1 +# include "openvdb/openvdb.h" +# include +# include +#endif + +#define POSITION_NAME "P" +#define FLAG_NAME "U" + +using namespace std; + +namespace Manta { + +#if OPENVDB==1 + +template +void importVDB(typename GridType::Ptr from, Grid* to) { + using ValueT = typename GridType::ValueType; + typename GridType::Accessor accessor = from->getAccessor(); + + FOR_IJK(*to) { + openvdb::Coord xyz(i, j, k); + ValueT vdbValue = accessor.getValue(xyz); + T toMantaValue; + convertFrom(vdbValue, &toMantaValue); + to->set(i, j, k, toMantaValue); + } +} + +template +void importVDB(VDBType vdbValue, ParticleDataImpl* to, int index, float voxelSize) { + (void) voxelSize; // Unused + T toMantaValue; + convertFrom(vdbValue, &toMantaValue); + to->set(index, toMantaValue); +} + +void importVDB(openvdb::points::PointDataGrid::Ptr from, BasicParticleSystem* to, std::vector& toPData, float voxelSize) { + openvdb::Index64 count = openvdb::points::pointCount(from->tree()); + to->resizeAll(count); + + int cnt = 0; + for (auto leafIter = from->tree().cbeginLeaf(); leafIter; ++leafIter) { + const openvdb::points::AttributeArray& positionArray = leafIter->constAttributeArray(POSITION_NAME); + const openvdb::points::AttributeArray& flagArray = leafIter->constAttributeArray(FLAG_NAME); + + openvdb::points::AttributeHandle positionHandle(positionArray); + openvdb::points::AttributeHandle flagHandle(flagArray); + + // Get vdb handles to pdata objects in pdata list + std::vector>> pDataHandlesInt; + std::vector>> pDataHandlesReal; + std::vector>> pDataHandlesVec3; + + int pDataIndex = 0; + for (ParticleDataBase* pdb : toPData) { + std::string name = pdb->getName(); + const openvdb::points::AttributeArray& pDataArray = leafIter->constAttributeArray(name); + + if (pdb->getType() == ParticleDataBase::TypeInt) { + openvdb::points::AttributeHandle intHandle(pDataArray); + std::tuple> tuple = std::make_tuple(pDataIndex, intHandle); + pDataHandlesInt.push_back(tuple); + } + else if (pdb->getType() == ParticleDataBase::TypeReal) { + openvdb::points::AttributeHandle floatHandle(pDataArray); + std::tuple> tuple = std::make_tuple(pDataIndex, floatHandle); + pDataHandlesReal.push_back(tuple); + } + else if (pdb->getType() == ParticleDataBase::TypeVec3) { + openvdb::points::AttributeHandle vec3Handle(pDataArray); + std::tuple> tuple = std::make_tuple(pDataIndex, vec3Handle); + pDataHandlesVec3.push_back(tuple); + } + else { + errMsg("importVDB: unknown ParticleDataBase type"); + } + ++pDataIndex; + } + + for (auto indexIter = leafIter->beginIndexOn(); indexIter; ++indexIter) { + // Extract the voxel-space position of the point (always between (-0.5, -0.5, -0.5) and (0.5, 0.5, 0.5)). + openvdb::Vec3s voxelPosition = positionHandle.get(*indexIter); + const openvdb::Vec3d xyz = indexIter.getCoord().asVec3d(); + // Compute the world-space position of the point. + openvdb::Vec3f worldPosition = from->transform().indexToWorld(voxelPosition + xyz); + int flag = flagHandle.get(*indexIter); + + Vec3 toMantaValue; + convertFrom(worldPosition, &toMantaValue); + (*to)[cnt].pos = toMantaValue; + (*to)[cnt].pos /= voxelSize; // convert from world space to grid space + (*to)[cnt].flag = flag; + + for (std::tuple> tuple : pDataHandlesInt) { + int pDataIndex = std::get<0>(tuple); + int vdbValue = std::get<1>(tuple).get(*indexIter); + + ParticleDataImpl* pdi = dynamic_cast*>(toPData[pDataIndex]); + importVDB(vdbValue, pdi, cnt, voxelSize); + } + for (std::tuple> tuple : pDataHandlesReal) { + int pDataIndex = std::get<0>(tuple); + float vdbValue = std::get<1>(tuple).get(*indexIter); + + ParticleDataImpl* pdi = dynamic_cast*>(toPData[pDataIndex]); + importVDB(vdbValue, pdi, cnt, voxelSize); + } + for (std::tuple> tuple : pDataHandlesVec3) { + int pDataIndex = std::get<0>(tuple); + openvdb::Vec3f voxelPosition = std::get<1>(tuple).get(*indexIter); + + ParticleDataImpl* pdi = dynamic_cast*>(toPData[pDataIndex]); + importVDB(voxelPosition, pdi, cnt, voxelSize); + } + ++cnt; + } + } +} + +template +static void setGridOptions(typename GridType::Ptr grid, string name, openvdb::GridClass cls, float voxelSize, bool precisionHalf) { + grid->setTransform(openvdb::math::Transform::createLinearTransform(voxelSize)); + grid->setGridClass(cls); + grid->setName(name); + grid->setSaveFloatAsHalf(precisionHalf); +} + +template +typename GridType::Ptr exportVDB(Grid* from) { + using ValueT = typename GridType::ValueType; + typename GridType::Ptr to = GridType::create(); + typename GridType::Accessor accessor = to->getAccessor(); + + FOR_IJK(*from) { + openvdb::Coord xyz(i, j, k); + T fromMantaValue = (*from)(i, j, k); + ValueT vdbValue; + convertTo(&vdbValue, fromMantaValue); + accessor.setValue(xyz, vdbValue); + } + return to; +} + +template +void exportVDB(ParticleDataImpl* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts) { + std::vector vdbValues; + std::string name = from->getName(); + + FOR_PARTS(*from) { + // Optionally, skip exporting particles that have been marked as deleted + BasicParticleSystem* pp = dynamic_cast(from->getParticleSys()); + if (skipDeletedParts && !pp->isActive(idx)) { + continue; + } + MantaType fromMantaValue = (*from)[idx]; + VDBType vdbValue; + convertTo(&vdbValue, fromMantaValue); + vdbValues.push_back(vdbValue); + } + + openvdb::NamePair attribute = openvdb::points::TypedAttributeArray::attributeType(); + openvdb::points::appendAttribute(to->tree(), name, attribute); + + // Create a wrapper around the vdb values vector. + const openvdb::points::PointAttributeVector wrapper(vdbValues); + + // Populate the attribute on the points + openvdb::points::populateAttribute>(to->tree(), pIndex->tree(), name, wrapper); +} + +openvdb::points::PointDataGrid::Ptr exportVDB(BasicParticleSystem* from, std::vector& fromPData, bool skipDeletedParts, float voxelSize) { + std::vector positions; + std::vector flags; + + FOR_PARTS(*from) { + // Optionally, skip exporting particles that have been marked as deleted + if (skipDeletedParts && !from->isActive(idx)) { + continue; + } + Vector3D pos = toVec3f( (*from)[idx].pos ); + pos *= voxelSize; // convert from grid space to world space + openvdb::Vec3s posVDB(pos.x, pos.y, pos.z); + positions.push_back(posVDB); + + int flag = (*from)[idx].flag; + flags.push_back(flag); + } + + const openvdb::points::PointAttributeVector positionsWrapper(positions); + openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform(voxelSize); + + openvdb::tools::PointIndexGrid::Ptr pointIndexGrid = openvdb::tools::createPointIndexGrid(positionsWrapper, *transform); + + // TODO (sebbas): Use custom codec for attributes? + //using Codec = openvdb::points::FixedPointCodec; + openvdb::points::PointDataGrid::Ptr to = openvdb::points::createPointDataGrid(*pointIndexGrid, positionsWrapper, *transform); + + openvdb::NamePair flagAttribute = openvdb::points::TypedAttributeArray::attributeType(); + openvdb::points::appendAttribute(to->tree(), FLAG_NAME, flagAttribute); + // Create a wrapper around the flag vector. + openvdb::points::PointAttributeVector flagWrapper(flags); + // Populate the "flag" attribute on the points + openvdb::points::populateAttribute>(to->tree(), pointIndexGrid->tree(), FLAG_NAME, flagWrapper); + + // Add all already buffered pdata to this particle grid + for (ParticleDataBase* pdb : fromPData) { + if (pdb->getType() == ParticleDataBase::TypeInt) { + debMsg("Writing int particle data '" << pdb->getName() << "'", 1); + ParticleDataImpl* pdi = dynamic_cast*>(pdb); + exportVDB(pdi, to, pointIndexGrid, skipDeletedParts); + } + else if (pdb->getType() == ParticleDataBase::TypeReal) { + debMsg("Writing real particle data '" << pdb->getName() << "'", 1); + ParticleDataImpl* pdi = dynamic_cast*>(pdb); + exportVDB(pdi, to, pointIndexGrid, skipDeletedParts); + } + else if (pdb->getType() == ParticleDataBase::TypeVec3) { + debMsg("Writing Vec3 particle data '" << pdb->getName() << "'", 1); + ParticleDataImpl* pdi = dynamic_cast*>(pdb); + exportVDB(pdi, to, pointIndexGrid, skipDeletedParts); + } + else { + errMsg("exportVDB: unknown ParticleDataBase type"); + } + } + return to; +} + +static void registerCustomCodecs() { + using Codec = openvdb::points::FixedPointCodec; + openvdb::points::TypedAttributeArray::registerType(); +} + +int writeObjectsVDB(const string& filename, std::vector* objects, float worldSize, bool skipDeletedParts, int compression, bool precisionHalf) { + openvdb::initialize(); + openvdb::io::File file(filename); + openvdb::GridPtrVec gridsVDB; + + // TODO (sebbas): Use custom codec for flag attribute? + // Register codecs one, this makes sure custom attributes can be read + //registerCustomCodecs(); + + std::vector pdbBuffer; + + for (std::vector::iterator iter = objects->begin(); iter != objects->end(); ++iter) { + openvdb::GridClass gClass = openvdb::GRID_UNKNOWN; + openvdb::GridBase::Ptr vdbGrid; + + PbClass* object = dynamic_cast(*iter); + const Real dx = object->getParent()->getDx(); + const Real voxelSize = worldSize * dx; + const string objectName = object->getName(); + + if (GridBase* mantaGrid = dynamic_cast(*iter)) { + + if (mantaGrid->getType() & GridBase::TypeInt) { + debMsg("Writing int grid '" << mantaGrid->getName() << "' to vdb file " << filename, 1); + Grid* mantaIntGrid = (Grid*) mantaGrid; + vdbGrid = exportVDB(mantaIntGrid); + gridsVDB.push_back(vdbGrid); + } + else if (mantaGrid->getType() & GridBase::TypeReal) { + debMsg("Writing real grid '" << mantaGrid->getName() << "' to vdb file " << filename, 1); + gClass = (mantaGrid->getType() & GridBase::TypeLevelset) ? openvdb::GRID_LEVEL_SET : openvdb::GRID_FOG_VOLUME; + Grid* mantaRealGrid = (Grid*) mantaGrid; + vdbGrid = exportVDB(mantaRealGrid); + gridsVDB.push_back(vdbGrid); + } + else if (mantaGrid->getType() & GridBase::TypeVec3) { + debMsg("Writing vec3 grid '" << mantaGrid->getName() << "' to vdb file " << filename, 1); + gClass = (mantaGrid->getType() & GridBase::TypeMAC) ? openvdb::GRID_STAGGERED : openvdb::GRID_UNKNOWN; + Grid* mantaVec3Grid = (Grid*) mantaGrid; + vdbGrid = exportVDB(mantaVec3Grid); + gridsVDB.push_back(vdbGrid); + } + else { + errMsg("writeObjectsVDB: unknown grid type"); + return 0; + } + } + else if (BasicParticleSystem* mantaPP = dynamic_cast(*iter)) { + debMsg("Writing particle system '" << mantaPP->getName() << "' (and buffered pData) to vdb file " << filename, 1); + vdbGrid = exportVDB(mantaPP, pdbBuffer, skipDeletedParts, voxelSize); + gridsVDB.push_back(vdbGrid); + pdbBuffer.clear(); + + } + // Particle data will only be saved if there is a particle system too. + else if (ParticleDataBase* mantaPPImpl = dynamic_cast(*iter)) { + debMsg("Buffering particle data '" << mantaPPImpl->getName() << "' to vdb file " << filename, 1); + pdbBuffer.push_back(mantaPPImpl); + + } + else { + errMsg("writeObjectsVDB: Unsupported Python object. Cannot write to .vdb file " << filename); + return 0; + } + + // Set additional grid attributes, e.g. name, grid class, compression level, etc. + if (vdbGrid) { + setGridOptions(vdbGrid, objectName, gClass, voxelSize, precisionHalf); + } + } + + // Give out a warning if pData items were present but could not be saved due to missing particle system. + if (!pdbBuffer.empty()) { + for (ParticleDataBase* pdb : pdbBuffer) { + debMsg("writeObjectsVDB Warning: Particle data '" << pdb->getName() << "' has not been saved. It's parent particle system was needs to be given too.", 1); + } + } + + // Write only if the is at least one grid, optionally write with compression. + if (gridsVDB.size()) { + int vdb_flags = openvdb::io::COMPRESS_ACTIVE_MASK; + switch (compression) { + case COMPRESSION_NONE: { + vdb_flags = openvdb::io::COMPRESS_NONE; + break; + } + case COMPRESSION_ZIP: { + vdb_flags |= openvdb::io::COMPRESS_ZIP; + break; + } + case COMPRESSION_BLOSC: { +#if OPENVDB_BLOSC==1 + vdb_flags |= openvdb::io::COMPRESS_BLOSC; +#else + debMsg("OpenVDB was built without Blosc support, using Zip compression instead", 1); + vdb_flags |= openvdb::io::COMPRESS_ZIP; +#endif // OPENVDB_BLOSC==1 + break; + } + } + file.setCompression(vdb_flags); + file.write(gridsVDB); + } + file.close(); + return 1; +} + +int readObjectsVDB(const string& filename, std::vector* objects, float worldSize) { + + openvdb::initialize(); + openvdb::io::File file(filename); + openvdb::GridPtrVec gridsVDB; + + // TODO (sebbas): Use custom codec for flag attribute? + // Register codecs one, this makes sure custom attributes can be read + //registerCustomCodecs(); + + try { + file.setCopyMaxBytes(0); + file.open(); + gridsVDB = *(file.getGrids()); + openvdb::MetaMap::Ptr metadata = file.getMetadata(); + (void) metadata; // Unused for now + } + catch (const openvdb::IoError &e) { + debMsg("readObjectsVDB: Could not open vdb file " << filename, 1); + file.close(); + return 0; + } + file.close(); + + // A buffer to store a handle to pData objects. These will be read alongside a particle system. + std::vector pdbBuffer; + + for (std::vector::iterator iter = objects->begin(); iter != objects->end(); ++iter) { + + if (gridsVDB.empty()) { + debMsg("readObjectsVDB: No vdb grids in file " << filename, 1); + } + // If there is just one grid in this file, load it regardless of name match (to vdb caches per grid). + bool onlyGrid = (gridsVDB.size() == 1); + + PbClass* object = dynamic_cast(*iter); + const Real dx = object->getParent()->getDx(); + const Real voxelSize = worldSize * dx; + + // Particle data objects are treated separately - buffered and inserted when reading the particle system + if (ParticleDataBase* mantaPPImpl = dynamic_cast(*iter)) { + debMsg("Buffering particle data '" << mantaPPImpl->getName() << "' from vdb file " << filename, 1); + pdbBuffer.push_back(mantaPPImpl); + continue; + } + + // For every manta object, we loop through the vdb grid list and check for a match + for (const openvdb::GridBase::Ptr& vdbGrid : gridsVDB) { + bool nameMatch = (vdbGrid->getName() == (*iter)->getName()); + + // Sanity checks: Only load valid grids and make sure names match. + if (!vdbGrid) { + debMsg("Skipping invalid vdb grid '" << vdbGrid->getName() << "' in file " << filename, 1); + continue; + } + if (!nameMatch && !onlyGrid) { + continue; + } + if (GridBase* mantaGrid = dynamic_cast(*iter)) { + + if (mantaGrid->getType() & GridBase::TypeInt) { + debMsg("Reading into grid '" << mantaGrid->getName() << "' from int grid '" << vdbGrid->getName() << "' in vdb file " << filename, 1); + openvdb::Int32Grid::Ptr vdbIntGrid = openvdb::gridPtrCast(vdbGrid); + Grid* mantaIntGrid = (Grid*) mantaGrid; + importVDB(vdbIntGrid, mantaIntGrid); + } + else if (mantaGrid->getType() & GridBase::TypeReal) { + debMsg("Reading into grid '" << mantaGrid->getName() << "' from real grid '" << vdbGrid->getName() << "' in vdb file " << filename, 1); + openvdb::FloatGrid::Ptr vdbFloatGrid = openvdb::gridPtrCast(vdbGrid); + Grid* mantaRealGrid = (Grid*) mantaGrid; + importVDB(vdbFloatGrid, mantaRealGrid); + } + else if (mantaGrid->getType() & GridBase::TypeVec3) { + debMsg("Reading into grid '" << mantaGrid->getName() << "' from vec3 grid '" << vdbGrid->getName() << "' in vdb file " << filename, 1); + openvdb::Vec3SGrid::Ptr vdbVec3Grid = openvdb::gridPtrCast(vdbGrid); + Grid* mantaVec3Grid = (Grid*) mantaGrid; + importVDB(vdbVec3Grid, mantaVec3Grid); + } + else { + errMsg("readObjectsVDB: unknown grid type"); + return 0; + } + } + else if (BasicParticleSystem* mantaPP = dynamic_cast(*iter)) { + debMsg("Reading into particle system '" << mantaPP->getName() << "' from particle system '" << vdbGrid->getName() << "' in vdb file " << filename, 1); + openvdb::points::PointDataGrid::Ptr vdbPointGrid = openvdb::gridPtrCast(vdbGrid); + importVDB(vdbPointGrid, mantaPP, pdbBuffer, voxelSize); + pdbBuffer.clear(); + + } + else { + errMsg("readObjectsVDB: Unsupported Python object. Cannot read from .vdb file " << filename); + return 0; + } + } + } + + // Give out a warning if pData items were present but could not be read due to missing particle system. + if (!pdbBuffer.empty()) { + for (ParticleDataBase* pdb : pdbBuffer) { + debMsg("readObjectsVDB Warning: Particle data '" << pdb->getName() << "' has not been read. The parent particle system needs to be given too.", 1); + } + } + + return 1; +} + +template void importVDB(int vdbValue, ParticleDataImpl* to, int index, float voxelSize=1.0); +template void importVDB(float vdbValue, ParticleDataImpl* to, int index, float voxelSize=1.0); +template void importVDB(openvdb::Vec3s vdbValue, ParticleDataImpl* to, int index, float voxelSize=1.0); + +void importVDB(openvdb::points::PointDataGrid::Ptr from, BasicParticleSystem *to, std::vector& toPData, float voxelSize=1.0); +template void importVDB(openvdb::Int32Grid::Ptr from, Grid *to); +template void importVDB(openvdb::FloatGrid::Ptr from, Grid *to); +template void importVDB(openvdb::Vec3SGrid::Ptr from, Grid *to); + +template openvdb::Int32Grid::Ptr exportVDB(Grid *from); +template openvdb::FloatGrid::Ptr exportVDB(Grid *from); +template openvdb::Vec3SGrid::Ptr exportVDB(Grid *from); + +openvdb::points::PointDataGrid::Ptr exportVDB(BasicParticleSystem *from, std::vector& fromPData, bool skipDeletedParts=false, float voxelSize=1.0); +template void exportVDB(ParticleDataImpl* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts=false); +template void exportVDB(ParticleDataImpl* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts=false); +template void exportVDB(ParticleDataImpl* from, openvdb::points::PointDataGrid::Ptr to, openvdb::tools::PointIndexGrid::Ptr pIndex, bool skipDeletedParts=false); + +#else + +int writeObjectsVDB(const string& filename, std::vector* objects, float worldSize, bool skipDeletedParts, int compression, bool precisionHalf) { + errMsg("Cannot save to .vdb file. Mantaflow has not been built with OpenVDB support."); + return 0; +} + +int readObjectsVDB(const string& filename, std::vector* objects, float worldSize) { + errMsg("Cannot load from .vdb file. Mantaflow has not been built with OpenVDB support."); + return 0; +} + +#endif // OPENVDB==1 + +} //namespace diff --git a/source/fileio/mantaio.cpp b/source/fileio/mantaio.cpp new file mode 100644 index 00000000..cae785eb --- /dev/null +++ b/source/fileio/mantaio.cpp @@ -0,0 +1,64 @@ +/****************************************************************************** + * + * MantaFlow fluid solver framework + * Copyright 2020 Sebastian Barschkis, Nils Thuerey + * + * This program is free software, distributed under the terms of the + * Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * General functions that make use of functions from other io files. + * + ******************************************************************************/ + +#include "mantaio.h" + +using namespace std; + +namespace Manta { + +PYTHON() int load(const string& name, std::vector& objects, float worldSize=1.0) { + if (name.find_last_of('.') == string::npos) + errMsg("file '" + name + "' does not have an extension"); + string ext = name.substr(name.find_last_of('.')); + + if (ext == ".raw") + return readGridsRaw(name, &objects); + else if (ext == ".uni") + return readGridsUni(name, &objects); + else if (ext == ".vol") + return readGridsVol(name, &objects); + if (ext == ".vdb") + return readObjectsVDB(name, &objects, worldSize); + else if (ext == ".npz") + return readGridsNumpy(name, &objects); + else if (ext == ".txt") + return readGridsTxt(name, &objects); + else + errMsg("file '" + name +"' filetype not supported"); + return 0; +} + +PYTHON() int save(const string& name, std::vector& objects, float worldSize=1.0, bool skipDeletedParts=false, int compression=COMPRESSION_ZIP, bool precisionHalf=true) { + if (name.find_last_of('.') == string::npos) + errMsg("file '" + name + "' does not have an extension"); + string ext = name.substr(name.find_last_of('.')); + + if (ext == ".raw") + return writeGridsRaw(name, &objects); + else if (ext == ".uni") + return writeGridsUni(name, &objects); + else if (ext == ".vol") + return writeGridsVol(name, &objects); + if (ext == ".vdb") + return writeObjectsVDB(name, &objects, worldSize, skipDeletedParts, compression, precisionHalf); + else if (ext == ".npz") + return writeGridsNumpy(name, &objects); + else if (ext == ".txt") + return writeGridsTxt(name, &objects); + else + errMsg("file '" + name +"' filetype not supported"); + return 0; +} + +} //namespace diff --git a/source/fileio/mantaio.h b/source/fileio/mantaio.h index ea970d35..1ddf6fd7 100644 --- a/source/fileio/mantaio.h +++ b/source/fileio/mantaio.h @@ -16,54 +16,85 @@ #include +#include "manta.h" + +// OpenVDB compression flags +#define COMPRESSION_NONE 0 +#define COMPRESSION_ZIP 1 +#define COMPRESSION_BLOSC 2 + namespace Manta { -// forward decl. +// Forward declations class Mesh; class FlagGrid; +class GridBase; template class Grid; template class Grid4d; class BasicParticleSystem; template class ParticleDataImpl; template class MeshDataImpl; -void writeObjFile(const std::string& name, Mesh* mesh); -void writeBobjFile(const std::string& name, Mesh* mesh); -void readObjFile(const std::string& name, Mesh* mesh, bool append); -void readBobjFile(const std::string& name, Mesh* mesh, bool append); +// Obj format +int writeObjFile(const std::string& name, Mesh* mesh); +int writeBobjFile(const std::string& name, Mesh* mesh); +int readObjFile(const std::string& name, Mesh* mesh, bool append); +int readBobjFile(const std::string& name, Mesh* mesh, bool append); -template void writeGridRaw(const std::string& name, Grid* grid); -template void writeGridUni(const std::string& name, Grid* grid); -template void writeGridVol(const std::string& name, Grid* grid); -template void writeGridTxt(const std::string& name, Grid* grid); +// Other formats (Raw, Uni, Vol) +template int readGridUni (const std::string& name, Grid* grid); +template int readGridRaw (const std::string& name, Grid* grid); +template int readGridVol (const std::string& name, Grid* grid); +int readGridsRaw(const std::string& name, std::vector* grids); +int readGridsUni(const std::string& name, std::vector* grids); +int readGridsVol(const std::string& name, std::vector* grids); +int readGridsTxt(const std::string& name, std::vector* grids); -#if OPENVDB==1 -template void writeGridVDB(const std::string& name, Grid* grid); -template void readGridVDB(const std::string& name, Grid* grid); -#endif // OPENVDB==1 -template void writeGridNumpy(const std::string& name, Grid* grid); -template void readGridNumpy (const std::string& name, Grid* grid); - -template void readGridUni (const std::string& name, Grid* grid); -template void readGridRaw (const std::string& name, Grid* grid); -template void readGridVol (const std::string& name, Grid* grid); - -template void writeGrid4dUni(const std::string& name, Grid4d* grid); -template void readGrid4dUni (const std::string& name, Grid4d* grid, int readTslice=-1, Grid4d* slice=NULL, void** fileHandle=NULL); +template int writeGridRaw(const std::string& name, Grid* grid); +template int writeGridUni(const std::string& name, Grid* grid); +template int writeGridVol(const std::string& name, Grid* grid); +template int writeGridTxt(const std::string& name, Grid* grid); +int writeGridsRaw(const std::string& name, std::vector* grids); +int writeGridsUni(const std::string& name, std::vector* grids); +int writeGridsVol(const std::string& name, std::vector* grids); +int writeGridsTxt(const std::string& name, std::vector* grids); + +// OpenVDB +int writeObjectsVDB(const std::string& filename, std::vector* objects, float scale=1.0, bool skipDeletedParts=false, int compression=COMPRESSION_ZIP, bool precisionHalf=true); +int readObjectsVDB(const std::string& filename, std::vector* objects, float scale=1.0); + +// Numpy +template int writeGridNumpy(const std::string& name, Grid* grid); +template int readGridNumpy (const std::string& name, Grid* grid); + +int writeGridsNumpy(const std::string& name, std::vector* grids); +int readGridsNumpy(const std::string& name, std::vector* grids); + +// 4D Grids +template int writeGrid4dUni(const std::string& name, Grid4d* grid); +template int readGrid4dUni (const std::string& name, Grid4d* grid, int readTslice=-1, Grid4d* slice=NULL, void** fileHandle=NULL); void readGrid4dUniCleanup(void** fileHandle); -template void writeGrid4dRaw(const std::string& name, Grid4d* grid); -template void readGrid4dRaw (const std::string& name, Grid4d* grid); +template int writeGrid4dRaw(const std::string& name, Grid4d* grid); +template int readGrid4dRaw (const std::string& name, Grid4d* grid); -void writeParticlesUni(const std::string& name, const BasicParticleSystem* parts ); -void readParticlesUni (const std::string& name, BasicParticleSystem* parts ); +// Particles + particle data +int writeParticlesUni(const std::string& name, const BasicParticleSystem* parts ); +int readParticlesUni (const std::string& name, BasicParticleSystem* parts ); -template void writePdataUni(const std::string& name, ParticleDataImpl* pdata ); -template void readPdataUni (const std::string& name, ParticleDataImpl* pdata ); +template int writePdataUni(const std::string& name, ParticleDataImpl* pdata ); +template int readPdataUni (const std::string& name, ParticleDataImpl* pdata ); -template void writeMdataUni(const std::string& name, MeshDataImpl* mdata ); -template void readMdataUni (const std::string& name, MeshDataImpl* mdata ); +// Mesh data +template int writeMdataUni(const std::string& name, MeshDataImpl* mdata ); +template int readMdataUni (const std::string& name, MeshDataImpl* mdata ); +// Helpers void getUniFileSize(const std::string& name, int& x, int& y, int& z, int* t = NULL, std::string* info = NULL); +void *safeGzopen(const char *filename, const char *mode); +#if OPENVDB==1 +template void convertFrom(S& in, T* out); +template void convertTo(S* out, T& in); +#endif } // namespace diff --git a/source/fluidsolver.cpp b/source/fluidsolver.cpp index 99b80f26..4558827b 100644 --- a/source/fluidsolver.cpp +++ b/source/fluidsolver.cpp @@ -107,7 +107,7 @@ template<> void FluidSolver::freeGrid4dPointer(Vec4* ptr) { FluidSolver::FluidSolver(Vec3i gridsize, int dim, int fourthDim) : PbClass(this), mDt(1.0), mTimeTotal(0.), mFrame(0), mCflCond(1000), mDtMin(1.), mDtMax(1.), mFrameLength(1.), - mGridSize(gridsize), mDim(dim) , mTimePerFrame(0.), mLockDt(false), mFourthDim(fourthDim) + mTimePerFrame(0.), mGridSize(gridsize), mDim(dim), mLockDt(false), mFourthDim(fourthDim) { if(dim==4 && mFourthDim>0) errMsg("Don't create 4D solvers, use 3D with fourth-dim parameter >0 instead."); assertMsg(dim==2 || dim==3, "Only 2D and 3D solvers allowed."); diff --git a/source/fluidsolver.h b/source/fluidsolver.h index a91b62f5..aa6a7aee 100644 --- a/source/fluidsolver.h +++ b/source/fluidsolver.h @@ -65,12 +65,14 @@ class FluidSolver : public PbClass { PYTHON(name=timestepMax) Real mDtMax; PYTHON(name=frameLength) Real mFrameLength; + //! Per frame duration. Blender needs access in order to restore value in new solver object + PYTHON(name=timePerFrame) Real mTimePerFrame; + protected: Vec3i mGridSize; const int mDim; - Real mTimePerFrame; bool mLockDt; - + //! subclass for managing grid memory //! stored as a stack to allow fast allocation template struct GridStorage { diff --git a/source/general.h b/source/general.h index 3adea738..3ce79a69 100644 --- a/source/general.h +++ b/source/general.h @@ -77,9 +77,9 @@ inline bool _chklevel(int level=0) { return gDebugLevel >= level; } #define assertMsg(cond,msg) if(!(cond)) throwError(msg) #define assertDeb(cond,msg) DEBUG_ONLY( assertMsg(cond,msg) ) -// for compatibility with blender, blender only defines WITH_MANTA, make sure we have "BLENDER" +// for compatibility with blender, blender only defines WITH_FLUID, make sure we have "BLENDER" #ifndef BLENDER -#ifdef WITH_MANTA +#ifdef WITH_FLUID #define BLENDER 1 #endif #endif diff --git a/source/grid.cpp b/source/grid.cpp index c8a92f23..84844a8a 100644 --- a/source/grid.cpp +++ b/source/grid.cpp @@ -19,6 +19,9 @@ #include #include +#include "commonkernels.h" + + using namespace std; namespace Manta { @@ -107,47 +110,49 @@ void Grid::swap(Grid& other) { } template -void Grid::load(string name) { +int Grid::load(string name) { if (name.find_last_of('.') == string::npos) errMsg("file '" + name + "' does not have an extension"); string ext = name.substr(name.find_last_of('.')); if (ext == ".raw") - readGridRaw(name, this); + return readGridRaw(name, this); else if (ext == ".uni") - readGridUni(name, this); + return readGridUni(name, this); else if (ext == ".vol") - readGridVol(name, this); + return readGridVol(name, this); else if (ext == ".npz") - readGridNumpy(name, this); -# if OPENVDB==1 - else if (ext == ".vdb") - readGridVDB(name, this); -# endif // OPENVDB==1 - else + return readGridNumpy(name, this); + else if (ext == ".vdb") { + std::vector grids; + grids.push_back(this); + return readObjectsVDB(name, &grids); + } else errMsg("file '" + name +"' filetype not supported"); + return 0; } template -void Grid::save(string name) { +int Grid::save(string name) { if (name.find_last_of('.') == string::npos) errMsg("file '" + name + "' does not have an extension"); string ext = name.substr(name.find_last_of('.')); if (ext == ".raw") - writeGridRaw(name, this); + return writeGridRaw(name, this); else if (ext == ".uni") - writeGridUni(name, this); + return writeGridUni(name, this); else if (ext == ".vol") - writeGridVol(name, this); -# if OPENVDB==1 - else if (ext == ".vdb") - writeGridVDB(name, this); -# endif // OPENVDB==1 + return writeGridVol(name, this); else if (ext == ".npz") - writeGridNumpy(name, this); - else if (ext == ".txt") - writeGridTxt(name, this); + return writeGridNumpy(name, this); + else if (ext == ".vdb") { + std::vector grids; + grids.push_back(this); + return writeObjectsVDB(name, &grids); + } else if (ext == ".txt") + return writeGridTxt(name, this); else errMsg("file '" + name +"' filetype not supported"); + return 0; } //****************************************************************************** @@ -220,11 +225,34 @@ template inline void stomp(T &v, const T &th) { if(v inline void stomp(Vec3 &v, const Vec3 &th) { if(v[0] void knGridStomp(Grid& me, const T& threshold) { stomp(me[idx], threshold); } +KERNEL() template void knPermuteAxes (Grid& self, Grid& target, int axis0, int axis1, int axis2) { + int i0 = axis0 == 0 ? i : (axis0 == 1 ? j : k); + int i1 = axis1 == 0 ? i : (axis1 == 1 ? j : k); + int i2 = axis2 == 0 ? i : (axis2 == 1 ? j : k); + target(i0,i1,i2) = self(i,j,k); +} + +KERNEL(idx) void knJoinVec(Grid& a, const Grid& b, bool keepMax) { + Real a1 = normSquare(a[idx]); + Real b1 = normSquare(b[idx]); + a[idx] = (keepMax) ? max(a1, b1) : min(a1, b1); +} +KERNEL(idx) void knJoinInt(Grid& a, const Grid& b, bool keepMax) { + a[idx] = (keepMax) ? max(a[idx], b[idx]) : min(a[idx], b[idx]); +} +KERNEL(idx) void knJoinReal(Grid& a, const Grid& b, bool keepMax) { + a[idx] = (keepMax) ? max(a[idx], b[idx]) : min(a[idx], b[idx]); +} + template Grid& Grid::safeDivide (const Grid& a) { knGridSafeDiv (*this, a); return *this; } +template int Grid::getGridType() { + return static_cast(mType); +} + template void Grid::add(const Grid& a) { gridAdd(*this, a); } @@ -254,6 +282,33 @@ template void Grid::clamp(Real min, Real max) { template void Grid::stomp(const T& threshold) { knGridStomp(*this, threshold); } +template void Grid::permuteAxes(int axis0, int axis1, int axis2) { + if(axis0 == axis1 || axis0 == axis2 || axis1 == axis2 || axis0 > 2 || axis1 > 2 || axis2 > 2 || axis0 < 0 || axis1 < 0 || axis2 < 0) + return; + Vec3i size = mParent->getGridSize(); + assertMsg( mParent->is2D() ? size.x == size.y : size.x == size.y && size.y == size.z, "Grid must be cubic!"); + Grid tmp(mParent); + knPermuteAxes(*this, tmp, axis0, axis1, axis2); + this->swap(tmp); +} +template void Grid::permuteAxesCopyToGrid(int axis0, int axis1, int axis2, Grid& out) { + if(axis0 == axis1 || axis0 == axis2 || axis1 == axis2 || axis0 > 2 || axis1 > 2 || axis2 > 2 || axis0 < 0 || axis1 < 0 || axis2 < 0) + return; + assertMsg( this->getGridType() == out.getGridType(), "Grids must have same data type!"); + Vec3i size = mParent->getGridSize(); + Vec3i sizeTarget = out.getParent()->getGridSize(); + assertMsg( sizeTarget[axis0] == size[0] && sizeTarget[axis1] == size[1] && sizeTarget[axis2] == size[2], "Permuted grids must have the same dimensions!"); + knPermuteAxes(*this, out, axis0, axis1, axis2); +} +template<> void Grid::join(const Grid& a, bool keepMax) { + knJoinVec(*this, a, keepMax); +} +template<> void Grid::join(const Grid& a, bool keepMax) { + knJoinInt(*this, a, keepMax); +} +template<> void Grid::join(const Grid& a, bool keepMax) { + knJoinReal(*this, a, keepMax); +} template<> Real Grid::getMax() const { return CompMaxReal (*this); @@ -328,7 +383,7 @@ template Real Grid::getL2(int bnd) { KERNEL(reduce=+) returns(int cnt=0) int knCountCells(const FlagGrid& flags, int flag, int bnd, Grid* mask) { if(mask) (*mask)(i,j,k) = 0.; - if( bnd>0 && (!flags.isInBounds(Vec3i(i,j,k))) ) return; + if( bnd>0 && (!flags.isInBounds(Vec3i(i,j,k), bnd)) ) return; if (flags(i,j,k) & flag ) { cnt++; if(mask) (*mask)(i,j,k) = 1.; @@ -374,56 +429,75 @@ PYTHON() Real gridMaxDiffVec3(Grid& g1, Grid& g2) return maxVal; } +KERNEL() void knCopyMacToVec3(MACGrid &source, Grid& target) +{ + target(i,j,k) = source(i,j,k); +} // simple helper functions to copy (convert) mac to vec3 , and levelset to real grids // (are assumed to be the same for running the test cases - in general they're not!) -PYTHON() void copyMacToVec3 (MACGrid &source, Grid& target) +PYTHON() void copyMacToVec3(MACGrid &source, Grid& target) { - FOR_IJK(target) { - target(i,j,k) = source(i,j,k); - } + knCopyMacToVec3(source, target); } PYTHON() void convertMacToVec3 (MACGrid &source , Grid &target) { debMsg("Deprecated - do not use convertMacToVec3... use copyMacToVec3 instead",1); copyMacToVec3(source,target); } +KERNEL(bnd=1) +void knResampleVec3ToMac(Grid& source, MACGrid &target) +{ + target(i,j,k)[0] = 0.5*(source(i-1,j,k)[0]+source(i,j,k))[0]; + target(i,j,k)[1] = 0.5*(source(i,j-1,k)[1]+source(i,j,k))[1]; + if(target.is3D()) { + target(i,j,k)[2] = 0.5*(source(i,j,k-1)[2]+source(i,j,k))[2]; } +} //! vec3->mac grid conversion , but with full resampling -PYTHON() void resampleVec3ToMac (Grid& source, MACGrid &target ) { - FOR_IJK_BND(target,1) { - target(i,j,k)[0] = 0.5*(source(i-1,j,k)[0]+source(i,j,k))[0]; - target(i,j,k)[1] = 0.5*(source(i,j-1,k)[1]+source(i,j,k))[1]; - if(target.is3D()) { - target(i,j,k)[2] = 0.5*(source(i,j,k-1)[2]+source(i,j,k))[2]; } - } +PYTHON() void resampleVec3ToMac(Grid& source, MACGrid &target) +{ + knResampleVec3ToMac(source, target); +} + +KERNEL(bnd=1) +void knResampleMacToVec3(MACGrid &source, Grid& target) +{ + target(i,j,k) = source.getCentered(i,j,k); } //! mac->vec3 grid conversion , with full resampling -PYTHON() void resampleMacToVec3 (MACGrid &source, Grid& target ) { - FOR_IJK_BND(target,1) { - target(i,j,k) = source.getCentered(i,j,k); - } +PYTHON() void resampleMacToVec3 (MACGrid &source, Grid& target) +{ + knResampleMacToVec3(source, target); } -PYTHON() void copyLevelsetToReal (LevelsetGrid &source , Grid &target) +KERNEL() void knCopyLevelsetToReal(LevelsetGrid &source , Grid &target) { - FOR_IJK(target) { - target(i,j,k) = source(i,j,k); - } + target(i,j,k) = source(i,j,k); +} +PYTHON() void copyLevelsetToReal(LevelsetGrid &source , Grid &target) +{ + knCopyLevelsetToReal(source, target); +} + +KERNEL() void knCopyVec3ToReal(Grid &source, Grid &targetX, Grid &targetY, Grid &targetZ) +{ + targetX(i,j,k) = source(i,j,k).x; + targetY(i,j,k) = source(i,j,k).y; + targetZ(i,j,k) = source(i,j,k).z; } PYTHON() void copyVec3ToReal (Grid &source, Grid &targetX, Grid &targetY, Grid &targetZ) { - FOR_IJK(source) { - targetX(i,j,k) = source(i,j,k).x; - targetY(i,j,k) = source(i,j,k).y; - targetZ(i,j,k) = source(i,j,k).z; - } + knCopyVec3ToReal(source, targetX, targetY, targetZ); } +KERNEL() void knCopyRealToVec3(Grid &sourceX, Grid &sourceY, Grid &sourceZ, Grid &target) +{ + target(i,j,k).x = sourceX(i,j,k); + target(i,j,k).y = sourceY(i,j,k); + target(i,j,k).z = sourceZ(i,j,k); +} PYTHON() void copyRealToVec3 (Grid &sourceX, Grid &sourceY, Grid &sourceZ, Grid &target) { - FOR_IJK(target) { - target(i,j,k).x = sourceX(i,j,k); - target(i,j,k).y = sourceY(i,j,k); - target(i,j,k).z = sourceZ(i,j,k); - } + knCopyRealToVec3(sourceX, sourceY, sourceZ, target); } + PYTHON() void convertLevelsetToReal (LevelsetGrid &source , Grid &target) { debMsg("Deprecated - do not use convertLevelsetToReal... use copyLevelsetToReal instead",1); copyLevelsetToReal(source,target); } template void Grid::printGrid(int zSlice, bool printIndex, int bnd) { @@ -470,13 +544,17 @@ static inline Real computeUvRamp(Real t) { return uvWeight; } -KERNEL() void knResetUvGrid (Grid& target) { target(i,j,k) = Vec3((Real)i,(Real)j,(Real)k); } +KERNEL() void knResetUvGrid (Grid& target, const Vec3* offset) { + Vec3 coord = Vec3((Real)i,(Real)j,(Real)k); + if (offset) coord += (*offset); + target(i,j,k) = coord; +} -PYTHON() void resetUvGrid (Grid &target) +PYTHON() void resetUvGrid (Grid &target, const Vec3* offset=NULL) { - knResetUvGrid reset(target); // note, llvm complains about anonymous declaration here... ? + knResetUvGrid reset(target, offset); // note, llvm complains about anonymous declaration here... ? } -PYTHON() void updateUvWeight(Real resetTime, int index, int numUvs, Grid &uv) +PYTHON() void updateUvWeight(Real resetTime, int index, int numUvs, Grid &uv, const Vec3* offset=NULL) { const Real t = uv.getParent()->getTime(); Real timeOff = resetTime/(Real)numUvs; @@ -494,8 +572,8 @@ PYTHON() void updateUvWeight(Real resetTime, int index, int numUvs, Grid & else uvWeight /= uvWTotal; // check for reset - if( currt < lastt ) - knResetUvGrid reset( uv ); + if( currt < lastt ) + knResetUvGrid reset(uv, offset); // write new weight value to grid uv[0] = Vec3( uvWeight, 0.,0.); @@ -809,6 +887,29 @@ void markIsolatedFluidCell(FlagGrid &flags, const int mark) knMarkIsolatedFluidCell(flags, mark); } +PYTHON() +void copyMACData(const MACGrid &source, MACGrid &target, const FlagGrid& flags, const int flag, const int bnd) +{ + assertMsg (source.getSize().x == target.getSize().x && source.getSize().y == target.getSize().y && source.getSize().z == target.getSize().z, "different grid resolutions " << source.getSize() << " vs " << target.getSize() ); + + // Grid divGrid(target.getParent()); + // DivergenceOpMAC(divGrid, target); + // Real fDivOrig = GridSumSqr(divGrid); + + FOR_IJK_BND(target, bnd) + { + if(flags.get(i,j,k) & flag) + { + target(i,j,k) = source(i,j,k); + } + } + + // DivergenceOpMAC(divGrid, target); + // Real fDivTransfer = GridSumSqr(divGrid); + // std::cout << "Divergence: " << fDivOrig << " -> " << fDivTransfer << std::endl; +} + + // explicit instantiation template class Grid; template class Grid; diff --git a/source/grid.h b/source/grid.h index ad745428..16104922 100644 --- a/source/grid.h +++ b/source/grid.h @@ -22,22 +22,22 @@ namespace Manta { class LevelsetGrid; - + //! Base class for all grids PYTHON() class GridBase : public PbClass { public: - enum GridType { TypeNone = 0, TypeReal = 1, TypeInt = 2, TypeVec3 = 4, TypeMAC = 8, TypeLevelset = 16, TypeFlags = 32 }; + PYTHON() enum GridType { TypeNone = 0, TypeReal = 1, TypeInt = 2, TypeVec3 = 4, TypeMAC = 8, TypeLevelset = 16, TypeFlags = 32 }; PYTHON() GridBase(FluidSolver* parent); //! Get the grids X dimension - inline int getSizeX() const { return mSize.x; } + PYTHON() inline int getSizeX() const { return mSize.x; } //! Get the grids Y dimension - inline int getSizeY() const { return mSize.y; } + PYTHON() inline int getSizeY() const { return mSize.y; } //! Get the grids Z dimension - inline int getSizeZ() const { return mSize.z; } + PYTHON() inline int getSizeZ() const { return mSize.z; } //! Get the grids dimensions - inline Vec3i getSize() const { return mSize; } + PYTHON() inline Vec3i getSize() const { return mSize; } //! Get Stride in X dimension inline IndexInt getStrideX() const { return 1; } @@ -60,11 +60,11 @@ PYTHON() class GridBase : public PbClass { inline bool isInBounds(const Vec3& p, int bnd = 0) const { return isInBounds(toVec3i(p), bnd); } //! Check if linear index is in the range of the array inline bool isInBounds(IndexInt idx) const; - + //! Get the type of grid inline GridType getType() const { return mType; } //! Check dimensionality - inline bool is3D() const { return m3D; } + PYTHON() inline bool is3D() const { return m3D; } //! Get index into the data inline IndexInt index(int i, int j, int k) const { DEBUG_ONLY(checkIndex(i,j,k)); return (IndexInt)i + (IndexInt)mSize.x * j + (IndexInt)mStrideZ * k; } @@ -72,9 +72,9 @@ PYTHON() class GridBase : public PbClass { inline IndexInt index(const Vec3i& pos) const { DEBUG_ONLY(checkIndex(pos.x,pos.y,pos.z)); return (IndexInt)pos.x + (IndexInt)mSize.x * pos.y + (IndexInt)mStrideZ * pos.z; } //! grid4d compatibility functions - inline bool is4D() const { return false; } - inline int getSizeT() const { return 1; } - inline int getStrideT() const { return 0; } + PYTHON() inline bool is4D() const { return false; } + PYTHON() inline int getSizeT() const { return 1; } + PYTHON() inline int getStrideT() const { return 0; } inline int index(int i, int j, int k, int unused) const { return index(i,j,k); } inline bool isInBounds(int i,int j, int k, int t, int bnd) const { if(t!=0) return false; return isInBounds( Vec3i(i,j,k), bnd ); } @@ -104,8 +104,8 @@ class Grid : public GridBase { typedef T BASETYPE; typedef GridBase BASETYPE_GRID; - PYTHON() void save(std::string name); - PYTHON() void load(std::string name); + PYTHON() int save(std::string name); + PYTHON() int load(std::string name); //! set all cells to zero PYTHON() void clear(); @@ -135,7 +135,10 @@ class Grid : public GridBase { inline T& operator[](IndexInt idx) { DEBUG_ONLY(checkIndex(idx)); return mData[idx]; } //! access data inline const T operator[](IndexInt idx) const { DEBUG_ONLY(checkIndex(idx)); return mData[idx]; } - + + //! set data + inline void set(int i, int j, int k, T& val) { mData[index(i,j,k)] = val; } + // interpolated access inline T getInterpolated(const Vec3& pos) const { return interpol(mData, mSize, mStrideZ, pos); } inline void setInterpolated(const Vec3& pos, const T& val, Grid& sumBuffer) const { setInterpol(mData, mSize, mStrideZ, pos, val, &sumBuffer[0]); } @@ -157,6 +160,9 @@ class Grid : public GridBase { // helper functions to work with grids in scene files + //! get grid type + PYTHON() int getGridType(); + //! add/subtract other grid PYTHON() void add(const Grid& a); PYTHON() void sub(const Grid& a); @@ -170,11 +176,19 @@ class Grid : public GridBase { PYTHON() void mult( const Grid& a); //! multiply each cell by a constant scalar value PYTHON() void multConst(T s); + //! safely divide contents of grid (with zero check) + PYTHON() Grid& safeDivide( const Grid& a); //! clamp content to range (for vec3, clamps each component separately) PYTHON() void clamp(Real min, Real max); //! reduce small values to zero PYTHON() void stomp(const T& threshold); - + //! permute grid axes, e.g. switch y with z (0,2,1) + PYTHON() void permuteAxes(int axis0, int axis1, int axis2); + //! permute grid axes, e.g. switch y with z (0,2,1) + PYTHON() void permuteAxesCopyToGrid(int axis0, int axis1, int axis2, Grid& out); + //! join other grid by either keeping min or max value at cell + PYTHON() void join(const Grid& a, bool keepMax=true); + // common compound operators //! get absolute max value in grid PYTHON() Real getMaxAbs() const; @@ -207,7 +221,6 @@ class Grid : public GridBase { template Grid& operator*=(const S& a); template Grid& operator/=(const Grid& a); template Grid& operator/=(const S& a); - Grid& safeDivide(const Grid& a); //! Swap data with another grid (no actual data is moved) void swap(Grid& other); @@ -276,7 +289,7 @@ PYTHON() class FlagGrid : public Grid { mType = (GridType)(TypeFlags | TypeInt); } //! types of cells, in/outflow can be combined, e.g., TypeFluid|TypeInflow - enum CellType { + PYTHON() enum CellType { TypeNone = 0, TypeFluid = 1, TypeObstacle = 2, diff --git a/source/grid4d.cpp b/source/grid4d.cpp index 3f709a13..db44528a 100644 --- a/source/grid4d.cpp +++ b/source/grid4d.cpp @@ -104,29 +104,31 @@ void Grid4d::swap(Grid4d& other) { } template -void Grid4d::load(string name) { +int Grid4d::load(string name) { if (name.find_last_of('.') == string::npos) errMsg("file '" + name + "' does not have an extension"); string ext = name.substr(name.find_last_of('.')); if (ext == ".uni") - readGrid4dUni(name, this); + return readGrid4dUni(name, this); else if (ext == ".raw") - readGrid4dRaw(name, this); + return readGrid4dRaw(name, this); else errMsg("file '" + name +"' filetype not supported"); + return 0; } template -void Grid4d::save(string name) { +int Grid4d::save(string name) { if (name.find_last_of('.') == string::npos) errMsg("file '" + name + "' does not have an extension"); string ext = name.substr(name.find_last_of('.')); if (ext == ".uni") - writeGrid4dUni(name, this); + return writeGrid4dUni(name, this); else if (ext == ".raw") - writeGrid4dRaw(name, this); + return writeGrid4dRaw(name, this); else errMsg("file '" + name +"' filetype not supported"); + return 0; } //****************************************************************************** diff --git a/source/grid4d.h b/source/grid4d.h index 4976cf43..5351d23c 100644 --- a/source/grid4d.h +++ b/source/grid4d.h @@ -31,15 +31,15 @@ PYTHON() class Grid4dBase : public PbClass { PYTHON() Grid4dBase(FluidSolver* parent); //! Get the grids X dimension - inline int getSizeX() const { return mSize.x; } + PYTHON() inline int getSizeX() const { return mSize.x; } //! Get the grids Y dimension - inline int getSizeY() const { return mSize.y; } + PYTHON() inline int getSizeY() const { return mSize.y; } //! Get the grids Z dimension - inline int getSizeZ() const { return mSize.z; } + PYTHON() inline int getSizeZ() const { return mSize.z; } //! Get the grids T dimension - inline int getSizeT() const { return mSize.t; } + PYTHON() inline int getSizeT() const { return mSize.t; } //! Get the grids dimensions - inline Vec4i getSize() const { return mSize; } + PYTHON() inline Vec4i getSize() const { return mSize; } //! Get Stride in X dimension inline IndexInt getStrideX() const { return 1; } @@ -68,8 +68,8 @@ PYTHON() class Grid4dBase : public PbClass { //! Get the type of grid inline Grid4dType getType() const { return mType; } //! Check dimensionality - inline bool is3D() const { return true; } - inline bool is4D() const { return true; } + PYTHON() inline bool is3D() const { return true; } + PYTHON() inline bool is4D() const { return true; } //! 3d compatibility inline bool isInBounds(int i,int j, int k, int t, int bnd) const { return isInBounds( Vec4i(i,j,k,t), bnd ); } @@ -102,8 +102,8 @@ class Grid4d : public Grid4dBase { typedef T BASETYPE; typedef Grid4dBase BASETYPE_GRID; - PYTHON() void save(std::string name); - PYTHON() void load(std::string name); + PYTHON() int save(std::string name); + PYTHON() int load(std::string name); //! set all cells to zero PYTHON() void clear(); diff --git a/source/gui/customctrl.cpp b/source/gui/customctrl.cpp index 5e479cf2..bcd3d839 100644 --- a/source/gui/customctrl.cpp +++ b/source/gui/customctrl.cpp @@ -83,8 +83,11 @@ void TextSlider::attach(QBoxLayout* layout) { void TextSlider::update(int val) { float v = get(); QString num; - num.sprintf("%.2g", v); - mLabel->setText(mSName + ": " + num); + QTextStream out(&num); + out.setRealNumberNotation(QTextStream::SmartNotation); + out.setRealNumberPrecision(2); + out << v; + mLabel->setText(mSName + ": " + num); } float TextSlider::get() { @@ -179,6 +182,9 @@ void Gui::setCamRot(float x, float y, float z) { void Gui::windowSize(int w, int h) { QMetaObject::invokeMethod(mGuiPtr->getWindow(), "windowSize", Q_ARG(int, w), Q_ARG(int, h)); } +void Gui::setPlane(int plane) { + QMetaObject::invokeMethod(mGuiPtr->getWindow(), "setPlane", Q_ARG(int, plane)); +} PbClass* Gui::addControl(PbType t) { _args.add("nocheck",true); diff --git a/source/gui/customctrl.h b/source/gui/customctrl.h index 4e9fa660..69e0b007 100644 --- a/source/gui/customctrl.h +++ b/source/gui/customctrl.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "manta.h" namespace Manta { @@ -131,6 +132,7 @@ PYTHON() class Gui : public PbClass { PYTHON() void setCamPos(float x, float y, float z); PYTHON() void setCamRot(float x, float y, float z); PYTHON() void windowSize(int w, int h); + PYTHON() void setPlane(int plane); protected: GuiThread* mGuiPtr; diff --git a/source/gui/glwidget.h b/source/gui/glwidget.h index 24feb5af..4b51a281 100644 --- a/source/gui/glwidget.h +++ b/source/gui/glwidget.h @@ -14,6 +14,11 @@ #ifndef _GLWIDGET_H__ #define _GLWIDGET_H__ +// OpenGL was deprecated in macOS 10.14. Silencing warnings for now. +#ifdef __APPLE__ +# define GL_SILENCE_DEPRECATION +#endif + #include #include #include "vectorbase.h" @@ -39,6 +44,8 @@ Q_OBJECT void setCamPos(Vec3 pos) { mCamPos = pos; } void setCamRot(Vec3 pos) { mRotX = pos.x; mRotY = pos.y; } + void setPlane(int plane) { updatePlane(plane); } + public slots: void setViewport(const Vec3i& gridsize); void keyPressEvent(QKeyEvent* e); diff --git a/source/gui/mainwindow.cpp b/source/gui/mainwindow.cpp index bcb643f8..6432c5c4 100644 --- a/source/gui/mainwindow.cpp +++ b/source/gui/mainwindow.cpp @@ -308,6 +308,9 @@ void MainWnd::windowSize(int w, int h) { mGlWidget->setMaximumSize( w,h ); mGlWidget->resize( w,h ); } +void MainWnd::setPlane(int plane) { + mGlWidget->setPlane(plane); +} MainWnd::~MainWnd() { } diff --git a/source/gui/mainwindow.h b/source/gui/mainwindow.h index dbee006f..e28d0de7 100644 --- a/source/gui/mainwindow.h +++ b/source/gui/mainwindow.h @@ -66,7 +66,8 @@ public slots: void setCamPos(float x, float y, float z); void setCamRot(float x, float y, float z); void windowSize(int w, int h); - + void setPlane(int plane); + signals: void painterEvent(int e, int param=0); void wakeMain(); diff --git a/source/gui/meshpainter.h b/source/gui/meshpainter.h index 6406a3ef..93207be9 100644 --- a/source/gui/meshpainter.h +++ b/source/gui/meshpainter.h @@ -14,6 +14,11 @@ #ifndef _MESHPAINTER_H_ #define _MESHPAINTER_H_ +// OpenGL was deprecated in macOS 10.14. Silencing warnings for now. +#ifdef __APPLE__ +# define GL_SILENCE_DEPRECATION +#endif + #include "painter.h" namespace Manta { diff --git a/source/gui/painter.h b/source/gui/painter.h index e6de9669..be390afa 100644 --- a/source/gui/painter.h +++ b/source/gui/painter.h @@ -14,6 +14,11 @@ #ifndef _PAINTER_H_ #define _PAINTER_H_ +// OpenGL was deprecated in macOS 10.14. Silencing warnings for now. +#ifdef __APPLE__ +# define GL_SILENCE_DEPRECATION +#endif + #include #include #include diff --git a/source/gui/particlepainter.h b/source/gui/particlepainter.h index d568855f..88f686a8 100644 --- a/source/gui/particlepainter.h +++ b/source/gui/particlepainter.h @@ -14,6 +14,11 @@ #ifndef _PARTICLEPAINTER_H_ #define _PARTICLEPAINTER_H_ +// OpenGL was deprecated in macOS 10.14. Silencing warnings for now. +#ifdef __APPLE__ +# define GL_SILENCE_DEPRECATION +#endif + #include "painter.h" #include "particle.h" diff --git a/source/levelset.cpp b/source/levelset.cpp index cc04a4bb..a2ad9b75 100644 --- a/source/levelset.cpp +++ b/source/levelset.cpp @@ -111,10 +111,11 @@ KERNEL(idx) void KnJoin(Grid& a, const Grid& b) { void LevelsetGrid::join(const LevelsetGrid& o) { KnJoin(*this, o); } //! subtract b, note does not preserve SDF! -KERNEL(idx) void KnSubtract(Grid& a, const Grid& b) { +KERNEL(idx) void KnSubtract(Grid& a, const Grid& b, const FlagGrid* flags, int subtractType) { + if(flags && ((*flags)(idx) & subtractType) == 0) return; if(b[idx]<0.) a[idx] = b[idx] * -1.; } -void LevelsetGrid::subtract(const LevelsetGrid& o) { KnSubtract(*this, o); } +void LevelsetGrid::subtract(const LevelsetGrid& o, const FlagGrid* flags, const int subtractType) { KnSubtract(*this, o, flags, subtractType); } //! re-init levelset and extrapolate velocities (in & out) // note - uses flags to identify border (could also be done based on ls values) @@ -236,15 +237,16 @@ void LevelsetGrid::initFromFlags(const FlagGrid& flags, bool ignoreWalls) { } } -void LevelsetGrid::fillHoles(int maxsize) { - Real cur, i1, i2, j1, j2, k1, k2; +void LevelsetGrid::fillHoles(int maxDepth, int boundaryWidth) { + Real curVal, i1, i2, j1, j2, k1, k2; Vec3i c, cTmp; - std::stack undo; - std::stack todo; + std::stack undoPos; + std::stack undoVal; + std::stack todoPos; - FOR_IJK_BND(*this, 1) { + FOR_IJK_BND(*this, boundaryWidth) { - cur = mData[index(i,j,k)]; + curVal = mData[index(i,j,k)]; i1 = mData[index(i-1,j,k)]; i2 = mData[index(i+1,j,k)]; j1 = mData[index(i,j-1,k)]; @@ -253,55 +255,72 @@ void LevelsetGrid::fillHoles(int maxsize) { k2 = mData[index(i,j,k+1)]; /* Skip cells inside and cells outside with no inside neighbours early */ - if (cur < 0.) continue; - if (cur > 0. && i1 > 0. && i2 > 0. && j1 > 0. && j2 > 0. && k1 > 0. && k2 > 0.) continue; - - /* Current cell is outside and has inside neighbour(s) */ - undo.push(Vec3i(i,j,k)); - todo.push(Vec3i(i,j,k)); + if (curVal < 0.) continue; + if (curVal > 0. && i1 > 0. && i2 > 0. && j1 > 0. && j2 > 0. && k1 > 0. && k2 > 0.) continue; /* Cell at c is positive (outside) and has at least one negative (inside) neighbour cell */ c = Vec3i(i,j,k); - /* Enforce negative cell - if search depth gets exceeded this will be reverted to +0.5 */ - mData[index(c.x, c.y, c.z)] = -0.5; + /* Current cell is outside and has inside neighbour(s) */ + undoPos.push(c); + undoVal.push(curVal); + todoPos.push(c); - while(!todo.empty()) { + /* Enforce negative cell - if search depth gets exceeded this will be reverted to the original value */ + mData[index(c.x, c.y, c.z)] = -0.5; - todo.pop(); + while(!todoPos.empty()) { + todoPos.pop(); - /* Add neighbouring positive (inside) cells to stacks */ - if (c.x > 0 && mData[index(c.x-1, c.y, c.z)] > 0.) { cTmp = Vec3i(c.x-1, c.y, c.z); undo.push(cTmp); todo.push(cTmp); mData[index(cTmp)] = -0.5;} - if (c.y > 0 && mData[index(c.x, c.y-1, c.z)] > 0.) { cTmp = Vec3i(c.x, c.y-1, c.z); undo.push(cTmp); todo.push(cTmp); mData[index(cTmp)] = -0.5; } - if (c.z > 0 && mData[index(c.x, c.y, c.z-1)] > 0.) { cTmp = Vec3i(c.x, c.y, c.z-1); undo.push(cTmp); todo.push(cTmp); mData[index(cTmp)] = -0.5; } - if (c.x < (*this).getSizeX()-1 && mData[index(c.x+1, c.y, c.z)] > 0.) { cTmp = Vec3i(c.x+1, c.y, c.z); undo.push(cTmp); todo.push(cTmp); mData[index(cTmp)] = -0.5; } - if (c.y < (*this).getSizeY()-1 && mData[index(c.x, c.y+1, c.z)] > 0.) { cTmp = Vec3i(c.x, c.y+1, c.z); undo.push(cTmp); todo.push(cTmp); mData[index(cTmp)] = -0.5; } - if (c.z < (*this).getSizeZ()-1 && mData[index(c.x, c.y, c.z+1)] > 0.) { cTmp = Vec3i(c.x, c.y, c.z+1); undo.push(cTmp); todo.push(cTmp); mData[index(cTmp)] = -0.5; } + /* Add neighbouring positive (inside) cells to stacks and set negavtive cell value */ + if (c.x > 0 && mData[index(c.x-1, c.y, c.z)] > 0.) { + cTmp = Vec3i(c.x-1, c.y, c.z); undoPos.push(cTmp); undoVal.push(mData[index(cTmp)]); todoPos.push(cTmp); mData[index(cTmp)] = -0.5; + } + if (c.y > 0 && mData[index(c.x, c.y-1, c.z)] > 0.) { + cTmp = Vec3i(c.x, c.y-1, c.z); undoPos.push(cTmp); undoVal.push(mData[index(cTmp)]); todoPos.push(cTmp); mData[index(cTmp)] = -0.5; + } + if (c.z > 0 && mData[index(c.x, c.y, c.z-1)] > 0.) { + cTmp = Vec3i(c.x, c.y, c.z-1); undoPos.push(cTmp); undoVal.push(mData[index(cTmp)]); todoPos.push(cTmp); mData[index(cTmp)] = -0.5; + } + if (c.x < (*this).getSizeX()-1 && mData[index(c.x+1, c.y, c.z)] > 0.) { + cTmp = Vec3i(c.x+1, c.y, c.z); undoPos.push(cTmp); undoVal.push(mData[index(cTmp)]); todoPos.push(cTmp); mData[index(cTmp)] = -0.5; + } + if (c.y < (*this).getSizeY()-1 && mData[index(c.x, c.y+1, c.z)] > 0.) { + cTmp = Vec3i(c.x, c.y+1, c.z); undoPos.push(cTmp); undoVal.push(mData[index(cTmp)]); todoPos.push(cTmp); mData[index(cTmp)] = -0.5; + } + if (c.z < (*this).getSizeZ()-1 && mData[index(c.x, c.y, c.z+1)] > 0.) { + cTmp = Vec3i(c.x, c.y, c.z+1); undoPos.push(cTmp); undoVal.push(mData[index(cTmp)]); todoPos.push(cTmp); mData[index(cTmp)] = -0.5; + } /* Restore original value in cells if undo needed ie once cell undo count exceeds given limit */ - if (undo.size() > maxsize) { + if (undoPos.size() > maxDepth) { /* Clear todo stack */ - while (!todo.empty()) { - todo.pop(); + while (!todoPos.empty()) { + todoPos.pop(); } /* Clear undo stack and revert value */ - while (!undo.empty()) { - c = undo.top(); - undo.pop(); - mData[index(c.x, c.y, c.z)] = 0.5; + while (!undoPos.empty()) { + c = undoPos.top(); + curVal = undoVal.top(); + undoPos.pop(); + undoVal.pop(); + mData[index(c.x, c.y, c.z)] = curVal; } break; } /* Ensure that undo stack is cleared at the end if no more items in todo stack left */ - if (todo.empty()) { - while (!undo.empty()) { - undo.pop(); + if (todoPos.empty()) { + while (!undoPos.empty()) { + undoPos.pop(); + } + while (!undoVal.empty()) { + undoVal.pop(); } } /* Pop value for next while iteration */ else { - c = todo.top(); + c = todoPos.top(); } } } @@ -321,9 +340,9 @@ void LevelsetGrid::createMesh(Mesh& mesh) { Grid edgeVY(mParent); Grid edgeVZ(mParent); - for(int i=0; i { //! union with another levelset PYTHON() void join(const LevelsetGrid& o); - PYTHON() void subtract(const LevelsetGrid& o); + PYTHON() void subtract(const LevelsetGrid& o, const FlagGrid* flags=NULL, const int subtractType=0); //! initialize levelset from flags (+/- 0.5 heaviside) PYTHON() void initFromFlags(const FlagGrid& flags, bool ignoreWalls=false); //! fill holes (pos cells enclosed by neg ones) up to given size with -0.5 (ie not preserving sdf) - PYTHON() void fillHoles(int maxsize=10); + PYTHON() void fillHoles(int maxDepth=10, int boundaryWidth=1); static Real invalidTimeValue(); }; diff --git a/source/mesh.cpp b/source/mesh.cpp index 088046c1..36b87fb8 100644 --- a/source/mesh.cpp +++ b/source/mesh.cpp @@ -20,6 +20,7 @@ #include "kernel.h" #include "shapes.h" #include "noisefield.h" +//#include "grid.h" #include #include @@ -323,6 +324,64 @@ void Mesh::offset(Vec3 o) { mNodes[i].pos += o; } +void Mesh::rotate(Vec3 thetas) { + // rotation thetas are in radians (e.g. pi is equal to 180 degrees) + auto rotate = [&](Real theta, unsigned int first_axis, unsigned int second_axis) + { + if (theta == 0.0f) + return; + + Real sin_t = sin(theta); + Real cos_t = cos(theta); + + Real sin_sign = first_axis == 0u && second_axis == 2u ? -1.0f : 1.0f; + sin_t *= sin_sign; + + size_t length = mNodes.size(); + for (size_t n = 0; n < length; ++n) + { + Vec3& node = mNodes[n].pos; + Real first_axis_val = node[first_axis]; + Real second_axis_val = node[second_axis]; + node[first_axis] = first_axis_val * cos_t - second_axis_val * sin_t; + node[second_axis] = second_axis_val * cos_t + first_axis_val * sin_t; + } + }; + + // rotate x + rotate(thetas[0], 1u, 2u); + // rotate y + rotate(thetas[1], 0u, 2u); + // rotate z + rotate(thetas[2], 0u, 1u); +} + +void Mesh::computeVelocity(Mesh& oldMesh, MACGrid& vel) { + // Early return if sizes do not match + if(oldMesh.mNodes.size() != mNodes.size()) + return; + + // temp grid + Grid veloMeanCounter(getParent()); + veloMeanCounter.setConst(0.0f); + + bool bIs2D = getParent()->is2D(); + + // calculate velocities from previous to current frame (per vertex) + for (size_t i=0; i 0.5f)) + continue; + + Vec3 velo = mNodes[i].pos - oldMesh.mNodes[i].pos; + vel.setInterpolated(mNodes[i].pos, velo, &(veloMeanCounter[0])); + } + + // discretize the vertex velocities by averaging them on the grid + vel.safeDivide(veloMeanCounter); +} + void Mesh::removeTri(int tri) { // delete triangles by overwriting them with elements from the end of the array. if(tri!=(int)mTris.size()-1) { @@ -761,10 +820,10 @@ void ApplyMeshToGrid (Grid* grid, Grid& sdf, T value, FlagGrid* respect } } -void Mesh::applyMeshToGrid(GridBase* grid, FlagGrid* respectFlags, Real cutoff) { +void Mesh::applyMeshToGrid(GridBase* grid, FlagGrid* respectFlags, Real cutoff, Real meshSigma) { FluidSolver dummy(grid->getSize()); LevelsetGrid mesh_sdf(&dummy, false); - meshSDF(*this, mesh_sdf, 2., cutoff); + meshSDF(*this, mesh_sdf, meshSigma, cutoff); // meshSigma=2 fixed here # if NOPYTHON!=1 if (grid->getType() & GridBase::TypeInt) @@ -784,6 +843,12 @@ void Mesh::computeLevelset(LevelsetGrid& levelset, Real sigma, Real cutoff) { meshSDF( *this, levelset, sigma, cutoff); } +LevelsetGrid Mesh::getLevelset(Real sigma, Real cutoff) { + LevelsetGrid phi(getParent()); + meshSDF(*this, phi, sigma, cutoff); + return phi; +} + void meshSDF(Mesh& mesh, LevelsetGrid& levelset, Real sigma, Real cutoff) { if (cutoff<0) cutoff = 2*sigma; diff --git a/source/mesh.h b/source/mesh.h index 96fecb73..a010552e 100644 --- a/source/mesh.h +++ b/source/mesh.h @@ -21,11 +21,13 @@ #include "manta.h" #include "vectorbase.h" #include +#include "levelset.h" + namespace Manta { // fwd decl class GridBase; -class LevelsetGrid; +//class LevelsetGrid; class FlagGrid; class MACGrid; class Shape; @@ -147,10 +149,14 @@ PYTHON() class Mesh : public PbClass { PYTHON() void advectInGrid(FlagGrid& flags, MACGrid& vel, int integrationMode); PYTHON() void scale(Vec3 s); PYTHON() void offset(Vec3 o); + PYTHON() void rotate(Vec3 thetas); + PYTHON() void computeVelocity(Mesh& oldMesh, MACGrid& vel); PYTHON() void computeLevelset(LevelsetGrid& levelset, Real sigma, Real cutoff=-1.); + PYTHON() LevelsetGrid getLevelset(Real sigma, Real cutoff = -1.); + //! map mesh to grid with sdf - PYTHON() void applyMeshToGrid(GridBase* grid, FlagGrid* respectFlags=0, Real cutoff=-1.); + PYTHON() void applyMeshToGrid(GridBase* grid, FlagGrid* respectFlags=0, Real cutoff=-1., Real meshSigma=2.); //! get data pointer of nodes PYTHON() std::string getNodesDataPointer(); diff --git a/source/particle.cpp b/source/particle.cpp index 406ae22b..4ed37bb1 100644 --- a/source/particle.cpp +++ b/source/particle.cpp @@ -154,10 +154,28 @@ void BasicParticleSystem::writeParticlesText(const string name) const ofs.close(); } +// The writeParticlesNumPyText method serializes the coordinates of the particles in NumPy text format. +// The NumPy text files can be read from a Jupyter notebook to instantiate NumPy arrays. +// The NumPy arrays can be used to visualize the particles in a Jupyter notebook using the K3D library. +void BasicParticleSystem::writeParticlesNumPyText(const string name) const +{ + Vec3 positions; + ofstream ofs(name.c_str()); + + if(!ofs.good()) errMsg("can't open file!"); + + for(IndexInt i=0; i < this->size(); ++i) { + positions = this->getPos(i); + ofs << positions[0] << " " << positions[1] << " " << positions[2] << "\n"; + } + + ofs.close(); +} + void BasicParticleSystem::writeParticlesRawPositionsGz(const string name) const { # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "wb1"); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); if(!gzf) errMsg("can't open file "<size(); ++i) { Vector3D p = toVec3f(this->getPos(i)); @@ -172,7 +190,7 @@ void BasicParticleSystem::writeParticlesRawPositionsGz(const string name) const void BasicParticleSystem::writeParticlesRawVelocityGz(const string name) const { # if NO_ZLIB!=1 - gzFile gzf = gzopen(name.c_str(), "wb1"); + gzFile gzf = (gzFile) safeGzopen(name.c_str(), "wb1"); if (!gzf) errMsg("can't open file "< parts; + parts.push_back(this); + return readObjectsVDB(name, &parts); + } else if(ext == ".raw") // raw = uni for now + return readParticlesUni(name, this ); else errMsg("particle '" + name +"' filetype not supported for loading"); + return 0; } -void BasicParticleSystem::save(const string name) const +int BasicParticleSystem::save(const string name) { if(name.find_last_of('.') == string::npos) errMsg("file '" + name + "' does not have an extension"); string ext = name.substr(name.find_last_of('.')); if(ext == ".txt") this->writeParticlesText(name); + else if(ext == ".nptxt") + this->writeParticlesNumPyText(name); else if(ext == ".uni") - writeParticlesUni(name, this); + return writeParticlesUni(name, this); else if(ext == ".raw") // raw = uni for now - writeParticlesUni(name, this); + return writeParticlesUni(name, this); + else if (ext == ".vdb") { + std::vector parts; + parts.push_back(this); + return writeObjectsVDB(name, &parts); // raw data formats, very basic for simple data transfer to other programs - else if(ext == ".posgz") + } else if(ext == ".posgz") this->writeParticlesRawPositionsGz(name); else if(ext == ".velgz") this->writeParticlesRawVelocityGz(name); else errMsg("particle '" + name +"' filetype not supported for saving"); + return 0; } void BasicParticleSystem::printParts(IndexInt start, IndexInt stop, bool printIndex) @@ -354,31 +384,43 @@ void ParticleDataImpl::initNewValue(IndexInt idx, Vec3 pos) } template -void ParticleDataImpl::load(string name) +int ParticleDataImpl::load(string name) { if(name.find_last_of('.') == string::npos) errMsg("file '" + name + "' does not have an extension"); string ext = name.substr(name.find_last_of('.')); if(ext == ".uni") - readPdataUni(name, this); + return readPdataUni(name, this); + else if (ext == ".vdb") { + std::vector parts; + parts.push_back(this); + return readObjectsVDB(name, &parts); + } else if(ext == ".raw") // raw = uni for now - readPdataUni(name, this); + return readPdataUni(name, this); else errMsg("particle data '" + name +"' filetype not supported for loading"); + return 0; } template -void ParticleDataImpl::save(string name) +int ParticleDataImpl::save(string name) { if(name.find_last_of('.') == string::npos) errMsg("file '" + name + "' does not have an extension"); string ext = name.substr(name.find_last_of('.')); if(ext == ".uni") - writePdataUni(name, this); + return writePdataUni(name, this); + else if (ext == ".vdb") { + std::vector parts; + parts.push_back(this); + return writeObjectsVDB(name, &parts); + } else if(ext == ".raw") // raw = uni for now - writePdataUni(name, this); + return writePdataUni(name, this); else errMsg("particle data '" + name +"' filetype not supported for saving"); + return 0; } // specializations diff --git a/source/particle.h b/source/particle.h index 079f376a..fb88fcda 100644 --- a/source/particle.h +++ b/source/particle.h @@ -151,7 +151,7 @@ PYTHON() template class ParticleSystem : public ParticleBase { PYTHON() void clear(); //! Advect particle in grid velocity field - PYTHON() void advectInGrid(const FlagGrid &flags, const MACGrid &vel, const int integrationMode, const bool deleteInObstacle=true, const bool stopInObstacle=true, const ParticleDataImpl *ptype=NULL, const int exclude=0); + PYTHON() void advectInGrid(const FlagGrid &flags, const MACGrid &vel, const int integrationMode, const bool deleteInObstacle=true, const bool stopInObstacle=true, const bool skipNew=false, const ParticleDataImpl *ptype=NULL, const int exclude=0); //! Project particles outside obstacles PYTHON() void projectOutside(Grid &gradient); @@ -195,11 +195,15 @@ PYTHON() class BasicParticleSystem : public ParticleSystem { PYTHON() BasicParticleSystem(FluidSolver* parent); //! file io - PYTHON() void save(const std::string name) const; - PYTHON() void load(const std::string name); + PYTHON() int save(const std::string name); + PYTHON() int load(const std::string name); //! save to text file void writeParticlesText(const std::string name) const; + + //! save to NumPy text file + void writeParticlesNumPyText(const std::string name) const; + //! other output formats void writeParticlesRawPositionsGz(const std::string name) const; void writeParticlesRawVelocityGz(const std::string name) const; @@ -289,8 +293,9 @@ PYTHON() class ParticleDataBase : public PbClass { virtual void resize(IndexInt size) { assertMsg( false , "Dont use, override..."); return; } virtual void copyValueSlow(IndexInt from, IndexInt to) { assertMsg( false , "Dont use, override..."); return; } - //! set base pointer + //! set / get base pointer to parent particle system void setParticleSys(ParticleBase* set) { mpParticleSys = set; } + ParticleBase* getParticleSys() { return mpParticleSys; } //! debugging inline void checkPartIndex(IndexInt idx) const; @@ -314,6 +319,9 @@ class ParticleDataImpl : public ParticleDataBase { inline T& operator[](const IndexInt idx) { DEBUG_ONLY(checkPartIndex(idx)); return mData[idx]; } inline const T& operator[](const IndexInt idx) const { DEBUG_ONLY(checkPartIndex(idx)); return mData[idx]; } + //! set data + inline void set(const IndexInt idx, T& val) { DEBUG_ONLY(checkPartIndex(idx)); mData[idx] = val; } + //! set all values to 0, note - different from particleSystem::clear! doesnt modify size of array (has to stay in sync with parent system) PYTHON() void clear(); @@ -363,8 +371,8 @@ class ParticleDataImpl : public ParticleDataBase { PYTHON() void printPdata(IndexInt start=-1, IndexInt stop=-1, bool printIndex=false); //! file io - PYTHON() void save(const std::string name); - PYTHON() void load(const std::string name); + PYTHON() int save(const std::string name); + PYTHON() int load(const std::string name); //! get data pointer of particle data PYTHON() std::string getDataPointer(); @@ -441,10 +449,10 @@ void ParticleSystem::transformPositions( Vec3i dimOld, Vec3i dimNew ) KERNEL(pts) returns(std::vector u(size)) template std::vector GridAdvectKernel( std::vector& p, const MACGrid& vel, const FlagGrid& flags, const Real dt, - const bool deleteInObstacle, const bool stopInObstacle, + const bool deleteInObstacle, const bool stopInObstacle, const bool skipNew, const ParticleDataImpl *ptype, const int exclude) { - if ((p[idx].flag & ParticleBase::PDELETE) || (ptype && ((*ptype)[idx] & exclude))) { + if ((p[idx].flag & ParticleBase::PDELETE) || (ptype && ((*ptype)[idx] & exclude)) || (skipNew && (p[idx].flag & ParticleBase::PNEW))) { u[idx] = 0.; return; } // special handling @@ -508,7 +516,7 @@ void KnClampPositions( template void ParticleSystem::advectInGrid( const FlagGrid &flags, const MACGrid &vel, const int integrationMode, - const bool deleteInObstacle, const bool stopInObstacle, + const bool deleteInObstacle, const bool stopInObstacle, const bool skipNew, const ParticleDataImpl *ptype, const int exclude) { // position clamp requires old positions, backup @@ -520,7 +528,7 @@ void ParticleSystem::advectInGrid( } // update positions - GridAdvectKernel kernel(mData, vel, flags, getParent()->getDt(), deleteInObstacle, stopInObstacle, ptype, exclude); + GridAdvectKernel kernel(mData, vel, flags, getParent()->getDt(), deleteInObstacle, stopInObstacle, skipNew, ptype, exclude); integratePointSet(kernel, integrationMode); if(!deleteInObstacle) { @@ -617,13 +625,13 @@ void ParticleSystem::compress() { //! insert buffered positions as new particles, update additional particle data template void ParticleSystem::insertBufferedParticles() { + // clear new flag everywhere + for(IndexInt i=0; i<(IndexInt)mData.size(); ++i) mData[i].flag &= ~PNEW; + if(mNewBufferPos.size()==0) return; IndexInt newCnt = mData.size(); resizeAll(newCnt + mNewBufferPos.size()); - // clear new flag everywhere - for(IndexInt i=0; i<(IndexInt)mData.size(); ++i) mData[i].flag &= ~PNEW; - for(IndexInt i=0; i<(IndexInt)mNewBufferPos.size(); ++i) { int flag = (mNewBufferFlag.size() > 0) ? mNewBufferFlag[i] : 0; // note, other fields are not initialized here... diff --git a/source/plugin/extforces.cpp b/source/plugin/extforces.cpp index e733edf0..731a8ceb 100644 --- a/source/plugin/extforces.cpp +++ b/source/plugin/extforces.cpp @@ -57,31 +57,32 @@ KERNEL(bnd=1) void KnApplyForce(const FlagGrid& flags, MACGrid& vel, Vec3 force, vel(i,j,k).z = (additive) ? vel(i,j,k).z+force.z : force.z; } -//! add gravity forces to all fluid cells, automatically adapts to different grid sizes -PYTHON() void addGravity(const FlagGrid& flags, MACGrid& vel, Vec3 gravity, const Grid* exclude=NULL) { - Vec3 f = gravity * flags.getParent()->getDt() / flags.getDx(); +//! add gravity forces to all fluid cells, optionally adapts to different grid sizes automatically +PYTHON() void addGravity(const FlagGrid& flags, MACGrid& vel, Vec3 gravity, const Grid* exclude=NULL, bool scale=true) { + float gridScale = (scale) ? flags.getDx() : 1; + Vec3 f = gravity * flags.getParent()->getDt() / gridScale; KnApplyForce(flags, vel, f, exclude, true); } -//! add gravity forces to all fluid cells , but dont account for changing cell size +//! Deprecated: use addGravity(scale=false) instead PYTHON() void addGravityNoScale(const FlagGrid& flags, MACGrid& vel, const Vec3& gravity, const Grid* exclude=NULL) { - const Vec3 f = gravity * flags.getParent()->getDt(); - KnApplyForce(flags, vel, f, exclude, true); + addGravity(flags, vel, gravity, exclude, false); } //! kernel to add Buoyancy force -KERNEL(bnd=1) void KnAddBuoyancy(const FlagGrid& flags, const Grid& factor, MACGrid& vel, Vec3 strength) { +KERNEL(bnd=1) void KnAddBuoyancy(const FlagGrid& flags, const Grid& factor, MACGrid& vel, Vec3 strength) { if (!flags.isFluid(i,j,k)) return; if (flags.isFluid(i-1,j,k)) vel(i,j,k).x += (0.5 * strength.x) * (factor(i,j,k)+factor(i-1,j,k)); if (flags.isFluid(i,j-1,k)) vel(i,j,k).y += (0.5 * strength.y) * (factor(i,j,k)+factor(i,j-1,k)); if (vel.is3D() && flags.isFluid(i,j,k-1)) - vel(i,j,k).z += (0.5 * strength.z) * (factor(i,j,k)+factor(i,j,k-1)); + vel(i,j,k).z += (0.5 * strength.z) * (factor(i,j,k)+factor(i,j,k-1)); } -//! add Buoyancy force based on fctor (e.g. smoke density) -PYTHON() void addBuoyancy(const FlagGrid& flags, const Grid& density, MACGrid& vel, Vec3 gravity, Real coefficient=1.) { - Vec3 f = -gravity * flags.getParent()->getDt() / flags.getParent()->getDx() * coefficient; +//! add Buoyancy force based on factor (e.g. smoke density), optionally adapts to different grid sizes automatically +PYTHON() void addBuoyancy(const FlagGrid& flags, const Grid& density, MACGrid& vel, Vec3 gravity, Real coefficient=1., bool scale=true) { + float gridScale = (scale) ? flags.getDx() : 1; + Vec3 f = -gravity * flags.getParent()->getDt() / gridScale * coefficient; KnAddBuoyancy(flags,density, vel, f); } @@ -249,10 +250,6 @@ KERNEL() void KnSetWallBcsFrac(const FlagGrid& flags, const MACGrid& vel, MACGri normalize(dphi); Vec3 velMAC = vel.getAtMACX(i,j,k); velTarget(i,j,k).x = velMAC.x - dot(dphi, velMAC) * dphi.x; - if (obvel) { // TODO (sebbas): TBC - Vec3 obvelMAC = (*obvel).getAtMACX(i,j,k); - velTarget(i,j,k).x += dot(dphi, obvelMAC) * dphi.x; - } } if( curObs | flags.isObstacle(i,j-1,k) ) { @@ -276,10 +273,6 @@ KERNEL() void KnSetWallBcsFrac(const FlagGrid& flags, const MACGrid& vel, MACGri normalize(dphi); Vec3 velMAC = vel.getAtMACY(i,j,k); velTarget(i,j,k).y = velMAC.y - dot(dphi, velMAC) * dphi.y; - if (obvel) { // TODO (sebbas): TBC - Vec3 obvelMAC = (*obvel).getAtMACY(i,j,k); - velTarget(i,j,k).y += dot(dphi, obvelMAC) * dphi.y; - } } if( phiObs->is3D() && (curObs | flags.isObstacle(i,j,k-1)) ) { @@ -304,10 +297,6 @@ KERNEL() void KnSetWallBcsFrac(const FlagGrid& flags, const MACGrid& vel, MACGri normalize(dphi); Vec3 velMAC = vel.getAtMACZ(i,j,k); velTarget(i,j,k).z = velMAC.z - dot(dphi, velMAC) * dphi.z; - if (obvel) { // TODO (sebbas): TBC - Vec3 obvelMAC = (*obvel).getAtMACZ(i,j,k); - velTarget(i,j,k).z += dot(dphi, obvelMAC) * dphi.z; - } } } // not at boundary @@ -360,22 +349,23 @@ PYTHON() void setInitialVelocity(const FlagGrid& flags, MACGrid& vel, const Grid } //! Kernel: gradient norm operator -KERNEL(bnd=1) void KnConfForce(Grid& force, const Grid& grid, const Grid& curl, Real str) { +KERNEL(bnd=1) void KnConfForce(Grid& force, const Grid& grid, const Grid& curl, Real str, const Grid* strGrid) { Vec3 grad = 0.5 * Vec3( grid(i+1,j,k)-grid(i-1,j,k), grid(i,j+1,k)-grid(i,j-1,k), 0.); if(grid.is3D()) grad[2]= 0.5*( grid(i,j,k+1)-grid(i,j,k-1) ); normalize(grad); + if (strGrid) str += (*strGrid)(i,j,k); force(i,j,k) = str * cross(grad, curl(i,j,k)); } -PYTHON() void vorticityConfinement(MACGrid& vel, const FlagGrid& flags, Real strength) { +PYTHON() void vorticityConfinement(MACGrid& vel, const FlagGrid& flags, Real strength=0, const Grid* strengthCell=NULL) { Grid velCenter(flags.getParent()), curl(flags.getParent()), force(flags.getParent()); Grid norm(flags.getParent()); GetCentered(velCenter, vel); CurlOp(velCenter, curl); GridNorm(norm, curl); - KnConfForce(force, norm, curl, strength); + KnConfForce(force, norm, curl, strength, strengthCell); KnApplyForceField(flags, vel, force, NULL, true, false); } @@ -387,4 +377,47 @@ PYTHON() void setForceField(const FlagGrid& flags, MACGrid& vel, const Grid& density, Grid* heat, + Grid* red, Grid* green, Grid* blue, int speed, bool logFalloff, float dydx, float fac) { + + bool curFluid = flags.isFluid(i,j,k); + if (!curFluid) return; + + if (logFalloff) { + density(i,j,k) *= fac; + if (heat) { + (*heat)(i,j,k) *= fac; + } + if (red) { + (*red)(i,j,k) *= fac; + (*green)(i,j,k) *= fac; + (*blue)(i,j,k) *= fac; + } + } + else { // linear falloff + float d = density(i,j,k); + density(i,j,k) -= dydx; + if (density(i,j,k) < 0.0f) + density(i,j,k) = 0.0f; + if (heat) { + if (fabs((*heat)(i,j,k)) < dydx) (*heat)(i,j,k) = 0.0f; + else if ((*heat)(i,j,k) > 0.0f) (*heat)(i,j,k) -= dydx; + else if ((*heat)(i,j,k) < 0.0f) (*heat)(i,j,k) += dydx; + } + if (red && notZero(d) ) { + (*red)(i,j,k) *= (density(i,j,k)/d); + (*green)(i,j,k) *= (density(i,j,k)/d); + (*blue)(i,j,k) *= (density(i,j,k)/d); + } + } +} + +PYTHON() void dissolveSmoke(const FlagGrid& flags, Grid& density, Grid* heat=NULL, + Grid* red=NULL, Grid* green=NULL, Grid* blue=NULL, int speed=5, bool logFalloff=true) +{ + float dydx = 1.0f / (float)speed; // max density/speed = dydx + float fac = 1.0f - dydx; + KnDissolveSmoke(flags, density, heat, red, green, blue, speed, logFalloff, dydx, fac); +} + } // namespace diff --git a/source/plugin/flip.cpp b/source/plugin/flip.cpp index 949f7cef..0c9c2415 100644 --- a/source/plugin/flip.cpp +++ b/source/plugin/flip.cpp @@ -13,6 +13,7 @@ ******************************************************************************/ #include "particle.h" +#include "general.h" #include "grid.h" #include "commonkernels.h" #include "randomstream.h" @@ -462,7 +463,6 @@ void correctLevelset(LevelsetGrid& phi, const Grid& pAcc, const Grid const Real radius, const Real t_low, const Real t_high) { if (rAcc(i, j, k) <= VECTOR_EPSILON) return; //outside nothing happens - Real x = pAcc(i, j, k).x; // create jacobian of pAcc via central differences Matrix3x3f jacobian = Matrix3x3f( @@ -487,7 +487,7 @@ void correctLevelset(LevelsetGrid& phi, const Grid& pAcc, const Grid Real t = (t_high - maxEV) / (t_high - t_low); correction = t*t*t - 3 * t*t + 3 * t; } - correction = (correction < 0) ? 0 : correction; // enforce correction factor to [0,1] (not explicitly in paper) + correction = clamp(correction, Real(0), Real(1)); // enforce correction factor to [0,1] (not explicitly in paper) const Vec3 gridPos = Vec3(i, j, k) + Vec3(0.5); // shifted by half cell const Real correctedPhi = fabs(norm(gridPos - pAcc(i, j, k))) - rAcc(i, j, k) * correction; diff --git a/source/plugin/fluidguiding.cpp b/source/plugin/fluidguiding.cpp index 127548ac..96194083 100644 --- a/source/plugin/fluidguiding.cpp +++ b/source/plugin/fluidguiding.cpp @@ -173,7 +173,6 @@ PYTHON() void getSpiralVelocity(const FlagGrid &flags, MACGrid &vel, Real streng if (with3D) nz = flags.getSizeZ(); Real midX = 0.5*(Real)(nx - 1); Real midY = 0.5*(Real)(ny - 1); - Real midZ = 0.5*(Real)(nz - 1); for (int i = 0; i < nx; i++) { for (int j = 0; j < ny; j++) { for (int k = 0; k < nz; k++) { @@ -276,15 +275,16 @@ void prox_f(MACGrid& v, const FlagGrid &flags, const MACGrid& Q, const MACGrid& // re-uses main pressure solve from pressure.cpp void solvePressure( MACGrid& vel, Grid& pressure, const FlagGrid& flags, Real cgAccuracy = 1e-3, - const Grid* phi = 0, - const Grid* perCellCorr = 0, - const MACGrid* fractions = 0, - Real gfClamp = 1e-04, - Real cgMaxIterFac = 1.5, - bool precondition = true, + const Grid* phi = 0, + const Grid* perCellCorr = 0, + const MACGrid* fractions = 0, + const MACGrid* obvel = 0, + Real gfClamp = 1e-04, + Real cgMaxIterFac = 1.5, + bool precondition = true, int preconditioner = 1, bool enforceCompatibility = false, - bool useL2Norm = false, + bool useL2Norm = false, bool zeroPressureFixing = false, const Grid *curv = NULL, const Real surfTens = 0.0, @@ -296,7 +296,7 @@ PYTHON() void PD_fluid_guiding(MACGrid& vel, MACGrid& velT, Real theta = 1.0, Real tau = 1.0, Real sigma = 1.0, Real epsRel = 1e-3, Real epsAbs = 1e-3, int maxIters = 200, // duplicated for pressure solve - Grid* phi = 0, Grid* perCellCorr = 0, MACGrid* fractions = 0, Real gfClamp = 1e-04, Real cgMaxIterFac = 1.5, Real cgAccuracy = 1e-3, + Grid* phi = 0, Grid* perCellCorr = 0, MACGrid* fractions = 0, MACGrid* obvel = 0, Real gfClamp = 1e-04, Real cgMaxIterFac = 1.5, Real cgAccuracy = 1e-3, int preconditioner = 1, bool zeroPressureFixing = false, const Grid *curv = NULL, const Real surfTens = 0.) { FluidSolver* parent = vel.getParent(); @@ -331,7 +331,7 @@ PYTHON() void PD_fluid_guiding(MACGrid& vel, MACGrid& velT, z.addScaled(x, -tau); Real cgAccuracyAdaptive = cgAccuracy; - solvePressure (z, pressure, flags, cgAccuracyAdaptive, phi, perCellCorr, fractions, gfClamp, + solvePressure (z, pressure, flags, cgAccuracyAdaptive, phi, perCellCorr, fractions, obvel, gfClamp, cgMaxIterFac, true, preconditioner, false, false, zeroPressureFixing, curv, surfTens ); // y-update diff --git a/source/plugin/initplugins.cpp b/source/plugin/initplugins.cpp index 22c75146..b642c984 100644 --- a/source/plugin/initplugins.cpp +++ b/source/plugin/initplugins.cpp @@ -107,19 +107,24 @@ PYTHON() LevelsetGrid obstacleLevelset(const FlagGrid& flags) { // blender init functions KERNEL() -void KnApplyEmission(const FlagGrid& flags, Grid& density, const Grid& emission, bool isAbsolute) +void KnApplyEmission(const FlagGrid& flags, Grid& target, const Grid& source, const Grid* emissionTexture, bool isAbsolute, int type) { - if (!flags.isFluid(i,j,k) || emission(i,j,k) == 0.) return; + // if type is given, only apply emission when celltype matches type from flaggrid + // and if emission texture is given, only apply emission when some emission is present at cell (important for emit from particles) + bool isInflow = (type & FlagGrid::TypeInflow && flags.isInflow(i,j,k)); + bool isOutflow = (type & FlagGrid::TypeOutflow && flags.isOutflow(i,j,k)); + if ( (type && !isInflow && !isOutflow) && (emissionTexture && !(*emissionTexture)(i,j,k)) ) return; + if (isAbsolute) - density(i,j,k) = emission(i,j,k); + target(i,j,k) = source(i,j,k); else - density(i,j,k) += emission(i,j,k); + target(i,j,k) += source(i,j,k); } //! Add emission values //isAbsolute: whether to add emission values to existing, or replace -PYTHON() void applyEmission(FlagGrid& flags, Grid& density, Grid& emission, bool isAbsolute) { - KnApplyEmission(flags, density, emission, isAbsolute); +PYTHON() void applyEmission(FlagGrid& flags, Grid& target, Grid& source, Grid* emissionTexture=NULL, bool isAbsolute=true, int type=0) { + KnApplyEmission(flags, target, source, emissionTexture, isAbsolute, type); } // blender init functions for meshes @@ -146,6 +151,37 @@ PYTHON() void densityInflowMesh(const FlagGrid& flags, Grid& density, Mesh KnApplyDensity(flags, density, sdf, value, sigma); } +KERNEL() void KnResetInObstacle(FlagGrid& flags, MACGrid& vel, Grid* density, Grid* heat, + Grid* fuel, Grid* flame, Grid* red, Grid* green, Grid* blue, Real resetValue) +{ + if (!flags.isObstacle(i,j,k)) return; + vel(i,j,k).x = resetValue; + vel(i,j,k).y = resetValue; + vel(i,j,k).z = resetValue; + + if (density) { + (*density)(i,j,k) = resetValue; + } + if (heat) { + (*heat)(i,j,k) = resetValue; + } + if (fuel) { + (*fuel)(i,j,k) = resetValue; + (*flame)(i,j,k) = resetValue; + } + if (red) { + (*red)(i,j,k) = resetValue; + (*green)(i,j,k) = resetValue; + (*blue)(i,j,k) = resetValue; + } +} + +PYTHON() void resetInObstacle(FlagGrid& flags, MACGrid& vel, Grid* density, Grid* heat=NULL, + Grid* fuel=NULL, Grid* flame=NULL, Grid* red=NULL, Grid* green=NULL, Grid* blue=NULL, Real resetValue=0) +{ + KnResetInObstacle(flags, vel, density, heat, fuel, flame, red, green, blue, resetValue); +} + //***************************************************************************** @@ -317,7 +353,7 @@ PYTHON() Vec3 calcCenterOfMass(const Grid& density) -inline static Real calcFraction(Real phi1, Real phi2) +inline static Real calcFraction(Real phi1, Real phi2, Real fracThreshold) { if(phi1>0. && phi2>0.) return 1.; if(phi1<0. && phi2<0.) return 0.; @@ -328,18 +364,18 @@ inline static Real calcFraction(Real phi1, Real phi2) if (denom > -1e-04) return 0.5; Real frac = 1. - phi1/denom; - if(frac<0.01) frac = 0.; // stomp small values , dont mark as fluid + if(frac& phiObs, MACGrid& fractions, const int &boundaryWidth) { +void KnUpdateFractions(const FlagGrid& flags, const Grid& phiObs, MACGrid& fractions, const int &boundaryWidth, const Real fracThreshold) { // walls at domain bounds and inner objects - fractions(i,j,k).x = calcFraction( phiObs(i,j,k) , phiObs(i-1,j,k)); - fractions(i,j,k).y = calcFraction( phiObs(i,j,k) , phiObs(i,j-1,k)); + fractions(i,j,k).x = calcFraction( phiObs(i,j,k) , phiObs(i-1,j,k), fracThreshold); + fractions(i,j,k).y = calcFraction( phiObs(i,j,k) , phiObs(i,j-1,k), fracThreshold); if(phiObs.is3D()) { - fractions(i,j,k).z = calcFraction( phiObs(i,j,k) , phiObs(i,j,k-1)); + fractions(i,j,k).z = calcFraction( phiObs(i,j,k) , phiObs(i,j,k-1), fracThreshold); } // remaining BCs at the domain boundaries @@ -398,13 +434,13 @@ void KnUpdateFractions(const FlagGrid& flags, const Grid& phiObs, MACGrid& } //! update fill fraction values -PYTHON() void updateFractions(const FlagGrid& flags, const Grid& phiObs, MACGrid& fractions, const int &boundaryWidth=0) { +PYTHON() void updateFractions(const FlagGrid& flags, const Grid& phiObs, MACGrid& fractions, const int &boundaryWidth=0, const Real fracThreshold=0.01) { fractions.setConst( Vec3(0.) ); - KnUpdateFractions(flags, phiObs, fractions, boundaryWidth); + KnUpdateFractions(flags, phiObs, fractions, boundaryWidth, fracThreshold); } -KERNEL (bnd=1) -void KnUpdateFlagsObs(FlagGrid& flags, const MACGrid* fractions, const Grid& phiObs, const Grid* phiOut ) { +KERNEL (bnd=boundaryWidth) +void KnUpdateFlagsObs(FlagGrid& flags, const MACGrid* fractions, const Grid& phiObs, const Grid* phiOut, const Grid* phiIn, int boundaryWidth) { bool isObs = false; if(fractions) { @@ -422,17 +458,20 @@ void KnUpdateFlagsObs(FlagGrid& flags, const MACGrid* fractions, const Grid& phiObs, const MACGrid* fractions=NULL, const Grid* phiOut=NULL ) { - KnUpdateFlagsObs(flags, fractions, phiObs, phiOut ); +PYTHON() void setObstacleFlags(FlagGrid& flags, const Grid& phiObs, const MACGrid* fractions=NULL, const Grid* phiOut=NULL, const Grid* phiIn=NULL, int boundaryWidth=1) { + KnUpdateFlagsObs(flags, fractions, phiObs, phiOut, phiIn, boundaryWidth); } diff --git a/source/plugin/numpyconvert.cpp b/source/plugin/numpyconvert.cpp index b8867b7f..c67b5be8 100644 --- a/source/plugin/numpyconvert.cpp +++ b/source/plugin/numpyconvert.cpp @@ -30,13 +30,21 @@ template void copyArrayToGridScalar(const PyArrayContainer source, T& target) { target.setConst(0.0f); - unsigned int uGridSize = target.getSizeX() * target.getSizeY() * target.getSizeZ(); + + unsigned int uSizeX = target.getSizeX(); + unsigned int uSizeY = target.getSizeY(); + unsigned int uSizeZ = target.getSizeZ(); + unsigned int uGridSize = uSizeX * uSizeY * uSizeZ; assertMsg(source.TotalSize == uGridSize, "The size of the numpy array doesn't match the size of the Grid!"); - + assertMsg(source.Dims[0] == uSizeZ && source.Dims[1] == uSizeY && source.Dims[2] == uSizeX, "The dimensions of source numpy array ("+to_string(source.Dims[2])+", "+to_string(source.Dims[1])+", "+to_string(source.Dims[0])+") and target grid ("+to_string(uSizeX)+", "+to_string(uSizeY)+", "+to_string(uSizeZ)+") do not match!"); + NumpyTypes eDataType = source.DataType; switch (eDataType) { + case NumpyTypes::N_INT: + FOR_IDX(target) { target(idx) = (reinterpret_cast(source.pData))[idx]; } + break; case NumpyTypes::N_FLOAT: FOR_IDX(target) { target(idx) = (reinterpret_cast(source.pData))[idx]; } break; @@ -53,12 +61,16 @@ template void copyGridToArrayScalar(const T& source, PyArrayContainer target) { unsigned int uGridsize = source.getSizeX() * source.getSizeY() * source.getSizeZ(); - assertMsg(target.TotalSize == uGridsize, "The size of the numpy array doesn't match the size of the grid!"); - + assertMsg(target.TotalSize == uGridsize, "The size of the numpy array "+to_string(target.TotalSize)+" doesn't match the size of the grid!"); + assertMsg(target.Dims[0] == source.getSizeZ() && target.Dims[1] == source.getSizeY() && target.Dims[2] == source.getSizeX(), "The dimensions of source grid ("+to_string(source.getSizeX())+", "+to_string(source.getSizeY())+", "+to_string(source.getSizeZ())+") and target numpy array ("+to_string(target.Dims[2])+", "+to_string(target.Dims[1])+", "+to_string(target.Dims[0])+") do not match!"); + NumpyTypes eDataType = target.DataType; switch (eDataType) { + case NumpyTypes::N_INT: + FOR_IDX(source) { reinterpret_cast(target.pData)[idx] = source(idx); } + break; case NumpyTypes::N_FLOAT: FOR_IDX(source) { reinterpret_cast(target.pData)[idx] = source(idx); } break; @@ -80,7 +92,8 @@ void copyArrayToGridVector(const PyArrayContainer source, T& target) unsigned int uSizeW = 3u; assertMsg(source.TotalSize == uSizeX * uSizeY * uSizeZ * uSizeW, "The size of the numpy array doesn't match the size of the grid!"); - + assertMsg(source.Dims[0] == uSizeZ && source.Dims[1] == uSizeY && source.Dims[2] == uSizeX, "The dimensions of source numpy array ("+to_string(source.Dims[2])+", "+to_string(source.Dims[1])+", "+to_string(source.Dims[0])+") and target grid ("+to_string(uSizeX)+", "+to_string(uSizeY)+", "+to_string(uSizeZ)+") do not match!"); + NumpyTypes eDataType = source.DataType; switch (eDataType) @@ -106,7 +119,8 @@ void copyGridToArrayVector(const T& source, PyArrayContainer target) unsigned int uSizeW = 3u; assertMsg(target.TotalSize == uSizeX * uSizeY * uSizeZ * uSizeW, "The size of the numpy array doesn't match the size of the grid!"); - + assertMsg(target.Dims[0] == uSizeZ && target.Dims[1] == uSizeY && target.Dims[2] == uSizeX, "The dimensions of source grid ("+to_string(uSizeX)+", "+to_string(uSizeY)+", "+to_string(uSizeZ)+") and target numpy array ("+to_string(target.Dims[2])+", "+to_string(target.Dims[1])+", "+to_string(target.Dims[0])+") do not match!"); + NumpyTypes eDataType = target.DataType; switch (eDataType) @@ -123,6 +137,7 @@ void copyGridToArrayVector(const T& source, PyArrayContainer target) } } + //==================================================================================================== // Python interface //---------------------------------------------------------------------------------------------------- @@ -135,6 +150,14 @@ PYTHON() void copyGridToArrayReal(const Grid& source, PyArrayContainer tar copyGridToArrayScalar>(source, target); } +PYTHON() void copyArrayToGridFlag(const PyArrayContainer source, FlagGrid& target) { + copyArrayToGridScalar(source, target); +} + +PYTHON() void copyGridToArrayFlag(const FlagGrid& source, PyArrayContainer target) { + copyGridToArrayScalar(source, target); +} + PYTHON() void copyArrayToGridLevelset(const PyArrayContainer source, LevelsetGrid& target) { copyArrayToGridScalar(source, target); } diff --git a/source/plugin/pressure.cpp b/source/plugin/pressure.cpp index 44653752..63a8201b 100644 --- a/source/plugin/pressure.cpp +++ b/source/plugin/pressure.cpp @@ -32,7 +32,7 @@ inline static Real surfTensHelper(const IndexInt idx, const int offset, const Gr KERNEL(bnd=1, reduce=+) returns(int cnt=0) returns(double sum=0) void MakeRhs( const FlagGrid& flags, Grid& rhs, const MACGrid& vel, - const Grid* perCellCorr, const MACGrid* fractions, + const Grid* perCellCorr, const MACGrid* fractions, const MACGrid* obvel, // note - all of the following are necessary for surface tension const Grid *phi, const Grid *curv, const Real surfTens, const Real gfClamp) { @@ -50,6 +50,12 @@ void MakeRhs( } else { set = (*fractions)(i,j,k).x * vel(i,j,k).x - (*fractions)(i+1,j,k).x * vel(i+1,j,k).x + (*fractions)(i,j,k).y * vel(i,j,k).y - (*fractions)(i,j+1,k).y * vel(i,j+1,k).y; if(vel.is3D()) set += (*fractions)(i,j,k).z * vel(i,j,k).z - (*fractions)(i,j,k+1).z * vel(i,j,k+1).z; + + // compute divergence from obstacle by using obstacle velocity (optional) + if (obvel) { + set += (1 - (*fractions)(i,j,k).x) * (*obvel)(i,j,k).x - (1 - (*fractions)(i+1,j,k).x) * (*obvel)(i+1,j,k).x + (1 - (*fractions)(i,j,k).y) * (*obvel)(i,j,k).y - (1 - (*fractions)(i,j+1,k).y) * (*obvel)(i,j+1,k).y; + if(obvel->is3D()) set += (1 - (*fractions)(i,j,k).z) * (*obvel)(i,j,k).z - (1 - (*fractions)(i,j,k+1).z) * (*obvel)(i,j,k+1).z; + } } // compute surface tension effect (optional) @@ -274,6 +280,7 @@ PYTHON() void computePressureRhs( const Grid* phi = 0, const Grid* perCellCorr = 0, const MACGrid* fractions = 0, + const MACGrid* obvel = 0, Real gfClamp = 1e-04, Real cgMaxIterFac = 1.5, bool precondition = true, // Deprecated, use preconditioner instead @@ -285,7 +292,7 @@ PYTHON() void computePressureRhs( const Real surfTens = 0. ) { // compute divergence and init right hand side - MakeRhs kernMakeRhs (flags, rhs, vel, perCellCorr, fractions, phi, curv, surfTens, gfClamp ); + MakeRhs kernMakeRhs (flags, rhs, vel, perCellCorr, fractions, obvel, phi, curv, surfTens, gfClamp ); if(enforceCompatibility) rhs += (Real)(-kernMakeRhs.sum / (Real)kernMakeRhs.cnt); @@ -412,6 +419,11 @@ PYTHON() void solvePressureSystem( maxIter = 100; pmg = gMapMG[parent]; + // Release MG from previous step if present (e.g. if previous solve was with MGStatic) + if (pmg && preconditioner == PcMGDynamic) { + releaseMG(parent); + pmg = nullptr; + } if(!pmg) { pmg = new GridMg(pressure.getSize()); gMapMG[parent] = pmg; @@ -470,6 +482,7 @@ PYTHON() void solvePressure( const Grid* phi = 0, const Grid* perCellCorr = 0, const MACGrid* fractions = 0, + const MACGrid* obvel = 0, Real gfClamp = 1e-04, Real cgMaxIterFac = 1.5, bool precondition = true, // Deprecated, use preconditioner instead @@ -485,7 +498,7 @@ PYTHON() void solvePressure( computePressureRhs( rhs, vel, pressure, flags, cgAccuracy, - phi, perCellCorr, fractions, gfClamp, + phi, perCellCorr, fractions, obvel, gfClamp, cgMaxIterFac, precondition, preconditioner, enforceCompatibility, useL2Norm, zeroPressureFixing, curv, surfTens); diff --git a/source/plugin/secondaryparticles.cpp b/source/plugin/secondaryparticles.cpp index 5af71d6a..736b10ea 100644 --- a/source/plugin/secondaryparticles.cpp +++ b/source/plugin/secondaryparticles.cpp @@ -49,7 +49,8 @@ void knFlipComputeSecondaryParticlePotentials( for (IndexInt x = i - radius; x <= i + radius; x++) { for (IndexInt y = j - radius; y <= j + radius; y++) { for (IndexInt z = k - radius; z <= k + radius; z++) { - if ((x == i && y == j && z == k) || !flags.isInBounds(Vec3i(x, y, z)) || (flags(x, y, z) & jtype)) continue; + //ensure that xyz is in bounds: use bnd=1 to ensure that vel.getCentered() always has a neighbor cell + if ((x == i && y == j && z == k) || !flags.isInBounds(Vec3i(x, y, z), 1) || (flags(x, y, z) & jtype)) continue; if (flags(x, y, z) & itype) { countFluid++; @@ -114,7 +115,7 @@ void knFlipSampleSecondaryParticlesMoreCylinders( if (!(flags(i, j, k) & itype)) return; - RandomStream mRand(9832); + static RandomStream mRand(9832); Real radius = 0.25; //diameter=0.5 => sampling with two cylinders in each dimension since cell size=1 for (Real x = i - radius; x <= i + radius; x += 2 * radius) { for (Real y = j - radius; y <= j + radius; y += 2 * radius) { @@ -171,9 +172,9 @@ void knFlipSampleSecondaryParticles( const int n = KE * (k_ta*TA + k_wc*WC) * dt; //number of secondary particles if (n == 0) return; - RandomStream mRand(9832); + static RandomStream mRand(9832); - Vec3 xi = Vec3(i + mRand.getReal(), j + mRand.getReal(), k + mRand.getReal()); //randomized offset uniform in cell + Vec3 xi = Vec3(i, j, k) + mRand.getVec3(); //randomized offset uniform in cell Vec3 vi = v.getInterpolated(xi); Vec3 dir = dt*vi; //direction of movement of current particle Vec3 e1 = getNormalized(Vec3(dir.z, 0, -dir.x)); //perpendicular to dir @@ -202,12 +203,16 @@ void flipSampleSecondaryParticles( const std::string mode, const FlagGrid &flags, const MACGrid &v, BasicParticleSystem &pts_sec, ParticleDataImpl &v_sec, ParticleDataImpl &l_sec, const Real lMin, const Real lMax, const Grid &potTA, const Grid &potWC, const Grid &potKE, const Grid &neighborRatio, - const Real c_s, const Real c_b, const Real k_ta, const Real k_wc, const Real dt, const int itype = FlagGrid::TypeFluid) { + const Real c_s, const Real c_b, const Real k_ta, const Real k_wc, const Real dt=0, const int itype = FlagGrid::TypeFluid) { + + float timestep = dt; + if (dt <= 0) timestep = flags.getParent()->getDt(); + if (mode == "single") { - knFlipSampleSecondaryParticles(flags, v, pts_sec, v_sec, l_sec, lMin, lMax, potTA, potWC, potKE, neighborRatio, c_s, c_b, k_ta, k_wc, dt, itype); + knFlipSampleSecondaryParticles(flags, v, pts_sec, v_sec, l_sec, lMin, lMax, potTA, potWC, potKE, neighborRatio, c_s, c_b, k_ta, k_wc, timestep, itype); } else if (mode == "multiple") { - knFlipSampleSecondaryParticlesMoreCylinders(flags, v, pts_sec, v_sec, l_sec, lMin, lMax, potTA, potWC, potKE, neighborRatio, c_s, c_b, k_ta, k_wc, dt, itype); + knFlipSampleSecondaryParticlesMoreCylinders(flags, v, pts_sec, v_sec, l_sec, lMin, lMax, potTA, potWC, potKE, neighborRatio, c_s, c_b, k_ta, k_wc, timestep, itype); } else { throw std::invalid_argument("Unknown mode: use \"single\" or \"multiple\" instead!"); @@ -219,7 +224,7 @@ void flipSampleSecondaryParticles( // evaluates cubic spline with radius h and distance l in dim dimensions Real cubicSpline(const Real h, const Real l, const int dim) { - const Real h2 = square(h), h3 = h2*h, h4 = h3*h, h5 = h4*h; + const Real h2 = square(h), h3 = h2*h; const Real c[] = { Real(2e0 / (3e0*h)), Real(10e0 / (7e0*M_PI*h2)), Real(1e0 / (M_PI*h3)) }; const Real q = l / h; if (q<1e0) return c[dim - 1] * (1e0 - 1.5*square(q) + 0.75*cubed(q)); @@ -242,9 +247,6 @@ void knFlipUpdateSecondaryParticlesLinear( } Vec3i gridpos = toVec3i(pts_sec[idx].pos); - int i = gridpos.x; - int j = gridpos.y; - int k = gridpos.z; //spray particle if (neighborRatio(gridpos) < c_s) { @@ -423,15 +425,20 @@ PYTHON() void flipUpdateSecondaryParticles( const std::string mode, BasicParticleSystem &pts_sec, ParticleDataImpl &v_sec, ParticleDataImpl &l_sec, const ParticleDataImpl &f_sec, FlagGrid &flags, const MACGrid &v, const Grid &neighborRatio, - const int radius, const Vec3 gravity, const Real k_b, const Real k_d, - const Real c_s, const Real c_b, const Real dt, const int exclude = ParticleBase::PTRACER, const int antitunneling=0, const int itype = FlagGrid::TypeFluid) { + const int radius, const Vec3 gravity, const Real k_b, const Real k_d, + const Real c_s, const Real c_b, const Real dt=0, bool scale=true, const int exclude = ParticleBase::PTRACER, const int antitunneling=0, const int itype = FlagGrid::TypeFluid) { + + float gridScale = (scale) ? flags.getParent()->getDx() : 1; + Vec3 g = gravity / gridScale; + + float timestep = dt; + if (dt <= 0) timestep = flags.getParent()->getDt(); - Vec3 g = gravity / flags.getDx(); if (mode == "linear") { - knFlipUpdateSecondaryParticlesLinear(pts_sec, v_sec, l_sec, f_sec, flags, v, neighborRatio, g, k_b, k_d, c_s, c_b, dt, exclude, antitunneling); + knFlipUpdateSecondaryParticlesLinear(pts_sec, v_sec, l_sec, f_sec, flags, v, neighborRatio, g, k_b, k_d, c_s, c_b, timestep, exclude, antitunneling); } else if (mode == "cubic") { - knFlipUpdateSecondaryParticlesCubic(pts_sec, v_sec, l_sec, f_sec, flags, v, neighborRatio, radius, g, k_b, k_d, c_s, c_b, dt, exclude, antitunneling, itype); + knFlipUpdateSecondaryParticlesCubic(pts_sec, v_sec, l_sec, f_sec, flags, v, neighborRatio, radius, g, k_b, k_d, c_s, c_b, timestep, exclude, antitunneling, itype); } else { throw std::invalid_argument("Unknown mode: use \"linear\" or \"cubic\" instead!"); @@ -456,7 +463,7 @@ void knFlipDeleteParticlesInObstacle( } int gridIndex = flags.index(xidx); //remove particles that penetrate obstacles - if (flags[gridIndex] == FlagGrid::TypeObstacle || flags[gridIndex] == FlagGrid::TypeOutflow) { + if (flags.isObstacle(gridIndex) || flags.isOutflow(gridIndex)) { pts.kill(idx); } } diff --git a/source/plugin/sndparticles.cpp b/source/plugin/sndparticles.cpp deleted file mode 100644 index 5c73a6d4..00000000 --- a/source/plugin/sndparticles.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/****************************************************************************** - * - * MantaFlow fluid solver framework - * Copyright 2016 Sebastian Barschkis, Nils Thuerey - * - * This program is free software, distributed under the terms of the - * GNU General Public License (GPL) - * http://www.gnu.org/licenses - * - * Secondary particle modeling plugin - * - ******************************************************************************/ - -#include "grid.h" -#include "levelset.h" -#include "particle.h" - -using namespace std; -namespace Manta { - -//! helper to calculate particle radius factor to cover the diagonal of a cell in 2d/3d -inline Real calculateRadiusFactor(Grid& grid, Real factor) { - return (grid.is3D() ? sqrt(3.) : sqrt(2.) ) * (factor+.01); -} - -//! adjust number of snd particles. optional parameters for life (0 = infinite life allowed) -PYTHON() void adjustSndParts(BasicParticleSystem& parts, FlagGrid& flags, LevelsetGrid& phi, ParticleDataImpl& partVel, - ParticleDataImpl* partLife=NULL, int maxDroplet=16, int maxBubble=16, int maxFloater=16, int maxTracer=16) -{ - const Real dt = flags.getParent()->getDt(); - Real radiusFactor = 1.; - const Real DROP_THRESH = calculateRadiusFactor(phi, radiusFactor); // cell diagonal - const Real FLOAT_THRESH = calculateRadiusFactor(phi, radiusFactor); - - Grid numDroplet( flags.getParent() ); - Grid numBubble( flags.getParent() ); - Grid numFloater( flags.getParent() ); - Grid numTracer( flags.getParent() ); - - // Delete invalid particles. Then set new position to those that survived - for (IndexInt idx=0; idx<(int)parts.size(); idx++) - { - if (!parts.isActive(idx)) continue; - - Vec3 p1 = parts.getPos(idx); - Vec3i p1i = toVec3i(p1); - - // Try to save float / tracer particle by pushing it into the valid region - Real phiv = phi.getInterpolated( parts.getPos(idx) ); - if (( parts.getStatus(idx) & ParticleBase::PFOAM && (phiv > FLOAT_THRESH || phiv < -FLOAT_THRESH)) || - ( parts.getStatus(idx) & ParticleBase::PTRACER && phiv > 0. )) - { - Vec3 grad = getGradient( phi, p1i.x,p1i.y,p1i.z ); - if ( normalize(grad) > VECTOR_EPSILON ) - { - int direction = (phiv > 0.) ? -1 : 1; - parts.setPos(idx, parts.getPos(idx) + direction * grad ); - - // Update values for new position - p1 = parts.getPos(idx); - p1i = toVec3i(p1); - phiv = phi.getInterpolated( parts.getPos(idx) ); - } - } - - // Next particle position (euler step) - Vec3 p2 = parts.getPos(idx) + partVel[idx] * dt; - Vec3i p2i = toVec3i(p2); - - // Kill particles depending on type. Especially those that were not converted to other particle type - if ( parts.isSpray(idx) && phiv < -DROP_THRESH ) { parts.kill(idx); continue; } - if ( parts.isBubble(idx) && phiv > 0. ) { parts.kill(idx); continue; } - if ( parts.isFoam(idx) && (phiv > FLOAT_THRESH || phiv < -FLOAT_THRESH)) { parts.kill(idx); continue; } - if ( parts.isTracer(idx) && phiv > 0. ) { parts.kill(idx); continue; } - - // Kill particles depending on current age - if ( partLife && (*partLife)[idx] <= 0.) { parts.kill(idx); continue; } - - // Kill particle if current or next position is invalid, ie outside or in obstacle - if (!flags.isInBounds(p1i) || flags.isObstacle(p1i) || !flags.isInBounds(p2i) || flags.isObstacle(p2i)) { - parts.kill(idx); - continue; - } - - // Kill excess particles - if ( parts.isSpray(idx) && numDroplet(p1i) > maxDroplet ) { parts.kill(idx); continue; } else { numDroplet(p1i) += 1; } - if ( parts.isBubble(idx) && numBubble(p1i) > maxBubble ) { parts.kill(idx); continue; } else { numBubble(p1i) += 1; } - if ( parts.isFoam(idx) && numFloater(p1i) > maxFloater ) { parts.kill(idx); continue; } else { numFloater(p1i) += 1; } - if ( parts.isTracer(idx) && numTracer(p1i) > maxTracer ) { parts.kill(idx); continue; } else { numTracer(p1i) += 1; } - } - parts.doCompress(); -} - -//! update velocities. set new particle position. optional: convert between particle types, partLife update -PYTHON() void updateSndParts(LevelsetGrid& phi, FlagGrid& flags, MACGrid& vel, Vec3 gravity, BasicParticleSystem& parts, - ParticleDataImpl& partVel, ParticleDataImpl* partLife=NULL, Real riseBubble=0.5, - Real lifeDroplet=30.0, Real lifeBubble=30.0, Real lifeFloater=30.0, Real lifeTracer=30.0) -{ - RandomStream mRand(9832); - const Vec3 grav = gravity * flags.getParent()->getDt() / flags.getParent()->getDx(); - const Real dt = flags.getParent()->getDt(); - const Real framelength = flags.getParent()->getFrameLength(); - Real radiusFactor = 1.; - const Real DROP_THRESH = 0.5f * calculateRadiusFactor(phi, radiusFactor); // half cell diagonal - - for (IndexInt idx=0; idx<(int)parts.size(); idx++) - { - if (!parts.isActive(idx)) continue; - - // Update all already existing particles - if ((parts.getStatus(idx) & ParticleBase::PNEW)==0) { - - // Update particle velocity - if (parts.isSpray(idx)) { - partVel[idx] += grav; - } - else if (parts.isBubble(idx)) { - Vec3 buoyancy = (-1) * grav * riseBubble; - Vec3 randomVel = vel.getInterpolated( parts[idx].pos ) * mRand.getFloat(0.25, 0.5); - partVel[idx] += buoyancy + randomVel; - } - else if (parts.isFoam(idx) || parts.isTracer(idx)) { - partVel[idx] = vel.getInterpolated( parts[idx].pos ); - } - - // Increase particle life - if (partLife && (*partLife)[idx] > 0.f) { - (*partLife)[idx] -= dt / framelength; - } - } - - // Update all new particles - if (parts.getStatus(idx) & ParticleBase::PNEW) { - - // Init new particles (any type) with flow velocity - partVel[idx] = vel.getInterpolated( parts[idx].pos ); - - // Init particle life - if (partLife) { - if (parts.isSpray(idx)) (*partLife)[idx] = lifeDroplet; - if (parts.isBubble(idx)) (*partLife)[idx] = lifeBubble; - if (parts.isFoam(idx)) (*partLife)[idx] = lifeFloater; - if (parts.isTracer(idx)) (*partLife)[idx] = lifeTracer; - } - // Make sure "new" flag gets removed - parts.setStatus(idx, parts.getStatus(idx) & ~ParticleBase::PNEW); - - // New particle done here. Dont try to convert to other type - continue; - } - - // Set next particle position - Vec3 pos = parts.getPos(idx) + partVel[idx] * dt; - parts.setPos(idx, pos); - - // Get phiv for current and next particle position - pos = parts.getPos(idx); - Real phiv = phi.getInterpolated(pos); - - // Convert particle type - if (parts.isSpray(idx) && phiv < -DROP_THRESH) { - parts.setStatus(idx, ParticleBase::PNEW | ParticleBase::PBUBBLE); - } - else if (parts.isBubble(idx) && phiv > 0.) { - parts.setStatus(idx, ParticleBase::PNEW | ParticleBase::PFOAM); - } - } -} - -//! sample new particles of given type. control amount of particles with amount and threshold fields -PYTHON() void sampleSndParts(LevelsetGrid& phi, LevelsetGrid& phiIn, FlagGrid& flags, MACGrid& vel, BasicParticleSystem& parts, int type, Real amountDroplet, Real amountFloater, Real amountTracer, Real thresholdDroplet) -{ - RandomStream mRand(9832); - int a; - Real radiusFactor = 1.; - - const Real DROP_THRESH = 0.5f * calculateRadiusFactor(phi, radiusFactor); // half cell diagonal - const Real FLOAT_THRESH = 0.5f * calculateRadiusFactor(phi, radiusFactor); - - // Split amount value into sampling steps (integral part) and probability (fractional part) - float samplesDroplet, probDroplet, samplesFloater, probFloater, samplesTracer, probTracer; - - // Split amount variables into sample count and sample probability per cell - float *amount, *samples, *probability; - for (a=0; a<3; a++) { - if (a==0) { amount = &amountDroplet; samples = &samplesDroplet; probability = &probDroplet; } - if (a==1) { amount = &amountFloater; samples = &samplesFloater; probability = &probFloater; } - if (a==2) { amount = &amountTracer; samples = &samplesTracer; probability = &probTracer; } - - // Actual 'amount variable' splitting - (*probability) = modf((double)(*amount), samples); - (*probability) = ((*probability) == 0) ? 1.0f : (*probability); // e.g. map amount 1.0 to 100 percent probability (instead of 0.0) - (*samples) = ceil(*amount); // e.g. 0.1 amount samples once, 1.0 as well, 1.1 samples twice, ... - } - - FOR_IJK_BND(phi, 0) { - if ( flags.isObstacle(i,j,k) ) continue; - if ( !flags.isFluid(i,j,k) && !flags.isEmpty(i,j,k) ) continue; - - const Vec3 pos = Vec3(i,j,k); - - // Droplets sampling - for (a=0; a probDroplet) continue; - - if (type & ParticleBase::PSPRAY) { - // Only generate drop particles at surface - if ( phi(i,j,k) < -DROP_THRESH || phi(i,j,k) > 0. ) continue; - - // Only generate drops in convex regions - Vec3 grad = getGradient(phi, i,j,k); - Vec3 velC = vel.getCentered(i,j,k); - if ( dot( getNormalized(grad), getNormalized(velC) ) < 0.75) continue; - - parts.addBuffered(pos + mRand.getVec3(), ParticleBase::PSPRAY); - } - } - - // Floater sampling - for (a=0; a probFloater) continue; - - if (type & ParticleBase::PFOAM) { - // Only generate float particles at surface - if ( phiIn(i,j,k) < -FLOAT_THRESH || phiIn(i,j,k) > FLOAT_THRESH ) continue; - - parts.addBuffered(pos + mRand.getVec3(), ParticleBase::PFOAM); - } - } - - // Tracer sampling - for (a=0; a probTracer) continue; - - if (type & ParticleBase::PTRACER) { - // Only generate tracer particles inside fluid - if ( phiIn(i,j,k) > 0. ) continue; - - parts.addBuffered(pos + mRand.getVec3(), ParticleBase::PTRACER); - } - } - } - // Insert buffered particles into particle system now. - parts.insertBufferedParticles(); -} - -} // namespace - diff --git a/source/preprocessor/code.h b/source/preprocessor/code.h index 6e68a0cc..6eb63471 100644 --- a/source/preprocessor/code.h +++ b/source/preprocessor/code.h @@ -18,6 +18,7 @@ #include struct Text { + virtual ~Text() {} int line0; std::string minimal, original; void reset() { minimal = original = ""; line0=0; } @@ -104,6 +105,20 @@ struct Class : Text { virtual std::string dynamicClass() { return "Class"; } }; +struct EnumEntry : Text { + EnumEntry() {}; + + std::string name; + int value; +}; + +struct Enum : Text { + Enum() {}; + + std::string name; + std::vector entries; +}; + struct Block : Text { Block() {}; diff --git a/source/preprocessor/codegen_python.cpp b/source/preprocessor/codegen_python.cpp index c4a4545f..a9322eb7 100644 --- a/source/preprocessor/codegen_python.cpp +++ b/source/preprocessor/codegen_python.cpp @@ -17,6 +17,7 @@ #include #include #include +#include using namespace std; // from codegen_kernel @@ -225,6 +226,10 @@ static bool $NAME$ (PbArgs& A) { } ); +const string TmpEnumEntry = STR( +static const Pb::Register _R_$IDX$ ("$ENUMNAME$",$ENUMVALUE$); +); + //****************************************************** // Code generation functions @@ -460,6 +465,120 @@ void processPythonClass(const Block& block, const string& code, Sink& sink, vect # endif } +EnumEntry parseEnumContent(string& _code, size_t _cur_pos, int _iLastValue){ + EnumEntry entry; + string token = _code.substr(0, _cur_pos); + + // search for "=" and assign value if found + size_t assignment = token.find("="); + size_t end_assignment = token.find(","); + if(end_assignment == string::npos) + end_assignment = token.size()-1; + + if(assignment != string::npos) { + entry.name = token.substr(0, assignment); + string str_value = token.substr(assignment + 1, end_assignment - assignment); + entry.value = std::atoi(str_value.c_str()); + } + else{ + entry.name = token.substr(0, end_assignment+1); + entry.value = _iLastValue + 1; + } + + //std::cout << "\t" << entry.name << ": " << entry.value << std::endl; + + return entry; +} + +void processPythonEnum(Enum& parsed_enum, const string& code, Sink& sink, vector& inst) { + string enum_code = code; + + // Process Enum content + // { TypeNone = 0, TypeReal = 1, TypeInt = 2, TypeVec3 = 4, TypeMAC = 8, TypeLevelset = 16, TypeFlags = 32 } + + /// Remove /* ... */ comments + size_t comment_start = enum_code.find("/*"); + size_t comment_end = string::npos; + do { + if (comment_start != string::npos){ + comment_end = enum_code.find("*/", comment_start); + enum_code = enum_code.substr(0,comment_start) + enum_code.substr(comment_end+2); + } + } while( (comment_start = enum_code.find("/*")) != string::npos); + + /// Remove // ... comments + comment_start = enum_code.find("//"); + comment_end = string::npos; + do { + if (comment_start != string::npos){ + comment_end = enum_code.find("\n", comment_start); + if(comment_end == string::npos) + enum_code = enum_code.substr(0,comment_start); + else + enum_code = enum_code.substr(0,comment_start) + enum_code.substr(comment_end); + } + } while( (comment_start = enum_code.find("//")) != string::npos); + + // Clean up code section by removing {} and spaces + enum_code.erase(remove_if(enum_code.begin(), enum_code.end(), ::isspace), enum_code.end()); + enum_code = enum_code.substr(1, enum_code.size()-2); + while(enum_code.size()-1 == enum_code.find_last_of(",")){ + enum_code = enum_code.substr(0, enum_code.size()-1); + } + + // Process enum values + string delimiter = ","; + size_t cur_pos = string::npos; + int iLastValue = -1; + + while( (cur_pos = enum_code.find(delimiter)) != string::npos ) { + EnumEntry entry = parseEnumContent(enum_code, cur_pos, iLastValue); + parsed_enum.entries.push_back(entry); + + iLastValue = entry.value; + + // remove already parsed entries + enum_code = enum_code.substr(cur_pos+1); + } + + // add last (or only) entry + EnumEntry entry = parseEnumContent(enum_code, cur_pos, iLastValue); + parsed_enum.entries.push_back(entry); + + if (!sink.isHeader) + errMsg(-1, "PYTHON enums can only be defined in header files."); + + if (gDocMode) { + return; + } + + sink.inplace << "enum " << parsed_enum.name << " {"; + + //sink.link << "#include \"registry.h\"\n"; + for( size_t i = 0; i < parsed_enum.entries.size(); ++i){ + EnumEntry& cur_entry = parsed_enum.entries[i]; + + stringstream ss; + ss << cur_entry.value; + string val_str = ss.str(); + + // static const Pb::Register _R_$IDX$ ($ENUMNAME$,$ENUMVALUE$); + // TmpEnumEntry + + const string table[] = {"ENUMNAME", parsed_enum.name+"_"+cur_entry.name, + "ENUMVALUE", val_str, + "" }; + sink.link << '&' << replaceSet(TmpEnumEntry, table) << '\n'; + + string entry_str = cur_entry.name + "=" + val_str; + if(i < parsed_enum.entries.size() - 1) + entry_str += ","; + sink.inplace << entry_str; + } + + sink.inplace << "}\n"; +} + void processPythonInstantiation(const Block& block, const Type& aliasType, Sink& sink, vector& inst) { string parent = block.parent ? block.parent->name : ""; // for template functions, add to instantiation list diff --git a/source/preprocessor/parse.cpp b/source/preprocessor/parse.cpp index 889b0473..fdcdeb73 100644 --- a/source/preprocessor/parse.cpp +++ b/source/preprocessor/parse.cpp @@ -244,6 +244,21 @@ Class parseClass(TokenPointer& parentPtr) { return cur; } +Enum parseEnum(TokenPointer& parentPtr) { + Enum cur; + TokenPointer tk(parentPtr, &cur); + + tkAssert(tk.curType() == TkEnum, ""); + tk.next(); + tkAssert(tk.curType() == TkDescriptor, "malformed preprocessor keyword block. Expected 'PYTHON enum name {}'"); + cur.name = tk.cur().text; + tk.next(); + tkAssert(tk.curType() == TkCodeBlock && tk.isLast(), "Malformed PYTHON enum, expected PYTHON enum name { assignments }"); + + return cur; +} + + // Parse syntax KEYWORD(opt1, opt2, ...) STATEMENTS [ {} or ; ] void parseBlock(const string& kw, const vector& tokens, const Class* parent, Sink& sink, vector& inst) { Block block = Block(); @@ -266,11 +281,11 @@ void parseBlock(const string& kw, const vector& tokens, const Class* pare // return values while (tk.curType() == TkDescriptor && tk.cur().text == "returns") { tk.next(); - tkAssert(tk.curType() == TkBracketL, "expext opening bracket"); + tkAssert(tk.curType() == TkBracketL, "expect opening bracket"); tk.next(); block.locals.push_back(parseArgument(tk, true, true)); tkAssert(tk.curType() == TkBracketR, "expected closing bracket"); - tk.next(); + tk.next(); } block.func = parseFunction(tk, true, true, true); @@ -313,7 +328,7 @@ void parseBlock(const string& kw, const vector& tokens, const Class* pare if (tk.curType() == TkTemplate) { TokenPointer t2(tk, &templText); t2.next(); - templTypes = parseTypeList(t2); + templTypes = parseTypeList(t2); } // python class @@ -324,6 +339,10 @@ void parseBlock(const string& kw, const vector& tokens, const Class* pare tkAssert(tk.curType() == TkCodeBlock && tk.isLast(), "malformed preprocessor keyword block. Expected 'PYTHON class name : public X {}'"); processPythonClass(block, tk.cur().text, sink, inst); } + else if (tk.curType() == TkEnum) { // python enum + Enum code_enum = parseEnum(tk); + processPythonEnum(code_enum, tk.cur().text, sink, inst); + } else // function or member { bool isConstructor = parent && tk.curType() == TkDescriptor && diff --git a/source/preprocessor/prep.h b/source/preprocessor/prep.h index 78e7f98b..967294ab 100644 --- a/source/preprocessor/prep.h +++ b/source/preprocessor/prep.h @@ -39,6 +39,7 @@ void processKernel(const Block& block, const std::string& code, Sink& sink); void processPythonFunction(const Block& block, const std::string& code, Sink& sink, std::vector& inst); void processPythonVariable(const Block& block, Sink& sink); void processPythonClass(const Block& block, const std::string& code, Sink& sink, std::vector& inst); +void processPythonEnum(Enum& parsed_enum, const std::string& code, Sink& sink, std::vector& inst); void processPythonInstantiation(const Block& block, const Type& aliasType, Sink& sink, std::vector& inst); void processPythonAlias(const Block& block, const Type& aliasType, const std::string& aliasName, Sink& sink); void postProcessInstantiations(Sink& sink, std::vector& inst); diff --git a/source/preprocessor/tokenize.cpp b/source/preprocessor/tokenize.cpp index a519840f..472fdb47 100644 --- a/source/preprocessor/tokenize.cpp +++ b/source/preprocessor/tokenize.cpp @@ -256,6 +256,9 @@ void convertKeywords(vector& tokens) { tokens[i].type = TkManta; else if (t == "struct" || t == "class" || t == "typename") tokens[i].type = TkClass; + else if (t == "enum"){ + tokens[i].type = TkEnum; + } else if (t == "inline") tokens[i].type = TkInline; else if (t == "public") @@ -353,7 +356,7 @@ void processText(const string& text, int baseline, Sink& sink, const Class* pare convertKeywords(tokens); parseBlock(word, tokens, parent, sink, inst); } else { - newText << word; + newText << word; newText << c; } word = ""; diff --git a/source/preprocessor/tokenize.h b/source/preprocessor/tokenize.h index b2048eac..90767a97 100644 --- a/source/preprocessor/tokenize.h +++ b/source/preprocessor/tokenize.h @@ -21,10 +21,10 @@ enum TokenType { TkNone = 0, TkComment, TkWhitespace, TkCodeBlock, TkDescriptor, TkComma, TkBracketL, TkBracketR, TkTBracketL, TkTBracketR, TkPointer, TkRef, TkDoubleColon, TkSemicolon, TkSimpleType, TkTypeQualifier, TkConst, TkEnd, TkManta, TkUnsupportedKW, TkClass, - TkInline, TkTemplate, TkStatic, TkVirtual, TkString, TkPublic, TkColon, TkOperator }; + TkInline, TkTemplate, TkStatic, TkVirtual, TkString, TkPublic, TkColon, TkOperator, TkEnum }; const std::string unsupportedKeywords[] = {"and", "and", "and_eq", "auto", "bitand", "bitor", "break", - "catch", "const_cast", "continue", "default", "delete", "do", "dynamic_cast", "else", "enum", + "catch", "const_cast", "continue", "default", "delete", "do", "dynamic_cast", "else", "explicit", "export", "extern", "for", "friend", "goto", "if", "mutable", "namespace", "new", "not", "not_eq", "or", "or_eq", "private", "protected", "register", "reinterpret_cast", "return", "sizeof", "static_cast", "switch", "this", "throw", "try", "typedef", diff --git a/source/pwrapper/numpyWrap.cpp b/source/pwrapper/numpyWrap.cpp index 59f238ad..9bec5103 100644 --- a/source/pwrapper/numpyWrap.cpp +++ b/source/pwrapper/numpyWrap.cpp @@ -64,8 +64,12 @@ PyArrayContainer::ExtractData(void *_pParentPyArray) { PyArrayObject *pParent = reinterpret_cast(pParentPyArray); - pData = PyArray_DATA(pParent); - TotalSize = PyArray_SIZE(pParent); + int numDims = PyArray_NDIM(pParent); + long* pDims = (long*)PyArray_DIMS(pParent); + + pData = PyArray_DATA(pParent); + TotalSize = PyArray_SIZE(pParent); + Dims = std::vector(&pDims[0], &pDims[numDims]); int iDataType = PyArray_TYPE(pParent); switch(iDataType) { @@ -87,16 +91,18 @@ PyArrayContainer::ExtractData(void *_pParentPyArray) // ------------------------------------------------------------------------ // Conversion Functions // ------------------------------------------------------------------------ + template<> PyArrayContainer fromPy(PyObject *obj) { if(PyArray_API == NULL) { -#if PY_VERSION_HEX >= 0x03000000 - import_array(); // python 3 uses the return value -#else + // python 3 uses the return value +# if PY_VERSION_HEX >= 0x03000000 + import_array(); +# else initNumpy(); -#endif +# endif } if(!PyArray_Check(obj)) { diff --git a/source/pwrapper/numpyWrap.h b/source/pwrapper/numpyWrap.h index 6db57040..75907c44 100644 --- a/source/pwrapper/numpyWrap.h +++ b/source/pwrapper/numpyWrap.h @@ -60,6 +60,7 @@ namespace Manta void *pData; NumpyTypes DataType; unsigned int TotalSize; + std::vector Dims; private: void *pParentPyArray; }; diff --git a/source/pwrapper/pclass.cpp b/source/pwrapper/pclass.cpp index 4c6af987..0aa6bb1e 100644 --- a/source/pwrapper/pclass.cpp +++ b/source/pwrapper/pclass.cpp @@ -129,6 +129,10 @@ bool PbClass::isNullRef(PyObject* obj) { return PyLong_Check(obj) && PyLong_AsDouble(obj)==0; } +bool PbClass::isNoneRef(PyObject* obj) { + return (obj == Py_None); +} + void PbClass::registerObject(PyObject* obj, PbArgs* args) { // cross link Pb::setReference(this, obj); diff --git a/source/pwrapper/pclass.h b/source/pwrapper/pclass.h index 85a3d690..ccaefbce 100644 --- a/source/pwrapper/pclass.h +++ b/source/pwrapper/pclass.h @@ -71,6 +71,7 @@ class PbClass { // converters static bool isNullRef(PyObject* o); + static bool isNoneRef(PyObject* o); static PbClass* createPyObject(const std::string& classname, const std::string& name, PbArgs& args, PbClass *parent); inline bool canConvertTo(const std::string& classname) { return Pb::canConvert(mPyObject, classname); } diff --git a/source/pwrapper/pconvert.cpp b/source/pwrapper/pconvert.cpp index a2fc2e9f..3b8bef02 100644 --- a/source/pwrapper/pconvert.cpp +++ b/source/pwrapper/pconvert.cpp @@ -87,6 +87,33 @@ template<> PyObject* toPy(const Vec4& v) { template<> PyObject* toPy(const PbClass_Ptr& obj) { return obj->getPyObject(); } +template<> PyObject* toPy>(const std::vector& vec) { + PyObject* listObj = PyList_New( vec.size() ); + if (!listObj) throw logic_error("Unable to allocate memory for Python list"); + for (unsigned int i = 0; i < vec.size(); i++) { + PbClass* pb = vec[i]; + PyObject *item = pb->getPyObject(); + if (!item) { + Py_DECREF(listObj); + throw logic_error("Unable to allocate memory for Python list"); + } + PyList_SET_ITEM(listObj, i, item); + } + return listObj; +} +template<> PyObject* toPy>(const std::vector& vec) { + PyObject* listObj = PyList_New( vec.size() ); + if (!listObj) throw logic_error("Unable to allocate memory for Python list"); + for (unsigned int i = 0; i < vec.size(); i++) { + PyObject *item = toPy(vec[i]); + if (!item) { + Py_DECREF(listObj); + throw logic_error("Unable to allocate memory for Python list"); + } + PyList_SET_ITEM(listObj, i, item); + } + return listObj; +} template<> float fromPy(PyObject* obj) { #if PY_MAJOR_VERSION <= 2 @@ -107,6 +134,39 @@ template<> double fromPy(PyObject* obj) { template<> PyObject* fromPy(PyObject *obj) { return obj; } +template<> PbClass* fromPy(PyObject *obj) { + PbClass* pbo = Pb::objFromPy(obj); + + if (!PyType_Check(obj)) + return pbo; + + const char* tname = ((PyTypeObject*)obj)->tp_name; + pbo->setName(tname); + + return pbo; +} +template<> std::vector fromPy>(PyObject *obj) { + std::vector vec; + if (PyList_Check(obj)) { + int sz = PyList_Size(obj); + for (int i = 0; i < sz; ++i) { + PyObject* lobj = PyList_GetItem(obj, i); + vec.push_back(fromPy(lobj)); + } + } + return vec; +} +template<> std::vector fromPy>(PyObject *obj) { + std::vector vec; + if (PyList_Check(obj)) { + int sz = PyList_Size(obj); + for (int i = 0; i < sz; ++i) { + PyObject* lobj = PyList_GetItem(obj, i); + vec.push_back(fromPy(lobj)); + } + } + return vec; +} template<> int fromPy(PyObject *obj) { #if PY_MAJOR_VERSION <= 2 if (PyInt_Check(obj)) return PyInt_AsLong(obj); @@ -122,7 +182,12 @@ template<> int fromPy(PyObject *obj) { } template<> string fromPy(PyObject *obj) { if (PyUnicode_Check(obj)) +#ifdef BLENDER + // Blender is completely UTF-8 based + return PyBytes_AsString(PyUnicode_AsUTF8String(obj)); +#else return PyBytes_AsString(PyUnicode_AsLatin1String(obj)); +#endif #if PY_MAJOR_VERSION <= 2 else if (PyString_Check(obj)) return PyString_AsString(obj); @@ -132,14 +197,19 @@ template<> string fromPy(PyObject *obj) { } template<> const char* fromPy(PyObject *obj) { if (PyUnicode_Check(obj)) +#ifdef BLENDER + // Blender is completely UTF-8 based + return PyBytes_AsString(PyUnicode_AsUTF8String(obj)); +#else return PyBytes_AsString(PyUnicode_AsLatin1String(obj)); +#endif #if PY_MAJOR_VERSION <= 2 else if (PyString_Check(obj)) return PyString_AsString(obj); #endif else errMsg("argument is not a string"); } -template<> bool fromPy(PyObject *obj) { +template<> bool fromPy(PyObject *obj) { if (!PyBool_Check(obj)) errMsg("argument is not a boolean"); return PyLong_AsLong(obj) != 0; } @@ -214,11 +284,10 @@ template<> PbTypeVec fromPy(PyObject* obj) { template T* tmpAlloc(PyObject* obj,std::vector* tmp) { if (!tmp) throw Error("dynamic de-ref not supported for this type"); - void* ptr = malloc(sizeof(T)); - tmp->push_back(ptr); - *((T*)ptr) = fromPy(obj); - return (T*)ptr; + T* ptr = new T(fromPy(obj)); + tmp->push_back(ptr); + return ptr; } template<> float* fromPyPtr(PyObject* obj, std::vector* tmp) { return tmpAlloc(obj,tmp); } template<> double* fromPyPtr(PyObject* obj, std::vector* tmp) { return tmpAlloc(obj,tmp); } @@ -229,6 +298,7 @@ template<> Vec3* fromPyPtr(PyObject* obj, std::vector* tmp) { retur template<> Vec3i* fromPyPtr(PyObject* obj, std::vector* tmp) { return tmpAlloc(obj,tmp); } template<> Vec4* fromPyPtr(PyObject* obj, std::vector* tmp) { return tmpAlloc(obj,tmp); } template<> Vec4i* fromPyPtr(PyObject* obj, std::vector* tmp) { return tmpAlloc(obj,tmp); } +template<> std::vector* fromPyPtr>(PyObject *obj, std::vector *tmp) { return tmpAlloc>(obj, tmp); } template<> bool isPy(PyObject* obj) { #if PY_MAJOR_VERSION <= 2 @@ -314,6 +384,14 @@ template<> bool isPy(PyObject* obj) { template<> bool isPy(PyObject* obj) { return PyType_Check(obj); } +template<> bool isPy>(PyObject* obj) { + if (PyList_Check(obj)) return true; + return false; +} +template<> bool isPy>(PyObject* obj) { + if (PyList_Check(obj)) return true; + return false; +} //****************************************************************************** // PbArgs class defs @@ -325,7 +403,7 @@ PbArgs::PbArgs(PyObject* linarg, PyObject* dict) : mLinArgs(0), mKwds(0) { } PbArgs::~PbArgs() { for(int i=0; i<(int)mTmpStorage.size(); i++) - free(mTmpStorage[i]); + operator delete (mTmpStorage[i]); mTmpStorage.clear(); } diff --git a/source/pwrapper/pconvert.h b/source/pwrapper/pconvert.h index cc2b5df0..64fb9b1f 100644 --- a/source/pwrapper/pconvert.h +++ b/source/pwrapper/pconvert.h @@ -39,7 +39,7 @@ PyObject* getPyNone(); // for PbClass-derived classes template T* fromPyPtr(PyObject* obj, std::vector* tmp) { - if (PbClass::isNullRef(obj)) + if (PbClass::isNullRef(obj) || PbClass::isNoneRef(obj)) return 0; PbClass* pbo = Pb::objFromPy(obj); const std::string& type = Namify::S; @@ -57,6 +57,8 @@ template<> Vec3* fromPyPtr(PyObject* obj, std::vector* tmp); template<> Vec3i* fromPyPtr(PyObject* obj, std::vector* tmp); template<> Vec4* fromPyPtr(PyObject* obj, std::vector* tmp); template<> Vec4i* fromPyPtr(PyObject* obj, std::vector* tmp); +template<> std::vector* fromPyPtr>(PyObject* obj, std::vector* tmp); +template<> std::vector* fromPyPtr>(PyObject* obj, std::vector* tmp); PyObject* incref(PyObject* obj); template PyObject* toPy(const T& v) { @@ -69,7 +71,7 @@ template PyObject* toPy(const T& v) { return Pb::copyObject(co,type); } template bool isPy(PyObject* obj) { - if (PbClass::isNullRef(obj)) + if (PbClass::isNullRef(obj) || PbClass::isNoneRef(obj)) return false; PbClass* pbo = Pb::objFromPy(obj); const std::string& type = Namify::type>::S; @@ -94,6 +96,9 @@ template<> Vec4 fromPy(PyObject* obj); template<> Vec4i fromPy(PyObject* obj); template<> PbType fromPy(PyObject* obj); template<> PbTypeVec fromPy(PyObject* obj); +template<> PbClass* fromPy(PyObject *obj); +template<> std::vector fromPy>(PyObject *obj); +template<> std::vector fromPy>(PyObject *obj); template<> PyObject* toPy( const int& v); template<> PyObject* toPy( const std::string& val); @@ -106,6 +111,8 @@ template<> PyObject* toPy( const Vec4i& v); template<> PyObject* toPy( const Vec4& v); typedef PbClass* PbClass_Ptr; template<> PyObject* toPy( const PbClass_Ptr & obj); +template<> PyObject* toPy>( const std::vector& vec); +template<> PyObject* toPy>( const std::vector& vec); template<> bool isPy(PyObject* obj); template<> bool isPy(PyObject* obj); @@ -119,6 +126,8 @@ template<> bool isPy(PyObject* obj); template<> bool isPy(PyObject* obj); template<> bool isPy(PyObject* obj); template<> bool isPy(PyObject* obj); +template<> bool isPy>(PyObject* obj); +template<> bool isPy>(PyObject* obj); //! Encapsulation of python arguments class PbArgs { diff --git a/source/pwrapper/registry.cpp b/source/pwrapper/registry.cpp index 8f12763f..ffb60afe 100644 --- a/source/pwrapper/registry.cpp +++ b/source/pwrapper/registry.cpp @@ -82,6 +82,7 @@ class WrapperRegistry { public: static WrapperRegistry& instance(); void addClass(const std::string& name, const std::string& internalName, const std::string& baseclass); + void addEnumEntry(const std::string& name, int value); void addExternalInitializer(InitFunc func); void addMethod(const std::string& classname, const std::string& methodname, GenericFunction method); void addOperator(const std::string& classname, const std::string& methodname, OperatorFunction method); @@ -106,12 +107,14 @@ class WrapperRegistry { void registerOperators(ClassData* cls); void addParentMethods(ClassData* cls, ClassData* base); WrapperRegistry(); + ~WrapperRegistry(); std::map mClasses; std::vector mClassList; std::vector mExtInitializers; std::vector mPaths; std::string mCode, mScriptName; std::vector args; + std::map mEnumValues; }; //****************************************************************************** @@ -161,7 +164,7 @@ int cbDisableConstructor(PyObject* self, PyObject* args, PyObject* kwds) { return -1; } -PyMODINIT_FUNC PyInit_Main(void) { +PyMODINIT_FUNC PyInit_manta_main(void) { MantaEnsureRegistration(); #if PY_MAJOR_VERSION >= 3 return WrapperRegistry::instance().initModule(); @@ -178,6 +181,12 @@ WrapperRegistry::WrapperRegistry() { addClass("PbClass", "PbClass", ""); } +WrapperRegistry::~WrapperRegistry() { + // Some static constructions may have called WrapperRegistry.instance() and added + // own classes, functions, etc. Ensure everything is cleaned up properly. + cleanup(); +} + ClassData* WrapperRegistry::getOrConstructClass(const string& classname) { map::iterator it = mClasses.find(classname); @@ -223,6 +232,13 @@ void WrapperRegistry::addClass(const string& pyName, const string& internalName, data->baseclassName = baseclass; } +void WrapperRegistry::addEnumEntry(const string& name, int value) { + /// Gather static definitions to add them as static python objects afterwards + if ( mEnumValues.insert( std::make_pair( name, value ) ).second == false) { + errMsg("Enum entry '"+name+"' already existing..."); + } +} + void WrapperRegistry::addExternalInitializer(InitFunc func) { mExtInitializers.push_back(func); } @@ -402,6 +418,15 @@ void WrapperRegistry::addConstants(PyObject* module) { #endif // cuda off for now PyModule_AddObject(module,"CUDA",Manta::toPy(false)); + + // expose enum entries + std::map::iterator it; + for ( it = mEnumValues.begin(); it != mEnumValues.end(); it++ ) + { + PyModule_AddObject(module, it->first.c_str(), Manta::toPy(it->second)); + // Alternative would be: + // e.g. PyModule_AddIntConstant(module, "FlagFluid", 1); + } } void WrapperRegistry::runPreInit() { @@ -460,7 +485,7 @@ void WrapperRegistry::construct(const string& scriptname, const vector& registerDummyTypes(); // work around for certain gcc versions, cast to char* - PyImport_AppendInittab( (char*)gDefaultModuleName.c_str(), PyInit_Main ); + PyImport_AppendInittab( (char*)gDefaultModuleName.c_str(), PyInit_manta_main ); } inline PyObject* castPy(PyTypeObject* p) { @@ -505,7 +530,7 @@ PyObject* WrapperRegistry::initModule() { return NULL; // load classes - for(vector::iterator it = mClassList.begin(); it != mClassList.end(); ++it) { + for(vector::iterator it = mClassList.begin(); it != mClassList.end(); ++it) { ClassData& data = **it; char* nameptr = (char*)data.pyName.c_str(); @@ -650,6 +675,9 @@ Register::Register(const string& className, const string& property, Getter gette Register::Register(const string& className, const string& pyName, const string& baseClass) { WrapperRegistry::instance().addClass(pyName, className, baseClass); } +Register::Register(const string& name, const int value) { + WrapperRegistry::instance().addEnumEntry(name, value); +} Register::Register(const string& file, const string& pythonCode) { WrapperRegistry::instance().addPythonCode(file, pythonCode); } diff --git a/source/pwrapper/registry.h b/source/pwrapper/registry.h index f43297c3..86771a76 100644 --- a/source/pwrapper/registry.h +++ b/source/pwrapper/registry.h @@ -59,7 +59,7 @@ void MantaEnsureRegistration(); #ifdef BLENDER #ifdef PyMODINIT_FUNC -PyMODINIT_FUNC PyInit_Main(void); +PyMODINIT_FUNC PyInit_manta_main(void); #endif #endif @@ -83,6 +83,8 @@ struct Register { Register(const std::string& className, const std::string& property, Getter getter, Setter setter); //! register class Register(const std::string& className, const std::string& pyName, const std::string& baseClass); + //! register enum entry + Register(const std::string& name, const int value); //! register python code Register(const std::string& file, const std::string& pythonCode); //! register external code diff --git a/source/python/defines.py b/source/python/defines.py index 87a103df..ec4299e9 100644 --- a/source/python/defines.py +++ b/source/python/defines.py @@ -55,3 +55,8 @@ PtypeFoam = 8 PtypeTracer = 16 +# OpenVDB export flags +Compression_None = 0 +Compression_Zip = 1 +Compression_Blosc = 2 + diff --git a/source/util/randomstream.h b/source/util/randomstream.h index 3cb5c371..03145c95 100644 --- a/source/util/randomstream.h +++ b/source/util/randomstream.h @@ -16,13 +16,13 @@ #ifndef _RANDOMSTREAM_H #define _RANDOMSTREAM_H -namespace Manta { - #include #include #include #include "vectorbase.h" +namespace Manta { + class MTRand { // Data public: diff --git a/source/util/rcmatrix.h b/source/util/rcmatrix.h index 39ebda6b..066f8777 100644 --- a/source/util/rcmatrix.h +++ b/source/util/rcmatrix.h @@ -17,15 +17,12 @@ // link to omp & tbb for now #if OPENMP==1 || TBB==1 -#define MANTA_ENABLE_PARALLEL 0 +#define MANTA_ENABLE_PARALLEL 1 // allow the preconditioner to be computed in parallel? (can lead to slightly non-deterministic results) #define MANTA_ENABLE_PARALLEL_PC 0 -// use c++11 code? -#define MANTA_USE_CPP11 1 #else #define MANTA_ENABLE_PARALLEL 0 #define MANTA_ENABLE_PARALLEL_PC 0 -#define MANTA_USE_CPP11 0 #endif #if MANTA_ENABLE_PARALLEL==1 @@ -408,11 +405,7 @@ template struct RCMatrix { for( N i=0; i struct RCMatrix { } } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix operator*(const RCMatrix &m) const { @@ -459,11 +448,7 @@ template struct RCMatrix { } } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix sqrt() const { @@ -476,11 +461,7 @@ template struct RCMatrix { result.set_element(i,j,std::sqrt(it_A.value())); } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix operator*(const double k) const { @@ -493,11 +474,7 @@ RCMatrix operator*(const double k) const { result.add_to_element(i,j,it_A.value()*k); } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix applyKernel(const RCMatrix &kernel, const int nx, const int ny) const { @@ -527,11 +504,7 @@ RCMatrix applyKernel(const RCMatrix &kernel, const int nx, const int ny) const { } } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix applyHorizontalKernel(const RCMatrix &kernel, const int nx, const int ny) const { @@ -561,11 +534,7 @@ RCMatrix applyHorizontalKernel(const RCMatrix &kernel, const int nx, const int n } } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix applyVerticalKernel(const RCMatrix &kernel, const int nx, const int ny) const { @@ -595,11 +564,7 @@ RCMatrix applyVerticalKernel(const RCMatrix &kernel, const int nx, const int ny) } } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix applySeparableKernel(const RCMatrix &kernelH, const RCMatrix &kernelV, const int nx, const int ny) const { @@ -613,11 +578,7 @@ RCMatrix applyVerticalKernel(const RCMatrix &kernel, const int nx, const int ny) std::vector operator*( const std::vector &rhs ) const { std::vector result(n,0.0); multiply(rhs,result); -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } void multiply( const std::vector &rhs, std::vector &result ) const { result.resize(n); @@ -683,11 +644,7 @@ RCMatrix applyVerticalKernel(const RCMatrix &kernel, const int nx, const int ny) for( N i=0; i struct RCFixedMatrix { std::vector operator*( const std::vector &rhs ) const { std::vector result(n,0.0); multiply(rhs,result); -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } void multiply( const std::vector &rhs, std::vector &result ) const { result.resize(n); @@ -877,11 +830,7 @@ template struct RCFixedMatrix { } } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } RCMatrix toRCMatrix () const { @@ -897,11 +846,7 @@ template struct RCFixedMatrix { result.matrix[i]->value[j] = value[rowstart[i]+j]; } } parallel_end -# if MANTA_USE_CPP11==1 - return std::move(result); -# else return result; -# endif } }; diff --git a/source/util/vectorbase.cpp b/source/util/vectorbase.cpp index d1fc0b00..a4796415 100644 --- a/source/util/vectorbase.cpp +++ b/source/util/vectorbase.cpp @@ -17,11 +17,12 @@ using namespace std; namespace Manta { -template<> const Vector3D Vector3D::Zero( 0, 0, 0 ); -template<> const Vector3D Vector3D::Zero( 0.f, 0.f, 0.f ); -template<> const Vector3D Vector3D::Zero( 0., 0., 0. ); -template<> const Vector3D Vector3D::Invalid( numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN() ); -template<> const Vector3D Vector3D::Invalid( numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN() ); +template<> const Vector3D Vector3D::Zero(0, 0, 0); +template<> const Vector3D Vector3D::Zero(0.f, 0.f, 0.f); +template<> const Vector3D Vector3D::Zero(0., 0., 0.); +template<> const Vector3D Vector3D::Invalid(numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN()); +template<> const Vector3D Vector3D::Invalid(numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN(), numeric_limits::quiet_NaN()); + template<> bool Vector3D::isValid() const { return !c_isnan(x) && !c_isnan(y) && !c_isnan(z); } template<> bool Vector3D::isValid() const { return !c_isnan(x) && !c_isnan(y) && !c_isnan(z); } diff --git a/source/util/vectorbase.h b/source/util/vectorbase.h index c64fcdc9..e6db9683 100644 --- a/source/util/vectorbase.h +++ b/source/util/vectorbase.h @@ -15,7 +15,7 @@ #define _VECTORBASE_H // get rid of windos min/max defines -#if defined(WIN32) || defined(_WIN32) +#if (defined(WIN32) || defined(_WIN32)) && !defined(NOMINMAX) # define NOMINMAX #endif @@ -220,6 +220,12 @@ class Vector3D }; +//! helper to check whether float/double value is non-zero +inline bool notZero(Real f) { + if( std::abs(f) > VECTOR_EPSILON ) return true; + return false; +} + //************************************************************************ // Additional operators //************************************************************************ diff --git a/tensorflow/example0_simple/tf_simple.py b/tensorflow/example0_simple/tf_simple.py index d0c949d2..cf2aad74 100644 --- a/tensorflow/example0_simple/tf_simple.py +++ b/tensorflow/example0_simple/tf_simple.py @@ -22,6 +22,8 @@ import tensorflow as tf import numpy as np import scipy.misc + +random.seed(13) np.random.seed(13) tf.set_random_seed(13) diff --git a/tensorflow/mantaGen/README.md b/tensorflow/mantaGen/README.md new file mode 100644 index 00000000..1fc7cfbf --- /dev/null +++ b/tensorflow/mantaGen/README.md @@ -0,0 +1,29 @@ +## mantagen + +# Agreed on naming scheme (180823): + +Directories should be of the form: .../ID/sim_XXXXXX/field_YYYYYY.npz + +Use consecutive numbered ranges for X & Y with format %06d. + +ID - global identifier string, eg, "liquid_2d" , "smoke_3d_low" + "smoke_3d_hi" +sim - fixed, dont change +field - name of quantity, e.g., "density" , "velocity" + +Per field npz file: +Single grid content, 2D [Y,X,c] , 3D [Z,Y,X,c] + +# sample call + +.../manta create_dataset.py --name=TEST --type smoke_simple -n 3 -s 10 -w 5 --seed 10 --dimension 2 --resolution_x 40 --resolution_y 40 + +will generate 2D smoke datasets in the directories: +datasets/TEST/sim_000000, +datasets/TEST/sim_000001, +..., +in total "-n" times. + +For 3D you can use: + +.../manta create_dataset.py --name=TEST --type smoke_simple -n 3 -s 10 -w 5 --seed 10 --resolution_x 40 --resolution_y 40 --resolution_z 40 + diff --git a/tensorflow/mantaGen/__init__.py b/tensorflow/mantaGen/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tensorflow/mantaGen/create_dataset.py b/tensorflow/mantaGen/create_dataset.py new file mode 100644 index 00000000..46333cf5 --- /dev/null +++ b/tensorflow/mantaGen/create_dataset.py @@ -0,0 +1,154 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from __future__ import print_function + +import os +import json +import random +import datetime +from math import pi +import numpy as np + +import scenes.volumes as v + +from util.path import find_dir, make_dir, get_unique_path +from util import arguments +from util.io import GridIO +from util.logger import * + +# Example Call +# ./build/manta create_dataset.py --name=TESTSIM -t smoke_buoyant -n 2 -s 10 -w 15 -d 2 -g --datasets_path="./datasets/" + +# Arguments +#-------------------------------- +args, unknown_args = arguments.create_dataset() + +# Scene initialization +#-------------------------------- +np.random.seed(seed=args.seed) +random.seed(args.seed) + +step_file_num = 0 + +simulation_steps = args.simulation_steps +simulation_steps *= args.skip_steps +simulation_steps += args.warmup + +if simulation_steps < 2 or args.skip_steps != 1: + warning("It will not be possible to train the LSTM with this dataset") + +# load scene class from string via module of the same name, e.g., "SmokeScene" loads "./scenes/SmokeScene.py" +#-------------------------------- +import importlib +moduleName = "scenes."+args.type+"_scene" +info("Loading module "+moduleName) +sceneModule = importlib.import_module(moduleName) + +scene = sceneModule.instantiate_scene(resolution=Vec3(args.resolution_x, args.resolution_y, args.resolution_z), boundary=1, dimension=args.dimension, timestep=0.5, gravity=Vec3(0,-0.003,0), merge_ghost_fluid=True, show_gui=args.gui, pause_on_start=args.pause, **unknown_args) + +# create output folder +#-------------------------------- +if not args.no_output: + # get the datasets directory, in all datasets should reside + if not args.datasets_path: + dataset_path = find_dir("datasets", 1) + else: + dataset_path = args.datasets_path + assert os.path.exists(dataset_path), ("Datasets directory {} does not exist".format(dataset_path)) + # set output parent directory name + dataset_path += "/" + args.name + # always append index + dataset_path = get_unique_path(dataset_path) + #warning("Dataset with name '{}' already exists. [{}]".format(args.name, dataset_path)) + + +# check for grids to be saved as outputs +#-------------------------------- +grids = [] +for grid_name in args.grids: + try: + getattr(scene, grid_name) + except AttributeError as err: + warning("Scene has no grid named {}".format(grid_name)) + else: + grids.append(grid_name) +args.grids = grids +info("Grids selected for output: {}".format(grids)) + +grid_io = GridIO() + +# Simulate scene +#-------------------------------- +def on_sim_step(scene, t): + global step_file_num + + if args.no_output: + return + # screenshot of the mantaflow gui + if t == args.warmup and scene.show_gui: + scene._gui.screenshot(output_path + "/screenshots/screen_{:06d}.jpg".format(step_file_num)) + # warmup / skip steps should not be written to file + if t < args.warmup or t % args.skip_steps != 0: + return + + # write grids to a file + step_file_num = t - args.warmup + output_name = "{:06d}".format(step_file_num) + + for grid_name in grids: + # it was already checked if the attribute is present in the scene + grid = getattr(scene, grid_name) + # save the grid to a npz file + grid_io.write_grid(grid, output_path + "/" + grid_name + "_" + output_name) + +# reset seed -> same starting condition for scene creation -> reproducibility +np.random.seed(seed=args.seed) +random.seed(args.seed) + +for scene_num in range(args.num_scenes): + # Output dirs, one per simulation + #-------------------------------- + if not args.no_output: + output_path = dataset_path + "/sim" + output_path = get_unique_path(output_path) + make_dir(output_path) + if args.gui: + make_dir(output_path + "/screenshots/") + # keep track of stored scenes + stored_scenes_num = 0 + + # Dataset description + #-------------------------------- + description = {} + description["version"] = "v0.01" + description["creation_date"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + description["grids"] = grids + description["resolution_x"] = args.resolution_x + description["resolution_y"] = args.resolution_y + description["resolution_z"] = args.resolution_z + description.update(vars(args)) # insert args + + if not args.no_output: + with open(output_path + "/description.json", 'w') as f: + json.dump(description, f, indent=4) + + info("<><><><><><><><><><><><><><><><><><><><><><><><><><><><>") + info("Scene {} ({})".format(scene_num, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + info("Output path: {}".format(output_path)) + # increase scene count + stored_scenes_num = scene_num + + # [the following "scene_selection" was moved to _create_scene all of derived class] + # scene_setup.scene_selection(args.type, scene, obstacles=args.obstacles, meshes=args.meshes) + + # restart the simulation with the new scene and write out the grids as .uni + scene.simulate(num_steps=simulation_steps, on_simulation_step=on_sim_step) diff --git a/tensorflow/mantaGen/datasets/dummy.txt b/tensorflow/mantaGen/datasets/dummy.txt new file mode 100644 index 00000000..f667799f --- /dev/null +++ b/tensorflow/mantaGen/datasets/dummy.txt @@ -0,0 +1 @@ +(generated data sets will end up here) diff --git a/tensorflow/mantaGen/display_dataset.py b/tensorflow/mantaGen/display_dataset.py new file mode 100644 index 00000000..b559c487 --- /dev/null +++ b/tensorflow/mantaGen/display_dataset.py @@ -0,0 +1,160 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + + +from __future__ import print_function + +import os +import numpy as np +import json +from random import randint, getrandbits, seed +import datetime +from enum import Enum +import subprocess + +from scenes.display_scene import DisplayScene +from scene_setup import MantaGridType +from util import uniio +from util.path import find_dir, make_dir, get_unique_path +from util import git +from util import arguments + +# Functions +#-------------------------------- +def load_description(directory): + desc = None + if os.path.isfile(directory + "description.json"): + with open(directory + "description.json", 'r') as f: + desc = json.load(f) + assert desc is not None, ("Description '" + directory + "description.json" + "' not found") + return desc + +# Arguments +#-------------------------------- +args = arguments.display_dataset() + +# Data Set +#-------------------------------- +if not args.datasets_path: + dataset_path = find_dir("datasets", 2) +else: + dataset_path = args.datasets_path +assert os.path.exists(dataset_path), ("Datasets directory {} does not exist".format(dataset_path)) +# set output parent directory name +dataset_path += "/" + args.dataset +dataset_path = dataset_path + "/" + +if args.video: + dataset_video_path = dataset_path+"dataset_display/" + make_dir(dataset_video_path) + +print("Reading data set from: {}".format(dataset_path)) + +# Description +#-------------------------------- +dataset_desc = load_description(dataset_path) + +# Grids +#-------------------------------- +dataset_grids = [] +for grid in args.grids: + for grid2 in dataset_desc["grids"]: + if grid == grid2: + dataset_grids.append(grid) + break +print("Found grids: {}".format(dataset_grids)) + +# Scenes +#-------------------------------- +dataset_start_scene = args.start_scene +assert 0 <= dataset_start_scene < dataset_desc["num_scenes"], "start_scene {} is beyond data set range [0,{}]".format(dataset_start_scene, dataset_desc.num_scenes) +dataset_end_scene = dataset_desc["num_scenes"] if args.num_scenes < 0 else args.num_scenes +dataset_end_scene = min(dataset_start_scene + dataset_end_scene, dataset_desc["num_scenes"]) +assert dataset_start_scene < dataset_end_scene, "start_scene {} must be smaller than end_scene {}".format(dataset_start_scene, dataset_end_scene) +print("Scenes to display: {} -> {}".format(dataset_start_scene, dataset_end_scene)) + +dataset_simulation_steps = dataset_desc["simulation_steps"] if args.simulation_steps < 0 else args.simulation_steps +dataset_simulation_steps = min(dataset_desc["simulation_steps"], dataset_simulation_steps) + +# Scene initialization +#-------------------------------- +scene = DisplayScene(resolution=dataset_desc["resolution"], dimension=dataset_desc.get("dimension", 3), time_to_wait=args.time_to_wait, skip_steps=args.skip_steps) + +# check scene for errors +grids = [] +for grid_name in dataset_grids: + try: + getattr(scene, grid_name) + grids.append(grid_name) + except AttributeError as err: + print("Scene has no grid named {}".format(grid_name)) +dataset_grids = grids +print("Remaining grids: {}".format(dataset_grids)) + +# Scene simulate +#-------------------------------- +def on_grid_copy_step(scene, t): + # iterate grids and transfer data + for name, content in scene.display_grids.items(): + scene_grid = getattr(scene, name) + grid_type = MantaGridType(scene_grid.getGridType()) + if grid_type == MantaGridType.TypeReal: + copyArrayToGridReal(content[t], scene_grid) + elif grid_type == MantaGridType.TypeInt: + assert False, "Not supported" + elif grid_type == MantaGridType.TypeVec3: + copyArrayToGridVec3(content[t], scene_grid) + elif grid_type == MantaGridType.TypeMAC or grid_type == MantaGridType.TypeMACVec3: + copyArrayToGridMAC(content[t], scene_grid) + elif grid_type == MantaGridType.TypeLevelset or grid_type == MantaGridType.TypeLevelsetReal: + copyArrayToGridReal(content[t], scene_grid) + +#-------------------------------- +def on_solver_step(scene, t): + # screenshot of the mantaflow gui + if scene.show_gui and args.video: + scene._gui.screenshot(dataset_path + "/dataset_display/screen_{:06d}.jpg".format(scene.file_num)) + +#-------------------------------- +# Display Loop +for scene_num in range(dataset_start_scene, dataset_end_scene): + print("<><><><><><><><><><><><><><><><><><><><><><><><><><><><>") + print("Scene {} ({})\n".format(scene_num, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) + + # load npz data + for grid_name in dataset_grids: + cur_npz = np.load(dataset_path + grid_name + "/{:06d}.npz".format(scene_num)) + scene.display_grids[grid_name] = cur_npz["data"] + cur_npz.close() + + # restart the simulation with the new scene and write out the grids as .uni + scene.simulate(num_steps=dataset_simulation_steps, on_grid_copy_step=on_grid_copy_step, on_solver_step=on_solver_step) + + # cleanup loaded grids + scene.display_grids.clear() + + +if args.video: + subprocess.call(['ffmpeg', + '-r', '30', + '-f', 'image2', + '-start_number', '0', + '-i', dataset_video_path + 'screen_%06d.jpg', + '-vcodec', 'libx264', + '-crf', '18', + '-pix_fmt', 'yuv420p', + '-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2', + dataset_video_path+"dataset.mp4"]) + + files_in_dir = os.listdir(dataset_video_path) + for item in files_in_dir: + if item.endswith(".jpg"): + os.remove(os.path.join(dataset_video_path, item)) \ No newline at end of file diff --git a/tensorflow/mantaGen/scene_setup.py b/tensorflow/mantaGen/scene_setup.py new file mode 100644 index 00000000..13477460 --- /dev/null +++ b/tensorflow/mantaGen/scene_setup.py @@ -0,0 +1,213 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from __future__ import print_function + +import os +import numpy as np +import json +from random import randint, getrandbits +import datetime +from math import pi + +import scenes.volumes as v +from util.path import find_dir, make_dir, get_unique_path +from util import git +from util import arguments +from enum import Enum +from manta import * + + +#-------------------------------- +from matplotlib import pyplot as plt +from matplotlib.colors import LinearSegmentedColormap + +#-------------------------------- +def plot_field(x, out_path, title="", vmin=None, vmax=None, plot_colorbar=True): + #print("plot_field", x.shape) + fig, ax1 = plt.subplots(1, 1, figsize=(8, 8)) + cmap = plt.cm.get_cmap('viridis') + colors = cmap(np.arange(cmap.N)) + + max_color = 10 + min_color = [1,1,1,1] + final_color = colors[max_color] + for i in range(max_color): + for j in range(4): + interpolant = (max_color - i) / max_color + colors[i][j] = min(final_color[j] * (1.0 - interpolant) + min_color[j] * interpolant, 1.0) + cm = LinearSegmentedColormap.from_list("custom_vir", colors, N=256) + + im1 = ax1.imshow(x[:,:], vmin=vmin, vmax=vmax, cmap=cm, interpolation='nearest') + + # contours + cmap = plt.cm.get_cmap('viridis') + colors = cmap(np.arange(cmap.N)) + for i in range(len(colors)): + for j in range(4): + colors[i][j] = min(colors[i][j] * 1.4, 1.0) + cm = LinearSegmentedColormap.from_list("custom_vir", colors, N=256) + #cm = plt.get_cmap('autumn') + + cs = ax1.contour(x[:,:], levels=np.arange(vmin+(vmax-vmin)*0.025,vmax,(vmax-vmin)/10.0), cmap=cm) + + #if plot_colorbar: + #divider = make_axes_locatable(ax1) + #cax = divider.append_axes('right', size='5%', pad = 0.05) + #fig.colorbar(im1, cax=cax, orientation='vertical') + ax1.set_xlim(0, x.shape[1]) + ax1.set_ylim(0, x.shape[0]) + ax1.get_xaxis().set_visible(False) + ax1.get_yaxis().set_visible(False) + if title: + fig.suptitle(title, fontsize=20) + if plot_colorbar: + fig.savefig(out_path, bbox_inches='tight') + else: + fig.savefig(out_path, bbox_inches='tight', transparent=True, pad_inches=0) + plt.close(fig) + +# Enum +#-------------------------------- +# mirror class of GridBase::GridType -> keep in sync +class MantaGridType(Enum): + TypeNone = 0 + TypeReal = 1 + TypeInt = 2 + TypeVec3 = 4 + TypeMAC = 8 + TypeLevelset = 16 + TypeFlags = 32 + # Manually added permutations + TypeMACVec3 = 12 + TypeLevelsetReal = 17 + TypeFlagsInt = 34 + +def get_manta_type(enum_type): + if enum_type is MantaGridType.TypeReal: + return RealGrid + if enum_type is MantaGridType.TypeLevelset or enum_type is MantaGridType.TypeLevelsetReal: + return LevelsetGrid + if enum_type is MantaGridType.TypeVec3: + return MACGrid + if enum_type is MantaGridType.TypeMAC or enum_type is MantaGridType.TypeMACVec3: + return MACGrid + assert False, "Not supported" + + +# Meshes +#-------------------------------- +mesh_types=["duck.obj", "bunny.obj", "simpletorus.obj", "anvil.obj"] + +def vec_to_str(vec): + return "{}, {}, {}".format(vec.x, vec.y, vec.z) + +def spawn_mesh(mesh_dir, scene): + # use random mesh from meshes directory with random position, rotation and scaling + mesh = mesh_dir + "/" + mesh_types[randint( 0, len(mesh_types)-1 )] + center = v.random_vec3(low=[0.05, 0.5, 0.05], high=[0.95, 0.95, 0.95]) + scale = np.random.uniform(low=0.4, high=0.9) + rotation = v.random_vec3(low=[-pi, -pi, -pi], high=[pi, pi, pi]) + print("Spawning '{}': Pos {} Rot {} Scale {}".format(mesh, vec_to_str(center), vec_to_str(rotation), scale )) + mesh = v.MeshVolume( + mesh, + center=center, + scale=scale, + rotation=rotation + ) + scene.add_fluid(mesh) + scene.set_velocity(mesh, v.random_velocity(vel_min=[-3.5, -3.5, -3.5], vel_max=[3.5, 0, 3.5])) + + +#---------------------------------------------------------------------------------- +# Drop (Box) +def spawn_drop(scene): + box = v.random_box(center_min=[0, 0.7, 0], center_max=[1, 0.9, 1], size_min=[0.005, 0.005, 0.005], size_max=[0.25, 0.25, 0.25]) + scene.add_fluid(box) + drop_velo = v.random_velocity(vel_min=[-2.5, -2.5, -2.5], vel_max=[2.5, 0, 2.5]) + scene.set_velocity(box, drop_velo) + # print("Drop {}:\n\t{},{},{}\n\t{},{},{}\n\t{},{},{}".format(i, box._center.x, box._center.y, box._center.z, + # box._size.x, box._size.y, box._size.z, + # drop_velo.x, drop_velo.y, drop_velo.z)) + +#---------------------------------------------------------------------------------- +# test +def initialize_simple_scene(scene): + source_count = randint(1, 2) + for i in range(source_count): + box = v.random_box(center_min=[0.2, 0.1, 0.2], center_max=[0.8, 0.6, 0.8], size_min=[0.005, 0.005, 0.005], size_max=[0.2, 0.2, 0.2]) + # TODO scene.add_source( box ) + +#---------------------------------------------------------------------------------- +def initialize_smoke_scene(scene): + source_count = randint(4, 10) + for i in range(source_count): + box = v.random_box(center_min=[0.2, 0.1, 0.2], center_max=[0.8, 0.6, 0.8], size_min=[0.005, 0.005, 0.005], size_max=[0.2, 0.2, 0.2]) + scene.add_source( box ) + #scene.set_velocity(box, random_vec3(vel_min=[-2.5, -2.5, -2.5], vel_max=[2.5, 2.5, 2.5])) + #scene.add_source( box.shape(scene.solver) ) + +#---------------------------------------------------------------------------------- +def initialize_liquid_scene(scene, simple=False, obstacles=False, meshes=False, mesh_path="meshes"): + if simple: + # low basin is the large volume of liquid at the bottom of the scene. in general adds most of the liquid in the scene + low_basin = v.random_box(center_min=[0.5, 0.0, 0.5], center_max=[0.5, 0.0, 0.5], size_min=[1, 0.3, 1], size_max=[1, 0.5, 1]) + scene.add_fluid(low_basin) + scene.set_velocity(low_basin, v.random_velocity(vel_min=[-0.5, 0, -0.5], vel_max=[0.5, 0, 0.5])) + # the high basin is a tall but narrow block of liquid, adding motion to the scene + high_basin = v.random_box(center_min=[0.1, 0.3, 0.1], center_max=[0.9, 0.4, 0.9], size_min=[0.1, 0.2, 0.1], size_max=[0.3, 0.4, 0.3]) + scene.add_fluid(high_basin) + scene.set_velocity(high_basin, v.random_velocity(vel_min=[-2.0, -2.0, -2.0], vel_max=[2.0, 0, 2.0])) + else: + # low basin is the large volume of liquid at the bottom of the scene. in general adds most of the liquid in the scene + low_basin = v.random_box(center_min=[0.5, 0.0, 0.5], center_max=[0.5, 0.0, 0.5], size_min=[1, 0.3, 1], size_max=[1, 0.6, 1]) + scene.add_fluid(low_basin) + low_basin_velo = v.random_velocity(vel_min=[-1.5, 0, -1.5], vel_max=[1.5, 0, 1.5]) + scene.set_velocity(low_basin, low_basin_velo) + # print("Low:\n\t{},{},{}\n\t{},{},{}\n\t{},{},{}".format(low_basin._center.x, low_basin._center.y, low_basin._center.z, + # low_basin._size.x, low_basin._size.y, low_basin._size.z, + # low_basin_velo.x, low_basin_velo.y, low_basin_velo.z)) + + # the high basin is a tall but narrow block of liquid, adding motion to the scene + high_basin = v.random_box(center_min=[0.0, 0.3, 0.0], center_max=[1.0, .3, 1.0], size_min=[0.1, 0.2, 0.1], size_max=[0.4, 0.5, 0.4]) + scene.add_fluid(high_basin) + high_basin_velo = v.random_velocity(vel_min=[-2.5, -2.5, -2.5], vel_max=[2.5, 0, 2.5]) + scene.set_velocity(high_basin, high_basin_velo) + # print("High:\n\t{},{},{}\n\t{},{},{}\n\t{},{},{}".format(high_basin._center.x, high_basin._center.y, high_basin._center.z, + # high_basin._size.x, high_basin._size.y, high_basin._size.z, + # high_basin_velo.x, high_basin_velo.y, high_basin_velo.z)) + + # drops: spawn at most 3 drops or meshes + drop_count = randint(0, 3) + for i in range(drop_count): + if meshes and bool(getrandbits(1)): + spawn_mesh(mesh_path, scene) + else: + spawn_drop(scene) + + # optional: add solid boxes to scene. more realistic, harder to learn + if obstacles: + obstacle = v.random_box(center_min=[0.1,0.1,0.1], center_max=[1.0,0.5,1.0], size_min=[0.2,0.2,0.2], size_max=[0.3,1.0,0.3]) + print("Added obstacle to the scene") + scene.add_obstacle(obstacle) + +#---------------------------------------------------------------------------------- +def scene_selection(scene_type, scene, obstacles=False, meshes=False): + print("Scene Type: {}".format(scene_type)) + if scene_type == "liquid": + initialize_liquid_scene(scene, simple=False, obstacles=obstacles) + elif scene_type == "smoke": + initialize_smoke_scene(scene) + elif scene_type == "simple": + initialize_simple_scene(scene) + else: + assert False, "Unknown scene type " + scene_type + diff --git a/tensorflow/mantaGen/scenes/__init__.py b/tensorflow/mantaGen/scenes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tensorflow/mantaGen/scenes/display_scene.py b/tensorflow/mantaGen/scenes/display_scene.py new file mode 100644 index 00000000..a460e77b --- /dev/null +++ b/tensorflow/mantaGen/scenes/display_scene.py @@ -0,0 +1,64 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from scenes.scene import NaiveScene +from manta import * +import numpy as np +import json +import os +import time + +class DisplayScene(NaiveScene): + #---------------------------------------------------------------------------------- + def __init__(self, resolution=64, dimension=2, time_to_wait=0.0, skip_steps=1, name="DisplayScene", show_gui=True): + super(DisplayScene,self).__init__(resolution=resolution, dimension=dimension, timestep=0.1, name=name, show_gui=show_gui) + self.display_grids = {} + self.time_to_wait = time_to_wait + self.skip_steps = skip_steps + self.file_num = 0 + + #---------------------------------------------------------------------------------- + def simulate(self, num_steps=500, on_grid_copy_step=None, on_solver_step=None): + self._create_scene() + + while self.solver.frame < num_steps: + print("\r{} Step {:3d}, Time {:3.3f}, dt {:0.3f}".format(self.name, self.solver.frame + 1, self.solver.timeTotal, self.solver.timestep), end='\r') + + # some steps should not be displayed + if self.solver.frame % self.skip_steps != 0: + print("Skipping {}".format(self.solver.frame)) + self.solver.step() + continue + + # otherwise continue as normal + if self.time_to_wait > 0.0: + time.sleep(self.time_to_wait) + + # read grids from data set + if callable(on_grid_copy_step): + assert on_grid_copy_step.__code__.co_argcount == 2, "on_grid_copy_step must be a function with 2 arguments (scene and timestep)!" + on_grid_copy_step(self, self.solver.frame) + + if self.show_gui and self.dimension > 2: + #self.phi_fluid.setBound(0.5, 0) # optionally, close sides + self.phi_fluid.createMesh(self.debugmesh) + + self.solver.step() + + # store screenshots + if callable(on_solver_step): + assert on_solver_step.__code__.co_argcount == 2, "on_solver_step must be a function with 2 arguments (scene and timestep)!" + on_solver_step(self, self.solver.frame-1) # solver already advanced one frame + self.file_num += 1 + + self._reset() + + return [], [] diff --git a/tensorflow/mantaGen/scenes/flip_scene.py b/tensorflow/mantaGen/scenes/flip_scene.py new file mode 100644 index 00000000..ecb5fccc --- /dev/null +++ b/tensorflow/mantaGen/scenes/flip_scene.py @@ -0,0 +1,198 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from manta import * + +from random import randint +from scenes.scene import Scene +from scenes.volumes import * +from scenes.functions import * +from util.logger import * + + +def instantiate_scene(**kwargs): # instantiate independent of name , TODO replace? + info(kwargs) + return FLIPScene(**kwargs) + + +class FLIPScene(Scene): + #---------------------------------------------------------------------------------- + def __init__(self, **kwargs): + super(FLIPScene,self).__init__(**kwargs) + + # const + self.max_iter_fac = 2 + self.accuracy = 5e-4 + self.narrow_band = 3 + + # arguments + self.max_drop_count = int(kwargs.get("max_drop_count", 3)) + self.max_obstacle_count = int(kwargs.get("max_obstacle_count", 0)) + + # grids and pp + self.vel_org = self.solver.create(MACGrid, name="VelOrg") + self.vel_parts = self.solver.create(MACGrid, name="VelParts") + self.tmp_vec3 = self.solver.create(MACGrid, name="temp_vec3") + self.phi_parts = self.solver.create(LevelsetGrid, name="PhiParticles") + + self.pp = self.solver.create(BasicParticleSystem) + self.pVel = self.pp.create(PdataVec3) + self.pindex = self.solver.create(ParticleIndexSystem) + self.gpi = self.solver.create(IntGrid) + + info("FLIPScene initialized") + + #---------------------------------------------------------------------------------- + def set_velocity(self, volume, velocity): + if self.dimension == 2: + velocity.z = 0.0 + volume.applyToGrid(solver=self.solver, grid=self.vel, value=velocity) + mapGridToPartsVec3(source=self.vel, parts=self.pp, target=self.pVel) + + #---------------------------------------------------------------------------------- + def add_fluid(self, volume): + vol_ls = volume.computeLevelset(self.solver) + self.phi_fluid.join(vol_ls) + + #---------------------------------------------------------------------------------- + def add_obstacle(self, volume): + vol_ls = volume.computeLevelset(self.solver) + self.phi_obs.join(vol_ls) + + #---------------------------------------------------------------------------------- + # def add_source(self, volume): + # print("WARNING - sources not yet supported for FLIP scene") + # vol_ls = volume.computeLevelset(self.solver) + # self.phi_source.join(vol_ls) + + #---------------------------------------------------------------------------------- + def _create_scene(self): + super(FLIPScene, self)._create_scene() + + # reset fields + self.vel_org.setConst(vec3(0,0,0)) + self.vel_parts.setConst(vec3(0,0,0)) + self.tmp_vec3.setConst(vec3(0,0,0)) + self.phi_parts.setConst(0) + + self.pp.clear() + self.pVel.setConst(vec3(0,0,0)) + self.pindex.clear() + self.gpi.setConst(0) + is3d = (self.dimension > 2) + + ##### spawn scene + # low basin is the large volume of liquid at the bottom of the scene. in general adds most of the liquid in the scene + low_basin = random_box(center_min=[0.5, 0.0, 0.5], center_max=[0.5, 0.0, 0.5], size_min=[1, 0.3, 1], size_max=[1, 0.6, 1], is3d=is3d) + self.add_fluid(low_basin) + low_basin_velo = random_velocity(vel_min=[-1.5, 0, -1.5], vel_max=[1.5, 0, 1.5]) + self.set_velocity(low_basin, low_basin_velo) + + # the high basin is a tall but narrow block of liquid, adding motion to the scene + high_basin = random_box(center_min=[0.0, 0.3, 0.0], center_max=[1.0, .3, 1.0], size_min=[0.1, 0.2, 0.1], size_max=[0.4, 0.5, 0.4], is3d=is3d) + self.add_fluid(high_basin) + high_basin_velo = random_velocity(vel_min=[-2.5, -2.5, -2.5], vel_max=[2.5, 0, 2.5]) + self.set_velocity(high_basin, high_basin_velo) + + # drops: spawn at most 3 drops or meshes + drop_count = randint(0, self.max_drop_count) + for i in range(drop_count): + box = random_box(center_min=[0, 0.7, 0], center_max=[1, 0.9, 1], size_min=[0.005, 0.005, 0.005], size_max=[0.25, 0.25, 0.25], is3d=is3d) + drop_velo = random_velocity(vel_min=[-2.5, -2.5, -2.5], vel_max=[2.5, 0, 2.5]) + self.add_fluid(box) + self.set_velocity(box, drop_velo) + + # optional: add solid box to scene + if self.max_obstacle_count > 0: + obstacle_count = randint(0, self.max_obstacle_count) + for i in range(obstacle_count): + obstacle = random_box(center_min=[0.1,0.1,0.1], center_max=[1.0,0.5,1.0], size_min=[0.2,0.2,0.2], size_max=[0.3,1.0,0.3], is3d=is3d) + self.add_obstacle(obstacle) + + # extrapolate velocities from 1-cell inside towards empty region for particles + self.phi_fluid.addConst( 1.) + self.flags.updateFromLevelset(self.phi_fluid) + self.phi_fluid.addConst(-1.) + extrapolateMACSimple(flags=self.flags, vel=self.vel , distance=3, intoObs=True) + sampleLevelsetWithParticles(phi=self.phi_fluid, flags=self.flags, parts=self.pp, discretization=2, randomness=0.05) + mapGridToPartsVec3(source=self.vel, parts=self.pp, target=self.pVel) + + #================================================================================== + # SIMULATION + #---------------------------------------------------------------------------------- + def _compute_simulation_step(self): + self._advect(ls_order=1) + self._enforce_boundaries(distance=2) + self._solve_pressure(max_iter_fac=self.max_iter_fac, accuracy=self.accuracy) + + #---------------------------------------------------------------------------------- + def _advect(self, extrapol_dist=3, ls_order=2): + # extrapolate the grids into empty cells + #self.phi_fluid.reinitMarching(flags=self.flags, velTransport=self.vel, maxTime=4.0) + + self.pp.advectInGrid(flags=self.flags, vel=self.vel, integrationMode=2, deleteInObstacle=False, stopInObstacle=False ) + pushOutofObs( parts=self.pp, flags=self.flags, phiObs=self.phi_obs ) + + # make sure nothings sticks to the top... (helper in test.cpp) + #deleteTopParts( parts=self.pp, phi=self.phi_fluid, maxHeight=self.resolution-1-(self.boundary+2) ) # needs 2 more to make sure it's out of the setBoundNeumann range + + # advect the levelset + advectSemiLagrange(flags=self.flags, vel=self.vel, grid=self.phi_fluid, order=ls_order) + + # velocity self-advection + advectSemiLagrange(flags=self.flags, vel=self.vel, grid=self.vel, order=2) + + # particle SDF + gridParticleIndex( parts=self.pp , flags=self.flags, indexSys=self.pindex, index=self.gpi ) + unionParticleLevelset( self.pp, self.pindex, self.flags, self.gpi, self.phi_parts ) + + # TODO: source & sink , not yet working... + #self.phi_fluid.subtract(self.phi_sink) + #self.phi_fluid.join(self.phi_source) + + # combine level set of particles with grid level set + self.phi_fluid.addConst(1.) # shrink slightly + self.phi_fluid.join( self.phi_parts ) + extrapolateLsSimple(phi=self.phi_fluid, distance=self.narrow_band+2, inside=True ) + extrapolateLsSimple(phi=self.phi_fluid, distance=3 ) + + # enforce boundaries + self.phi_fluid.setBoundNeumann(self.boundary-1 if self.boundary>0 else 0) # 1 cell less... + self.flags.updateFromLevelset(self.phi_fluid) + + # combine particles velocities with advected grid velocities + mapPartsToMAC(vel=self.vel_parts, flags=self.flags, velOld=self.vel_org, parts=self.pp, partVel=self.pVel, weight=self.tmp_vec3) + extrapolateMACFromWeight( vel=self.vel_parts , distance=2, weight=self.tmp_vec3 ) + combineGridVel(vel=self.vel_parts, weight=self.tmp_vec3 , combineVel=self.vel, phi=self.phi_fluid, narrowBand=(self.narrow_band-1), thresh=0) + self.vel_org.copyFrom(self.vel) + + addGravity(flags=self.flags, vel=self.vel, gravity=self.gravity) + + #---------------------------------------------------------------------------------- + def _enforce_boundaries(self, distance): + extrapolateMACSimple( flags=self.flags, vel=self.vel , distance=distance, intoObs=True ) + setWallBcs(flags=self.flags, vel=self.vel, fractions=self.fractions, phiObs=self.phi_obs) + + #---------------------------------------------------------------------------------- + def _solve_pressure(self, max_iter_fac=2, accuracy=5e-4): + solvePressure(vel=self.vel, pressure=self.pressure, flags=self.flags, fractions=self.fractions, cgAccuracy=accuracy, cgMaxIterFac=max_iter_fac, phi=self.phi_fluid) + + # remove pressure discontinuity at boundary - note: only outer boundary, does not influence the required pressure gradients in any way + if self.boundary>0: + self.pressure.setBoundNeumann(self.boundary-1) + + self._enforce_boundaries(4) + + minParticles = pow(2,self.dimension) + self.pVel.setSource( self.vel, isMAC=True ) + adjustNumber( parts=self.pp, vel=self.vel, flags=self.flags, minParticles=1*minParticles, maxParticles=2*minParticles, phi=self.phi_fluid, exclude=self.phi_obs, narrowBand=self.narrow_band ) + flipVelocityUpdate(vel=self.vel, velOld=self.vel_org, flags=self.flags, parts=self.pp, partVel=self.pVel, flipRatio=0.97 ) + diff --git a/tensorflow/mantaGen/scenes/functions.py b/tensorflow/mantaGen/scenes/functions.py new file mode 100644 index 00000000..ac04d04d --- /dev/null +++ b/tensorflow/mantaGen/scenes/functions.py @@ -0,0 +1,39 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from manta import * +import numpy +from scenes.volumes import * + +def random_vec3(vmin=[-1,-1,-1], vmax=[1,1,1]): + return vec3( + numpy.random.uniform(low=vmin[0], high=vmax[0]), + numpy.random.uniform(low=vmin[1], high=vmax[1]), + numpy.random.uniform(low=vmin[2], high=vmax[2]) + ) + +def random_vec3s(vmin=-1, vmax=1): # scalar params + return vec3( + numpy.random.uniform(low=vmin, high=vmax), + numpy.random.uniform(low=vmin, high=vmax), + numpy.random.uniform(low=vmin, high=vmax) + ) + +def random_box(center_min=[0,0,0], center_max=[1,1,1], size_min=[0,0,0], size_max=[1,1,1], is3d=True): + size = random_vec3(size_min, size_max) + center = random_vec3(center_min, center_max) + if not is3d: + size.z = 1.0 + center.z = 0.5 + return BoxVolume(center=center, size=size) + +def random_velocity(vel_min=[-1,-1,-1], vel_max=[1,0,1]): + return random_vec3(vel_min, vel_max) diff --git a/tensorflow/mantaGen/scenes/generate_obj_scene.py b/tensorflow/mantaGen/scenes/generate_obj_scene.py new file mode 100644 index 00000000..1e859f4f --- /dev/null +++ b/tensorflow/mantaGen/scenes/generate_obj_scene.py @@ -0,0 +1,161 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +#!/usr/bin/python3 + +from subprocess import call, check_output +import argparse +import shutil +import glob +import numpy as np +import scipy as sp +import re + +import os, sys, inspect +currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) +parentdir = os.path.dirname(currentdir) +sys.path.insert(0,parentdir+"/src/") + + + +def make_dir(directory): + """ + check if directory exists, otherwise makedir + + (workaround for python2 incompatibility) + """ + if not os.path.exists(directory): + os.makedirs(directory) + + +parser = argparse.ArgumentParser() +parser.add_argument("-i", "--input_path", type=str, default="./uni/", help="Input file path") +parser.add_argument("-if", "--input_flip_file_name", type=str, default="flipParts_{:04d}.uni", help="Input flip file name") +parser.add_argument("-il", "--input_levelset_file_name", type=str, default="levelset_{:04d}.uni", help="Input levelset file name") +parser.add_argument("-o", "--output_path", type=str, default="./obj/", help="Output file path ") +parser.add_argument("-of", "--output_file_name", type=str, default="fluidsurface_final_{:04d}.bobj.gz", help="Output file name") +parser.add_argument("-mb", "--motion_blur", type=int, default=0, help="Motion blur for particles to reduce surface generation flickering") + +args = parser.parse_args() + +#---------------------------------------------------------------------------------- +def main(): + # input file + partfile = args.input_path + args.input_flip_file_name + levelsetfile = args.input_path + args.input_levelset_file_name + interval = 1 + + # how much larger? + upres = 2.0 + + # create output dir + make_dir(args.output_path) + print("Writing output to '{}'".format(args.output_path)) + # output file name so that blender can directly read it... + meshfile = args.output_path + args.output_file_name + + # resolution for level set / output mesh + refName = args.input_path + "ref_" + args.input_flip_file_name.format(0) + gs = getUniFileSize(refName) + if gs.x<=0: + mantaMsg("Warning! File '%s' not found, cannot determine size...\n"%refName, 0) + exit(1) + + # low res solver + s_LR = Solver(name='lowres', gridSize = gs, dim=3) + + # high res solver + gs.x = int(gs.x*upres) + gs.y = int(gs.y*upres) + gs.z = int(gs.z*upres) + s = Solver(name='main', gridSize = gs , dim=3) + + # kernel radius for surface creation + radiusFactor = 2.0 + + # triangle scale relative to cell size + #scale = 0.5 + + # counters + outCnt = 0 + frame = 0 + + # prepare grids and particles + flags = s.create(FlagGrid) + phi = s.create(LevelsetGrid) + phiParts = s.create(LevelsetGrid) + pp = s.create(BasicParticleSystem) + ppMb = s.create(BasicParticleSystem) # optional + mesh = s.create(Mesh) + # low res + phi_LR = s_LR.create(LevelsetGrid) + + # acceleration data for particle nbs + pindex = s.create(ParticleIndexSystem) + gpi = s.create(IntGrid) + + # scene setup + flags.initDomain(boundaryWidth=0) + + # main loop + endFrame = len(glob.glob( os.path.dirname(args.input_path)+"/*.uni")) + + while frame < endFrame: + meshfileCurr = meshfile.format(outCnt) + #phi.setBound(value=0.5, boundaryWidth=1) + + # generate mesh; first read input sim particles + if os.path.isfile( partfile.format(frame) ) and os.path.isfile( levelsetfile.format(frame) ): + pp.load( partfile.format(frame) ) + if args.motion_blur>0: + for mb in range(args.motion_blur): + frameMb = frame-mb if frame-mb>=0 else 0 + ppMb.load( partfile.format(frameMb) ) + pp.appendParticles(ppMb) + # load low res phi from file and upscale to main solver + phi_LR.load( levelsetfile.format(frame) ) + interpolateGrid(phi, phi_LR) + flags.updateFromLevelset(phi) + phi.reinitMarching(flags, 4.0) + + # create surface + gridParticleIndex( parts=pp , flags=flags, indexSys=pindex, index=gpi ) + #unionParticleLevelset( pp, pindex, flags, gpi, phi , radiusFactor ) # faster, but not as smooth + #averagedParticleLevelset( pp, pindex, flags, gpi, phiParts , radiusFactor , 2, 2 ) + improvedParticleLevelset( pp, pindex, flags, gpi, phiParts , radiusFactor , 1, 1 ) + #phi.setBound(value=0.5, boundaryWidth=1) + + # Merge levelset and particles + phi.addConst(1.); # shrink slightly + phi.join( phiParts ) + # extrapolateLsSimple(phi=phi, distance=narrowBand+2, inside=True ) + # extrapolateLsSimple(phi=phi, distance=3 ) + # phi.setBoundNeumann(1) # make sure no particles are placed at outer boundary + phi.setBound(0.0,3) + + phi.createMesh(mesh) + # beautify mesh, too slow right now! + #subdivideMesh(mesh=mesh, minAngle=0.01, minLength=scale, maxLength=3*scale, cutTubes=False) + # perform smoothing + #for iters in range(10): + #smoothMesh(mesh=mesh, strength=1e-3, steps=10) + #subdivideMesh(mesh=mesh, minAngle=0.01, minLength=scale, maxLength=3*scale, cutTubes=True) + # write output file: + mesh.save( meshfileCurr ) + + outCnt += 1 + frame += interval + s.step() + +#---------------------------------------------------------------------------------- +# execute the prediction +if __name__ == "__main__": + main() diff --git a/tensorflow/mantaGen/scenes/scene.py b/tensorflow/mantaGen/scenes/scene.py new file mode 100644 index 00000000..a31c2227 --- /dev/null +++ b/tensorflow/mantaGen/scenes/scene.py @@ -0,0 +1,234 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Basic simulation scene class +# Contains only simulation code, I/O specific code is external, typically create_dataset.py +# See "on_simulation_step" call below +# +#****************************************************************************** + +from __future__ import print_function +import time +from manta import * +#import scenes.volumes + +class Scene(object): + """ Superclass for all simulation scenes """ + #---------------------------------------------------------------------------------- + def __init__(self, resolution=vec3(64,64,64), dimension=2, timestep=1.0, boundary=1, gravity=vec3(0, -0.01, 0), name="Scene", show_gui=True, pause_on_start=False, **kwargs): + print("INIT base") + self.resolution = resolution + self.dimension = dimension + self.timestep = timestep + self.boundary = boundary + self.gravity = gravity + self.name = name + self.show_gui = show_gui + self.pause_on_start = pause_on_start + + self._parse_arguments(**kwargs) + + # previously _init_solver(self): + self._grid_size = self.resolution + if (self.dimension == 2): + self._grid_size.z = 1 + + self.solver = FluidSolver(name=self.name, gridSize=self._grid_size, dim=self.dimension) + self.solver.timestep = self.timestep + self.max_iter_fac = 10 + self.accuracy = 5e-5 + + # adaptive timestepping settings, not used at the moment + self.solver.frameLength = self.timestep + self.solver.timestepMin = self.timestep * 0.25 + self.solver.timestepMax = self.timestep * 4.0 + self.solver.cfl = 2.0 + + setDebugLevel(0) + + # previously _init_grids(self): + self.flags = self.solver.create(FlagGrid, name="Flags") + self.vel = self.solver.create(MACGrid, name="Velocity") + self.pressure = self.solver.create(RealGrid, name="Pressure") + self.phi_fluid = self.solver.create(LevelsetGrid, name="Fluid") + self.phi_obs = self.solver.create(LevelsetGrid, name="Obstacles") + #self.phi_sink = self.solver.create(LevelsetGrid, name="Sinks") + #self.phi_source = self.solver.create(LevelsetGrid, name="Sources") + self.fractions = self.solver.create(MACGrid, name="Fractions") + + if self.show_gui and self.dimension > 2: + self.debugmesh = self.solver.create(Mesh) + self.flags.initDomain(boundaryWidth=self.boundary, phiWalls=self.phi_obs) + + #---------------------------------------------------------------------------------- + # Interface + #---------------------------------------------------------------------------------- + def _parse_arguments(self, **kwargs): + """ Parse additional arguments and store them in internal variables """ + pass + + #---------------------------------------------------------------------------------- + def _create_scene(self): + """ Reset and initialize randomized initial conditions for simulation, override in derived classes """ + + # TODO, should be done in scene init + self.flags.setConst(0) + self.vel.setConst(vec3(0,0,0)) + self.pressure.setConst(0) + self.phi_fluid.setConst(0) + self.phi_obs.setConst(0) + #self.phi_sink.setConst(0) + #self.phi_source.setConst(0) + self.fractions.setConst(vec3(0,0,0)) + self.flags.initDomain(boundaryWidth=self.boundary, phiWalls=self.phi_obs) + + # TODO potentially dangerous - make optional? + self.phi_fluid.subtract(self.phi_obs) + updateFractions(flags=self.flags, phiObs=self.phi_obs, fractions=self.fractions, boundaryWidth=self.boundary) + setObstacleFlags(flags=self.flags, phiObs=self.phi_obs, fractions=self.fractions) + + extrapolateLsSimple(phi=self.phi_fluid, distance=5) + self.flags.updateFromLevelset(self.phi_fluid) + + if self.show_gui and "_gui" not in self.__dict__: + self._gui = Gui() + self._gui.show(True) + self._gui.setCamRot(40.0,0,0) + self._gui.setCamPos(0,0,-1.5) + self._gui.setPlane(2) + if self.pause_on_start: + self._gui.pause() + + #---------------------------------------------------------------------------------- + def _compute_simulation_step(self): + """ Compute simulation step for simulations, override in derived classes """ + # ... implement simulation step ... + pass + + #---------------------------------------------------------------------------------- + # Functions + #---------------------------------------------------------------------------------- +# def get_settings(self): +# settings = { +# "resolution": self.resolution, +# "dimension": self.dimension, +# "timestep": self.timestep, +# "boundary": self.boundary, +# "name": self.name +# } +# return settings +# +# #---------------------------------------------------------------------------------- +# def add_obstacle(self, volume): +# vol_ls = volume.computeLevelset(self.solver) +# self.phi_obs.join(vol_ls) +# +# #---------------------------------------------------------------------------------- +# def add_fluid(self, volume): +# vol_ls = volume.computeLevelset(self.solver) +# self.phi_fluid.join(vol_ls) +# +# #---------------------------------------------------------------------------------- +# def set_velocity(self, volume, velocity): +# if self.dimension == 2: +# velocity.z = 0.0 +# volume.applyToGrid(solver=self.solver, grid=self.vel, value=velocity) +# +# #---------------------------------------------------------------------------------- +# def add_sink(self, volume): +# vol_ls = volume.computeLevelset(self.solver) +# self.phi_sink.join(vol_ls) +# +# #---------------------------------------------------------------------------------- +# def add_source(self, volume): +# vol_ls = volume.computeLevelset(self.solver) +# self.phi_source.join(vol_ls) + + #---------------------------------------------------------------------------------- + def simulate(self, num_steps=100, on_simulation_step=None): + self._create_scene() + + self.solver.frame = 0 + last_frame = -1 + while self.solver.frame < num_steps: + # TODO, make optional via derived class? + #maxVel = self.vel.getMax() + #self.solver.adaptTimestep( maxVel ) + print("\r{} Step {:3d}, Time {:3.3f}, dt {:0.3f}".format(self.name, self.solver.frame + 1, self.solver.timeTotal, self.solver.timestep), end='\r') + + # Execute simulation step + self._compute_simulation_step() + + # Update GUI + if self.show_gui and self.dimension > 2: + #self.phi_fluid.setBound(0.5, 0) # optionally, close sides for display + self.phi_fluid.createMesh(self.debugmesh) + + # Advance solver and call callback function + self.solver.step() + if callable(on_simulation_step) and (last_frame != self.solver.frame): + assert on_simulation_step.__code__.co_argcount == 2, "on_simulation_step must be a function with 2 arguments (scene and timestep)!" + on_simulation_step(self, self.solver.frame-1) # solver already progressed one frame + last_frame = self.solver.frame + + + + +#========================================================================================================================= +# TODO, NaiveScene NT_DEBUG update? +class NaiveScene_dontUse_(Scene): + #---------------------------------------------------------------------------------- + def _init_solver(self): + super(NaiveScene,self)._init_solver() + + #---------------------------------------------------------------------------------- + def _parse_arguments(self, **kwargs): + self.merge_ghost_fluid = kwargs.get("merge_ghost_fluid", False) + + #---------------------------------------------------------------------------------- + def _compute_simulation_step(self): + self._advect(ls_order=1) + self._enforce_boundaries(distance=2) + self._solve_pressure(max_iter_fac=self.max_iter_fac, accuracy=self.accuracy) + + #================================================================================== + # SIMULATION + #---------------------------------------------------------------------------------- + def _advect(self, extrapol_dist=3, ls_order=2): + # extrapolate the grids into empty cells + self.phi_fluid.reinitMarching(flags=self.flags, velTransport=self.vel, maxTime=32.0) + + # advect the levelset + advectSemiLagrange(flags=self.flags, vel=self.vel, grid=self.phi_fluid, order=ls_order) + + # source & sink + self.phi_fluid.subtract(self.phi_sink) + #self.phi_fluid.join(self.phi_source) + + # enforce boundaries + self.phi_fluid.setBoundNeumann(self.boundary) + self.flags.updateFromLevelset(self.phi_fluid) + + # velocity self-advection + advectSemiLagrange(flags=self.flags, vel=self.vel, grid=self.vel, order=2) + addGravity(flags=self.flags, vel=self.vel, gravity=self.gravity) + + #---------------------------------------------------------------------------------- + def _enforce_boundaries(self, distance): + # enforce boundaries + setWallBcs(flags=self.flags, vel=self.vel, fractions=self.fractions, phiObs=self.phi_obs) + + #---------------------------------------------------------------------------------- + def _solve_pressure(self, max_iter_fac, accuracy): + solvePressure(vel=self.vel, pressure=self.pressure, flags=self.flags, fractions=self.fractions, cgAccuracy=accuracy, cgMaxIterFac=max_iter_fac, phi=self.phi_fluid) + + if self.boundary>0: + self.pressure.setBoundNeumann(self.boundary-1) + + self._enforce_boundaries(distance=4) diff --git a/tensorflow/mantaGen/scenes/simple_scene.py b/tensorflow/mantaGen/scenes/simple_scene.py new file mode 100644 index 00000000..a5ec4ce6 --- /dev/null +++ b/tensorflow/mantaGen/scenes/simple_scene.py @@ -0,0 +1,138 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from manta import * + +import os +import numpy +import argparse +import datetime +import time +from random import randint, seed +from scenes.scene import Scene +import scenes.volumes as volumes + +def random_vec3(vmin=[-1,-1,-1], vmax=[1,1,1]): + return vec3( + numpy.random.uniform(low=vmin[0], high=vmax[0]), + numpy.random.uniform(low=vmin[1], high=vmax[1]), + numpy.random.uniform(low=vmin[2], high=vmax[2]) + ) + +def random_vec3s(vmin=-1, vmax=1): # scalar params + return vec3( + numpy.random.uniform(low=vmin, high=vmax), + numpy.random.uniform(low=vmin, high=vmax), + numpy.random.uniform(low=vmin, high=vmax) + ) + + +class SimpleScene(Scene): + #file_num = 0 + open_bound = True + sources = [] + source_strengths = [] + +# #---------------------------------------------------------------------------------- +# def set_velocity(self, volume, velocity): +# super(SmokeScene,self).set_velocity(volume, velocity) +# +# #---------------------------------------------------------------------------------- +# def add_sink(self, volume): +# print("WARNING - sinks not yet supported for smoke scene") +# +# #---------------------------------------------------------------------------------- +# # sources used as smoke inflow in the following +# def add_source(self, volume): +# shape = volume.shape(self.solver) +# self.sources.append(shape) +# +# #---------------------------------------------------------------------------------- +# # this is kind of a hack, since for smoke sources are much more desirable than just adding a fluid once +# def add_fluid(self, volume): +# self.add_source(volume) +# + def _init(self): + # solver + self.max_iter_fac = 2 + self.accuracy =5e-4 + + # grids + self.density = self.solver.create(RealGrid, name="Density") + self.pressure_static = self.solver.create(RealGrid, name="StatPressure_dummy_") # not used for smoke + + noise = self.solver.create(NoiseField, loadFromFile=True) + noise.posScale = vec3(40) * numpy.random.uniform(low=0.25, high=1.) + noise.posOffset = random_vec3s( vmin=0. ) * 100. + noise.clamp = True + noise.clampNeg = 0 + noise.clampPos = 1. + noise.valOffset = 0.15 + noise.timeAnim = 0.4 * numpy.random.uniform(low=0.2, high=1.) + self.noise = noise + + self.source_strengths = [] + for i in range(100): # some more for safety + self.source_strengths.append( numpy.random.uniform(low=0.5, high=1.) ) + + #---------------------------------------------------------------------------------- + def _create_scene(self): + # from reset + self.sources = [] + self.density.setConst(0) + self.vel.setConst(vec3(0)) + is3d = (self.dimension > 2) + + # TODO , needed? + # dont reset! multiple sims written into single file with increasing index... + #self.file_num = 0 + + source_count = randint(1, 2) # from scene_setup.scene_selection + for i in range(source_count): + print("_create_scene rand box ") + box = volumes.random_box(center_min=[0.2, 0.1, 0.2], center_max=[0.8, 0.6, 0.8], size_min=[0.005, 0.005, 0.005], size_max=[0.2, 0.2, 0.2], is3d=is3d) + + super(SimpleScene, self)._create_scene() + self.flags.initDomain(boundaryWidth=self.boundary) + self.flags.fillGrid() + if self.open_bound: + setOpenBound(self.flags, self.boundary, 'yY', 16 | 4) # FlagOutflow|FlagEmpty) + + print("_create_scene done") + + #================================================================================== + # SIMULATION + #---------------------------------------------------------------------------------- + + def _compute_simulation_step(self): + # Add source + # randomize noise offset , note - sources are turned off earlier, the more there are + for i in range(len(self.sources)): + if self.solver.frame0: + self.pressure.setBoundNeumann(self.boundary-1) + + # done in solveP, correctVelocities(vel=self.vel, pressure=self.pressure, flags=self.flags) + self.vel.setBoundNeumann(self.boundary) + diff --git a/tensorflow/mantaGen/scenes/smoke_buoyant_scene.py b/tensorflow/mantaGen/scenes/smoke_buoyant_scene.py new file mode 100644 index 00000000..f16dbcdd --- /dev/null +++ b/tensorflow/mantaGen/scenes/smoke_buoyant_scene.py @@ -0,0 +1,116 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from manta import * + +import numpy +from random import randint +from scenes.scene import Scene +from scenes.volumes import * +from scenes.functions import * +from util.logger import * + + +def instantiate_scene(**kwargs): # instantiate independent of name , TODO replace? + info(kwargs) + return SmokeBuoyantScene(**kwargs) + + +class SmokeBuoyantScene(Scene): + #---------------------------------------------------------------------------------- + def __init__(self, **kwargs): + super(SmokeBuoyantScene,self).__init__(**kwargs) + # optionally, init more grids etc. + + self.max_iter_fac = 2 + self.accuracy = 5e-4 + self.max_source_count = int(kwargs.get("max_source_count", 5)) + self.velocity_scale = float(kwargs.get("velocity_scale", self.resolution.y * 0.05)) + self.use_inflow_sources = kwargs.get("use_inflow_sources", "True") == "True" + self.open_bound = kwargs.get("use_open_bound", "True") == "True" + self.sources = [] + self.source_strengths = [] + + # smoke sims need to track the density + self.density = self.solver.create(RealGrid, name="Density") + + noise = self.solver.create(NoiseField, loadFromFile=True) + noise.posScale = vec3(40) * numpy.random.uniform(low=0.25, high=1.) + noise.posOffset = random_vec3s(vmin=0.0) * 100. + noise.clamp = True + noise.clampNeg = 0 + noise.clampPos = 1. + noise.valOffset = 0.15 + noise.timeAnim = 0.4 * numpy.random.uniform(low=0.2, high=1.) + self.noise = noise + + info("SmokeBuoyantScene initialized") + + #---------------------------------------------------------------------------------- + def set_velocity(self, volume, velocity): + if self.dimension == 2: + velocity.z = 0.0 + volume.applyToGrid(solver=self.solver, grid=self.vel, value=velocity) + + #---------------------------------------------------------------------------------- + # sources used as smoke inflow in the following + def add_source(self, volume): + shape = volume.shape(self.solver) + self.sources.append(shape) + self.source_strengths.append(numpy.random.uniform(low=0.5, high=1.)) + + #---------------------------------------------------------------------------------- + def _create_scene(self): + super(SmokeBuoyantScene, self)._create_scene() + + self.sources = [] + self.source_strengths = [] + self.density.setConst(0) + self.vel.setConst(vec3(0)) + is3d = (self.dimension > 2) + + self.flags.initDomain(boundaryWidth=self.boundary) + self.flags.fillGrid() + if self.open_bound: + setOpenBound(self.flags, self.boundary, 'yY', CellType_TypeOutflow|CellType_TypeEmpty) + + # formerly initialize_smoke_scene(scene): + source_count = randint(1, self.max_source_count) + for i in range(source_count): + volume = random_box(center_min=[0.2, 0.1, 0.2], center_max=[0.8, 0.6, 0.8], size_min=[0.005, 0.005, 0.005], size_max=[0.2, 0.2, 0.2], is3d=is3d) + self.add_source(volume) + src, sstr = self.sources[-1], self.source_strengths[-1] + densityInflow(flags=self.flags, density=self.density, noise=self.noise, shape=src, scale=2.0*sstr, sigma=0.5) + + if self.show_gui: + # central view is more interesting for smoke + self._gui.setPlane(self.resolution.z // 2) + + info("SmokeBuoyantScene created with {} sources".format(len(self.sources))) + + #================================================================================== + # SIMULATION + #---------------------------------------------------------------------------------- + def _compute_simulation_step(self): + # Note - sources are turned off earlier, the more there are in the scene + for i in range(len(self.sources)): + if self.use_inflow_sources: + src, sstr = self.sources[i], self.source_strengths[i] + densityInflow(flags=self.flags, density=self.density, noise=self.noise, shape=src, scale=2.0*sstr, sigma=0.5) + + advectSemiLagrange(flags=self.flags, vel=self.vel, grid=self.density, order=2, clampMode=2) + advectSemiLagrange(flags=self.flags, vel=self.vel, grid=self.vel , order=2, clampMode=2) + + vorticityConfinement(vel=self.vel, flags=self.flags, strength=0.1) + addBuoyancy(density=self.density, vel=self.vel, gravity=0.2*self.gravity, flags=self.flags) + + setWallBcs(flags=self.flags, vel=self.vel) + solvePressure(flags=self.flags, vel=self.vel, pressure=self.pressure, cgMaxIterFac=self.max_iter_fac, cgAccuracy=self.accuracy) diff --git a/tensorflow/mantaGen/scenes/smoke_simple_scene.py b/tensorflow/mantaGen/scenes/smoke_simple_scene.py new file mode 100644 index 00000000..cd7000cd --- /dev/null +++ b/tensorflow/mantaGen/scenes/smoke_simple_scene.py @@ -0,0 +1,91 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from manta import * + +from random import randint +from scenes.scene import Scene +from scenes.volumes import * +from scenes.functions import * +from util.logger import * + + +def instantiate_scene(**kwargs): # instantiate independent of name , TODO replace? + info(kwargs) + return SmokeSimpleScene(**kwargs) + + +class SmokeSimpleScene(Scene): + #---------------------------------------------------------------------------------- + def __init__(self, **kwargs): + super(SmokeSimpleScene,self).__init__(**kwargs) + # optionally, init more grids etc. + + self.max_iter_fac = 2 + self.accuracy = 5e-4 + self.max_source_count = int(kwargs.get("max_source_count", 2)) + self.velocity_scale = float(kwargs.get("velocity_scale", self.resolution.y * 0.05)) + self.use_inflow_sources = kwargs.get("use_inflow_sources", "True") == "True" + self.open_bound = kwargs.get("use_open_bound", "True") == "True" + self.sources = [] + + info("SmokeSimpleScene initialized") + + #---------------------------------------------------------------------------------- + def set_velocity(self, volume, velocity): + if self.dimension == 2: + velocity.z = 0.0 + volume.applyToGrid(solver=self.solver, grid=self.vel, value=velocity) + + #---------------------------------------------------------------------------------- + def _create_scene(self): + super(SmokeSimpleScene, self)._create_scene() + + self.sources = [] + self.vel.setConst(vec3(0)) + + self.flags.initDomain(boundaryWidth=self.boundary) + self.flags.fillGrid() + + if self.open_bound: + setOpenBound(self.flags, self.boundary, 'yY', CellType_TypeOutflow|CellType_TypeEmpty) + + is3d = (self.dimension > 2) + source_count = randint(1, self.max_source_count) + for i in range(source_count): + volume = random_box(center_min=[0.2, 0.1, 0.2], center_max=[0.8, 0.6, 0.8], size_min=[0.005, 0.005, 0.005], size_max=[0.2, 0.2, 0.2], is3d=is3d) + velo = random_vec3( vmin=[-self.velocity_scale, -self.velocity_scale, -self.velocity_scale], + vmax=[self.velocity_scale, self.velocity_scale, self.velocity_scale]) + self.sources.append( (volume, velo) ) + # set velocity on startup if needed + if not self.use_inflow_sources: + self.set_velocity( volume, velo ) + + if self.show_gui: + # central view is more interesting for smoke + self._gui.setPlane( self.resolution.z // 2 ) + + info("SmokeSimpleScene created with {} sources".format(len(self.sources))) + + #================================================================================== + # SIMULATION + #---------------------------------------------------------------------------------- + def _compute_simulation_step(self): + advectSemiLagrange(flags=self.flags, vel=self.vel, grid=self.vel, order=2, clampMode=2) + # apply velocity source + if self.use_inflow_sources: + for vol, vel in self.sources: + self.set_velocity( vol, vel ) + + vorticityConfinement( vel=self.vel, flags=self.flags, strength=0.1 ) + + setWallBcs(flags=self.flags, vel=self.vel) + solvePressure(flags=self.flags, vel=self.vel, pressure=self.pressure, cgMaxIterFac=self.max_iter_fac, cgAccuracy=self.accuracy) \ No newline at end of file diff --git a/tensorflow/mantaGen/scenes/volumes.py b/tensorflow/mantaGen/scenes/volumes.py new file mode 100644 index 00000000..334f9ed1 --- /dev/null +++ b/tensorflow/mantaGen/scenes/volumes.py @@ -0,0 +1,81 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from manta import * +import numpy + +class Volume(): + def shape(self, solver): + pass + + def applyToGrid(self, solver, grid, value): + self.shape(solver).applyToGrid(grid=grid, value=value) + + def computeLevelset(self, solver): + return self.shape(solver).computeLevelset() + +class CylinderVolume(Volume): + def __init__(self, center, radius, height): + self._center = center + self._radius = radius + self._height = height + + def shape(self, solver): + return Cylinder(parent=solver, center=solver.getGridSize() * self._center, radius=solver.getGridSize().x * self._radius, z=solver.getGridSize().y * self._height) + +class SphereVolume(Volume): + def __init__(self, center, radius): + self._center = center + self._radius = radius + + def shape(self, solver): + return Sphere(parent=solver, center=solver.getGridSize() * self._center, radius=solver.getGridSize().x * self._radius) + +class MeshVolume(Volume): + def __init__(self, file_path, center, scale, rotation): + self._center = center + self._scale = scale + self._rotation = rotation + self._file_path = file_path + self._mesh = None + + def shape(self, solver): + if self._mesh is None: + self._mesh = solver.create(Mesh) + self._mesh.load(self._file_path) + self._mesh.scale( (solver.getGridSize() / 3.0) * self._scale ) + self._mesh.rotate( self._rotation ) + self._mesh.offset( solver.getGridSize() * self._center ) + return self._mesh + + def applyToGrid(self, solver, grid, value): + self.shape(solver).applyMeshToGrid(grid=grid, value=value) + + def computeLevelset(self, solver): + return self.shape(solver).getLevelset(sigma=2.0) + +class BoxVolume(Volume): + def __init__(self, p0=None, p1=None, center=None, size=None): + if p0 is not None and p1 is not None: + self._p0 = p0 + self._p1 = p1 + elif center is not None and size is not None: + self._center = center + self._size = size + else: + raise ValueError("Arguments must either be p0 and p1 OR center and size") + + def shape(self, solver): + if "_center" in self.__dict__ and "_size" in self.__dict__: + shape = Box(parent=solver, center=solver.getGridSize() * self._center, size=solver.getGridSize() * self._size) + else: + shape = Box(parent=solver, p0=solver.getGridSize() * self._p0, p1=solver.getGridSize() * self._p1) + return shape diff --git a/tensorflow/mantaGen/test_exec.sh b/tensorflow/mantaGen/test_exec.sh new file mode 100755 index 00000000..b771a5fa --- /dev/null +++ b/tensorflow/mantaGen/test_exec.sh @@ -0,0 +1,23 @@ +# a selection of test runs + +# path to executable +MANTA=manta +MANTA=../mbbManta/buildSecondStd/manta + +# optional GUI +GUI= +#GUI=-g + +# 2D +${MANTA} create_dataset.py --name=TEST_smoke_simple -t smoke_simple -n 2 -s 10 -w 15 -d 2 ${GUI} + +${MANTA} create_dataset.py --name=TEST_smoke_buoyant -t smoke_buoyant -n 2 -s 10 -w 15 -d 2 ${GUI} + +${MANTA} create_dataset.py --name=TESTSIM -t flip -n 5 -s 10 -w 15 -d 2 --max_obstacle_count 3 --max_drop_count 5 ${GUI} + +# 3D +${MANTA} create_dataset.py --name=TEST_smoke_simple -t smoke_simple -n 2 -s 10 -w 15 -d 3 ${GUI} + +${MANTA} create_dataset.py --name=TEST_smoke_buoyant -t smoke_buoyant -n 2 -s 10 -w 15 -d 3 ${GUI} + +${MANTA} create_dataset.py --name=TESTSIM -t flip -n 3 -s 10 -w 15 -d 3 --max_obstacle_count 0 --max_drop_count 3 ${GUI} diff --git a/tensorflow/mantaGen/util/__init__.py b/tensorflow/mantaGen/util/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/tensorflow/mantaGen/util/arguments.py b/tensorflow/mantaGen/util/arguments.py new file mode 100644 index 00000000..740fb88a --- /dev/null +++ b/tensorflow/mantaGen/util/arguments.py @@ -0,0 +1,105 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +import argparse + +def create_dataset(): + parser = argparse.ArgumentParser() + output = parser.add_argument_group("Output") + output.add_argument("--no_output", action="store_true", help="do not generate output") + output.add_argument("--name", type=str, required=True, help="A distinguishable name for the dataset") + output.add_argument("--datasets_path", type=str, required=False, help="Optional path for the datasets directory. Especially useful, if datasets should be written to second disk") + output.add_argument("--grids", nargs="*", default=["pressure", "pressure_static", "pressure_dynamic", "vel", "phi_fluid", "density"], choices=["pressure", "pressure_static", "pressure_dynamic", "vel", "phi_fluid", "density"], help="Specify which grids should be written to uni files.") + output.add_argument("--quantization", type=int, default=0, help="Decimal places for quantization of grids. 0 means off") + + scene = parser.add_argument_group("Scene") + scene.add_argument("-n", "--num_scenes", type=int, default=1, help="number of scenes used in data generation") + parser.add_argument('-t', "--type", default="flip", \ + choices=["flip", "smoke_buoyant", "smoke_simple"], help='simulation type (default: %(default)s)') + scene.add_argument("-s", "--simulation_steps", type=int, default=100, help="number of simulation steps for a scene. must be constant for all scenes") + scene.add_argument("-w", "--warmup", type=int, default=75, help="number of steps to discard in the beginning of the scene") + scene.add_argument("--seed", type=int, default=1337, help="Seed for the random number generator") + scene.add_argument("--skip_steps", type=int, default=1, help="Write out interval. Value of 1 writes every step") + scene.add_argument("--obstacles", action="store_true", help="add obstacles to the scene") + scene.add_argument("--meshes", action="store_true", help="add meshes as water surface scene") + scene.add_argument("-rx", "--resolution_x", type=int, default=64, help="simulation resolution x") + scene.add_argument("-ry", "--resolution_y", type=int, default=64, help="simulation resolution y") + scene.add_argument("-rz", "--resolution_z", type=int, default=64, help="simulation resolution z") + scene.add_argument("-d", "--dimension", type=int, default=3, help="simulation dimension (usually 2D or 3D)") + + mantaflow = parser.add_argument_group("Mantaflow") + mantaflow.add_argument("-g", "--gui", action="store_true") + + debug = parser.add_argument_group("Debug") + debug.add_argument("-p", "--pause", action="store_true", help="Pause on start") + + #args = parser.parse_args() + args, unknown_args = parser.parse_known_args() + + def process_arg_name(arg_name): + while arg_name[0] is "-": + arg_name = arg_name[1:] + return arg_name + def unpack_list(arg_list): + if type(arg_list) is list and len(arg_list) == 1: + return arg_list[0] + return arg_list + + # parse unknown arguments + unknown_args_dict = {} + last_split = 0 + for i, b in enumerate(unknown_args): + if type(b) is str and (b.startswith("-") or b.startswith("--")): + list_part = unknown_args[last_split:i] + if list_part: + # remove the leading "-" or "--" + arg_name = process_arg_name(list_part[0]) + # if the list_part has only one value, it is seen as a bool var otherwise as list + unknown_args_dict[arg_name] = unpack_list(list_part[1:] if len(list_part) > 1 else True) + last_split = i + + # add last arg + if unknown_args: + arg_name = process_arg_name(unknown_args[last_split]) + unknown_args_dict[arg_name] = unpack_list(unknown_args[last_split+1:] if len(unknown_args[last_split:]) > 1 else True) + + #args=parser.parse_args() + del output + del scene + del mantaflow + del debug + + return args, unknown_args_dict + +# --------------------------------------------------------------------------------------------------------- +def display_dataset(): + parser = argparse.ArgumentParser() + dataset = parser.add_argument_group("Data Set") + dataset.add_argument("--dataset", type=str, required=True, help="Path to already existing data set") + dataset.add_argument("--datasets_path", type=str, required=False, help="Optional path for the datasets directory. Especially useful, if datasets should be read from second disk") + dataset.add_argument("--grids", nargs="*", default=["pressure", "pressure_static", "pressure_dynamic", "vel", "phi_fluid", "density"], choices=["pressure", "pressure_static", "pressure_dynamic", "vel", "phi_fluid", "density"], help="specify the displayed grids.") + + scene = parser.add_argument_group("Scene") + scene.add_argument("-s", "--start_scene", type=int, default=0, help="scene number to start") + scene.add_argument("-n", "--num_scenes", type=int, default=-1, help="number of scenes to display beginning from start_scene. Default of -1 displays all scenes") + scene.add_argument("-t", "--time_to_wait", type=float, default=0.0, help="time to wait in each iteration (additionally to IO)") + scene.add_argument("--simulation_steps", type=int, default=-1, help="number of simulation steps to display for a scene") + scene.add_argument("--skip_steps", type=int, default=1, help="display interval. Value of 1 displays all stored time steps") + + general = parser.add_argument_group("General") + general.add_argument("--video", action="store_true", help="create a video of the dataset content") + + args = parser.parse_args() + del dataset + del scene + del general + + return args diff --git a/tensorflow/mantaGen/util/git.py b/tensorflow/mantaGen/util/git.py new file mode 100644 index 00000000..be93541b --- /dev/null +++ b/tensorflow/mantaGen/util/git.py @@ -0,0 +1,21 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from subprocess import check_output + +def revision(): + return check_output(["git", "rev-parse", "--short", "HEAD"], universal_newlines=True).rstrip() + +def status(): + return check_output(["git", "status", "-s"], universal_newlines=True) + +def is_clean(): + return not bool(status()) \ No newline at end of file diff --git a/tensorflow/mantaGen/util/io.py b/tensorflow/mantaGen/util/io.py new file mode 100644 index 00000000..81bc37e2 --- /dev/null +++ b/tensorflow/mantaGen/util/io.py @@ -0,0 +1,145 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from __future__ import print_function + +from manta import * +import numpy as np +import os.path +from util.path import find_dir, make_dir, get_unique_path + +# IO +#-------------------------------- +class GridIO(object): + """ Store manta grids as npz """ + #---------------------------------------------------------------------------------- + def __init__(self, **kwargs): + self._buffer_list = [] + + #---------------------------------------------------------------------------------- + def _print_grid_info(self, grid): + # e.g. grid_name phi_fluid density + # grid + # grid.__class__ + # grid._class LevelsetGrid Grid + # grid._cname LevelsetGrid Grid + # grid._T --EMPTY-- Real + # grid.getSize() [+64,000000,+64,000000,+1,000000] [+64,000000,+64,000000,+1,000000] + # grid.is3D() False False + print(grid) + print(grid.__class__) + print(grid._class) + print(grid._cname) + print(grid._T) + print(grid.getSize()) + print(grid.is3D()) + #print(inspect.getmembers(grid)) + + #---------------------------------------------------------------------------------- + def _get_grid_description(self, grid): + grid_desc = {} + grid_desc["__class__"] = str(grid.__class__) + grid_desc["__doc__"] = str(grid.__doc__) + grid_desc["_class"] = str(grid._class) + grid_desc["_cname"] = str(grid._cname) + grid_desc["_T"] = str(grid._T) + grid_desc["size"] = str(grid.getSize()) + grid_desc["is3D"] = grid.is3D() + grid_desc["is4D"] = grid.is4D() + return grid_desc + + #---------------------------------------------------------------------------------- + def _get_buffer_handle(self, grid): + #name = grid._cname + size = grid.getSize() + + dimension = 0 + data_type = np.float32 + if grid._class == "Grid": + if grid._T == "Real": + dimension = 1 + data_type = np.float32 + elif grid._T == "int": + dimension = 1 + data_type = np.int32 + elif grid._T == "Vec3": + dimension = 3 + data_type = np.float32 + elif grid._T == "Vec4": + dimension = 4 + data_type = np.float32 + elif grid._class == "FlagGrid": + dimension = 1 + data_type = np.int32 + elif grid._class == "LevelsetGrid": + dimension = 1 + data_type = np.float32 + elif grid._class == "MACGrid": + dimension = 3 + data_type = np.float32 + else: + assert False, "grid._class {} is not supported".format(grid._class) + return + + # search for buffer by comparing shapes (Numpy Format: z,y,x,d | Manta Format: x,y,z,d) + search_shape = (int(size.z), int(size.y), int(size.x), dimension) # resulting tuple for vel, e.g.: (64,64,128,3) + + for i in range(len(self._buffer_list)): + if search_shape == self._buffer_list[i].shape and data_type == self._buffer_list[i].dtype: + # return if exists + return i + + # create new, add to list and return + new_buffer = np.zeros(shape=search_shape, order='C', dtype=data_type) + self._buffer_list.append(new_buffer) + return len(self._buffer_list) - 1 + + #---------------------------------------------------------------------------------- + # Store grids as npz + def write_grid(self, grid, path): + # check for support + if grid._class != "Grid" and grid._class != "FlagGrid" and grid._class != "LevelsetGrid" and grid._class != "MACGrid": + assert False, "grid._class {} is not supported".format(grid._class) + return + # check if path exists + make_dir(os.path.dirname(path)) + # find buffer handle + handle = self._get_buffer_handle(grid) + vec3content = False + # copy grid to buffer + if grid._class == "Grid": + if grid._T == "Real": + copyGridToArrayReal(grid, self._buffer_list[handle]) + elif grid._T == "Vec3": + copyGridToArrayVec3(grid, self._buffer_list[handle]) + vec3content = True + elif grid._class == "FlagGrid": + copyGridToArrayFlag(grid, self._buffer_list[handle]) + elif grid._class == "LevelsetGrid": + copyGridToArrayLevelset(grid, self._buffer_list[handle]) + elif grid._class == "MACGrid": + copyGridToArrayMAC(grid, self._buffer_list[handle]) + vec3content = True + else: + print("Grid is not supported") + self._print_grid_info(grid) + assert False + + # store buffer in npz + grid_desc = self._get_grid_description(grid) + np_grid = self._buffer_list[handle] + is2d = (np_grid.shape[0] == 1) + if vec3content and is2d: + np_grid = np_grid[...,0:2] # remove z component of vectors + if is2d: + np_grid = np.squeeze(np_grid, axis=0) # remove z axis + + np.savez_compressed(path, data=np_grid, header=grid_desc) diff --git a/tensorflow/mantaGen/util/io.py.bak01 b/tensorflow/mantaGen/util/io.py.bak01 new file mode 100644 index 00000000..f26ee60d --- /dev/null +++ b/tensorflow/mantaGen/util/io.py.bak01 @@ -0,0 +1,141 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +from __future__ import print_function + +from manta import * +import numpy as np +import os.path +from util.path import find_dir, make_dir, get_unique_path + +# IO +#-------------------------------- +class GridIO(object): + """ Store manta grids as npz """ + #---------------------------------------------------------------------------------- + def __init__(self, **kwargs): + self._buffer_list = [] + + #---------------------------------------------------------------------------------- + def _print_grid_info(self, grid): + # e.g. grid_name phi_fluid density + # grid + # grid.__class__ + # grid._class LevelsetGrid Grid + # grid._cname LevelsetGrid Grid + # grid._T --EMPTY-- Real + # grid.getSize() [+64,000000,+64,000000,+1,000000] [+64,000000,+64,000000,+1,000000] + # grid.is3D() False False + print(grid) + print(grid.__class__) + print(grid._class) + print(grid._cname) + print(grid._T) + #print(grid.getSize()) + #print(grid.is3D()) + #print(inspect.getmembers(grid)) + + #---------------------------------------------------------------------------------- + def _get_grid_description(self, grid): + grid_desc = {} + grid_desc["__class__"] = str(grid.__class__) + grid_desc["__doc__"] = str(grid.__doc__) + grid_desc["_class"] = str(grid._class) + grid_desc["_cname"] = str(grid._cname) + grid_desc["_T"] = str(grid._T) + grid_desc["size"] = "[64,64,64]" # NT_DEBUG str(grid.getSize()) + grid_desc["is3D"] = "1" # grid.is3D() + grid_desc["is4D"] = "0" # grid.is4D() + return grid_desc + + #---------------------------------------------------------------------------------- + def _get_buffer_handle(self, grid): + #name = grid._cname + size = vec3(64,64,64) # NT_DEBUG grid.getSize() + + dimension = 0 + data_type = np.float32 + if grid._class == "Grid": + if grid._T == "Real": + dimension = 1 + data_type = np.float32 + elif grid._T == "int": + dimension = 1 + data_type = np.int32 + elif grid._T == "Vec3": + dimension = 3 + data_type = np.float32 + elif grid._T == "Vec4": + dimension = 4 + data_type = np.float32 + elif grid._class == "FlagGrid": + dimension = 1 + data_type = np.int32 + elif grid._class == "LevelsetGrid": + dimension = 1 + data_type = np.float32 + elif grid._class == "MACGrid": + dimension = 3 + data_type = np.float32 + else: + assert False, "grid._class {} is not supported".format(grid._class) + return + + # search for buffer by comparing shapes + search_shape = (int(size.x), int(size.y), int(size.z), dimension) # resulting tuple for vel, e.g.: (64,64,128,3) + + for i in range(len(self._buffer_list)): + if search_shape == self._buffer_list[i].shape and data_type == self._buffer_list[i].dtype: + # return if exists + return i + + # create new, add to list and return + new_buffer = np.zeros(shape=search_shape, order='C', dtype=data_type) + self._buffer_list.append(new_buffer) + return len(self._buffer_list) - 1 + + #---------------------------------------------------------------------------------- + # Store grids as npz + def store_grid(self, grid, path): + # check for support + if grid._class != "Grid" and grid._class != "FlagGrid" and grid._class != "LevelsetGrid" and grid._class != "MACGrid": + assert False, "grid._class {} is not supported".format(grid._class) + return + # check if path exists + make_dir(os.path.dirname(path)) + # find buffer handle + handle = self._get_buffer_handle(grid) + # copy grid to buffer + if grid._class == "Grid": + if grid._T == "Real": + copyGridToArrayReal(grid, self._buffer_list[handle]) + elif grid._T == "Vec3": + copyGridToArrayVec3(grid, self._buffer_list[handle]) + elif grid._class == "FlagGrid": + copyGridToArrayFlag(grid, self._buffer_list[handle]) + elif grid._class == "LevelsetGrid": + copyGridToArrayLevelset(grid, self._buffer_list[handle]) + elif grid._class == "MACGrid": + copyGridToArrayMAC(grid, self._buffer_list[handle]) + else: + print("Grid is not supported") + self._print_grid_info(grid) + assert False + + # store buffer in npz + grid_desc = self._get_grid_description(grid) + np_grid = self._buffer_list[handle] + np_grid = np.swapaxes(np_grid, 0, 2) # transfer format from XYZD to ZYXD [YXD] + if np_grid.shape[0] == 1: + np_grid = np.reshape(np_grid, np_grid.shape[1:]) + np.savez_compressed(path, data=np_grid, header=grid_desc) + + diff --git a/tensorflow/mantaGen/util/logger.py b/tensorflow/mantaGen/util/logger.py new file mode 100644 index 00000000..e2d1d6fb --- /dev/null +++ b/tensorflow/mantaGen/util/logger.py @@ -0,0 +1,75 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +# https://stackoverflow.com/questions/31875/is-there-a-simple-elegant-way-to-define-singletons/12850496#12850496 +def singleton(cls): + obj = cls() + cls.__new__ = staticmethod(lambda cls: obj) + try: + del cls.__init__ + except AttributeError: + pass + return cls + + +from enum import Enum +class LogType(Enum): + Info = 0 + Warning = 1 + Error = 2 + Fatal = 3 + +@singleton +class Logger(object): + def __init__(self, mode=LogType.Info, summary=True): + print("Initialized Logger") + self.__mode = mode.value + self.__summary = summary + self.__messages = {} + for entry in LogType: + self.__messages[entry] = [] + def __del__(self): + # print all warnings and errors on application end (depending on mode) + if self.__summary: + print("\nWarning/Error Summary:") + for log_type in LogType: + if log_type.value > self.__mode: + self.print_all(log_type) + def __handle_log(self, log_type, msg): + self.__messages[log_type].append(msg) + if log_type.value >= self.__mode: + format_msg = "[{}] {}".format(log_type.name, msg) + if log_type == LogType.Fatal: + assert False, format_msg + else: + print(format_msg) + def info(self, msg): + self.__handle_log(LogType.Info, msg) + def warning(self, msg): + self.__handle_log(LogType.Warning, msg) + def error(self, msg): + self.__handle_log(LogType.Error, msg) + def fatal(self, msg): + self.__handle_log(LogType.Fatal, msg) + def print_all(self, log_type): + messages = set(self.__messages[log_type]) + counts = [self.__messages[log_type].count(ele) for ele in messages] + for count, msg in zip(counts, messages): + print("[{}] {} [Count: {}]".format(log_type.name, msg, count)) + +def info(msg): + Logger().info(msg) +def warning(msg): + Logger().warning(msg) +def error(msg): + Logger().error(msg) +def fatal(msg): + Logger().fatal(msg) \ No newline at end of file diff --git a/tensorflow/mantaGen/util/path.py b/tensorflow/mantaGen/util/path.py new file mode 100644 index 00000000..8ae49f42 --- /dev/null +++ b/tensorflow/mantaGen/util/path.py @@ -0,0 +1,51 @@ +#****************************************************************************** +# +# MantaGen +# Copyright 2018 Steffen Wiewel, Moritz Becher, Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +#****************************************************************************** + +import os + +def find_dir(dirname, parent_levels=0): + """ + find_dir searches for a directory in the proximity of the working directory with the specified name + It will always search in the subdirectories of the working directory, but can be allowed to search in + parent directories as well. + * __dirname__: the name of the directory to search for + * __parent_levels__: a value of 0 to disable search in parent directories, > 0 max levels of steps upwards in the directory tree + """ + dirs_to_search = ["./"] + for i in range(1, parent_levels + 1): + dirs_to_search.append("../" * i) + for start_dir in map(os.path.abspath, dirs_to_search): + for dirpath, _, _ in os.walk(start_dir): + if dirpath.split(os.path.sep)[-1] == dirname: + return dirpath + raise RuntimeError("Could not find directory '{}'. Check if programm is executed in the right place.".format(dirname)) + +def make_dir(directory): + """ + check if directory exists, otherwise makedir + + (workaround for python2 incompatibility) + """ + if not os.path.exists(directory): + os.makedirs(directory) + +def get_unique_path(path): + """ + create directory with unique name + + if directory already exists, count up until unique name + """ + output_num = 0 + unique_path = path + "_{:06d}".format(output_num) + while os.path.exists(unique_path): + output_num += 1 + unique_path = path + "_{:06d}".format(output_num) + return unique_path diff --git a/tensorflow/mantaGen/util/uniio.py b/tensorflow/mantaGen/util/uniio.py new file mode 100755 index 00000000..b4abb7a2 --- /dev/null +++ b/tensorflow/mantaGen/util/uniio.py @@ -0,0 +1,81 @@ +import os,gzip +import struct +from collections import namedtuple +import glob,re +import numpy as np + +# this is for reading the field +def _read_content(bytestream, head): + #dt = np.dtype(np.float64).newbyteorder('>') + assert (head['bytesPerElement'] == 12 and head['elementType'] == 2) or head['bytesPerElement'] == 4 + if (head['elementType'] == 0): + data = np.frombuffer(bytestream.read(), dtype="int32") + else: + data = np.frombuffer(bytestream.read(), dtype="float32") + if (head['elementType'] == 2): + return data.reshape(head['dimX'], head['dimY'], head['dimZ'], 3, order='C') + else: + return data.reshape(head['dimX'], head['dimY'], head['dimZ'], order='C') + +# read important information in the header such as dimensions and grid type +def _read_head(bytestream): + ID = bytestream.read(4) + #dimX, dimY, dimZ, gridType, elementType, bytesPerElement, info, timestamp = struct.unpack('iiiiii256sQ', bytestream.read(288)) + + # unpack header struct object + head = namedtuple('UniHeader', 'dimX, dimY, dimZ, gridType, elementType, bytesPerElement, info, timestamp') + # convert to namedtuple and then directly to a dict + head = head._asdict(head._make(struct.unpack('iiiiii256sQ', bytestream.read(288)))) + #head['ID'] = ID + + return head + +# use this to read the .uni file. It will return the header as dictionary and the content as np-array +def readuni(filename): + with gzip.open(filename, 'rb') as bytestream: + head = _read_head(bytestream) + content = _read_content(bytestream, head) + + return head, content + +# use this to write a .uni file. The head has to be supplied in the same dictionary format as the output of readuni +def writeuni(filename, head, content): + with gzip.open(filename, 'wb') as bytestream: + # write the head of the uni file + bytestream.write(b'MNT2') + head_tuple = namedtuple('GenericDict', head.keys())(**head) + head_buffer = struct.pack('iiiiii256sQ', *head_tuple) + bytestream.write(head_buffer) + if (head['elementType'] == 2): + content = content.reshape(head['dimX']*head['dimY']*head['dimZ']*3, order='C') + else: + content = content.reshape(head['dimX']*head['dimY']*head['dimZ'], order='C') + + bytestream.write(content.tobytes()) + +def convert_to_npz(dir, file_name, header, quantization=0): + #TODO reactivate assert path.suffix == '.npz', ("Serialization must be to a .npz file") + + fields = [] + # gather file list of all files with ending .uni in dir + dirlist = glob.glob(dir+"*.uni") + + # natural sort the array + convert = lambda text: int(text) if text.isdigit() else text + dirlist.sort(key=lambda k: [ convert(c) for c in re.split('([0-9]+)', k) ] ) + + # load every file remaining in list + for index, filename in enumerate(dirlist): + # extract data from uni file (headers are ignored) + field = readuni(filename)[1] + # if 3d add 4th dimension + if field.ndim == 3: + field = field[...,np.newaxis] + fields.append(field) + fields = np.array(fields) + if quantization != 0: + fields = np.around(fields, decimals=quantization) + + np.savez_compressed(dir + file_name, data=fields, header=header) + for f in glob.glob(dir + "*.uni"): + os.remove(f) diff --git a/tensorflow/tools/numpy2uni.py b/tensorflow/tools/numpy2uni.py new file mode 100644 index 00000000..54f996e8 --- /dev/null +++ b/tensorflow/tools/numpy2uni.py @@ -0,0 +1,73 @@ +#****************************************************************************** +# +# MantaFlow fluid solver framework +# Copyright 2019 Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +# numpty to uni, 2 command line parameters: +# 1) uni input +# 2) npz output +# (distinction between scalar vs vec3 content is determined by npz format, in contrast to uni2numpy.py) +# +#****************************************************************************** + +import sys , os , re , shutil +from manta import * +import numpy as np + +print("Converting npz file '"+sys.argv[1]+ "' to '" + sys.argv[2] + "' ") + +filename = sys.argv[1] +if(os.path.isfile(sys.argv[2])): + print("Error: target already exists") + exit(1) + +a=np.load(filename); +a=a[a.files[-1]] # for npz files , note: not necessary for .npy +print("Read npz file with shape & stats: "+format([a.shape, np.mean(a), np.std(a), np.min(a),np.max(a)] )) +channels = a.shape[-1] # scalar + +if (len(a.shape)==4): + dim=3 + if channels!=1 and channels!=3: + print("Error: invalid no of channels in numpy array (only 1 or 3 allowed)") + exit(1) +elif (len(a.shape)==3): + dim = 2 + if channels!=1 and channels!=2: + print("Error: invalid no of channels in numpy array (only 1 or 2 allowed for 2D)") + exit(1) + if channels==2: + # append third coord for 2d vectors + print("Current shape & stats: "+format([a.shape, np.mean(a),np.std(a), np.min(a),np.max(a)] )) + app = np.zeros([ a.shape[0], a.shape[1] , 1 ]) + a = np.concatenate( [a,app], axis=-1) + print("New shape & stats: "+format([a.shape, np.mean(a),np.std(a), np.min(a),np.max(a)] )) +else: + print("Error: invalid shape") + exit(1) + +gs = vec3(0,0,0) +if dim==3: + gs = vec3( int(a.shape[2]), int(a.shape[1]), int(a.shape[0]) ) # mantaflow uses [x,y,z] +if dim==2: + gs = vec3( int(a.shape[1]), int(a.shape[0]) , 1 ) + +print("Using grid size " + str(gs) + " , dim "+str(dim) ) + +# solver setup +s = Solver(name='main', gridSize = gs, dim=dim) +realGrid = s.create(RealGrid) +vecGrid = s.create(VecGrid) + +if channels==1: + copyArrayToGridReal(source=a, target=realGrid ) + realGrid.save(sys.argv[2]) +else: + copyArrayToGridVec3(source=a, target=vecGrid ) + vecGrid.save(sys.argv[2]) + +print("Wrote '" + sys.argv[2] +"' ") diff --git a/tensorflow/tools/uni2numpy.py b/tensorflow/tools/uni2numpy.py new file mode 100644 index 00000000..fa142cc3 --- /dev/null +++ b/tensorflow/tools/uni2numpy.py @@ -0,0 +1,92 @@ +#****************************************************************************** +# +# MantaFlow fluid solver framework +# Copyright 2019 Nils Thuerey +# +# This program is free software, distributed under the terms of the +# Apache License, Version 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Uni to numpy converter, 3 command line parameters: +# 1) uni input +# 2) npz output +# [3) optional: no of channels, default 1, 3 for vec3] +# +#****************************************************************************** + +import sys , os , re , shutil +from manta import * +import numpy as np + +# test npz files with: +# import sys,numpy as np; a=np.load(filename); a=a[a.files[-1]]; print(format([a.shape, np.mean(a),np.std(a), np.min(a),np.max(a)] )); + +print("Converting uni file '"+sys.argv[1]+ "' to npz '" + sys.argv[2] + "' ") + +filename = sys.argv[1] +if(os.path.isfile(sys.argv[2])): + print("Error: target already exists") + exit(1) + +channels = 1 # scalar +if len(sys.argv)>3: + channels = int(sys.argv[3]) + if channels!=1 and channels!=3: + print("Error: invalid no of channels (only 1 or 3 allowed)") + exit(1) + +def tryToGetSize( filename ): + rfile = filename + size = vec3(0,0,0) + if(os.path.isfile(rfile)): + size = getUniFileSize(rfile) + #print("Tried to read " + str(rfile) + " with " + format(size) ) + return size + +def tryToLoad( grid, filename): + rfile = filename + print("Trying to load " + rfile) + if(os.path.isfile(rfile)): + grid.load(rfile) + #printUniFileInfoString(rfile) # more detailed build info + +# ------------------------------------------------------------------------------------------ +# setup + +gs = vec3(0,0,0) +gs = tryToGetSize( filename ) + +if(gs.x==0): + print("Couldnt get file size!") + exit(1) + +dim = 3 +if (gs.z==1): + dim=2 +print("Using grid size " + str(gs) + " , dim "+str(dim) ) + + +# solver setup +s = Solver(name='main', gridSize = gs, dim=dim) +realGrid = s.create(RealGrid) +vecGrid = s.create(VecGrid) + + +data = np.zeros([ int(gs.z), int(gs.y), int(gs.x), channels ]) +if channels==1: + tryToLoad( realGrid, filename ) + copyGridToArrayReal(source=realGrid, target=data ) +else: + tryToLoad( vecGrid, filename ) + copyGridToArrayVec3(source=vecGrid, target=data ) + if dim==2: + data = data[...,0:2] # remove z components of vec3s + +if dim==2: + data = np.reshape(data, data.shape[1:] ) + +print("Copied to np array "+format(data.shape)) + +np.savez_compressed( sys.argv[2] , data ) +#np.save( sys.argv[2] , data ) # for testing, write uncompressed .npy files +print("Wrote '" + sys.argv[2] +"' ") diff --git a/tools/maya/bobjFluidObject.cpp b/tools/maya/bobjFluidObject.cpp index 0ca2ee44..3ba4ec53 100644 --- a/tools/maya/bobjFluidObject.cpp +++ b/tools/maya/bobjFluidObject.cpp @@ -266,7 +266,7 @@ bool bobjFluidObject::loadMeshData(const MTime& time, MFnMesh meshFn; // open file - gzf = gzopen(filename, "rb1"); + gzf = (gzFile) safeGzopen(filename, "rb1"); if (!gzf) bailOut("cannot open file " << filename); diff --git a/tools/maya/densityloader.cpp b/tools/maya/densityloader.cpp index 553d5e97..41a4bb88 100644 --- a/tools/maya/densityloader.cpp +++ b/tools/maya/densityloader.cpp @@ -221,7 +221,7 @@ void FluidGridObject::loadDensity(const char* mask, int offset, MTime& time, MOb MFnFluid fluid(obj); // read file - gzFile gzf = gzopen(filename, "rb"); + gzFile gzf = (gzFile) safeGzopen(filename, "rb"); if (!gzf) { cerr << "can't open file "<< filename << endl; return;