Vala プログラミング

WebGPU プログラミング

おなが@京都先端科学大

Julia MeshCat SPH 流体シミュレーション (Mesh)

update 2022/03/03
Julia MeshCat SPH 流体シミュレーション (Mesh) の記事のupdate です。
GeometryTypes ライブラリーが GeometryBasics に変更されました。
この変更により、MeshCat でメッシュの表示ができなくなっていました。
この変更に合わせて、Meshing ライブラリーも update され、新たな書式で
表示できるようになっています。
Meshデータの生成は、直接 pov ファイルを生成するように変更しました。
(変更後のプログラムを掲載しています。)

Julia MeshCat ライブラリを用いた、水柱崩壊のシミュレーションです。
流体のシミュレーションには、SPH (Smoothed Particle Hydrodynamics) 法を使用しています。
前回は、PointCloudを用いて、粒子による描画を行いました。
今回は、Marching cubes法を用いて、meshによる描画を行います。
水柱崩壊のシミュレーション(meshによる描画)
f:id:onagat12:20220303141459g:plain
水柱崩壊のシミュレーション(povrayによる描画)
f:id:onagat12:20220303141708g:plain

1. meshの描画
 粒子による描画をmeshによる描画にするには、粒子の集まりの表面にmeshを
張ります。具体的には、粒子の密度と静止状態の密度の差(density - rest_density)
がゼロとなる境界にmeshを張っていきます。
これは、「修士論文 水の実時間アニメーション 天田崇」
 (https://library.naist.jp/mylimedio/dllimedio/showpdf2.cgi/DLPDFR003344_P1-56)の方法を参考にしました。
・meshの生成
 ①distance field 値を計算する。
  陰関数には、粒子の密度を用いる。
 ②distance field 値から、Marching cubes法を用いて、meshのvertexとfaceを生成する。

## MeshCat-sph-mc.jl

using MeshCat
vis = Visualizer()
#open(vis)

using Blink
AtomShell.isinstalled() || AtomShell.install()
open(vis, Window())

using Meshing
using ColorTypes
using CoordinateTransformations
using Rotations
using LinearAlgebra
using LinearAlgebra: norm
using GeometryBasics
using GeometryBasics: Mesh

mutable struct Particle
    position::Point3
    velocity::Point3
    density
    pressure
    force::Point3
end

# weight function
w_rho(r, h) = r < h ? (h^2 - r^2)^3 : 0.0
w_pressure(r,h) = r < h ? (h - r)^2 : 0.0
w_viscosity(r,h) = r < h ? (h- r) : 0.0

# Gravity
accel_g = [0.0, 0.0, -9.8]

# constants
mass = 0.00020543
pressure_stiffness = 1.0
rest_density = 600.0
h = 0.01
l0 = (mass / rest_density)^(1.0 / 3.0)
simscale = 0.004
d = l0 / simscale * 0.87
visc = 0.2
limit = 200.0
radius = 0.004
epsiron = 0.00001
extstiff = 10000.0
extdamp = 256.0

w_rho_coef = 315.0 / (64.0 * pi * h^9)
w_pressure_coef = 45.0 / (pi * h^6)
w_visc_coef = w_pressure_coef

init_min = [ 0.0,  0.0, 0.0]
init_max = [10.0, 20.0, 10.0] 
pos_min = [0.0, 0.0, 0.0]
pos_max = [10.0, 20.0, 20.0]

nx = 5; ny = 5; nz = 8

particle_list = []

for iz = 0:nz+1, iy = 0:ny+1, ix = 0:nx+1
    x = init_min[1] + d*ix; y = init_min[2] + d*iy; z = init_min[3] + d*iz

    position = Point3(x, y, z)
    velocity = Point3(0, 0, 0)
    density = 0.0
    pressure = 0.0
    force = Point3(0.0, 0.0, 0.0)
    push!(particle_list,
       Particle(position, velocity, density, pressure, force))
end
particles = length(particle_list)
   println("number of particles: ", particles)
## number of particles: (nx+2)*(ny+2)*(nz+2)

function Simulation(DT)
   # neghiber_list
    neighbor_list = []
    for i = 1:particles
        position_i = particle_list[i].position
        neighbors = []
        for j = 1:particles
            j == i && continue
            position_j = particle_list[j].position
            distance = norm(position_j - position_i) * simscale
            if distance < h
                push!(neighbors, j)
            end
        end
        push!(neighbor_list, neighbors)
    end

    # density(rho)
    for i = 1:particles
        position_i = particle_list[i].position

        sum = 0.0
        map(neighbor_list[i]) do j
            position_j = particle_list[j].position
            distance = norm(position_j - position_i) * simscale
            sum += w_rho(distance, h)
        end
        # density
        particle_list[i].density = sum * mass * w_rho_coef

        # pressure
        particle_list[i].pressure =
            pressure_stiffness * (particle_list[i].density - rest_density)
    end
    
    # force: pressure gradient, viscosity and adj(force at wall)
    for i = 1:particles
        pressure_gradient = [0.0, 0.0, 0.0]
        visc_term = [0.0, 0.0, 0.0]
        position_i = particle_list[i].position
        
        map(neighbor_list[i]) do j
            position_j = particle_list[j].position
            distance = norm(position_j - position_i) * simscale
            
            diffji =  (position_j - position_i) * simscale
            pweight = w_pressure_coef * w_pressure(distance, h) * diffji / distance
            
            pji = particle_list[j].pressure + particle_list[i].pressure

            rho_i = particle_list[i].density
            rho_j = particle_list[j].density
            pgradient = -0.5 * pji / (rho_i * rho_j) * pweight
            pressure_gradient .+= pgradient

            vweight = w_visc_coef * w_viscosity(distance, h)
            vji = particle_list[j].velocity - particle_list[i].velocity
            vterm = visc  * vji / (rho_i * rho_j) * vweight 
            
            visc_term .+= vterm
        end
        particle_list[i].force = pressure_gradient + visc_term
    end
    
    # adj force
    accel_adj = Point3(0.0, 0.0, 0.0)
    diff_min = Point3(0.0, 0.0, 0.0)
    diff_max = Point3(0.0, 0.0, 0.0)
    map(particle_list) do particle
        accel = particle.force * mass

        speed = norm( accel )^2;
        if speed > limit^2
            accel *= limit / sqrt(speed)
        end
        
        diff_min = 2.0 * radius .- ( particle.position - pos_min ) * simscale
        diff_max = 2.0 * radius .- ( pos_max - particle.position ) * simscale
        
        # Z
        if diff_min[3] > epsiron
            normal = Point3( 0.0, 0.0, 1.0 )
            adj = extstiff * diff_min[3] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        if diff_max[3] > epsiron
            normal = Point3( 0.0, 0.0, -1.0 )
            adj = extstiff * diff_max[3] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end

        # X
        if diff_min[1] > epsiron
            normal = Point3( 1.0, 0.0, 0.0 )
            adj = extstiff * diff_min[1] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        if diff_max[1] > epsiron
            normal = Point3( -1.0, 0.0, 0.0 )
            adj = extstiff * diff_max[1] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        
        # Y
        if diff_min[2] > epsiron
            normal = Point3( 0.0, 1.0, 0.0 )
            adj = extstiff * diff_min[2] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        if diff_max[2] > epsiron
            normal = Point3( 0.0, -1.0, 0.0 )
            adj = extstiff * diff_max[2] - extdamp * dot( normal, particle.velocity )
            accel += adj * normal
        end
        
        accel += accel_g

        veloc = particle.velocity
        veloc = veloc .+ accel * DT

        posit = particle.position
        posit = posit .+ veloc * DT / simscale
        particle.position = posit
        particle.velocity = veloc
    end
    positions = map(particle_list) do particle
        particle.position
    end
    positions
end

function create_mesh(nloop)
    println("nloop ", nloop)

    DT = 0.004
    positions = Simulation(DT)

    function sdfunc(v)
        sum = 0.0
        map(positions) do position
            distance = norm(v - position) * simscale
            sum += w_rho(distance, h)
        end
        # density
        density = sum * mass * w_rho_coef
        density - rest_density
    end

    m1 = Mesh(sdfunc, Rect(Vec(-2.,-2,-2),Vec(14.,24,20)),
            MarchingCubes(), samples=(20,20,20))
end

### Mesh settings
color = RGBA{Float32}(0.0, 0.84, 1.0, 0.7)
material = MeshPhongMaterial(color = color)

N = 200

for i = 1:N    
    m = create_mesh(i)

    m_vertices = decompose(Point{3, Float32}, m)
    m_vertices .*= 0.1

    ### display Mesh
    setobject!(vis, m, material)

    trans = Translation(0., -1, 0)
    rot = LinearMap(RotYZ(0.2, -0.2))
    transform = compose(rot, trans)
    settransform!(vis, transform)
    
    #sleep(0.01)
    #Base.prompt("continue?") # for capture
end

2. povrayによる描画
 povファイルは、「粒子法入門 越塚他著」にあるpovファイルを参考にして
います。

meshデータの生成
meshデータ(vertex_vectorsとface_indices)は以下のようにして生成します。

### create pov file
function writeData(i, mesh)
    string1 = ["#include \"colors.inc\"", "#include \"glass.inc\"", "",
    "global_settings{max_trace_level 20}",
    "camera{ location <-2.5, 0.7, -2.5> look_at <0.5, 0.5, -1.5>}",
    "light_source{ <0.5, 1, 0.25> color rgb<1, 1, 1>}",
    "sky_sphere{",
    "   pigment{", "      gradient y",
    "      color_map{",
    "         [0.0 color rgb<1.0,1.0,1.0>*0.5]",
    "         [0.2 color rgb<1.0,1.0,1.0>*0.2]", "      }", "   }", "}", "",
    "plane { y, 0.0",
    "   texture {",
    "      pigment{checker color White color Gray70 scale 0.2}", "   }",
    "   finish { ambient 0.5}", "}", "", "mesh2{"]

    string2 = ["   rotate x*(-90)",
        "   texture { T_Glass3 } interior{ I_Glass ior 1.33 }", "}"]

    filename = string("out", lpad(i,4,"0"), ".pov")
    out = open(filename, "w")
    # strin1 (pre)
    foreach(line -> println(out, line), string1)

    # vertices
    vertices = decompose(Point{3, Float32}, mesh)
    println(out, "vertex_vectors { ", length(vertices), ",")
    foreach(vertex -> println(out, "<", vertex[1], ",", vertex[2], ",", vertex[3], ">"),
        vertices)
    println(out, "}")
    
    # face indices
    ### notice
    ### for i = 1:length(faces)
    ### => Parse Error: Mesh face index out of range.
    ### => "for i = 1:(length(faces) - 1)"
    faces = decompose(TriangleFace{Int}, mesh)
    println(out, "face_indices { ", length(faces) - 1, ",")
    for i = 1:(length(faces) - 1)
        println(out, "<", faces[i][1], ",", faces[i][2], ",", faces[i][3], ">")
    end
    println(out, "}")

    # strin2 (post)
    foreach(line -> println(out, line), string2)

    close(out)
end

このwrireData関数を上のプログラムに追加し、
m_vertices .*= 0.1 の下で、writeData(i, m) を実行します。