{"id":13456402,"url":"https://github.com/portsmouth/Trinity","last_synced_at":"2025-03-24T10:32:34.314Z","repository":{"id":46732676,"uuid":"127552383","full_name":"portsmouth/Trinity","owner":"portsmouth","description":"Programmable 3D fluid simulator running in WebGL.","archived":false,"fork":false,"pushed_at":"2021-01-12T20:56:57.000Z","size":15469,"stargazers_count":316,"open_issues_count":0,"forks_count":14,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-10-29T00:32:10.316Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/portsmouth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-31T16:55:38.000Z","updated_at":"2024-10-18T09:25:29.000Z","dependencies_parsed_at":"2022-08-20T15:50:53.133Z","dependency_job_id":null,"html_url":"https://github.com/portsmouth/Trinity","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portsmouth%2FTrinity","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portsmouth%2FTrinity/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portsmouth%2FTrinity/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/portsmouth%2FTrinity/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/portsmouth","download_url":"https://codeload.github.com/portsmouth/Trinity/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245252477,"owners_count":20585072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T08:01:21.308Z","updated_at":"2025-03-24T10:32:33.274Z","avatar_url":"https://github.com/portsmouth.png","language":"JavaScript","funding_links":[],"categories":["Physics","JavaScript"],"sub_categories":[],"readme":"\n# Trinity\n\n\u003ca href=\"https://portsmouth.github.io/Trinity/\"\u003eTrinity\u003c/a\u003e is a programmable 3D fluid simulator which runs in the browser on GPU via WebGL. The fluid simulation logic is fully customizable via GLSL programs coded directly into the browser (using [CodeMirror](https://codemirror.net)). Click the thumbnails below for a selection of the preset simulations, running live in the browser:\n\n\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Basic plume\"'\u003e\u003cimg src=\"./thumbs/Basic-plume.png\" width=\"33%\"/\u003e\u003c/a\u003e\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Plume + sphere collider I\"'\u003e\u003cimg src=\"./thumbs/Plume-sphere.png\" width=\"33%\"/\u003e\u003c/a\u003e\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Plume + walls\"'\u003e\u003cimg src=\"./thumbs/Plume-walls.png\" width=\"33%\"/\u003e\u003c/a\u003e\n\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Nuke III\"'\u003e\u003cimg src=\"./thumbs/nuke.png\" width=\"33%\"/\u003e\u003c/a\u003e\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Nuke\"'\u003e\u003cimg src=\"./thumbs/nuke-II.png\" width=\"33%\"/\u003e\u003c/a\u003e\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Moving fireball III\"'\u003e\u003cimg src=\"./thumbs/fireball.png\" width=\"33%\"/\u003e\u003c/a\u003e\n\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Dust devil\"'\u003e\u003cimg src=\"./thumbs/dust-devil.png\" width=\"33%\"/\u003e\u003c/a\u003e\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Dye collision\"'\u003e\u003cimg src=\"./thumbs/Dye-collision.png\" width=\"33%\"/\u003e\u003c/a\u003e\u003ca href='https://portsmouth.github.io/Trinity/?preset=\"Vortex street\"'\u003e\u003cimg src=\"./thumbs/vortex-street.png\" width=\"33%\"/\u003e\u003c/a\u003e\n\n## Overview\n\n### Simulation and Rendering\n\nTrinity is a WebGL application which solves the [Navier–Stokes equations](https://en.wikipedia.org/wiki/Navier%E2%80%93Stokes_equations) of fluid/gas dynamics on GPU, and volume renders the resulting fields. \n\nOnly the core simulation logic is hard-coded, while most of the dynamics is determined by user-written GLSL programs (written in the browser) which specify the injection of fluid velocity and temperature, application of external forces, and the presence of solid obstacles which the fluid collides with. Hot fluid is simulated by injection of a scalar field representing temperature, which is then passively advected and made to affect the dynamics according to buoyancy forces. In general, up to four scalar fields (collectively referred to as \"the temperature\") may be passively advected and used to drive the dynamics.\n\nFor rendering, two color fields representing the extinction (i.e. density) and albedo of an absorbing/scattering/emitting medium, such as dust or ink, are injected and passively advected. These are volume rendered via raymarching, illuminated by a single distant light (the \"sun\"). The map from the temperature field to emission radiance, to simulate blackbody radiation for example, is provided by the user.\n\nThe following 6 user-written GLSL programs (as well as various in-built parameters of the [solver](#solver-parameters) and [renderer](#renderer-parameters)) specify the dynamics and rendering:\n\n  - \u003ca href=\"#common\"\u003eCommon\u003c/a\u003e: declare uniform float and vec3 quantities, and bind them to UI sliders and color pickers.\n  - \u003ca href=\"#initial\"\u003eInitial\u003c/a\u003e: specify initial conditions for velocity, temperature and medium density and albedo.\n  - \u003ca href=\"#inject\"\u003eInject\u003c/a\u003e: inject velocity, heat or media into the simulation.\n  - \u003ca href=\"#influence\"\u003eInfluence\u003c/a\u003e: apply external forces (due to e.g. buoyancy, wind).\n  - \u003ca href=\"#collide\"\u003eCollide\u003c/a\u003e: specify collision geometry via an SDF.\n  - \u003ca href=\"#render\"\u003eRender\u003c/a\u003e: specify how temperature maps to emission, and the phase-function.\n  \nThe programs and full state of the UI can be  [saved/loaded](#Saving-and-loading-the-scene) as a JSON file.\n\n### Grid geometry\n\nThe simulation is done on a fixed size Eulerian grid.\nIn all programs, the variable `vec3 wsP` refers to the world space position in coordinates which range from the origin to `vec3 L`, where `L` is in units of voxels.\nFor example a grid of resolution `(128, 512, 128)` has its lower left corner at `(0, 0, 0)` and its upper right corner at `L=(128.0, 512.0, 128.0)`.\nThe center of the grid is at `L/2`.\n\n### Technical details\n\n - As WebGL does not currently support writing to 3D textures from within fragment shaders, the 3D grid has to be represented via 2D textures.\n   This is done similarly to the [\"flat 3D textures\"](https://dl.acm.org/doi/10.5555/844174.844189) of Harris et al (2003). See the commentary [here](https://github.com/portsmouth/Trinity/blob/master/js/solver.js#L144) for a detailed description of the scheme used.\n - Pressure projection is currently rather simplistic and done via Jacobi iteration. The scheme for handling pressure projection in the presence of solid boundaries is taken from [\"Real-Time Simulation and Rendering of 3D Fluids\"](http://developer.download.nvidia.com/books/gpu_gems_3/samples/gems3_ch30.pdf) by Crane et al (2008).\n - Semi-Lagrangian advection is done via a 4th order Runge-Kutta method.\n - Colliders are currently assumed to be static (i.e. if the SDF is time-dependent, the velocity of the walls will not be transferred to the fluid).\n - Diffusion of the advected terms, as well as fluid viscosity, is ignored (as is common in CFD for graphics).\n - \"Neumann\" boundary conditions are applied at the edges of the grid (i.e. derivatives are zero at the boundaries, so material flows out freely).\n - Trinity is named after the code name of the [first nuclear weapon test](https://en.wikipedia.org/wiki/Trinity_(nuclear_test)).\n\n\n## Programs\n\nThe simulation is specified in detail by the following six GLSL programs (here with example implementations):\n\n### Common\n\nTo allow for real-time interaction with the simulation, there is a simple system for declaring \"uniform\" float and vec3 variables in the user-code,\nand binding them to UI sliders and color-pickers which control them:\n\n```glsl\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n// Bind UI parameters to uniforms used in the various programs\n// The metadata after the // on each line containing a uniform declaration is a JSON object which is used to\n// generate a uniform variable for the shader, which is \"bound\" to (i.e. driven by) a UI slider or color picker\n// (depending on whether \"default\" is a number or array).\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n\n// \"Physics\"\nuniform float gravity;          // {\"name\":\"gravity\",          \"min\":0.0, \"max\":1.0,   \"step\":0.01, \"default\":0.05}\nuniform float buoyancy;         // {\"name\":\"buoyancy\",         \"min\":0.0, \"max\":0.5,   \"step\":0.01, \"default\":0.5}\nuniform float radiationLoss;    // {\"name\":\"radiationLoss\",    \"min\":0.9, \"max\":1.0,   \"step\":0.01,  \"default\":0.99}\n\n// Blast geometry\nuniform float blast_height;     // {\"name\":\"blast_height\",     \"min\":0.1, \"max\":0.9,   \"step\":0.01, \"default\":0.25}\nuniform float blast_radius;     // {\"name\":\"blast_radius\",     \"min\":0.0, \"max\":0.1,   \"step\":0.01, \"default\":0.1}\nuniform float blast_velocity;   // {\"name\":\"blast_velocity\",   \"min\":0.0, \"max\":100.0, \"step\":0.1,  \"default\":50.0}\nuniform float blast_heat_flux;  // {\"name\":\"blast_heat_flux\",  \"min\":0.0, \"max\":100.0, \"step\":1.0,  \"default\":100.0}\n\n// Dust\nuniform float dust_inflow_rate; // {\"name\":\"dust_inflow_rate\", \"min\":0.0, \"max\":10.0,   \"step\":0.01, \"default\":1.0}\nuniform vec3  dust_absorption;  // {\"name\":\"dust_absorption\",  \"default\":[0.5,0.5,0.5], \"scale\":1.0}\nuniform vec3  dust_scattering;  // {\"name\":\"dust_scattering\",  \"default\":[0.5,0.5,0.5], \"scale\":1.0}\n\n// Rendering\nuniform float TtoKelvin;        // {\"name\":\"TtoKelvin\",        \"min\":0.0, \"max\":300.0,  \"step\":0.01, \"default\":10.0}\n\nfloat Tambient;\n\n/******************************************************/\n/*                 mandatory function                 */\n/******************************************************/\nvoid init()\n{\n    // Any global constants defined here are available in all functions\n    Tambient = 1.0;\n}\n```\n\n### Initial\n\nSpecify initial conditions for velocity, temperature and medium density and albedo.\n\n```glsl\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n// Specify the initial conditions for the simulation,\n// i.e. populate all the relevant fields (velocity, temperature, debris density/albedo) at time 0.0\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n\n/******************************************************/\n/*                 mandatory function                 */\n/******************************************************/\nvoid initial_conditions(in vec3 wsP,               // world space center of current voxel\n                        in vec3 L, in float dL,    // world-space extents of grid, and voxel-size\n                        inout vec3 v,              // initial velocity\n                        inout vec4 T,              // initial temperature\n                        inout vec3 medium,         // initial per-channel medium density (extinction)\n                        inout vec3 mediumAlbedo)   // initial per-channel medium albedo\n{\n    v = vec3(0.0);\n    T = vec4(Tambient);\n    medium = vec3(0.0);\n    mediumAlbedo = vec3(0.0);\n}\n```\n\n### Inject\n\nInject velocity, heat or media into the simulation.\n\nThe temperature field `vec4 T` is in general 4 arbitrary scalar fields which are advected with the flow, which can be used to influence the dynamics in whatever fashion. (For example, one of these fields could be used to represent density of fuel, if simulating combustion). The medium density and albedo fields are \"special\" in the sense that they determine the absorption and scattering of the rendered media.\n\nThe velocity and temperature fields may be injected either by specifying the inflow rate (i.e. supplying a \"source\" term), or by directly setting the value of the field (i.e like a Dirichlet boundary condition). While only the inflow version is available for the medium field (as using Dirichlet boundary conditions for medium density would be odd).\n\nNote that the simulation time starts at `0.0`, incrementing by one timestep each frame\n(looping on reaching max_timesteps).\n\n```glsl\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n// Update the velocity, temperature via either:\n//  - specification of volumetric inflow/outflow rate due to sources/sinks (vInflow, Tinflow)\n//  - modification in-place, i.e. Dirichlet boundary conditions (v, T)\n// Also specify the injected medium density inflow rate, and its scattering albedo.\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n\n/******************************************************/\n/*                 mandatory function                 */\n/******************************************************/\n\nvoid inject(in vec3 wsP,                 // world space center of current voxel\n            in float time,               // time (i.e. frame count times timestep value)\n            in vec3 L, in float dL,      // world-space extents of grid, and voxel-size\n            inout vec3 v,                // modify velocity in-place (defaults to no change)\n            inout vec3 vInflow,          // velocity inflow rate (defaults to zero)\n            inout vec4 T,                // modify temperature in-place (defaults to no change)\n            inout vec4 Tinflow,          // temperature inflow rate (defaults to zero)\n            inout vec3 mediumInflow,     // medium density inflow rate (defaults to zero)\n            inout vec3 mediumAlbedo)     // medium albedo\n{\n    vec3 blast_center = vec3(0.5*L.x, blast_height*L.y, 0.5*L.z);\n    vec3 dir = wsP - blast_center;\n    float r = length(dir);\n    dir /= r;\n    float rt = r/(blast_radius*L.y);\n    if (rt \u003c= 1.0 \u0026\u0026 time\u003c400.0)\n    {\n        // Within blast radius: inject velocity and temperature\n        float radial_falloff = max(0.0, 1.0 - rt*rt*(3.0 - 2.0*rt));\n        vInflow = dir * blast_velocity * radial_falloff;\n        Tinflow.r = blast_heat_flux * radial_falloff;\n\n        // Also inject absorbing/scattering \"dust\"\n        vec3 dust_extinction = dust_absorption + dust_scattering;\n        mediumInflow = dust_extinction * dust_inflow_rate * radial_falloff;\n        mediumAlbedo = dust_scattering / dust_extinction;\n    }\n    else\n    {\n        // Apply thermal relaxation due to \"radiation loss\"\n        T.r *= radiationLoss;\n    }\n}\n\n```\n\n### Influence\n\nApply external forces (due to e.g. buoyancy, wind), i.e. return the sum of forces to be applied at the given voxel.\n\n```glsl\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n// Apply any external forces to the fluid\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n\n/******************************************************/\n/*                 mandatory function                 */\n/******************************************************/\n\nvec3 externalForces(in vec3 wsP,                       // world space center of current voxel\n                    in float time,                     // time\n                    in vec3 L, in float dL,            // world-space extents of grid, and voxel-size\n                    in vec3 v, in float P, in vec4 T,  // velocity, pressure, temperature at current voxel\n                    in vec3 medium)                    // medium density at current voxel\n{\n    // Boussinesq approximation (a la Fedkiw \u0026 Stam)\n    float densityAvg = (medium.r + medium.g + medium.b)/3.0;\n    float buoyancy_force = -densityAvg*gravity + buoyancy*(T.r - Tambient);\n    return vec3(0.0, buoyancy_force, 0.0);\n}\n```\n\n### Collide\n\nSpecify collision geometry via an SDF (that is, the volume in which the returned value is negative is taken to be in the interior of a solid obstacle).\n\n```glsl\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n// Specify regions which contain impenetrable, static collider material\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n\nfloat sdSphere(vec3 X, in vec3 C, float r) { return length(X-C) - r; }\n\n/******************************************************/\n/*                 mandatory function                 */\n/******************************************************/\n\nfloat collisionSDF(in vec3 wsP,            // world space center of current voxel\n                   in float time,          // time\n                   in vec3 L, in float dL) // world-space extents of grid, and voxel-size\n{\n    // Return SDF of the collider surface.\n    // (where the interior with SDF \u003c 0.0 is a solid obstacle)\n    return sdSphere(wsP, vec3(L.x/2.0, L.y/3.0, L.z/2.0), 0.5*L.x*collider_radius);\n}\n```\n\n### Render\n\nSpecify how temperature maps to emission, and phase-function.\n\n```glsl\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n// Specify the fluid emission field and phase function\n//////////////////////////////////////////////////////////////////////////////////////////////////////\n\n// Approximate map from temperature in Kelvin to blackbody emission color.\n// Valid from 1000 to 40000 K (and additionally 0 for pure full white)\nvec3 colorTemperatureToRGB(const in float temperature)\n{\n    // (implementation omitted here)\n}\n\n/******************************************************/\n/*                 mandatory functions                */\n/******************************************************/\n\n// Specify how the temperature is mapped to the local emission radiance\nvec3 temperatureToEmission(in vec4 T)\n{\n    vec3 emission = colorTemperatureToRGB(T.r * TtoKelvin) * pow(T.r/100.0, 4.0);\n    return emission;\n}\n\n// Optionally remap the medium density (extinction) and albedo\nvoid mediumRemap(inout vec3 medium,\n                 inout vec3 mediumAlbedo)\n{}\n\n// Specify phase function of medium\nfloat phaseFunction(float mu,         // cosine of angle between incident and scattered ray\n                    float anisotropy) // anisotropy coefficient\n{\n    const float pi = 3.141592653589793;\n    float g = anisotropy;\n    float gSqr = g*g;\n    return (1.0/(4.0*pi)) * (1.0 - gSqr) / pow(1.0 - 2.0*g*mu + gSqr, 1.5);\n}\n```\n\n## Parameters\n\nFor reference, these are the parameters of the solver and volume renderer:\n\n### Solver parameters\n\nThe fluid solver has the following parameters:\n\n- *Nx*, *Ny*, *Nz*: the voxel resolution on each axis.\n- *NprojSteps*: the number of Jacobi iterations for the pressure projection step\n- *max_timesteps*: the maximum timestep count, after which the simulation loops\n- *vorticity_scale*: controls the amount of \"vorticity confinement\" applied, which enhances detail at the expense of simulation stability and correctness (see [Fedkiw \u0026 Stam](https://dl.acm.org/doi/10.1145/383259.383260)).\n- *expansion*: simulates local fluid expansion due to heating.\n- *timestep*: timestep value, normally fixed at 1.0\n\n### Renderer parameters\n\nVolume rendering parameters:\n\n- *extinctionScale*: scale the medium density (extinction) value\n- *emissionScale*: scale the emission radiance\n- *anisotropy*: set the anisotropy of the phase-function\n- *exposure*: tonemapping exposure\n- *gamma*: tonemapping gamma\n- *saturation*: tonemapping color saturation\n- *sunLatitude*: latitude angle of directional light\n- *sunLongitude*: longitude angle of directional light\n- *sunPower*: power of directional light\n- *sunColor*: color of directional light\n- *skyColor*: color of (uniform) sky dome light\n- *Nraymarch*: number of raymarch steps\n- *max_spp*: maximum number of samples to average the jittered raymarch over\n- *spp_per_frame*: number of spp to run for each frame\n- *show_bounds*: show wireframe grid bounds\n- *colliderDiffuse*: collision SDF diffuse reflection color\n- *colliderSpec*: collision SDF specular reflection color\n- *colliderRoughness*: collision SDF specular roughness\n\n### Saving and loading the scene\n\n - *save scene*: save scene out to a JSON file (filename auto-generated with a timestamp)\n - *load scene*: load a previously saved JSON scene file\n\n### Keys\n\n  - Space bar: toggle the simulation play/pause state\n  - ESC key: restart the simulation from time 0.0\n  - F11 key: go fullscreen\n  - O key: dump JSON application state to console, used to generate new presets.\n  - F key: move camera to the standard orientation\n  - H key: toggle the menus and HUD\n  - P key: export current framebuffer to PNG image\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fportsmouth%2FTrinity","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fportsmouth%2FTrinity","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fportsmouth%2FTrinity/lists"}