aboutsummaryrefslogtreecommitdiff
path: root/demo/main.cpp
diff options
context:
space:
mode:
authorMiles Macklin <[email protected]>2017-03-10 14:51:31 +1300
committerMiles Macklin <[email protected]>2017-03-10 14:51:31 +1300
commitad3d90fafe5ee79964bdfe1f1e0704c3ffcdfd5f (patch)
tree4cc6f3288363889d7342f7f8407c0251e6904819 /demo/main.cpp
downloadflex-ad3d90fafe5ee79964bdfe1f1e0704c3ffcdfd5f.tar.xz
flex-ad3d90fafe5ee79964bdfe1f1e0704c3ffcdfd5f.zip
Initial 1.1.0 binary release
Diffstat (limited to 'demo/main.cpp')
-rw-r--r--demo/main.cpp3012
1 files changed, 3012 insertions, 0 deletions
diff --git a/demo/main.cpp b/demo/main.cpp
new file mode 100644
index 0000000..1ee4049
--- /dev/null
+++ b/demo/main.cpp
@@ -0,0 +1,3012 @@
+// This code contains NVIDIA Confidential Information and is disclosed to you
+// under a form of NVIDIA software license agreement provided separately to you.
+//
+// Notice
+// NVIDIA Corporation and its licensors retain all intellectual property and
+// proprietary rights in and to this software and related documentation and
+// any modifications thereto. Any use, reproduction, disclosure, or
+// distribution of this software and related documentation without an express
+// license agreement from NVIDIA Corporation is strictly prohibited.
+//
+// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES
+// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO
+// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT,
+// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE.
+//
+// Information and code furnished is believed to be accurate and reliable.
+// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such
+// information or for any infringement of patents or other rights of third parties that may
+// result from its use. No license is granted by implication or otherwise under any patent
+// or patent rights of NVIDIA Corporation. Details are subject to change without notice.
+// This code supersedes and replaces all information previously supplied.
+// NVIDIA Corporation products are not authorized for use as critical
+// components in life support devices or systems without express written approval of
+// NVIDIA Corporation.
+//
+// Copyright (c) 2013-2017 NVIDIA Corporation. All rights reserved.
+
+#include "../core/types.h"
+#include "../core/maths.h"
+#include "../core/platform.h"
+#include "../core/mesh.h"
+#include "../core/voxelize.h"
+#include "../core/sdf.h"
+#include "../core/pfm.h"
+#include "../core/tga.h"
+#include "../core/perlin.h"
+#include "../core/convex.h"
+#include "../core/cloth.h"
+
+#include "../external/SDL2-2.0.4/include/SDL.h"
+
+#include "../include/NvFlex.h"
+#include "../include/NvFlexExt.h"
+#include "../include/NvFlexDevice.h"
+
+#include <iostream>
+#include <map>
+
+#include "shaders.h"
+#include "imgui.h"
+
+SDL_Window* g_window; // window handle
+unsigned int g_windowId; // window id
+
+#define SDL_CONTROLLER_BUTTON_LEFT_TRIGGER (SDL_CONTROLLER_BUTTON_MAX + 1)
+#define SDL_CONTROLLER_BUTTON_RIGHT_TRIGGER (SDL_CONTROLLER_BUTTON_MAX + 2)
+
+int GetKeyFromGameControllerButton(SDL_GameControllerButton button)
+{
+ switch (button)
+ {
+ case SDL_CONTROLLER_BUTTON_DPAD_UP: { return SDLK_q; } // -- camera translate up
+ case SDL_CONTROLLER_BUTTON_DPAD_DOWN: { return SDLK_z; } // -- camera translate down
+ case SDL_CONTROLLER_BUTTON_DPAD_LEFT: { return SDLK_h; } // -- hide GUI
+ case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: { return -1; } // -- unassigned
+ case SDL_CONTROLLER_BUTTON_START: { return SDLK_RETURN; } // -- start selected scene
+ case SDL_CONTROLLER_BUTTON_BACK: { return SDLK_ESCAPE; } // -- quit
+ case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: { return SDLK_UP; } // -- select prev scene
+ case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: { return SDLK_DOWN; } // -- select next scene
+ case SDL_CONTROLLER_BUTTON_A: { return SDLK_g; } // -- toggle gravity
+ case SDL_CONTROLLER_BUTTON_B: { return SDLK_p; } // -- pause
+ case SDL_CONTROLLER_BUTTON_X: { return SDLK_r; } // -- reset
+ case SDL_CONTROLLER_BUTTON_Y: { return SDLK_o; } // -- step sim
+ case SDL_CONTROLLER_BUTTON_RIGHT_TRIGGER: { return SDLK_SPACE; } // -- emit particles
+ default: { return -1; } // -- nop
+ };
+};
+
+//
+// Gamepad thresholds taken from XINPUT API
+//
+#define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849
+#define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689
+#define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30
+
+int deadzones[3] = { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE, XINPUT_GAMEPAD_TRIGGER_THRESHOLD };
+
+inline float joyAxisFilter(int value, int stick)
+{
+ //clamp values in deadzone to zero, and remap rest of range so that it linearly rises in value from edge of deadzone toward max value.
+ if (value < -deadzones[stick])
+ return (value + deadzones[stick]) / (32768.0f - deadzones[stick]);
+ else if (value > deadzones[stick])
+ return (value - deadzones[stick]) / (32768.0f - deadzones[stick]);
+ else
+ return 0.0f;
+}
+
+SDL_GameController* g_gamecontroller = NULL;
+
+using namespace std;
+
+int g_screenWidth = 1280;
+int g_screenHeight = 720;
+int g_msaaSamples = 8;
+
+int g_numSubsteps;
+
+// a setting of -1 means Flex will use the device specified in the NVIDIA control panel
+int g_device = -1;
+char g_deviceName[256];
+bool g_vsync = true;
+
+bool g_benchmark = false;
+bool g_extensions = true;
+bool g_teamCity = false;
+bool g_interop = true;
+bool g_d3d12 = false;
+
+FluidRenderer* g_fluidRenderer;
+FluidRenderBuffers g_fluidRenderBuffers;
+DiffuseRenderBuffers g_diffuseRenderBuffers;
+
+NvFlexSolver* g_flex;
+NvFlexLibrary* g_flexLib;
+NvFlexParams g_params;
+NvFlexTimers g_timers;
+int g_numDetailTimers;
+NvFlexDetailTimer * g_detailTimers;
+
+int g_maxDiffuseParticles;
+unsigned char g_maxNeighborsPerParticle;
+int g_numExtraParticles;
+int g_numExtraMultiplier = 1;
+
+// mesh used for deformable object rendering
+Mesh* g_mesh;
+vector<int> g_meshSkinIndices;
+vector<float> g_meshSkinWeights;
+vector<Point3> g_meshRestPositions;
+const int g_numSkinWeights = 4;
+
+// mapping of collision mesh to render mesh
+std::map<NvFlexConvexMeshId, GpuMesh*> g_convexes;
+std::map<NvFlexTriangleMeshId, GpuMesh*> g_meshes;
+std::map<NvFlexDistanceFieldId, GpuMesh*> g_fields;
+
+// flag to request collision shapes be updated
+bool g_shapesChanged = false;
+
+struct SimBuffers
+{
+ NvFlexVector<Vec4> positions;
+ NvFlexVector<Vec4> restPositions;
+ NvFlexVector<Vec3> velocities;
+ NvFlexVector<int> phases;
+ NvFlexVector<float> densities;
+ NvFlexVector<Vec4> anisotropy1;
+ NvFlexVector<Vec4> anisotropy2;
+ NvFlexVector<Vec4> anisotropy3;
+ NvFlexVector<Vec4> normals;
+ NvFlexVector<Vec4> smoothPositions;
+ NvFlexVector<Vec4> diffusePositions;
+ NvFlexVector<Vec4> diffuseVelocities;
+ NvFlexVector<int> diffuseIndices;
+ NvFlexVector<int> activeIndices;
+
+ // convexes
+ NvFlexVector<NvFlexCollisionGeometry> shapeGeometry;
+ NvFlexVector<Vec4> shapePositions;
+ NvFlexVector<Quat> shapeRotations;
+ NvFlexVector<Vec4> shapePrevPositions;
+ NvFlexVector<Quat> shapePrevRotations;
+ NvFlexVector<int> shapeFlags;
+
+ // rigids
+ NvFlexVector<int> rigidOffsets;
+ NvFlexVector<int> rigidIndices;
+ NvFlexVector<int> rigidMeshSize;
+ NvFlexVector<float> rigidCoefficients;
+ NvFlexVector<Quat> rigidRotations;
+ NvFlexVector<Vec3> rigidTranslations;
+ NvFlexVector<Vec3> rigidLocalPositions;
+ NvFlexVector<Vec4> rigidLocalNormals;
+
+ // inflatables
+ NvFlexVector<int> inflatableTriOffsets;
+ NvFlexVector<int> inflatableTriCounts;
+ NvFlexVector<float> inflatableVolumes;
+ NvFlexVector<float> inflatableCoefficients;
+ NvFlexVector<float> inflatablePressures;
+
+ // springs
+ NvFlexVector<int> springIndices;
+ NvFlexVector<float> springLengths;
+ NvFlexVector<float> springStiffness;
+
+ NvFlexVector<int> triangles;
+ NvFlexVector<Vec3> triangleNormals;
+ NvFlexVector<Vec3> uvs;
+
+ SimBuffers(NvFlexLibrary* l) :
+ positions(l), restPositions(l), velocities(l), phases(l), densities(l),
+ anisotropy1(l), anisotropy2(l), anisotropy3(l), normals(l), smoothPositions(l),
+ diffusePositions(l), diffuseVelocities(l), diffuseIndices(l), activeIndices(l),
+ shapeGeometry(l), shapePositions(l), shapeRotations(l), shapePrevPositions(l),
+ shapePrevRotations(l), shapeFlags(l), rigidOffsets(l), rigidIndices(l), rigidMeshSize(l),
+ rigidCoefficients(l), rigidRotations(l), rigidTranslations(l),
+ rigidLocalPositions(l), rigidLocalNormals(l), inflatableTriOffsets(l),
+ inflatableTriCounts(l), inflatableVolumes(l), inflatableCoefficients(l),
+ inflatablePressures(l), springIndices(l), springLengths(l),
+ springStiffness(l), triangles(l), triangleNormals(l), uvs(l)
+ {}
+};
+
+SimBuffers* g_buffers;
+
+void MapBuffers(SimBuffers* buffers)
+{
+ buffers->positions.map();
+ buffers->restPositions.map();
+ buffers->velocities.map();
+ buffers->phases.map();
+ buffers->densities.map();
+ buffers->anisotropy1.map();
+ buffers->anisotropy2.map();
+ buffers->anisotropy3.map();
+ buffers->normals.map();
+ buffers->diffusePositions.map();
+ buffers->diffuseVelocities.map();
+ buffers->diffuseIndices.map();
+ buffers->smoothPositions.map();
+ buffers->activeIndices.map();
+
+ // convexes
+ buffers->shapeGeometry.map();
+ buffers->shapePositions.map();
+ buffers->shapeRotations.map();
+ buffers->shapePrevPositions.map();
+ buffers->shapePrevRotations.map();
+ buffers->shapeFlags.map();
+
+ buffers->rigidOffsets.map();
+ buffers->rigidIndices.map();
+ buffers->rigidMeshSize.map();
+ buffers->rigidCoefficients.map();
+ buffers->rigidRotations.map();
+ buffers->rigidTranslations.map();
+ buffers->rigidLocalPositions.map();
+ buffers->rigidLocalNormals.map();
+
+ buffers->springIndices.map();
+ buffers->springLengths.map();
+ buffers->springStiffness.map();
+
+ // inflatables
+ buffers->inflatableTriOffsets.map();
+ buffers->inflatableTriCounts.map();
+ buffers->inflatableVolumes.map();
+ buffers->inflatableCoefficients.map();
+ buffers->inflatablePressures.map();
+
+ buffers->triangles.map();
+ buffers->triangleNormals.map();
+ buffers->uvs.map();
+}
+
+void UnmapBuffers(SimBuffers* buffers)
+{
+ // particles
+ buffers->positions.unmap();
+ buffers->restPositions.unmap();
+ buffers->velocities.unmap();
+ buffers->phases.unmap();
+ buffers->densities.unmap();
+ buffers->anisotropy1.unmap();
+ buffers->anisotropy2.unmap();
+ buffers->anisotropy3.unmap();
+ buffers->normals.unmap();
+ buffers->diffusePositions.unmap();
+ buffers->diffuseVelocities.unmap();
+ buffers->diffuseIndices.unmap();
+ buffers->smoothPositions.unmap();
+ buffers->activeIndices.unmap();
+
+ // convexes
+ buffers->shapeGeometry.unmap();
+ buffers->shapePositions.unmap();
+ buffers->shapeRotations.unmap();
+ buffers->shapePrevPositions.unmap();
+ buffers->shapePrevRotations.unmap();
+ buffers->shapeFlags.unmap();
+
+ // rigids
+ buffers->rigidOffsets.unmap();
+ buffers->rigidIndices.unmap();
+ buffers->rigidMeshSize.unmap();
+ buffers->rigidCoefficients.unmap();
+ buffers->rigidRotations.unmap();
+ buffers->rigidTranslations.unmap();
+ buffers->rigidLocalPositions.unmap();
+ buffers->rigidLocalNormals.unmap();
+
+ // springs
+ buffers->springIndices.unmap();
+ buffers->springLengths.unmap();
+ buffers->springStiffness.unmap();
+
+ // inflatables
+ buffers->inflatableTriOffsets.unmap();
+ buffers->inflatableTriCounts.unmap();
+ buffers->inflatableVolumes.unmap();
+ buffers->inflatableCoefficients.unmap();
+ buffers->inflatablePressures.unmap();
+
+ // triangles
+ buffers->triangles.unmap();
+ buffers->triangleNormals.unmap();
+ buffers->uvs.unmap();
+
+}
+
+SimBuffers* AllocBuffers(NvFlexLibrary* lib)
+{
+ return new SimBuffers(lib);
+}
+
+void DestroyBuffers(SimBuffers* buffers)
+{
+ // particles
+ buffers->positions.destroy();
+ buffers->restPositions.destroy();
+ buffers->velocities.destroy();
+ buffers->phases.destroy();
+ buffers->densities.destroy();
+ buffers->anisotropy1.destroy();
+ buffers->anisotropy2.destroy();
+ buffers->anisotropy3.destroy();
+ buffers->normals.destroy();
+ buffers->diffusePositions.destroy();
+ buffers->diffuseVelocities.destroy();
+ buffers->diffuseIndices.destroy();
+ buffers->smoothPositions.destroy();
+ buffers->activeIndices.destroy();
+
+ // convexes
+ buffers->shapeGeometry.destroy();
+ buffers->shapePositions.destroy();
+ buffers->shapeRotations.destroy();
+ buffers->shapePrevPositions.destroy();
+ buffers->shapePrevRotations.destroy();
+ buffers->shapeFlags.destroy();
+
+ // rigids
+ buffers->rigidOffsets.destroy();
+ buffers->rigidIndices.destroy();
+ buffers->rigidMeshSize.destroy();
+ buffers->rigidCoefficients.destroy();
+ buffers->rigidRotations.destroy();
+ buffers->rigidTranslations.destroy();
+ buffers->rigidLocalPositions.destroy();
+ buffers->rigidLocalNormals.destroy();
+
+ // springs
+ buffers->springIndices.destroy();
+ buffers->springLengths.destroy();
+ buffers->springStiffness.destroy();
+
+ // inflatables
+ buffers->inflatableTriOffsets.destroy();
+ buffers->inflatableTriCounts.destroy();
+ buffers->inflatableVolumes.destroy();
+ buffers->inflatableCoefficients.destroy();
+ buffers->inflatablePressures.destroy();
+
+ // triangles
+ buffers->triangles.destroy();
+ buffers->triangleNormals.destroy();
+ buffers->uvs.destroy();
+
+ delete buffers;
+}
+
+Vec3 g_camPos(6.0f, 8.0f, 18.0f);
+Vec3 g_camAngle(0.0f, -DegToRad(20.0f), 0.0f);
+Vec3 g_camVel(0.0f);
+Vec3 g_camSmoothVel(0.0f);
+
+float g_camSpeed;
+float g_camNear;
+float g_camFar;
+
+Vec3 g_lightPos;
+Vec3 g_lightDir;
+Vec3 g_lightTarget;
+
+bool g_pause = false;
+bool g_step = false;
+bool g_capture = false;
+bool g_showHelp = true;
+bool g_tweakPanel = true;
+bool g_fullscreen = false;
+bool g_wireframe = false;
+bool g_debug = false;
+
+bool g_emit = false;
+bool g_warmup = false;
+
+float g_windTime = 0.0f;
+float g_windFrequency = 0.1f;
+float g_windStrength = 0.0f;
+
+bool g_wavePool = false;
+float g_waveTime = 0.0f;
+float g_wavePlane;
+float g_waveFrequency = 1.5f;
+float g_waveAmplitude = 1.0f;
+float g_waveFloorTilt = 0.0f;
+
+Vec3 g_sceneLower;
+Vec3 g_sceneUpper;
+
+float g_blur;
+float g_ior;
+bool g_drawEllipsoids;
+bool g_drawPoints;
+bool g_drawMesh;
+bool g_drawCloth;
+float g_expandCloth; // amount to expand cloth along normal (to account for particle radius)
+
+bool g_drawOpaque;
+int g_drawSprings; // 0: no draw, 1: draw stretch 2: draw tether
+bool g_drawBases = false;
+bool g_drawContacts = false;
+bool g_drawNormals = false;
+bool g_drawDiffuse;
+bool g_drawShapeGrid = false;
+bool g_drawDensity = false;
+bool g_drawRopes;
+float g_pointScale;
+float g_ropeScale;
+float g_drawPlaneBias; // move planes along their normal for rendering
+
+float g_diffuseScale;
+float g_diffuseMotionScale;
+bool g_diffuseShadow;
+float g_diffuseInscatter;
+float g_diffuseOutscatter;
+
+float g_dt = 1.0f / 60.0f; // the time delta used for simulation
+float g_realdt; // the real world time delta between updates
+
+float g_waitTime; // the CPU time spent waiting for the GPU
+float g_updateTime; // the CPU time spent on Flex
+float g_renderTime; // the CPU time spent calling OpenGL to render the scene
+ // the above times don't include waiting for vsync
+float g_simLatency; // the time the GPU spent between the first and last NvFlexUpdateSolver() operation. Because some GPUs context switch, this can include graphics time.
+
+int g_scene = 0;
+int g_selectedScene = g_scene;
+int g_levelScroll; // offset for level selection scroll area
+bool g_resetScene = false; //if the user clicks the reset button or presses the reset key this is set to true;
+
+int g_frame = 0;
+int g_numSolidParticles = 0;
+
+int g_mouseParticle = -1;
+float g_mouseT = 0.0f;
+Vec3 g_mousePos;
+float g_mouseMass;
+bool g_mousePicked = false;
+
+// mouse
+int g_lastx;
+int g_lasty;
+int g_lastb = -1;
+
+bool g_profile = false;
+
+ShadowMap* g_shadowMap;
+
+Vec4 g_fluidColor;
+Vec4 g_diffuseColor;
+Vec3 g_meshColor;
+Vec3 g_clearColor;
+float g_lightDistance;
+float g_fogDistance;
+
+FILE* g_ffmpeg;
+
+void DrawShapes();
+
+class Scene;
+vector<Scene*> g_scenes;
+
+struct Emitter
+{
+ Emitter() : mSpeed(0.0f), mEnabled(false), mLeftOver(0.0f), mWidth(8) {}
+
+ Vec3 mPos;
+ Vec3 mDir;
+ Vec3 mRight;
+ float mSpeed;
+ bool mEnabled;
+ float mLeftOver;
+ int mWidth;
+};
+
+vector<Emitter> g_emitters(1); // first emitter is the camera 'gun'
+
+struct Rope
+{
+ std::vector<int> mIndices;
+};
+
+vector<Rope> g_ropes;
+
+inline float sqr(float x) { return x*x; }
+
+#include "helpers.h"
+#include "scenes.h"
+#include "benchmark.h"
+
+void Init(int scene, bool centerCamera = true)
+{
+ RandInit();
+
+ if (g_flex)
+ {
+ if (g_buffers)
+ DestroyBuffers(g_buffers);
+
+ DestroyFluidRenderBuffers(g_fluidRenderBuffers);
+ DestroyDiffuseRenderBuffers(g_diffuseRenderBuffers);
+
+ for (auto& iter : g_meshes)
+ {
+ NvFlexDestroyTriangleMesh(g_flexLib, iter.first);
+ DestroyGpuMesh(iter.second);
+ }
+
+ for (auto& iter : g_fields)
+ {
+ NvFlexDestroyDistanceField(g_flexLib, iter.first);
+ DestroyGpuMesh(iter.second);
+ }
+
+ for (auto& iter : g_convexes)
+ {
+ NvFlexDestroyConvexMesh(g_flexLib, iter.first);
+ DestroyGpuMesh(iter.second);
+ }
+
+
+ g_fields.clear();
+ g_meshes.clear();
+ g_convexes.clear();
+
+ NvFlexDestroySolver(g_flex);
+ g_flex = NULL;
+ }
+
+ // alloc buffers
+ g_buffers = AllocBuffers(g_flexLib);
+
+ // map during initialization
+ MapBuffers(g_buffers);
+
+ g_buffers->positions.resize(0);
+ g_buffers->velocities.resize(0);
+ g_buffers->phases.resize(0);
+
+ g_buffers->rigidOffsets.resize(0);
+ g_buffers->rigidIndices.resize(0);
+ g_buffers->rigidMeshSize.resize(0);
+ g_buffers->rigidRotations.resize(0);
+ g_buffers->rigidTranslations.resize(0);
+ g_buffers->rigidCoefficients.resize(0);
+ g_buffers->rigidLocalPositions.resize(0);
+ g_buffers->rigidLocalNormals.resize(0);
+
+ g_buffers->springIndices.resize(0);
+ g_buffers->springLengths.resize(0);
+ g_buffers->springStiffness.resize(0);
+ g_buffers->triangles.resize(0);
+ g_buffers->triangleNormals.resize(0);
+ g_buffers->uvs.resize(0);
+
+ g_meshSkinIndices.resize(0);
+ g_meshSkinWeights.resize(0);
+
+ g_emitters.resize(1);
+ g_emitters[0].mEnabled = false;
+ g_emitters[0].mSpeed = 1.0f;
+
+ g_buffers->shapeGeometry.resize(0);
+ g_buffers->shapePositions.resize(0);
+ g_buffers->shapeRotations.resize(0);
+ g_buffers->shapePrevPositions.resize(0);
+ g_buffers->shapePrevRotations.resize(0);
+ g_buffers->shapeFlags.resize(0);
+
+ g_ropes.resize(0);
+
+ // remove collision shapes
+ delete g_mesh; g_mesh = NULL;
+
+ g_frame = 0;
+ g_pause = false;
+
+ g_dt = 1.0f / 60.0f;
+ g_waveTime = 0.0f;
+ g_windTime = 0.0f;
+ g_windStrength = 1.0f;
+
+ g_blur = 1.0f;
+ g_fluidColor = Vec4(0.1f, 0.4f, 0.8f, 1.0f);
+ g_meshColor = Vec3(0.9f, 0.9f, 0.9f);
+ g_drawEllipsoids = false;
+ g_drawPoints = true;
+ g_drawCloth = true;
+ g_expandCloth = 0.0f;
+
+ g_drawOpaque = false;
+ g_drawSprings = false;
+ g_drawDiffuse = false;
+ g_drawMesh = true;
+ g_drawRopes = true;
+ g_drawDensity = false;
+ g_ior = 1.0f;
+ g_lightDistance = 2.0f;
+ g_fogDistance = 0.005f;
+
+ g_camSpeed = 0.075f;
+ g_camNear = 0.01f;
+ g_camFar = 1000.0f;
+
+ g_pointScale = 1.0f;
+ g_ropeScale = 1.0f;
+ g_drawPlaneBias = 0.0f;
+
+ // sim params
+ g_params.gravity[0] = 0.0f;
+ g_params.gravity[1] = -9.8f;
+ g_params.gravity[2] = 0.0f;
+
+ g_params.wind[0] = 0.0f;
+ g_params.wind[1] = 0.0f;
+ g_params.wind[2] = 0.0f;
+
+ g_params.radius = 0.15f;
+ g_params.viscosity = 0.0f;
+ g_params.dynamicFriction = 0.0f;
+ g_params.staticFriction = 0.0f;
+ g_params.particleFriction = 0.0f; // scale friction between particles by default
+ g_params.freeSurfaceDrag = 0.0f;
+ g_params.drag = 0.0f;
+ g_params.lift = 0.0f;
+ g_params.numIterations = 3;
+ g_params.fluidRestDistance = 0.0f;
+ g_params.solidRestDistance = 0.0f;
+
+ g_params.anisotropyScale = 1.0f;
+ g_params.anisotropyMin = 0.1f;
+ g_params.anisotropyMax = 2.0f;
+ g_params.smoothing = 1.0f;
+
+ g_params.dissipation = 0.0f;
+ g_params.damping = 0.0f;
+ g_params.particleCollisionMargin = 0.0f;
+ g_params.shapeCollisionMargin = 0.0f;
+ g_params.collisionDistance = 0.0f;
+ g_params.plasticThreshold = 0.0f;
+ g_params.plasticCreep = 0.0f;
+ g_params.fluid = false;
+ g_params.sleepThreshold = 0.0f;
+ g_params.shockPropagation = 0.0f;
+ g_params.restitution = 0.0f;
+
+ g_params.maxSpeed = FLT_MAX;
+ g_params.maxAcceleration = 100.0f; // approximately 10x gravity
+
+ g_params.relaxationMode = eNvFlexRelaxationLocal;
+ g_params.relaxationFactor = 1.0f;
+ g_params.solidPressure = 1.0f;
+ g_params.adhesion = 0.0f;
+ g_params.cohesion = 0.025f;
+ g_params.surfaceTension = 0.0f;
+ g_params.vorticityConfinement = 0.0f;
+ g_params.buoyancy = 1.0f;
+ g_params.diffuseThreshold = 100.0f;
+ g_params.diffuseBuoyancy = 1.0f;
+ g_params.diffuseDrag = 0.8f;
+ g_params.diffuseBallistic = 16;
+ g_params.diffuseSortAxis[0] = 0.0f;
+ g_params.diffuseSortAxis[1] = 0.0f;
+ g_params.diffuseSortAxis[2] = 0.0f;
+ g_params.diffuseLifetime = 2.0f;
+
+ g_numSubsteps = 2;
+
+ // planes created after particles
+ g_params.numPlanes = 1;
+
+ g_diffuseScale = 0.5f;
+ g_diffuseColor = 1.0f;
+ g_diffuseMotionScale = 1.0f;
+ g_diffuseShadow = false;
+ g_diffuseInscatter = 0.8f;
+ g_diffuseOutscatter = 0.53f;
+
+ // reset phase 0 particle color to blue
+ extern Colour gColors[];
+ gColors[0] = Colour(0.0f, 0.5f, 1.0f);
+
+ g_numSolidParticles = 0;
+
+ g_waveFrequency = 1.5f;
+ g_waveAmplitude = 1.5f;
+ g_waveFloorTilt = 0.0f;
+ g_emit = false;
+ g_warmup = false;
+
+ g_mouseParticle = -1;
+
+ g_maxDiffuseParticles = 0; // number of diffuse particles
+ g_maxNeighborsPerParticle = 96;
+ g_numExtraParticles = 0; // number of particles allocated but not made active
+
+ g_sceneLower = FLT_MAX;
+ g_sceneUpper = -FLT_MAX;
+
+ // create scene
+ g_scenes[g_scene]->Initialize();
+
+ uint32_t numParticles = g_buffers->positions.size();
+ uint32_t maxParticles = numParticles + g_numExtraParticles*g_numExtraMultiplier;
+
+ // by default solid particles use the maximum radius
+ if (g_params.fluid && g_params.solidRestDistance == 0.0f)
+ g_params.solidRestDistance = g_params.fluidRestDistance;
+ else
+ g_params.solidRestDistance = g_params.radius;
+
+ // collision distance with shapes half the radius
+ if (g_params.collisionDistance == 0.0f)
+ {
+ g_params.collisionDistance = g_params.radius*0.5f;
+
+ if (g_params.fluid)
+ g_params.collisionDistance = g_params.fluidRestDistance*0.5f;
+ }
+
+ // default particle friction to 10% of shape friction
+ if (g_params.particleFriction == 0.0f)
+ g_params.particleFriction = g_params.dynamicFriction*0.1f;
+
+ // add a margin for detecting contacts between particles and shapes
+ if (g_params.shapeCollisionMargin == 0.0f)
+ g_params.shapeCollisionMargin = g_params.collisionDistance*0.5f;
+
+ // calculate particle bounds
+ Vec3 particleLower, particleUpper;
+ GetParticleBounds(particleLower, particleUpper);
+
+ // accommodate shapes
+ Vec3 shapeLower, shapeUpper;
+ GetShapeBounds(shapeLower, shapeUpper);
+
+ // update bounds
+ g_sceneLower = Min(Min(g_sceneLower, particleLower), shapeLower);
+ g_sceneUpper = Max(Max(g_sceneUpper, particleUpper), shapeUpper);
+
+ g_sceneLower -= g_params.collisionDistance;
+ g_sceneUpper += g_params.collisionDistance;
+
+ // update collision planes to match flexs
+ Vec3 up = Normalize(Vec3(-g_waveFloorTilt, 1.0f, 0.0f));
+
+ (Vec4&)g_params.planes[0] = Vec4(up.x, up.y, up.z, 0.0f);
+ (Vec4&)g_params.planes[1] = Vec4(0.0f, 0.0f, 1.0f, -g_sceneLower.z);
+ (Vec4&)g_params.planes[2] = Vec4(1.0f, 0.0f, 0.0f, -g_sceneLower.x);
+ (Vec4&)g_params.planes[3] = Vec4(-1.0f, 0.0f, 0.0f, g_sceneUpper.x);
+ (Vec4&)g_params.planes[4] = Vec4(0.0f, 0.0f, -1.0f, g_sceneUpper.z);
+ (Vec4&)g_params.planes[5] = Vec4(0.0f, -1.0f, 0.0f, g_sceneUpper.y);
+
+ g_wavePlane = g_params.planes[2][3];
+
+ g_buffers->diffusePositions.resize(g_maxDiffuseParticles);
+ g_buffers->diffuseVelocities.resize(g_maxDiffuseParticles);
+ g_buffers->diffuseIndices.resize(g_maxDiffuseParticles);
+
+ // for fluid rendering these are the Laplacian smoothed positions
+ g_buffers->smoothPositions.resize(maxParticles);
+
+ g_buffers->normals.resize(0);
+ g_buffers->normals.resize(maxParticles);
+
+ // initialize normals (just for rendering before simulation starts)
+ int numTris = g_buffers->triangles.size() / 3;
+ for (int i = 0; i < numTris; ++i)
+ {
+ Vec3 v0 = Vec3(g_buffers->positions[g_buffers->triangles[i * 3 + 0]]);
+ Vec3 v1 = Vec3(g_buffers->positions[g_buffers->triangles[i * 3 + 1]]);
+ Vec3 v2 = Vec3(g_buffers->positions[g_buffers->triangles[i * 3 + 2]]);
+
+ Vec3 n = Cross(v1 - v0, v2 - v0);
+
+ g_buffers->normals[g_buffers->triangles[i * 3 + 0]] += Vec4(n, 0.0f);
+ g_buffers->normals[g_buffers->triangles[i * 3 + 1]] += Vec4(n, 0.0f);
+ g_buffers->normals[g_buffers->triangles[i * 3 + 2]] += Vec4(n, 0.0f);
+ }
+
+ for (int i = 0; i < int(maxParticles); ++i)
+ g_buffers->normals[i] = Vec4(SafeNormalize(Vec3(g_buffers->normals[i]), Vec3(0.0f, 1.0f, 0.0f)), 0.0f);
+
+
+ // save mesh positions for skinning
+ if (g_mesh)
+ {
+ g_meshRestPositions = g_mesh->m_positions;
+ }
+ else
+ {
+ g_meshRestPositions.resize(0);
+ }
+
+ // main create method for the Flex solver
+ g_flex = NvFlexCreateSolver(g_flexLib, maxParticles, g_maxDiffuseParticles, g_maxNeighborsPerParticle);
+
+ // give scene a chance to do some post solver initialization
+ g_scenes[g_scene]->PostInitialize();
+
+ // center camera on particles
+ if (centerCamera)
+ {
+ g_camPos = Vec3((g_sceneLower.x + g_sceneUpper.x)*0.5f, min(g_sceneUpper.y*1.25f, 6.0f), g_sceneUpper.z + min(g_sceneUpper.y, 6.0f)*2.0f);
+ g_camAngle = Vec3(0.0f, -DegToRad(15.0f), 0.0f);
+
+ // give scene a chance to modify camera position
+ g_scenes[g_scene]->CenterCamera();
+ }
+
+ // create active indices (just a contiguous block for the demo)
+ g_buffers->activeIndices.resize(g_buffers->positions.size());
+ for (int i = 0; i < g_buffers->activeIndices.size(); ++i)
+ g_buffers->activeIndices[i] = i;
+
+ // resize particle buffers to fit
+ g_buffers->positions.resize(maxParticles);
+ g_buffers->velocities.resize(maxParticles);
+ g_buffers->phases.resize(maxParticles);
+
+ g_buffers->densities.resize(maxParticles);
+ g_buffers->anisotropy1.resize(maxParticles);
+ g_buffers->anisotropy2.resize(maxParticles);
+ g_buffers->anisotropy3.resize(maxParticles);
+
+ // save rest positions
+ g_buffers->restPositions.resize(g_buffers->positions.size());
+ for (int i = 0; i < g_buffers->positions.size(); ++i)
+ g_buffers->restPositions[i] = g_buffers->positions[i];
+
+ // builds rigids constraints
+ if (g_buffers->rigidOffsets.size())
+ {
+ assert(g_buffers->rigidOffsets.size() > 1);
+
+ const int numRigids = g_buffers->rigidOffsets.size() - 1;
+
+ // calculate local rest space positions
+ g_buffers->rigidLocalPositions.resize(g_buffers->rigidOffsets.back());
+ CalculateRigidLocalPositions(&g_buffers->positions[0], g_buffers->positions.size(), &g_buffers->rigidOffsets[0], &g_buffers->rigidIndices[0], numRigids, &g_buffers->rigidLocalPositions[0]);
+
+ g_buffers->rigidRotations.resize(g_buffers->rigidOffsets.size() - 1, Quat());
+ g_buffers->rigidTranslations.resize(g_buffers->rigidOffsets.size() - 1, Vec3());
+
+ }
+
+ // unmap so we can start transferring data to GPU
+ UnmapBuffers(g_buffers);
+
+ //-----------------------------
+ // Send data to Flex
+
+ NvFlexSetParams(g_flex, &g_params);
+ NvFlexSetParticles(g_flex, g_buffers->positions.buffer, numParticles);
+ NvFlexSetVelocities(g_flex, g_buffers->velocities.buffer, numParticles);
+ NvFlexSetNormals(g_flex, g_buffers->normals.buffer, numParticles);
+ NvFlexSetPhases(g_flex, g_buffers->phases.buffer, g_buffers->phases.size());
+ NvFlexSetRestParticles(g_flex, g_buffers->restPositions.buffer, g_buffers->restPositions.size());
+
+ NvFlexSetActive(g_flex, g_buffers->activeIndices.buffer, numParticles);
+
+ // springs
+ if (g_buffers->springIndices.size())
+ {
+ assert((g_buffers->springIndices.size() & 1) == 0);
+ assert((g_buffers->springIndices.size() / 2) == g_buffers->springLengths.size());
+
+ NvFlexSetSprings(g_flex, g_buffers->springIndices.buffer, g_buffers->springLengths.buffer, g_buffers->springStiffness.buffer, g_buffers->springLengths.size());
+ }
+
+ // rigids
+ if (g_buffers->rigidOffsets.size())
+ {
+ NvFlexSetRigids(g_flex, g_buffers->rigidOffsets.buffer, g_buffers->rigidIndices.buffer, g_buffers->rigidLocalPositions.buffer, g_buffers->rigidLocalNormals.buffer, g_buffers->rigidCoefficients.buffer, g_buffers->rigidRotations.buffer, g_buffers->rigidTranslations.buffer, g_buffers->rigidOffsets.size() - 1, g_buffers->rigidIndices.size());
+ }
+
+ // inflatables
+ if (g_buffers->inflatableTriOffsets.size())
+ {
+ NvFlexSetInflatables(g_flex, g_buffers->inflatableTriOffsets.buffer, g_buffers->inflatableTriCounts.buffer, g_buffers->inflatableVolumes.buffer, g_buffers->inflatablePressures.buffer, g_buffers->inflatableCoefficients.buffer, g_buffers->inflatableTriOffsets.size());
+ }
+
+ // dynamic triangles
+ if (g_buffers->triangles.size())
+ {
+ NvFlexSetDynamicTriangles(g_flex, g_buffers->triangles.buffer, g_buffers->triangleNormals.buffer, g_buffers->triangles.size() / 3);
+ }
+
+ // collision shapes
+ if (g_buffers->shapeFlags.size())
+ {
+ NvFlexSetShapes(
+ g_flex,
+ g_buffers->shapeGeometry.buffer,
+ g_buffers->shapePositions.buffer,
+ g_buffers->shapeRotations.buffer,
+ g_buffers->shapePrevPositions.buffer,
+ g_buffers->shapePrevRotations.buffer,
+ g_buffers->shapeFlags.buffer,
+ int(g_buffers->shapeFlags.size()));
+ }
+
+ // create render buffers
+ g_fluidRenderBuffers = CreateFluidRenderBuffers(maxParticles, g_interop);
+ g_diffuseRenderBuffers = CreateDiffuseRenderBuffers(g_maxDiffuseParticles, g_interop);
+
+ // perform initial sim warm up
+ if (g_warmup)
+ {
+ printf("Warming up sim..\n");
+
+ // warm it up (relax positions to reach rest density without affecting velocity)
+ NvFlexParams copy = g_params;
+ copy.numIterations = 4;
+
+ NvFlexSetParams(g_flex, &copy);
+
+ const int kWarmupIterations = 100;
+
+ for (int i = 0; i < kWarmupIterations; ++i)
+ {
+ NvFlexUpdateSolver(g_flex, 0.0001f, 1, false);
+ NvFlexSetVelocities(g_flex, g_buffers->velocities.buffer, maxParticles);
+ }
+
+ // udpate host copy
+ NvFlexGetParticles(g_flex, g_buffers->positions.buffer, g_buffers->positions.size());
+ NvFlexGetSmoothParticles(g_flex, g_buffers->smoothPositions.buffer, g_buffers->smoothPositions.size());
+ NvFlexGetAnisotropy(g_flex, g_buffers->anisotropy1.buffer, g_buffers->anisotropy2.buffer, g_buffers->anisotropy3.buffer);
+
+ printf("Finished warm up.\n");
+ }
+}
+
+void Reset()
+{
+ Init(g_scene, false);
+}
+
+void Shutdown()
+{
+ // free buffers
+ DestroyBuffers(g_buffers);
+
+ for (auto& iter : g_meshes)
+ {
+ NvFlexDestroyTriangleMesh(g_flexLib, iter.first);
+ DestroyGpuMesh(iter.second);
+ }
+
+ for (auto& iter : g_fields)
+ {
+ NvFlexDestroyDistanceField(g_flexLib, iter.first);
+ DestroyGpuMesh(iter.second);
+ }
+
+ for (auto& iter : g_convexes)
+ {
+ NvFlexDestroyConvexMesh(g_flexLib, iter.first);
+ DestroyGpuMesh(iter.second);
+ }
+
+ g_fields.clear();
+ g_meshes.clear();
+
+ NvFlexDestroySolver(g_flex);
+ NvFlexShutdown(g_flexLib);
+
+#if _WIN32
+ if (g_ffmpeg)
+ _pclose(g_ffmpeg);
+#endif
+}
+
+void UpdateEmitters()
+{
+ float spin = DegToRad(15.0f);
+
+ const Vec3 forward(-sinf(g_camAngle.x + spin)*cosf(g_camAngle.y), sinf(g_camAngle.y), -cosf(g_camAngle.x + spin)*cosf(g_camAngle.y));
+ const Vec3 right(Normalize(Cross(forward, Vec3(0.0f, 1.0f, 0.0f))));
+
+ g_emitters[0].mDir = Normalize(forward + Vec3(0.0, 0.4f, 0.0f));
+ g_emitters[0].mRight = right;
+ g_emitters[0].mPos = g_camPos + forward*1.f + Vec3(0.0f, 0.2f, 0.0f) + right*0.65f;
+
+ // process emitters
+ if (g_emit)
+ {
+ int activeCount = NvFlexGetActiveCount(g_flex);
+
+ size_t e = 0;
+
+ // skip camera emitter when moving forward or things get messy
+ if (g_camSmoothVel.z >= 0.025f)
+ e = 1;
+
+ for (; e < g_emitters.size(); ++e)
+ {
+ if (!g_emitters[e].mEnabled)
+ continue;
+
+ Vec3 emitterDir = g_emitters[e].mDir;
+ Vec3 emitterRight = g_emitters[e].mRight;
+ Vec3 emitterPos = g_emitters[e].mPos;
+
+ float r;
+ int phase;
+
+ if (g_params.fluid)
+ {
+ r = g_params.fluidRestDistance;
+ phase = NvFlexMakePhase(0, eNvFlexPhaseSelfCollide | eNvFlexPhaseFluid);
+ }
+ else
+ {
+ r = g_params.solidRestDistance;
+ phase = NvFlexMakePhase(0, eNvFlexPhaseSelfCollide);
+ }
+
+ float numParticles = (g_emitters[e].mSpeed / r)*g_dt;
+
+ // whole number to emit
+ int n = int(numParticles + g_emitters[e].mLeftOver);
+
+ if (n)
+ g_emitters[e].mLeftOver = (numParticles + g_emitters[e].mLeftOver) - n;
+ else
+ g_emitters[e].mLeftOver += numParticles;
+
+ // create a grid of particles (n particles thick)
+ for (int k = 0; k < n; ++k)
+ {
+ int emitterWidth = g_emitters[e].mWidth;
+ int numParticles = emitterWidth*emitterWidth;
+ for (int i = 0; i < numParticles; ++i)
+ {
+ float x = float(i%emitterWidth) - float(emitterWidth/2);
+ float y = float((i / emitterWidth) % emitterWidth) - float(emitterWidth/2);
+
+ if ((sqr(x) + sqr(y)) <= (emitterWidth / 2)*(emitterWidth / 2))
+ {
+ Vec3 up = Normalize(Cross(emitterDir, emitterRight));
+ Vec3 offset = r*(emitterRight*x + up*y) + float(k)*emitterDir*r;
+
+ if (activeCount < g_buffers->positions.size())
+ {
+ g_buffers->positions[activeCount] = Vec4(emitterPos + offset, 1.0f);
+ g_buffers->velocities[activeCount] = emitterDir*g_emitters[e].mSpeed;
+ g_buffers->phases[activeCount] = phase;
+
+ g_buffers->activeIndices.push_back(activeCount);
+
+ activeCount++;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void UpdateCamera()
+{
+ Vec3 forward(-sinf(g_camAngle.x)*cosf(g_camAngle.y), sinf(g_camAngle.y), -cosf(g_camAngle.x)*cosf(g_camAngle.y));
+ Vec3 right(Normalize(Cross(forward, Vec3(0.0f, 1.0f, 0.0f))));
+
+ g_camSmoothVel = Lerp(g_camSmoothVel, g_camVel, 0.1f);
+ g_camPos += (forward*g_camSmoothVel.z + right*g_camSmoothVel.x + Cross(right, forward)*g_camSmoothVel.y);
+}
+
+void UpdateMouse()
+{
+ // mouse button is up release particle
+ if (g_lastb == -1)
+ {
+ if (g_mouseParticle != -1)
+ {
+ // restore particle mass
+ g_buffers->positions[g_mouseParticle].w = g_mouseMass;
+
+ // deselect
+ g_mouseParticle = -1;
+ }
+ }
+
+ // mouse went down, pick new particle
+ if (g_mousePicked)
+ {
+ assert(g_mouseParticle == -1);
+
+ Vec3 origin, dir;
+ GetViewRay(g_lastx, g_screenHeight - g_lasty, origin, dir);
+
+ const int numActive = NvFlexGetActiveCount(g_flex);
+
+ g_mouseParticle = PickParticle(origin, dir, &g_buffers->positions[0], &g_buffers->phases[0], numActive, g_params.radius*0.8f, g_mouseT);
+
+ if (g_mouseParticle != -1)
+ {
+ printf("picked: %d, mass: %f v: %f %f %f\n", g_mouseParticle, g_buffers->positions[g_mouseParticle].w, g_buffers->velocities[g_mouseParticle].x, g_buffers->velocities[g_mouseParticle].y, g_buffers->velocities[g_mouseParticle].z);
+
+ g_mousePos = origin + dir*g_mouseT;
+ g_mouseMass = g_buffers->positions[g_mouseParticle].w;
+ g_buffers->positions[g_mouseParticle].w = 0.0f; // increase picked particle's mass to force it towards the point
+ }
+
+ g_mousePicked = false;
+ }
+
+ // update picked particle position
+ if (g_mouseParticle != -1)
+ {
+ Vec3 p = Lerp(Vec3(g_buffers->positions[g_mouseParticle]), g_mousePos, 0.8f);
+ Vec3 delta = p - Vec3(g_buffers->positions[g_mouseParticle]);
+
+ g_buffers->positions[g_mouseParticle].x = p.x;
+ g_buffers->positions[g_mouseParticle].y = p.y;
+ g_buffers->positions[g_mouseParticle].z = p.z;
+
+ g_buffers->velocities[g_mouseParticle].x = delta.x / g_dt;
+ g_buffers->velocities[g_mouseParticle].y = delta.y / g_dt;
+ g_buffers->velocities[g_mouseParticle].z = delta.z / g_dt;
+ }
+}
+
+void UpdateWind()
+{
+ g_windTime += g_dt;
+
+ const Vec3 kWindDir = Vec3(3.0f, 15.0f, 0.0f);
+ const float kNoise = Perlin1D(g_windTime*g_windFrequency, 10, 0.25f);
+ Vec3 wind = g_windStrength*kWindDir*Vec3(kNoise, fabsf(kNoise), 0.0f);
+
+ g_params.wind[0] = wind.x;
+ g_params.wind[1] = wind.y;
+ g_params.wind[2] = wind.z;
+
+ if (g_wavePool)
+ {
+ g_waveTime += g_dt;
+
+ g_params.planes[2][3] = g_wavePlane + (sinf(float(g_waveTime)*g_waveFrequency - kPi*0.5f)*0.5f + 0.5f)*g_waveAmplitude;
+ }
+}
+
+void SyncScene()
+{
+ // let the scene send updates to flex directly
+ g_scenes[g_scene]->Sync();
+}
+
+void UpdateScene()
+{
+ // give scene a chance to make changes to particle buffers
+ g_scenes[g_scene]->Update();
+}
+
+void RenderScene()
+{
+ const int numParticles = NvFlexGetActiveCount(g_flex);
+ const int numDiffuse = NvFlexGetDiffuseParticles(g_flex, NULL, NULL, NULL);
+
+ //---------------------------------------------------
+ // use VBO buffer wrappers to allow Flex to write directly to the OpenGL buffers
+ // Flex will take care of any CUDA interop mapping/unmapping during the get() operations
+
+ if (numParticles)
+ {
+
+ if (g_interop)
+ {
+ // copy data directly from solver to the renderer buffers
+ UpdateFluidRenderBuffers(g_fluidRenderBuffers, g_flex, g_drawEllipsoids, g_drawDensity);
+ }
+ else
+ {
+ // copy particle data to GPU render device
+
+ if (g_drawEllipsoids)
+ {
+ // if fluid surface rendering then update with smooth positions and anisotropy
+ UpdateFluidRenderBuffers(g_fluidRenderBuffers,
+ &g_buffers->smoothPositions[0],
+ (g_drawDensity) ? &g_buffers->densities[0] : (float*)&g_buffers->phases[0],
+ &g_buffers->anisotropy1[0],
+ &g_buffers->anisotropy2[0],
+ &g_buffers->anisotropy3[0],
+ g_buffers->positions.size(),
+ &g_buffers->activeIndices[0],
+ numParticles);
+ }
+ else
+ {
+ // otherwise just send regular positions and no anisotropy
+ UpdateFluidRenderBuffers(g_fluidRenderBuffers,
+ &g_buffers->positions[0],
+ (float*)&g_buffers->phases[0],
+ NULL, NULL, NULL,
+ g_buffers->positions.size(),
+ &g_buffers->activeIndices[0],
+ numParticles);
+ }
+ }
+ }
+
+ if (numDiffuse)
+ {
+ if (g_interop)
+ {
+ // copy data directly from solver to the renderer buffers
+ UpdateDiffuseRenderBuffers(g_diffuseRenderBuffers, g_flex);
+ }
+ else
+ {
+ // copy diffuse particle data from host to GPU render device
+ UpdateDiffuseRenderBuffers(g_diffuseRenderBuffers,
+ &g_buffers->diffusePositions[0],
+ &g_buffers->diffuseVelocities[0],
+ &g_buffers->diffuseIndices[0],
+ numDiffuse);
+ }
+ }
+
+ //---------------------------------------
+ // setup view and state
+
+ float fov = kPi / 4.0f;
+ float aspect = float(g_screenWidth) / g_screenHeight;
+
+ Matrix44 proj = ProjectionMatrix(RadToDeg(fov), aspect, g_camNear, g_camFar);
+ Matrix44 view = RotationMatrix(-g_camAngle.x, Vec3(0.0f, 1.0f, 0.0f))*RotationMatrix(-g_camAngle.y, Vec3(cosf(-g_camAngle.x), 0.0f, sinf(-g_camAngle.x)))*TranslationMatrix(-Point3(g_camPos));
+
+ //------------------------------------
+ // lighting pass
+
+ // expand scene bounds to fit most scenes
+ g_sceneLower = Min(g_sceneLower, Vec3(-2.0f, 0.0f, -2.0f));
+ g_sceneUpper = Max(g_sceneUpper, Vec3(2.0f, 2.0f, 2.0f));
+
+ Vec3 sceneExtents = g_sceneUpper - g_sceneLower;
+ Vec3 sceneCenter = 0.5f*(g_sceneUpper + g_sceneLower);
+
+ g_lightDir = Normalize(Vec3(5.0f, 15.0f, 7.5f));
+ g_lightPos = sceneCenter + g_lightDir*Length(sceneExtents)*g_lightDistance;
+ g_lightTarget = sceneCenter;
+
+ // calculate tight bounds for shadow frustum
+ float lightFov = 2.0f*atanf(Length(g_sceneUpper - sceneCenter) / Length(g_lightPos - sceneCenter));
+
+ // scale and clamp fov for aesthetics
+ lightFov = Clamp(lightFov, DegToRad(25.0f), DegToRad(65.0f));
+
+ Matrix44 lightPerspective = ProjectionMatrix(RadToDeg(lightFov), 1.0f, 1.0f, 1000.0f);
+ Matrix44 lightView = LookAtMatrix(Point3(g_lightPos), Point3(g_lightTarget));
+ Matrix44 lightTransform = lightPerspective*lightView;
+
+ // non-fluid particles maintain radius distance (not 2.0f*radius) so multiply by a half
+ float radius = g_params.solidRestDistance;
+
+ // fluid particles overlap twice as much again, so half the radius again
+ if (g_params.fluid)
+ radius = g_params.fluidRestDistance;
+
+ radius *= 0.5f;
+ radius *= g_pointScale;
+
+ //-------------------------------------
+ // shadowing pass
+
+ if (g_meshSkinIndices.size())
+ SkinMesh();
+
+ // create shadow maps
+ ShadowBegin(g_shadowMap);
+
+ SetView(lightView, lightPerspective);
+ SetCullMode(false);
+
+ // give scene a chance to do custom drawing
+ g_scenes[g_scene]->Draw(1);
+
+ if (g_drawMesh)
+ DrawMesh(g_mesh, g_meshColor);
+
+ DrawShapes();
+
+ if (g_drawCloth && g_buffers->triangles.size())
+ {
+ DrawCloth(&g_buffers->positions[0], &g_buffers->normals[0], g_buffers->uvs.size() ? &g_buffers->uvs[0].x : NULL, &g_buffers->triangles[0], g_buffers->triangles.size() / 3, g_buffers->positions.size(), 3, g_expandCloth);
+ }
+
+ if (g_drawRopes)
+ {
+ for (size_t i = 0; i < g_ropes.size(); ++i)
+ DrawRope(&g_buffers->positions[0], &g_ropes[i].mIndices[0], g_ropes[i].mIndices.size(), radius*g_ropeScale, i);
+ }
+
+ int shadowParticles = numParticles;
+ int shadowParticlesOffset = 0;
+
+ if (!g_drawPoints)
+ {
+ shadowParticles = 0;
+
+ if (g_drawEllipsoids && g_params.fluid)
+ {
+ shadowParticles = numParticles - g_numSolidParticles;
+ shadowParticlesOffset = g_numSolidParticles;
+ }
+ }
+ else
+ {
+ int offset = g_drawMesh ? g_numSolidParticles : 0;
+
+ shadowParticles = numParticles - offset;
+ shadowParticlesOffset = offset;
+ }
+
+ if (g_buffers->activeIndices.size())
+ DrawPoints(g_fluidRenderBuffers.mPositionVBO, g_fluidRenderBuffers.mDensityVBO, g_fluidRenderBuffers.mIndices, shadowParticles, shadowParticlesOffset, radius, 2048, 1.0f, lightFov, g_lightPos, g_lightTarget, lightTransform, g_shadowMap, g_drawDensity);
+
+ ShadowEnd();
+
+ //----------------
+ // lighting pass
+
+ BindSolidShader(g_lightPos, g_lightTarget, lightTransform, g_shadowMap, 0.0f, Vec4(g_clearColor, g_fogDistance));
+
+ SetView(view, proj);
+ SetCullMode(true);
+
+ DrawPlanes((Vec4*)g_params.planes, g_params.numPlanes, g_drawPlaneBias);
+
+ if (g_drawMesh)
+ DrawMesh(g_mesh, g_meshColor);
+
+
+ DrawShapes();
+
+ if (g_drawCloth && g_buffers->triangles.size())
+ DrawCloth(&g_buffers->positions[0], &g_buffers->normals[0], g_buffers->uvs.size() ? &g_buffers->uvs[0].x : NULL, &g_buffers->triangles[0], g_buffers->triangles.size() / 3, g_buffers->positions.size(), 3, g_expandCloth);
+
+ if (g_drawRopes)
+ {
+ for (size_t i = 0; i < g_ropes.size(); ++i)
+ DrawRope(&g_buffers->positions[0], &g_ropes[i].mIndices[0], g_ropes[i].mIndices.size(), g_params.radius*0.5f*g_ropeScale, i);
+ }
+
+ // give scene a chance to do custom drawing
+ g_scenes[g_scene]->Draw(0);
+
+ UnbindSolidShader();
+
+
+ // first pass of diffuse particles (behind fluid surface)
+ if (g_drawDiffuse)
+ RenderDiffuse(g_fluidRenderer, g_diffuseRenderBuffers, numDiffuse, radius*g_diffuseScale, float(g_screenWidth), aspect, fov, g_diffuseColor, g_lightPos, g_lightTarget, lightTransform, g_shadowMap, g_diffuseMotionScale, g_diffuseInscatter, g_diffuseOutscatter, g_diffuseShadow, false);
+
+ if (g_drawEllipsoids && g_params.fluid)
+ {
+ // draw solid particles separately
+ if (g_numSolidParticles && g_drawPoints)
+ DrawPoints(g_fluidRenderBuffers.mPositionVBO, g_fluidRenderBuffers.mDensityVBO, g_fluidRenderBuffers.mIndices, g_numSolidParticles, 0, radius, float(g_screenWidth), aspect, fov, g_lightPos, g_lightTarget, lightTransform, g_shadowMap, g_drawDensity);
+
+ // render fluid surface
+ RenderEllipsoids(g_fluidRenderer, g_fluidRenderBuffers, numParticles - g_numSolidParticles, g_numSolidParticles, radius, float(g_screenWidth), aspect, fov, g_lightPos, g_lightTarget, lightTransform, g_shadowMap, g_fluidColor, g_blur, g_ior, g_drawOpaque);
+
+ // second pass of diffuse particles for particles in front of fluid surface
+ if (g_drawDiffuse)
+ RenderDiffuse(g_fluidRenderer, g_diffuseRenderBuffers, numDiffuse, radius*g_diffuseScale, float(g_screenWidth), aspect, fov, g_diffuseColor, g_lightPos, g_lightTarget, lightTransform, g_shadowMap, g_diffuseMotionScale, g_diffuseInscatter, g_diffuseOutscatter, g_diffuseShadow, true);
+ }
+ else
+ {
+ // draw all particles as spheres
+ if (g_drawPoints)
+ {
+ int offset = g_drawMesh ? g_numSolidParticles : 0;
+
+ if (g_buffers->activeIndices.size())
+ DrawPoints(g_fluidRenderBuffers.mPositionVBO, g_fluidRenderBuffers.mDensityVBO, g_fluidRenderBuffers.mIndices, numParticles - offset, offset, radius, float(g_screenWidth), aspect, fov, g_lightPos, g_lightTarget, lightTransform, g_shadowMap, g_drawDensity);
+ }
+ }
+
+}
+
+void RenderDebug()
+{
+ if (g_mouseParticle != -1)
+ {
+ // draw mouse spring
+ BeginLines();
+ DrawLine(g_mousePos, Vec3(g_buffers->positions[g_mouseParticle]), Vec4(1.0f));
+ EndLines();
+ }
+
+ // springs
+ if (g_drawSprings)
+ {
+ Vec4 color;
+
+ if (g_drawSprings == 1)
+ {
+ // stretch
+ color = Vec4(0.0f, 0.0f, 1.0f, 0.8f);
+ }
+ if (g_drawSprings == 2)
+ {
+ // tether
+ color = Vec4(0.0f, 1.0f, 0.0f, 0.8f);
+ }
+
+ BeginLines();
+
+ int start = 0;
+
+ for (int i = start; i < g_buffers->springLengths.size(); ++i)
+ {
+ if (g_drawSprings == 1 && g_buffers->springStiffness[i] < 0.0f)
+ continue;
+ if (g_drawSprings == 2 && g_buffers->springStiffness[i] > 0.0f)
+ continue;
+
+ int a = g_buffers->springIndices[i * 2];
+ int b = g_buffers->springIndices[i * 2 + 1];
+
+ DrawLine(Vec3(g_buffers->positions[a]), Vec3(g_buffers->positions[b]), color);
+ }
+
+ EndLines();
+ }
+
+ // visualize contacts against the environment
+ if (g_drawContacts)
+ {
+ const int maxContactsPerParticle = 6;
+
+ NvFlexVector<Vec4> contactPlanes(g_flexLib, g_buffers->positions.size()*maxContactsPerParticle);
+ NvFlexVector<Vec4> contactVelocities(g_flexLib, g_buffers->positions.size()*maxContactsPerParticle);
+ NvFlexVector<int> contactIndices(g_flexLib, g_buffers->positions.size());
+ NvFlexVector<unsigned int> contactCounts(g_flexLib, g_buffers->positions.size());
+
+ NvFlexGetContacts(g_flex, contactPlanes.buffer, contactVelocities.buffer, contactIndices.buffer, contactCounts.buffer);
+
+ // ensure transfers have finished
+ contactPlanes.map();
+ contactVelocities.map();
+ contactIndices.map();
+ contactCounts.map();
+
+ BeginLines();
+
+ for (int i = 0; i < int(g_buffers->activeIndices.size()); ++i)
+ {
+ const int contactIndex = contactIndices[g_buffers->activeIndices[i]];
+ const unsigned int count = contactCounts[contactIndex];
+
+ const float scale = 0.1f;
+
+ for (unsigned int c = 0; c < count; ++c)
+ {
+ Vec4 plane = contactPlanes[contactIndex*maxContactsPerParticle + c];
+
+ DrawLine(Vec3(g_buffers->positions[g_buffers->activeIndices[i]]),
+ Vec3(g_buffers->positions[g_buffers->activeIndices[i]]) + Vec3(plane)*scale,
+ Vec4(0.0f, 1.0f, 0.0f, 0.0f));
+ }
+ }
+
+ EndLines();
+ }
+
+ if (g_drawBases)
+ {
+ for (int i = 0; i < int(g_buffers->rigidRotations.size()); ++i)
+ {
+ BeginLines();
+
+ float size = 0.1f;
+
+ for (int b = 0; b < 3; ++b)
+ {
+ Vec3 color;
+ color[b] = 1.0f;
+
+ Matrix33 frame(g_buffers->rigidRotations[i]);
+
+ DrawLine(Vec3(g_buffers->rigidTranslations[i]),
+ Vec3(g_buffers->rigidTranslations[i] + frame.cols[b] * size),
+ Vec4(color, 0.0f));
+ }
+
+ EndLines();
+ }
+ }
+
+ if (g_drawNormals)
+ {
+ NvFlexGetNormals(g_flex, g_buffers->normals.buffer, g_buffers->normals.size());
+
+ BeginLines();
+
+ for (int i = 0; i < g_buffers->normals.size(); ++i)
+ {
+ DrawLine(Vec3(g_buffers->positions[i]),
+ Vec3(g_buffers->positions[i] - g_buffers->normals[i] * g_buffers->normals[i].w),
+ Vec4(0.0f, 1.0f, 0.0f, 0.0f));
+ }
+
+ EndLines();
+ }
+}
+
+void DrawShapes()
+{
+ for (int i = 0; i < g_buffers->shapeFlags.size(); ++i)
+ {
+ const int flags = g_buffers->shapeFlags[i];
+
+ // unpack flags
+ int type = int(flags&eNvFlexShapeFlagTypeMask);
+ //bool dynamic = int(flags&eNvFlexShapeFlagDynamic) > 0;
+
+ Vec3 color = Vec3(0.9f);
+
+ if (flags & eNvFlexShapeFlagTrigger)
+ {
+ color = Vec3(0.6f, 1.0, 0.6f);
+
+ SetFillMode(true);
+ }
+
+ // render with prev positions to match particle update order
+ // can also think of this as current/next
+ const Quat rotation = g_buffers->shapePrevRotations[i];
+ const Vec3 position = Vec3(g_buffers->shapePrevPositions[i]);
+
+ NvFlexCollisionGeometry geo = g_buffers->shapeGeometry[i];
+
+ if (type == eNvFlexShapeSphere)
+ {
+ Mesh* sphere = CreateSphere(20, 20, geo.sphere.radius);
+
+ Matrix44 xform = TranslationMatrix(Point3(position))*RotationMatrix(Quat(rotation));
+ sphere->Transform(xform);
+
+ DrawMesh(sphere, Vec3(color));
+
+ delete sphere;
+ }
+ else if (type == eNvFlexShapeCapsule)
+ {
+ Mesh* capsule = CreateCapsule(10, 20, geo.capsule.radius, geo.capsule.halfHeight);
+
+ // transform to world space
+ Matrix44 xform = TranslationMatrix(Point3(position))*RotationMatrix(Quat(rotation))*RotationMatrix(DegToRad(-90.0f), Vec3(0.0f, 0.0f, 1.0f));
+ capsule->Transform(xform);
+
+ DrawMesh(capsule, Vec3(color));
+
+ delete capsule;
+ }
+ else if (type == eNvFlexShapeBox)
+ {
+ Mesh* box = CreateCubeMesh();
+
+ Matrix44 xform = TranslationMatrix(Point3(position))*RotationMatrix(Quat(rotation))*ScaleMatrix(Vec3(geo.box.halfExtents)*2.0f);
+ box->Transform(xform);
+
+ DrawMesh(box, Vec3(color));
+ delete box;
+ }
+ else if (type == eNvFlexShapeConvexMesh)
+ {
+ if (g_convexes.find(geo.convexMesh.mesh) != g_convexes.end())
+ {
+ GpuMesh* m = g_convexes[geo.convexMesh.mesh];
+
+ if (m)
+ {
+ Matrix44 xform = TranslationMatrix(Point3(g_buffers->shapePositions[i]))*RotationMatrix(Quat(g_buffers->shapeRotations[i]))*ScaleMatrix(geo.convexMesh.scale);
+ DrawGpuMesh(m, xform, Vec3(color));
+ }
+ }
+ }
+ else if (type == eNvFlexShapeTriangleMesh)
+ {
+ if (g_meshes.find(geo.triMesh.mesh) != g_meshes.end())
+ {
+ GpuMesh* m = g_meshes[geo.triMesh.mesh];
+
+ if (m)
+ {
+ Matrix44 xform = TranslationMatrix(Point3(position))*RotationMatrix(Quat(rotation))*ScaleMatrix(geo.triMesh.scale);
+ DrawGpuMesh(m, xform, Vec3(color));
+ }
+ }
+ }
+ else if (type == eNvFlexShapeSDF)
+ {
+ if (g_fields.find(geo.sdf.field) != g_fields.end())
+ {
+ GpuMesh* m = g_fields[geo.sdf.field];
+
+ if (m)
+ {
+ Matrix44 xform = TranslationMatrix(Point3(position))*RotationMatrix(Quat(rotation))*ScaleMatrix(geo.sdf.scale);
+ DrawGpuMesh(m, xform, Vec3(color));
+ }
+ }
+ }
+ }
+
+ SetFillMode(g_wireframe);
+}
+
+
+// returns the new scene if one is selected
+int DoUI()
+{
+ // gui may set a new scene
+ int newScene = -1;
+
+ if (g_showHelp)
+ {
+ const int numParticles = NvFlexGetActiveCount(g_flex);
+ const int numDiffuse = NvFlexGetDiffuseParticles(g_flex, NULL, NULL, NULL);
+
+ int x = g_screenWidth - 200;
+ int y = g_screenHeight - 23;
+
+ // imgui
+ unsigned char button = 0;
+ if (g_lastb == SDL_BUTTON_LEFT)
+ button = IMGUI_MBUT_LEFT;
+ else if (g_lastb == SDL_BUTTON_RIGHT)
+ button = IMGUI_MBUT_RIGHT;
+
+ imguiBeginFrame(g_lastx, g_screenHeight - g_lasty, button, 0);
+
+ x += 180;
+
+ int fontHeight = 13;
+
+ if (1)
+ {
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Frame: %d", g_frame); y -= fontHeight * 2;
+
+ if (!g_ffmpeg)
+ {
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Frame Time: %.2fms", g_realdt*1000.0f); y -= fontHeight * 2;
+
+ // If detailed profiling is enabled, then these timers will contain the overhead of the detail timers, so we won't display them.
+ if (!g_profile)
+ {
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Sim Time (CPU): %.2fms", g_updateTime*1000.0f); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(0.97f, 0.59f, 0.27f), IMGUI_ALIGN_RIGHT, "Sim Latency (GPU): %.2fms", g_simLatency); y -= fontHeight * 2;
+ }
+ else
+ {
+ y -= fontHeight * 3;
+ }
+ }
+
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Particle Count: %d", numParticles); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Diffuse Count: %d", numDiffuse); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Rigid Count: %d", g_buffers->rigidOffsets.size() > 0 ? g_buffers->rigidOffsets.size() - 1 : 0); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Spring Count: %d", g_buffers->springLengths.size()); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Num Substeps: %d", g_numSubsteps); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Num Iterations: %d", g_params.numIterations); y -= fontHeight * 2;
+
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Device: %s", g_deviceName); y -= fontHeight * 2;
+ }
+
+ if (g_profile)
+ {
+ DrawImguiString(x, y, Vec3(0.97f, 0.59f, 0.27f), IMGUI_ALIGN_RIGHT, "Total GPU Sim Latency: %.2fms", g_timers.total); y -= fontHeight * 2;
+
+ DrawImguiString(x, y, Vec3(0.0f, 1.0f, 0.0f), IMGUI_ALIGN_RIGHT, "GPU Latencies"); y -= fontHeight;
+
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Predict: %.2fms", g_timers.predict); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Create Cell Indices: %.2fms", g_timers.createCellIndices); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Sort Cell Indices: %.2fms", g_timers.sortCellIndices); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Reorder: %.2fms", g_timers.reorder); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "CreateGrid: %.2fms", g_timers.createGrid); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Collide Particles: %.2fms", g_timers.collideParticles); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Collide Shapes: %.2fms", g_timers.collideShapes); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Collide Triangles: %.2fms", g_timers.collideTriangles); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Calculate Density: %.2fms", g_timers.calculateDensity); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Solve Densities: %.2fms", g_timers.solveDensities); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Solve Velocities: %.2fms", g_timers.solveVelocities); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Solve Rigids: %.2fms", g_timers.solveShapes); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Solve Springs: %.2fms", g_timers.solveSprings); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Solve Inflatables: %.2fms", g_timers.solveInflatables); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Solve Contacts: %.2fms", g_timers.solveContacts); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Apply Deltas: %.2fms", g_timers.applyDeltas); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Finalize: %.2fms", g_timers.finalize); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Update Triangles: %.2fms", g_timers.updateTriangles); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Update Normals: %.2fms", g_timers.updateNormals); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Update Bounds: %.2fms", g_timers.updateBounds); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Calculate Anisotropy: %.2fms", g_timers.calculateAnisotropy); y -= fontHeight;
+ DrawImguiString(x, y, Vec3(1.0f), IMGUI_ALIGN_RIGHT, "Update Diffuse: %.2fms", g_timers.updateDiffuse); y -= fontHeight * 2;
+ }
+
+ x -= 180;
+
+ int uiOffset = 250;
+ int uiBorder = 20;
+ int uiWidth = 200;
+ int uiHeight = g_screenHeight - uiOffset - uiBorder * 3;
+ int uiLeft = uiBorder;
+
+ if (g_tweakPanel)
+ imguiBeginScrollArea("Scene", uiLeft, g_screenHeight - uiBorder - uiOffset, uiWidth, uiOffset, &g_levelScroll);
+ else
+ imguiBeginScrollArea("Scene", uiLeft, uiBorder, uiWidth, g_screenHeight - uiBorder - uiBorder, &g_levelScroll);
+
+ for (int i = 0; i < int(g_scenes.size()); ++i)
+ {
+ unsigned int color = g_scene == i ? imguiRGBA(255, 151, 61, 255) : imguiRGBA(255, 255, 255, 200);
+ if (imguiItem(g_scenes[i]->GetName(), true, color, i == g_selectedScene))
+ {
+ newScene = i;
+ }
+ }
+
+ imguiEndScrollArea();
+
+ if (g_tweakPanel)
+ {
+ static int scroll = 0;
+
+ imguiBeginScrollArea("Options", uiLeft, g_screenHeight - uiBorder - uiHeight - uiOffset - uiBorder, uiWidth, uiHeight, &scroll);
+ imguiSeparatorLine();
+
+ // global options
+ imguiLabel("Global");
+ if (imguiCheck("Emit particles", g_emit))
+ g_emit = !g_emit;
+
+ if (imguiCheck("Pause", g_pause))
+ g_pause = !g_pause;
+
+ imguiSeparatorLine();
+
+ if (imguiCheck("Wireframe", g_wireframe))
+ g_wireframe = !g_wireframe;
+
+ if (imguiCheck("Draw Points", g_drawPoints))
+ g_drawPoints = !g_drawPoints;
+
+ if (imguiCheck("Draw Fluid", g_drawEllipsoids))
+ g_drawEllipsoids = !g_drawEllipsoids;
+
+ if (imguiCheck("Draw Mesh", g_drawMesh))
+ {
+ g_drawMesh = !g_drawMesh;
+ g_drawRopes = !g_drawRopes;
+ }
+
+ if (imguiCheck("Draw Basis", g_drawBases))
+ g_drawBases = !g_drawBases;
+
+ if (imguiCheck("Draw Springs", bool(g_drawSprings != 0)))
+ g_drawSprings = (g_drawSprings) ? 0 : 1;
+
+ if (imguiCheck("Draw Contacts", g_drawContacts))
+ g_drawContacts = !g_drawContacts;
+
+ imguiSeparatorLine();
+
+ // scene options
+ g_scenes[g_scene]->DoGui();
+
+ if (imguiButton("Reset Scene"))
+ g_resetScene = true;
+
+ imguiSeparatorLine();
+
+ float n = float(g_numSubsteps);
+ if (imguiSlider("Num Substeps", &n, 1, 10, 1))
+ g_numSubsteps = int(n);
+
+ n = float(g_params.numIterations);
+ if (imguiSlider("Num Iterations", &n, 1, 20, 1))
+ g_params.numIterations = int(n);
+
+ imguiSeparatorLine();
+ imguiSlider("Gravity X", &g_params.gravity[0], -50.0f, 50.0f, 1.0f);
+ imguiSlider("Gravity Y", &g_params.gravity[1], -50.0f, 50.0f, 1.0f);
+ imguiSlider("Gravity Z", &g_params.gravity[2], -50.0f, 50.0f, 1.0f);
+
+ imguiSeparatorLine();
+ imguiSlider("Radius", &g_params.radius, 0.01f, 0.5f, 0.01f);
+ imguiSlider("Solid Radius", &g_params.solidRestDistance, 0.0f, 0.5f, 0.001f);
+ imguiSlider("Fluid Radius", &g_params.fluidRestDistance, 0.0f, 0.5f, 0.001f);
+
+ // common params
+ imguiSeparatorLine();
+ imguiSlider("Dynamic Friction", &g_params.dynamicFriction, 0.0f, 1.0f, 0.01f);
+ imguiSlider("Static Friction", &g_params.staticFriction, 0.0f, 1.0f, 0.01f);
+ imguiSlider("Particle Friction", &g_params.particleFriction, 0.0f, 1.0f, 0.01f);
+ imguiSlider("Restitution", &g_params.restitution, 0.0f, 1.0f, 0.01f);
+ imguiSlider("SleepThreshold", &g_params.sleepThreshold, 0.0f, 1.0f, 0.01f);
+ imguiSlider("Shock Propagation", &g_params.shockPropagation, 0.0f, 10.0f, 0.01f);
+ imguiSlider("Damping", &g_params.damping, 0.0f, 10.0f, 0.01f);
+ imguiSlider("Dissipation", &g_params.dissipation, 0.0f, 0.01f, 0.0001f);
+ imguiSlider("SOR", &g_params.relaxationFactor, 0.0f, 5.0f, 0.01f);
+
+ imguiSlider("Collision Distance", &g_params.collisionDistance, 0.0f, 0.5f, 0.001f);
+ imguiSlider("Collision Margin", &g_params.shapeCollisionMargin, 0.0f, 5.0f, 0.01f);
+
+ // rigid params
+ imguiSeparatorLine();
+ imguiSlider("Plastic Creep", &g_params.plasticCreep, 0.0f, 1.0f, 0.001f);
+ imguiSlider("Plastic Threshold", &g_params.plasticThreshold, 0.0f, 0.5f, 0.001f);
+
+ // cloth params
+ imguiSeparatorLine();
+ imguiSlider("Wind", &g_windStrength, -1.0f, 1.0f, 0.01f);
+ imguiSlider("Drag", &g_params.drag, 0.0f, 1.0f, 0.01f);
+ imguiSlider("Lift", &g_params.lift, 0.0f, 1.0f, 0.01f);
+ imguiSeparatorLine();
+
+ // fluid params
+ if (imguiCheck("Fluid", g_params.fluid))
+ g_params.fluid = !g_params.fluid;
+
+ imguiSlider("Adhesion", &g_params.adhesion, 0.0f, 10.0f, 0.01f);
+ imguiSlider("Cohesion", &g_params.cohesion, 0.0f, 0.2f, 0.0001f);
+ imguiSlider("Surface Tension", &g_params.surfaceTension, 0.0f, 50.0f, 0.01f);
+ imguiSlider("Viscosity", &g_params.viscosity, 0.0f, 120.0f, 0.01f);
+ imguiSlider("Vorticicty Confinement", &g_params.vorticityConfinement, 0.0f, 120.0f, 0.1f);
+ imguiSlider("Solid Pressure", &g_params.solidPressure, 0.0f, 1.0f, 0.01f);
+ imguiSlider("Surface Drag", &g_params.freeSurfaceDrag, 0.0f, 1.0f, 0.01f);
+ imguiSlider("Buoyancy", &g_params.buoyancy, -1.0f, 1.0f, 0.01f);
+
+ imguiSeparatorLine();
+ imguiSlider("Anisotropy Scale", &g_params.anisotropyScale, 0.0f, 30.0f, 0.01f);
+ imguiSlider("Smoothing", &g_params.smoothing, 0.0f, 1.0f, 0.01f);
+
+ // diffuse params
+ imguiSeparatorLine();
+ imguiSlider("Diffuse Threshold", &g_params.diffuseThreshold, 0.0f, 1000.0f, 1.0f);
+ imguiSlider("Diffuse Buoyancy", &g_params.diffuseBuoyancy, 0.0f, 2.0f, 0.01f);
+ imguiSlider("Diffuse Drag", &g_params.diffuseDrag, 0.0f, 2.0f, 0.01f);
+ imguiSlider("Diffuse Scale", &g_diffuseScale, 0.0f, 1.5f, 0.01f);
+ imguiSlider("Diffuse Alpha", &g_diffuseColor.w, 0.0f, 3.0f, 0.01f);
+ imguiSlider("Diffuse Inscatter", &g_diffuseInscatter, 0.0f, 2.0f, 0.01f);
+ imguiSlider("Diffuse Outscatter", &g_diffuseOutscatter, 0.0f, 2.0f, 0.01f);
+ imguiSlider("Diffuse Motion Blur", &g_diffuseMotionScale, 0.0f, 5.0f, 0.1f);
+
+ n = float(g_params.diffuseBallistic);
+ if (imguiSlider("Diffuse Ballistic", &n, 1, 40, 1))
+ g_params.diffuseBallistic = int(n);
+
+ imguiEndScrollArea();
+ }
+ imguiEndFrame();
+
+ // kick render commands
+ imguiGraphDraw();
+ }
+
+ // update benchmark and change scene if one is requested
+ if (g_benchmark)
+ newScene = BenchmarkUpdate();
+
+ return newScene;
+}
+
+void UpdateFrame()
+{
+ static double lastTime;
+
+ // real elapsed frame time
+ double frameBeginTime = GetSeconds();
+
+ g_realdt = float(frameBeginTime - lastTime);
+ lastTime = frameBeginTime;
+
+ // do gamepad input polling
+ double currentTime = frameBeginTime;
+ static double lastJoyTime = currentTime;
+
+ if (g_gamecontroller && currentTime - lastJoyTime > g_dt)
+ {
+ lastJoyTime = currentTime;
+
+ int leftStickX = SDL_GameControllerGetAxis(g_gamecontroller, SDL_CONTROLLER_AXIS_LEFTX);
+ int leftStickY = SDL_GameControllerGetAxis(g_gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
+ int rightStickX = SDL_GameControllerGetAxis(g_gamecontroller, SDL_CONTROLLER_AXIS_RIGHTX);
+ int rightStickY = SDL_GameControllerGetAxis(g_gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY);
+ int leftTrigger = SDL_GameControllerGetAxis(g_gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
+ int rightTrigger = SDL_GameControllerGetAxis(g_gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
+
+ Vec2 leftStick(joyAxisFilter(leftStickX, 0), joyAxisFilter(leftStickY, 0));
+ Vec2 rightStick(joyAxisFilter(rightStickX, 1), joyAxisFilter(rightStickY, 1));
+ Vec2 trigger(leftTrigger / 32768.0f, rightTrigger / 32768.0f);
+
+ if (leftStick.x != 0.0f || leftStick.y != 0.0f ||
+ rightStick.x != 0.0f || rightStick.y != 0.0f)
+ {
+ // note constant factor to speed up analog control compared to digital because it is more controllable.
+ g_camVel.z = -4 * g_camSpeed * leftStick.y;
+ g_camVel.x = 4 * g_camSpeed * leftStick.x;
+
+ // cam orientation
+ g_camAngle.x -= rightStick.x * 0.05f;
+ g_camAngle.y -= rightStick.y * 0.05f;
+ }
+
+ // Handle left stick motion
+ static bool bLeftStick = false;
+
+ if ((leftStick.x != 0.0f || leftStick.y != 0.0f) && !bLeftStick)
+ {
+ bLeftStick = true;
+ }
+ else if ((leftStick.x == 0.0f && leftStick.y == 0.0f) && bLeftStick)
+ {
+ bLeftStick = false;
+ g_camVel.z = -4 * g_camSpeed * leftStick.y;
+ g_camVel.x = 4 * g_camSpeed * leftStick.x;
+ }
+
+ // Handle triggers as controller button events
+ void ControllerButtonEvent(SDL_ControllerButtonEvent event);
+
+ static bool bLeftTrigger = false;
+ static bool bRightTrigger = false;
+ SDL_ControllerButtonEvent e;
+
+ if (!bLeftTrigger && trigger.x > 0.0f)
+ {
+ e.type = SDL_CONTROLLERBUTTONDOWN;
+ e.button = SDL_CONTROLLER_BUTTON_LEFT_TRIGGER;
+ ControllerButtonEvent(e);
+ bLeftTrigger = true;
+ }
+ else if (bLeftTrigger && trigger.x == 0.0f)
+ {
+ e.type = SDL_CONTROLLERBUTTONUP;
+ e.button = SDL_CONTROLLER_BUTTON_LEFT_TRIGGER;
+ ControllerButtonEvent(e);
+ bLeftTrigger = false;
+ }
+
+ if (!bRightTrigger && trigger.y > 0.0f)
+ {
+ e.type = SDL_CONTROLLERBUTTONDOWN;
+ e.button = SDL_CONTROLLER_BUTTON_RIGHT_TRIGGER;
+ ControllerButtonEvent(e);
+ bRightTrigger = true;
+ }
+ else if (bRightTrigger && trigger.y == 0.0f)
+ {
+ e.type = SDL_CONTROLLERBUTTONDOWN;
+ e.button = SDL_CONTROLLER_BUTTON_RIGHT_TRIGGER;
+ ControllerButtonEvent(e);
+ bRightTrigger = false;
+ }
+ }
+
+ //-------------------------------------------------------------------
+ // Scene Update
+
+ double waitBeginTime = GetSeconds();
+
+ MapBuffers(g_buffers);
+
+ double waitEndTime = GetSeconds();
+
+ UpdateCamera();
+
+ if (!g_pause || g_step)
+ {
+ UpdateEmitters();
+ UpdateMouse();
+ UpdateWind();
+ UpdateScene();
+ }
+
+ //-------------------------------------------------------------------
+ // Render
+
+ double renderBeginTime = GetSeconds();
+
+ if (g_profile && (!g_pause || g_step)) {
+ if (g_benchmark) {
+ g_numDetailTimers = NvFlexGetDetailTimers(g_flex, &g_detailTimers);
+ }
+ else {
+ memset(&g_timers, 0, sizeof(g_timers));
+ NvFlexGetTimers(g_flex, &g_timers);
+ }
+ }
+
+ float newSimLatency = NvFlexGetDeviceLatency(g_flex);
+
+ StartFrame(Vec4(g_clearColor, 1.0f));
+
+ // main scene render
+ RenderScene();
+ RenderDebug();
+
+ EndFrame();
+
+ const int newScene = DoUI();
+
+ UnmapBuffers(g_buffers);
+
+ // move mouse particle (must be done here as GetViewRay() uses the GL projection state)
+ if (g_mouseParticle != -1)
+ {
+ Vec3 origin, dir;
+ GetViewRay(g_lastx, g_screenHeight - g_lasty, origin, dir);
+
+ g_mousePos = origin + dir*g_mouseT;
+ }
+
+ if (g_capture)
+ {
+ TgaImage img;
+ img.m_width = g_screenWidth;
+ img.m_height = g_screenHeight;
+ img.m_data = new uint32_t[g_screenWidth*g_screenHeight];
+
+ ReadFrame((int*)img.m_data, g_screenWidth, g_screenHeight);
+
+ fwrite(img.m_data, sizeof(uint32_t)*g_screenWidth*g_screenHeight, 1, g_ffmpeg);
+
+ delete[] img.m_data;
+ }
+
+ double renderEndTime = GetSeconds();
+
+ // if user requested a scene reset process it now
+ if (g_resetScene)
+ {
+ Reset();
+ g_resetScene = false;
+ }
+
+ // if gui requested a scene change process it now
+ if (newScene != -1)
+ {
+ g_scene = newScene;
+ Init(g_scene);
+ return;
+ }
+
+
+
+ //-------------------------------------------------------------------
+ // Flex Update
+
+ double updateBeginTime = GetSeconds();
+
+ // send any particle updates to the solver
+ NvFlexSetParticles(g_flex, g_buffers->positions.buffer, g_buffers->positions.size());
+ NvFlexSetVelocities(g_flex, g_buffers->velocities.buffer, g_buffers->velocities.size());
+ NvFlexSetPhases(g_flex, g_buffers->phases.buffer, g_buffers->phases.size());
+ NvFlexSetActive(g_flex, g_buffers->activeIndices.buffer, g_buffers->activeIndices.size());
+
+ // allow scene to update constraints etc
+ SyncScene();
+
+ if (g_shapesChanged)
+ {
+ NvFlexSetShapes(
+ g_flex,
+ g_buffers->shapeGeometry.buffer,
+ g_buffers->shapePositions.buffer,
+ g_buffers->shapeRotations.buffer,
+ g_buffers->shapePrevPositions.buffer,
+ g_buffers->shapePrevRotations.buffer,
+ g_buffers->shapeFlags.buffer,
+ int(g_buffers->shapeFlags.size()));
+
+ g_shapesChanged = false;
+ }
+
+ if (!g_pause || g_step)
+ {
+ // tick solver
+ NvFlexSetParams(g_flex, &g_params);
+ NvFlexUpdateSolver(g_flex, g_dt, g_numSubsteps, g_profile);
+
+ g_frame++;
+ g_step = false;
+ }
+
+ // read back base particle data
+ // Note that flexGet calls don't wait for the GPU, they just queue a GPU copy
+ // to be executed later.
+ // When we're ready to read the fetched buffers we'll Map them, and that's when
+ // the CPU will wait for the GPU flex update and GPU copy to finish.
+ NvFlexGetParticles(g_flex, g_buffers->positions.buffer, g_buffers->positions.size());
+ NvFlexGetVelocities(g_flex, g_buffers->velocities.buffer, g_buffers->velocities.size());
+ NvFlexGetNormals(g_flex, g_buffers->normals.buffer, g_buffers->normals.size());
+
+ // readback triangle normals
+ if (g_buffers->triangles.size())
+ NvFlexGetDynamicTriangles(g_flex, g_buffers->triangles.buffer, g_buffers->triangleNormals.buffer, g_buffers->triangles.size() / 3);
+
+ // readback rigid transforms
+ if (g_buffers->rigidOffsets.size())
+ NvFlexGetRigidTransforms(g_flex, g_buffers->rigidRotations.buffer, g_buffers->rigidTranslations.buffer);
+
+ if (!g_interop)
+ {
+ // if not using interop then we read back fluid data to host
+ if (g_drawEllipsoids)
+ {
+ NvFlexGetSmoothParticles(g_flex, g_buffers->smoothPositions.buffer, g_buffers->smoothPositions.size());
+ NvFlexGetAnisotropy(g_flex, g_buffers->anisotropy1.buffer, g_buffers->anisotropy2.buffer, g_buffers->anisotropy3.buffer);
+ }
+
+ // read back diffuse data to host
+ if (g_drawDensity)
+ NvFlexGetDensities(g_flex, g_buffers->densities.buffer, g_buffers->positions.size());
+
+ if (g_diffuseRenderBuffers.mNumDiffuseParticles)
+ {
+ NvFlexGetDiffuseParticles(g_flex, g_buffers->diffusePositions.buffer, g_buffers->diffuseVelocities.buffer, g_buffers->diffuseIndices.buffer);
+ }
+ }
+
+ double updateEndTime = GetSeconds();
+
+ //-------------------------------------------------------
+ // Update the on-screen timers
+
+ float newUpdateTime = float(updateEndTime - updateBeginTime);
+ float newRenderTime = float(renderEndTime - renderBeginTime);
+ float newWaitTime = float(waitBeginTime - waitEndTime);
+
+ // Exponential filter to make the display easier to read
+ const float timerSmoothing = 0.05f;
+
+ g_updateTime = (g_updateTime == 0.0f) ? newUpdateTime : Lerp(g_updateTime, newUpdateTime, timerSmoothing);
+ g_renderTime = (g_renderTime == 0.0f) ? newRenderTime : Lerp(g_renderTime, newRenderTime, timerSmoothing);
+ g_waitTime = (g_waitTime == 0.0f) ? newWaitTime : Lerp(g_waitTime, newWaitTime, timerSmoothing);
+ g_simLatency = (g_simLatency == 0.0f) ? newSimLatency : Lerp(g_simLatency, newSimLatency, timerSmoothing);
+
+ PresentFrame(g_vsync);
+}
+
+void ReshapeWindow(int width, int height)
+{
+ if (!g_benchmark)
+ printf("Reshaping\n");
+
+ ReshapeRender(g_window);
+
+ if (!g_fluidRenderer || (width != g_screenWidth || height != g_screenHeight))
+ {
+ if (g_fluidRenderer)
+ DestroyFluidRenderer(g_fluidRenderer);
+ g_fluidRenderer = CreateFluidRenderer(width, height);
+ }
+
+ g_screenWidth = width;
+ g_screenHeight = height;
+}
+
+void InputArrowKeysDown(int key, int x, int y)
+{
+ switch (key)
+ {
+ case SDLK_DOWN:
+ {
+ if (g_selectedScene < int(g_scenes.size()) - 1)
+ g_selectedScene++;
+
+ // update scroll UI to center on selected scene
+ g_levelScroll = max((g_selectedScene - 4) * 24, 0);
+ break;
+ }
+ case SDLK_UP:
+ {
+ if (g_selectedScene > 0)
+ g_selectedScene--;
+
+ // update scroll UI to center on selected scene
+ g_levelScroll = max((g_selectedScene - 4) * 24, 0);
+ break;
+ }
+ case SDLK_LEFT:
+ {
+ if (g_scene > 0)
+ --g_scene;
+ Init(g_scene);
+
+ // update scroll UI to center on selected scene
+ g_levelScroll = max((g_scene - 4) * 24, 0);
+ break;
+ }
+ case SDLK_RIGHT:
+ {
+ if (g_scene < int(g_scenes.size()) - 1)
+ ++g_scene;
+ Init(g_scene);
+
+ // update scroll UI to center on selected scene
+ g_levelScroll = max((g_scene - 4) * 24, 0);
+ break;
+ }
+ }
+}
+
+void InputArrowKeysUp(int key, int x, int y)
+{
+}
+
+bool InputKeyboardDown(unsigned char key, int x, int y)
+{
+ if (key > '0' && key <= '9')
+ {
+ g_scene = key - '0' - 1;
+ Init(g_scene);
+ return false;
+ }
+
+ float kSpeed = g_camSpeed;
+
+ switch (key)
+ {
+ case 'w':
+ {
+ g_camVel.z = kSpeed;
+ break;
+ }
+ case 's':
+ {
+ g_camVel.z = -kSpeed;
+ break;
+ }
+ case 'a':
+ {
+ g_camVel.x = -kSpeed;
+ break;
+ }
+ case 'd':
+ {
+ g_camVel.x = kSpeed;
+ break;
+ }
+ case 'q':
+ {
+ g_camVel.y = kSpeed;
+ break;
+ }
+ case 'z':
+ {
+ //g_drawCloth = !g_drawCloth;
+ g_camVel.y = -kSpeed;
+ break;
+ }
+
+ case 'u':
+ {
+#ifndef ANDROID
+ if (g_fullscreen)
+ {
+ SDL_SetWindowFullscreen(g_window, 0);
+ ReshapeWindow(1280, 720);
+ g_fullscreen = false;
+ }
+ else
+ {
+ SDL_SetWindowFullscreen(g_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+ g_fullscreen = true;
+ }
+#endif
+ break;
+ }
+ case 'r':
+ {
+ g_resetScene = true;
+ break;
+ }
+ case 'y':
+ {
+ g_wavePool = !g_wavePool;
+ break;
+ }
+ case 'c':
+ {
+#if _WIN32
+ if (!g_ffmpeg)
+ {
+ // open ffmpeg stream
+
+ int i = 0;
+ char buf[255];
+ FILE* f = NULL;
+
+ do
+ {
+ sprintf(buf, "../../movies/output%d.mp4", i);
+ f = fopen(buf, "rb");
+ if (f)
+ fclose(f);
+
+ ++i;
+ } while (f);
+
+ const char* str = "ffmpeg -r 60 -f rawvideo -pix_fmt rgba -s 1280x720 -i - "
+ "-threads 0 -preset fast -y -crf 19 -pix_fmt yuv420p -tune animation -vf vflip %s";
+
+ char cmd[1024];
+ sprintf(cmd, str, buf);
+
+ g_ffmpeg = _popen(cmd, "wb");
+ assert(g_ffmpeg);
+ }
+ else
+ {
+ _pclose(g_ffmpeg);
+ g_ffmpeg = NULL;
+ }
+
+ g_capture = !g_capture;
+ g_frame = 0;
+#endif
+ break;
+ }
+ case 'p':
+ {
+ g_pause = !g_pause;
+ break;
+ }
+ case 'o':
+ {
+ g_step = true;
+ break;
+ }
+ case 'h':
+ {
+ g_showHelp = !g_showHelp;
+ break;
+ }
+ case 'e':
+ {
+ g_drawEllipsoids = !g_drawEllipsoids;
+ break;
+ }
+ case 't':
+ {
+ g_drawOpaque = !g_drawOpaque;
+ break;
+ }
+ case 'v':
+ {
+ g_drawPoints = !g_drawPoints;
+ break;
+ }
+ case 'f':
+ {
+ g_drawSprings = (g_drawSprings + 1) % 3;
+ break;
+ }
+ case 'i':
+ {
+ g_drawDiffuse = !g_drawDiffuse;
+ break;
+ }
+ case 'm':
+ {
+ g_drawMesh = !g_drawMesh;
+ break;
+ }
+ case 'n':
+ {
+ g_drawRopes = !g_drawRopes;
+ break;
+ }
+ case 'j':
+ {
+ g_windTime = 0.0f;
+ g_windStrength = 1.5f;
+ g_windFrequency = 0.2f;
+ break;
+ }
+ case '.':
+ {
+ g_profile = !g_profile;
+ break;
+ }
+ case 'g':
+ {
+ if (g_params.gravity[1] != 0.0f)
+ g_params.gravity[1] = 0.0f;
+ else
+ g_params.gravity[1] = -9.8f;
+
+ break;
+ }
+ case '-':
+ {
+ if (g_params.numPlanes)
+ g_params.numPlanes--;
+
+ break;
+ }
+ case ' ':
+ {
+ g_emit = !g_emit;
+ break;
+ }
+ case ';':
+ {
+ g_debug = !g_debug;
+ break;
+ }
+ case 13:
+ {
+ g_scene = g_selectedScene;
+ Init(g_scene);
+ break;
+ }
+ case 27:
+ {
+ // return quit = true
+ return true;
+ }
+ };
+
+ g_scenes[g_scene]->KeyDown(key);
+
+ return false;
+}
+
+void InputKeyboardUp(unsigned char key, int x, int y)
+{
+ switch (key)
+ {
+ case 'w':
+ case 's':
+ {
+ g_camVel.z = 0.0f;
+ break;
+ }
+ case 'a':
+ case 'd':
+ {
+ g_camVel.x = 0.0f;
+ break;
+ }
+ case 'q':
+ case 'z':
+ {
+ g_camVel.y = 0.0f;
+ break;
+ }
+ };
+}
+
+void MouseFunc(int b, int state, int x, int y)
+{
+ switch (state)
+ {
+ case SDL_RELEASED:
+ {
+ g_lastx = x;
+ g_lasty = y;
+ g_lastb = -1;
+
+ break;
+ }
+ case SDL_PRESSED:
+ {
+ g_lastx = x;
+ g_lasty = y;
+ g_lastb = b;
+#ifdef ANDROID
+ extern void setStateLeft(bool bLeftDown);
+ setStateLeft(false);
+#else
+ if ((SDL_GetModState() & KMOD_LSHIFT) && g_lastb == SDL_BUTTON_LEFT)
+ {
+ // record that we need to update the picked particle
+ g_mousePicked = true;
+ }
+#endif
+ break;
+ }
+ };
+}
+
+void MousePassiveMotionFunc(int x, int y)
+{
+ g_lastx = x;
+ g_lasty = y;
+}
+
+void MouseMotionFunc(unsigned state, int x, int y)
+{
+ float dx = float(x - g_lastx);
+ float dy = float(y - g_lasty);
+
+ g_lastx = x;
+ g_lasty = y;
+
+ if (state & SDL_BUTTON_RMASK)
+ {
+ const float kSensitivity = DegToRad(0.1f);
+ const float kMaxDelta = FLT_MAX;
+
+ g_camAngle.x -= Clamp(dx*kSensitivity, -kMaxDelta, kMaxDelta);
+ g_camAngle.y -= Clamp(dy*kSensitivity, -kMaxDelta, kMaxDelta);
+ }
+}
+
+bool g_Error = false;
+
+void ErrorCallback(NvFlexErrorSeverity, const char* msg, const char* file, int line)
+{
+ printf("Flex: %s - %s:%d\n", msg, file, line);
+ g_Error = true;
+ //assert(0); asserts are bad for TeamCity
+}
+
+void ControllerButtonEvent(SDL_ControllerButtonEvent event)
+{
+ // map controller buttons to keyboard keys
+ if (event.type == SDL_CONTROLLERBUTTONDOWN)
+ {
+ InputKeyboardDown(GetKeyFromGameControllerButton(SDL_GameControllerButton(event.button)), 0, 0);
+ InputArrowKeysDown(GetKeyFromGameControllerButton(SDL_GameControllerButton(event.button)), 0, 0);
+
+ if (event.button == SDL_CONTROLLER_BUTTON_LEFT_TRIGGER)
+ {
+ // Handle picking events using the game controller
+ g_lastx = g_screenWidth / 2;
+ g_lasty = g_screenHeight / 2;
+ g_lastb = 1;
+
+ // record that we need to update the picked particle
+ g_mousePicked = true;
+ }
+ }
+ else
+ {
+ InputKeyboardUp(GetKeyFromGameControllerButton(SDL_GameControllerButton(event.button)), 0, 0);
+ InputArrowKeysUp(GetKeyFromGameControllerButton(SDL_GameControllerButton(event.button)), 0, 0);
+
+ if (event.button == SDL_CONTROLLER_BUTTON_LEFT_TRIGGER)
+ {
+ // Handle picking events using the game controller
+ g_lastx = g_screenWidth / 2;
+ g_lasty = g_screenHeight / 2;
+ g_lastb = -1;
+ }
+ }
+}
+
+void ControllerDeviceUpdate()
+{
+ if (SDL_NumJoysticks() > 0)
+ {
+ SDL_JoystickEventState(SDL_ENABLE);
+ if (SDL_IsGameController(0))
+ {
+ g_gamecontroller = SDL_GameControllerOpen(0);
+ }
+ }
+}
+
+void SDLInit(const char* title)
+{
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) // Initialize SDL's Video subsystem and game controllers
+ printf("Unable to initialize SDL");
+
+ // Create our window centered
+ g_window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ g_screenWidth, g_screenHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
+
+ g_windowId = SDL_GetWindowID(g_window);
+}
+
+void SDLMainLoop()
+{
+ bool quit = false;
+ SDL_Event e;
+ while (!quit)
+ {
+ UpdateFrame();
+
+ while (SDL_PollEvent(&e))
+ {
+ switch (e.type)
+ {
+ case SDL_QUIT:
+ quit = true;
+ break;
+
+ case SDL_KEYDOWN:
+ if (e.key.keysym.sym < 256 && (e.key.keysym.mod == KMOD_NONE || (e.key.keysym.mod & KMOD_NUM)))
+ quit = InputKeyboardDown(e.key.keysym.sym, 0, 0);
+ InputArrowKeysDown(e.key.keysym.sym, 0, 0);
+ break;
+
+ case SDL_KEYUP:
+ if (e.key.keysym.sym < 256 && (e.key.keysym.mod == 0 || (e.key.keysym.mod & KMOD_NUM)))
+ InputKeyboardUp(e.key.keysym.sym, 0, 0);
+ InputArrowKeysUp(e.key.keysym.sym, 0, 0);
+ break;
+
+ case SDL_MOUSEMOTION:
+ if (e.motion.state)
+ MouseMotionFunc(e.motion.state, e.motion.x, e.motion.y);
+ else
+ MousePassiveMotionFunc(e.motion.x, e.motion.y);
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ MouseFunc(e.button.button, e.button.state, e.motion.x, e.motion.y);
+ break;
+
+ case SDL_WINDOWEVENT:
+ if (e.window.windowID == g_windowId)
+ {
+ if (e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
+ ReshapeWindow(e.window.data1, e.window.data2);
+ }
+ break;
+
+ case SDL_WINDOWEVENT_LEAVE:
+ g_camVel = Vec3(0.0f, 0.0f, 0.0f);
+ break;
+
+ case SDL_CONTROLLERBUTTONUP:
+ case SDL_CONTROLLERBUTTONDOWN:
+ ControllerButtonEvent(e.cbutton);
+ break;
+
+ case SDL_JOYDEVICEADDED:
+ case SDL_JOYDEVICEREMOVED:
+ ControllerDeviceUpdate();
+ break;
+ }
+ }
+ }
+}
+
+
+int main(int argc, char* argv[])
+{
+ // process command line args
+ for (int i = 1; i < argc; ++i)
+ {
+ int d;
+ if (sscanf(argv[i], "-device=%d", &d))
+ g_device = d;
+
+ if (sscanf(argv[i], "-extensions=%d", &d))
+ g_extensions = d != 0;
+
+ if (strstr(argv[i], "-benchmark"))
+ {
+ g_benchmark = true;
+ g_profile = true;
+ }
+
+ if (strstr(argv[i], "-d3d12"))
+ g_d3d12 = true;
+
+ if (strstr(argv[i], "-tc"))
+ g_teamCity = true;
+
+ if (sscanf(argv[i], "-msaa=%d", &d))
+ g_msaaSamples = d;
+
+ int w = 1280;
+ int h = 720;
+ if (sscanf(argv[i], "-fullscreen=%dx%d", &w, &h) == 2)
+ {
+ g_screenWidth = w;
+ g_screenHeight = h;
+ g_fullscreen = true;
+ }
+ else if (strstr(argv[i], "-fullscreen"))
+ {
+ g_screenWidth = w;
+ g_screenHeight = h;
+ g_fullscreen = true;
+ }
+
+ if (sscanf(argv[i], "-vsync=%d", &d))
+ g_vsync = d != 0;
+
+ if (sscanf(argv[i], "-multiplier=%d", &d) == 1)
+ {
+ g_numExtraMultiplier = d;
+ }
+
+ if (strstr(argv[i], "-disabletweak"))
+ {
+ g_tweakPanel = false;
+ }
+
+ if (strstr(argv[i], "-disableinterop"))
+ {
+ g_interop = false;
+ }
+ }
+
+ // opening scene
+ g_scenes.push_back(new PotPourri("Pot Pourri"));
+
+
+ // soft body scenes
+ SoftBody* softOctopus = new SoftBody("Soft Octopus", "../../data/softs/octopus.obj");
+ softOctopus->mScale = Vec3(32.0f);
+ softOctopus->mClusterSpacing = 2.75f;
+ softOctopus->mClusterRadius = 3.0f;
+ softOctopus->mClusterStiffness = 0.15f;
+ softOctopus->mSurfaceSampling = 1.0f;
+ softOctopus->mStack[1] = 3;
+
+ SoftBody* softRope = new SoftBody("Soft Rope", "../../data/rope.obj");
+ softRope->mScale = Vec3(50.0f);
+ softRope->mClusterSpacing = 1.5f;
+ softRope->mClusterRadius = 0.0f;
+ softRope->mClusterStiffness = 0.55f;
+
+ SoftBody* softBowl = new SoftBody("Soft Bowl", "../../data/bowl_high.ply");
+ softBowl->mScale = Vec3(10.0f);
+ softBowl->mClusterSpacing = 2.0f;
+ softBowl->mClusterRadius = 2.0f;
+ softBowl->mClusterStiffness = 0.55f;
+
+ SoftBody* softCloth = new SoftBody("Soft Cloth", "../../data/box_ultra_high.ply");
+ softCloth->mScale = Vec3(20.0f, 0.2f, 20.0f);
+ softCloth->mRadius = 0.05f;
+ softCloth->mClusterSpacing = 1.0f;
+ softCloth->mClusterRadius = 2.0f;
+ softCloth->mClusterStiffness = 0.2f;
+ softCloth->mLinkRadius = 2.0f;
+ softCloth->mLinkStiffness = 1.0f;
+ softCloth->mSkinningFalloff = 1.0f;
+ softCloth->mSkinningMaxDistance = 100.f;
+
+ SoftBodyFixed* softRod = new SoftBodyFixed("Soft Rod", "../../data/box_very_high.ply");
+ softRod->mScale = Vec3(20.0f, 2.0f, 2.0f);
+ softRod->mOffset = Vec3(-0.3f, 1.0f, 0.0f);
+ softRod->mClusterSpacing = 2.0f;
+ softRod->mClusterRadius = 2.0f;
+ softRod->mClusterStiffness = 0.225f;
+ softRod->mStack[2] = 3;
+
+ SoftBody* softTeapot = new SoftBody("Soft Teapot", "../../data/teapot.ply");
+ softTeapot->mScale = Vec3(25.0f);
+ softTeapot->mClusterSpacing = 3.0f;
+ softTeapot->mClusterRadius = 0.0f;
+ softTeapot->mClusterStiffness = 0.1f;
+
+ SoftBody* softArmadillo = new SoftBody("Soft Armadillo", "../../data/armadillo.ply");
+ softArmadillo->mScale = Vec3(25.0f);
+ softArmadillo->mClusterSpacing = 3.0f;
+ softArmadillo->mClusterRadius = 0.0f;
+
+ SoftBody* softBunny = new SoftBody("Soft Bunny", "../../data/bunny.ply");
+ softBunny->mScale = Vec3(20.0f);
+ softBunny->mClusterSpacing = 3.5f;
+ softBunny->mClusterRadius = 0.0f;
+ softBunny->mClusterStiffness = 0.2f;
+
+ SoftBody* plasticBunnies = new SoftBody("Plastic Bunnies", "../../data/bunny.ply");
+ plasticBunnies->mScale = Vec3(10.0f);
+ plasticBunnies->mClusterSpacing = 1.0f;
+ plasticBunnies->mClusterRadius = 0.0f;
+ plasticBunnies->mClusterStiffness = 0.0f;
+ plasticBunnies->mGlobalStiffness = 1.0f;
+ plasticBunnies->mPlasticThreshold = 0.0015f;
+ plasticBunnies->mPlasticCreep = 0.15f;
+ plasticBunnies->mRelaxationFactor = 1.0f;
+ plasticBunnies->mOffset[1] = 5.0f;
+ plasticBunnies->mStack[1] = 10;
+ plasticBunnies->mPlinth = true;
+
+ g_scenes.push_back(softOctopus);
+ g_scenes.push_back(softTeapot);
+ g_scenes.push_back(softRope);
+ g_scenes.push_back(softCloth);
+ g_scenes.push_back(softBowl);
+ g_scenes.push_back(softRod);
+ g_scenes.push_back(softArmadillo);
+ g_scenes.push_back(softBunny);
+ g_scenes.push_back(plasticBunnies);
+
+
+ // collision scenes
+ g_scenes.push_back(new FrictionRamp("Friction Ramp"));
+ g_scenes.push_back(new FrictionMovingShape("Friction Moving Box", 0));
+ g_scenes.push_back(new FrictionMovingShape("Friction Moving Sphere", 1));
+ g_scenes.push_back(new FrictionMovingShape("Friction Moving Capsule", 2));
+ g_scenes.push_back(new ShapeCollision("Shape Collision"));
+ g_scenes.push_back(new TriangleCollision("Triangle Collision"));
+ g_scenes.push_back(new LocalSpaceFluid("Local Space Fluid"));
+ g_scenes.push_back(new LocalSpaceCloth("Local Space Cloth"));
+ g_scenes.push_back(new CCDFluid("World Space Fluid"));
+
+
+ // cloth scenes
+ g_scenes.push_back(new EnvironmentalCloth("Env Cloth Small", 6, 6, 40, 16));
+ g_scenes.push_back(new EnvironmentalCloth("Env Cloth Large", 16, 32, 10, 3));
+ g_scenes.push_back(new FlagCloth("Flag Cloth"));
+ g_scenes.push_back(new Inflatable("Inflatables"));
+ g_scenes.push_back(new ClothLayers("Cloth Layers"));
+ g_scenes.push_back(new SphereCloth("Sphere Cloth"));
+ g_scenes.push_back(new Tearing("Tearing"));
+ g_scenes.push_back(new Pasta("Pasta"));
+
+
+ // game mesh scenes
+ g_scenes.push_back(new GameMesh("Game Mesh Rigid", 0));
+ g_scenes.push_back(new GameMesh("Game Mesh Particles", 1));
+ g_scenes.push_back(new GameMesh("Game Mesh Fluid", 2));
+ g_scenes.push_back(new GameMesh("Game Mesh Cloth", 3));
+ g_scenes.push_back(new RigidDebris("Rigid Debris"));
+
+ // viscous fluids
+ g_scenes.push_back(new Viscosity("Viscosity Low", 0.5f));
+ g_scenes.push_back(new Viscosity("Viscosity Med", 3.0f));
+ g_scenes.push_back(new Viscosity("Viscosity High", 5.0f, 0.12f));
+ g_scenes.push_back(new Adhesion("Adhesion"));
+ g_scenes.push_back(new GooGun("Goo Gun", true));
+
+ // regular fluids
+ g_scenes.push_back(new Buoyancy("Buoyancy"));
+ g_scenes.push_back(new Melting("Melting"));
+ g_scenes.push_back(new SurfaceTension("Surface Tension Low", 0.0f));
+ g_scenes.push_back(new SurfaceTension("Surface Tension Med", 10.0f));
+ g_scenes.push_back(new SurfaceTension("Surface Tension High", 20.0f));
+ g_scenes.push_back(new DamBreak("DamBreak 5cm", 0.05f));
+ g_scenes.push_back(new DamBreak("DamBreak 10cm", 0.1f));
+ g_scenes.push_back(new DamBreak("DamBreak 15cm", 0.15f));
+ g_scenes.push_back(new RockPool("Rock Pool"));
+ g_scenes.push_back(new RayleighTaylor2D("Rayleigh Taylor 2D"));
+
+ // misc feature scenes
+ g_scenes.push_back(new TriggerVolume("Trigger Volume"));
+ g_scenes.push_back(new ForceField("Force Field"));
+ g_scenes.push_back(new InitialOverlap("Initial Overlap"));
+
+ // rigid body scenes
+ g_scenes.push_back(new RigidPile("Rigid2", 2));
+ g_scenes.push_back(new RigidPile("Rigid4", 4));
+ g_scenes.push_back(new RigidPile("Rigid8", 12));
+ g_scenes.push_back(new BananaPile("Bananas"));
+ g_scenes.push_back(new LowDimensionalShapes("Low Dimensional Shapes"));
+ g_scenes.push_back(new PlasticStack("Plastic Stack"));
+
+ // granular scenes
+ g_scenes.push_back(new GranularPile("Granular Pile"));
+
+ // coupling scenes
+ g_scenes.push_back(new ParachutingBunnies("Parachuting Bunnies"));
+ g_scenes.push_back(new WaterBalloon("Water Balloons"));
+ g_scenes.push_back(new RigidFluidCoupling("Rigid Fluid Coupling"));
+ g_scenes.push_back(new FluidBlock("Fluid Block"));
+ g_scenes.push_back(new FluidClothCoupling("Fluid Cloth Coupling Water", false));
+ g_scenes.push_back(new FluidClothCoupling("Fluid Cloth Coupling Goo", true));
+ g_scenes.push_back(new BunnyBath("Bunny Bath Dam", true));
+
+ // init gl
+#ifndef ANDROID
+
+#if FLEX_DX
+ const char* title = "Flex Demo (Direct Compute)";
+#else
+ const char* title = "Flex Demo (CUDA)";
+#endif
+
+ SDLInit(title);
+
+ InitRender(g_window, g_fullscreen, g_msaaSamples);
+
+ if (g_fullscreen)
+ SDL_SetWindowFullscreen(g_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+
+ ReshapeWindow(g_screenWidth, g_screenHeight);
+
+#endif // ifndef ANDROID
+
+#if !FLEX_DX
+
+ // use the PhysX GPU selected from the NVIDIA control panel
+ if (g_device == -1)
+ g_device = NvFlexDeviceGetSuggestedOrdinal();
+
+ // Create an optimized CUDA context for Flex and set it on the
+ // calling thread. This is an optional call, it is fine to use
+ // a regular CUDA context, although creating one through this API
+ // is recommended for best performance.
+ bool success = NvFlexDeviceCreateCudaContext(g_device);
+
+ if (!success)
+ {
+ printf("Error creating CUDA context.\n");
+ exit(-1);
+ }
+
+#endif
+
+ NvFlexInitDesc desc;
+ desc.deviceIndex = g_device;
+ desc.enableExtensions = g_extensions;
+ desc.renderDevice = 0;
+ desc.renderContext = 0;
+ desc.computeType = eNvFlexCUDA;
+
+#if FLEX_DX
+
+ if (g_d3d12)
+ desc.computeType = eNvFlexD3D12;
+ else
+ desc.computeType = eNvFlexD3D11;
+
+ if (g_device == -1 && !g_d3d12)
+ {
+ // use the renderer device
+ GetRenderDevice((ID3D11Device**)&desc.renderDevice,
+ (ID3D11DeviceContext**)&desc.renderContext);
+ }
+ else
+ {
+ // disable shared resources
+ g_interop = false;
+ }
+
+#endif
+
+ // Init Flex library, note that no CUDA methods should be called before this
+ // point to ensure we get the device context we want
+ g_flexLib = NvFlexInit(NV_FLEX_VERSION, ErrorCallback, &desc);
+
+ if (g_Error || g_flexLib == NULL)
+ {
+ printf("Could not initialize Flex, exiting.\n");
+ exit(-1);
+ }
+
+ // store device name
+ strcpy(g_deviceName, NvFlexGetDeviceName(g_flexLib));
+ printf("Compute Device: %s\n\n", g_deviceName);
+
+ if (g_benchmark)
+ BenchmarkInit();
+
+
+ // create shadow maps
+ g_shadowMap = ShadowCreate();
+
+ // init default scene
+ Init(g_scene);
+
+ SDLMainLoop();
+
+ if (g_fluidRenderer)
+ DestroyFluidRenderer(g_fluidRenderer);
+
+ DestroyFluidRenderBuffers(g_fluidRenderBuffers);
+ DestroyDiffuseRenderBuffers(g_diffuseRenderBuffers);
+
+ ShadowDestroy(g_shadowMap);
+ DestroyRender();
+
+ Shutdown();
+
+ SDL_DestroyWindow(g_window);
+ SDL_Quit();
+
+ return 0;
+}