Skip to content

Commit b7d883f

Browse files
committed
v2.2 update: added velocity initialization in revoxelization to enable simulating moving/rotating geometries
1 parent 245303e commit b7d883f

File tree

7 files changed

+265
-31
lines changed

7 files changed

+265
-31
lines changed

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,40 @@
22

33
The fastest and most memory efficient lattice Boltzmann CFD software, running on all GPUs via [OpenCL](https://github.com/ProjectPhysX/OpenCL-Wrapper "OpenCL-Wrapper").
44

5-
<a href="https://youtu.be/o3TPN142HxM"><img src="https://img.youtube.com/vi/o3TPN142HxM/maxresdefault.jpg" alt="FluidX3D - A New Era of Computational Fluid Dynamics Software" width="50%"></img></a><a href="https://youtu.be/oC6U1M0Fsug"><img src="https://img.youtube.com/vi/oC6U1M0Fsug/maxresdefault.jpg" alt="8 billion voxel raindrop simulation" width="50%"></img></a><br>
6-
<a href="https://youtu.be/NQPgumd3Ei8"><img src="https://img.youtube.com/vi/NQPgumd3Ei8/maxresdefault.jpg" alt="Hydraulic jump simulation" width="50%"></img></a><a href="https://youtu.be/3JNVBQyetMA"><img src="https://img.youtube.com/vi/3JNVBQyetMA/maxresdefault.jpg" alt="Star Wars X-wing simulation" width="50%"></img></a>
5+
<a href="https://youtu.be/o3TPN142HxM"><img src="https://img.youtube.com/vi/o3TPN142HxM/maxresdefault.jpg" width="50%"></img></a><a href="https://youtu.be/oC6U1M0Fsug"><img src="https://img.youtube.com/vi/oC6U1M0Fsug/maxresdefault.jpg" width="50%"></img></a><br>
6+
<a href="https://youtu.be/NQPgumd3Ei8"><img src="https://img.youtube.com/vi/NQPgumd3Ei8/maxresdefault.jpg" width="50%"></img></a><a href="https://youtu.be/aqG8qZ_Gc4U"><img src="https://img.youtube.com/vi/aqG8qZ_Gc4U/maxresdefault.jpg" width="50%"></img></a>
7+
8+
9+
<details><summary>Update History</summary>
10+
11+
- v1.0
12+
- initial release
13+
- v1.1
14+
- added solid voxelization on GPU (slow algorithm)
15+
- added tool to print current camera position (key_H)
16+
- minor bug fix (workaround for Intel iGPU driver bug with triangle rendering)
17+
- v1.2
18+
- added functions to compute force/torque on objects
19+
- added function to translate Mesh
20+
- added Stokes drag validation setup
21+
- v1.3
22+
- added unit conversion functions for torque
23+
- `FORCE_FIELD` and `VOLUME_FORCE` can now be used independently
24+
- minor bug fix (workaround for AMD legacy driver bug with binary number literals)
25+
- v1.4
26+
- added interactive graphics mode on Linux with X11
27+
- fixed streamline visualization bug in 2D
28+
- v2.0
29+
- added (cross-vendor) multi-GPU support on a single node (PC/laptop/server)
30+
- v2.1
31+
- made solid voxelization on GPU lightning fast (new algorithm, from minutes to milliseconds)
32+
- v2.2
33+
- added option to voxelize moving/rotating geometry on GPU, with automatic velocity initialization for each grid point based on center of rotation, linear velocity and rotational velocity
34+
- cells that are converted from solid->fluid during re-voxelization now have their DDFs properly initialized
35+
- added option to not auto-scale mesh during `read_stl(...)`, with negative `size` parameter
36+
- added kernel for solid boundary rendering with marching-cubes
737

38+
</details>
839

940

1041
## Compute Features

src/info.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ void Info::print_logo() const {
7070
print("| "); print("\\ \\ / /", c); print(" |\n");
7171
print("| "); print("\\ ' /", c); print(" |\n");
7272
print("| "); print("\\ /", c); print(" |\n");
73-
print("| "); print("\\ /", c); print(" FluidX3D Version 2.0 |\n");
73+
print("| "); print("\\ /", c); print(" FluidX3D Version 2.2 |\n");
7474
print("| "); print("'", c); print(" Copyright (c) Moritz Lehmann |\n");
7575
}
7676
void Info::print_initialize() {

src/kernel.cpp

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,9 +2065,13 @@ string opencl_c_container() { return R( // ########################## begin of O
20652065

20662066

20672067

2068-
)+R(kernel void voxelize_mesh(const uint direction, global uchar* flags, const uchar flag, const global float* p0, const global float* p1, const global float* p2, const uint triangle_number, float x0, float y0, float z0, float x1, float y1, float z1) { // voxelize triangle mesh
2068+
)+R(kernel void voxelize_mesh(const uint direction, global fpxx* fi, global float* u, global uchar* flags, const ulong t, const uchar flag, const global float* p0, const global float* p1, const global float* p2, const global float* bbu) { // voxelize triangle mesh
20692069
const uint a=get_global_id(0), A=get_area(direction); // a = domain area index for each side, A = area of the domain boundary
20702070
if(a>=A) return; // area might not be a multiple of def_workgroup_size, so return here to avoid writing in unallocated memory space
2071+
const uint triangle_number = as_uint(bbu[0]);
2072+
const float x0=bbu[ 1], y0=bbu[ 2], z0=bbu[ 3], x1=bbu[ 4], y1=bbu[ 5], z1=bbu[ 6];
2073+
const float cx=bbu[ 7], cy=bbu[ 8], cz=bbu[ 9], ux=bbu[10], uy=bbu[11], uz=bbu[12], rx=bbu[13], ry=bbu[14], rz=bbu[15];
2074+
20712075
const uint3 xyz = direction==0u ? (uint3)((uint)max(0, (int)x0-def_Ox), a%def_Ny, a/def_Ny) : direction==1u ? (uint3)(a/def_Nz, (uint)max(0, (int)y0-def_Oy), a%def_Nz) : (uint3)(a%def_Nx, a/def_Nx, (uint)max(0, (int)z0-def_Oz));
20722076
const float3 p = position(xyz);
20732077
const float3 offset = (float3)(0.5f*(float)((def_Nx-2u*(def_Dx>1u))*def_Dx)-0.5f, 0.5f*(float)((def_Ny-2u*(def_Dy>1u))*def_Dy)-0.5f, 0.5f*(float)((def_Nz-2u*(def_Dz>1u))*def_Dz)-0.5f)+(float3)(def_domain_offset_x, def_domain_offset_y, def_domain_offset_z);
@@ -2127,10 +2131,7 @@ string opencl_c_container() { return R( // ########################## begin of O
21272131
}
21282132
}/**/
21292133

2130-
if(intersections==0u) return; // no intersection for the entire column, so return immediately
2131-
bool inside = (intersections%2u)&&(intersections_check%2u);
2132-
2133-
for(int i=1; i<(int)intersections; i++) { // insertion sort of distances
2134+
for(int i=1; i<(int)intersections; i++) { // insertion-sort distances
21342135
ushort t = distances[i];
21352136
int j = i-1;
21362137
while(distances[j]>t&&j>=0) {
@@ -2140,15 +2141,47 @@ string opencl_c_container() { return R( // ########################## begin of O
21402141
distances[j+1] = t;
21412142
}
21422143

2143-
uint intersection = 0u; // iterate through column
2144+
bool inside = (intersections%2u)&&(intersections_check%2u);
2145+
const bool set_u = sq(ux)+sq(uy)+sq(uz)+sq(rx)+sq(ry)+sq(rz)>0.0f;
2146+
uint intersection = intersections%2u!=intersections_check%2u; // iterate through column, start with 0 regularly, start with 1 if forward and backward intersection count evenness differs (error correction)
21442147
const uint h0 = direction==0u ? xyz.x : direction==1u ? xyz.y : xyz.z;
2145-
for(uint h=h0; h<min(h0+(uint)distances[intersections-1u], (uint)def_N/A); h++) {
2148+
const uint hmax = direction==0u ? (uint)clamp((int)x1-def_Ox, 0, (int)def_Nx-1) : direction==1u ? (uint)clamp((int)y1-def_Oy, 0, (int)def_Ny-1) : (uint)clamp((int)z1-def_Oz, 0, (int)def_Nz-1);
2149+
const uint hmesh = h0+(uint)distances[intersections-1u];
2150+
for(uint h=h0; h<hmax; h++) {
21462151
while(intersection<intersections&&h>h0+(uint)distances[intersection]) {
21472152
inside = !inside; // passed mesh intersection, so switch inside/outside state
21482153
intersection++;
21492154
}
2155+
inside &= (intersection<intersections&&h<hmesh); // point must be outside if there are no more ray-mesh intersections ahead (error correction)
21502156
const ulong n = index((uint3)(direction==0u?h:xyz.x, direction==1u?h:xyz.y, direction==2u?h:xyz.z));
2151-
if(inside) flags[n] |= flag;
2157+
uchar flagsn = flags[n];
2158+
if(inside) {
2159+
flagsn = (flagsn&~TYPE_BO)|flag;
2160+
if(set_u) {
2161+
const float3 p = position(coordinates(n))+offset;
2162+
const float3 un = (float3)(ux, uy, uz)+cross((float3)(cx, cy, cz)-p, (float3)(rx, ry, rz));
2163+
u[ n] = un.x;
2164+
u[ def_N+(ulong)n] = un.y;
2165+
u[2ul*def_N+(ulong)n] = un.z;
2166+
}
2167+
} else {
2168+
if(set_u) {
2169+
const float3 un = (float3)(u[n], u[def_N+(ulong)n], u[2ul*def_N+(ulong)n]); // for velocity voxelization, only clear moving boundaries
2170+
if((flagsn&TYPE_BO)==TYPE_S) { // reconstruct DDFs when boundary point is converted to fluid
2171+
uint j[def_velocity_set]; // neighbor indices
2172+
neighbors(n, j); // calculate neighbor indices
2173+
float feq[def_velocity_set]; // f_equilibrium
2174+
calculate_f_eq(1.0f, un.x, un.y, un.z, feq);
2175+
store_f(n, feq, fi, j, t); // write to fi
2176+
}
2177+
if(sq(un.x)+sq(un.y)+sq(un.z)>0.0f) {
2178+
flagsn = (flagsn&TYPE_BO)==TYPE_MS ? flagsn&~TYPE_MS : flagsn&~flag;
2179+
}
2180+
} else {
2181+
flagsn = (flagsn&TYPE_BO)==TYPE_MS ? flagsn&~TYPE_MS : flagsn&~flag;
2182+
}
2183+
}
2184+
flags[n] = flagsn;
21522185
}
21532186
} // voxelize_mesh()
21542187

@@ -2164,7 +2197,7 @@ string opencl_c_container() { return R( // ########################## begin of O
21642197

21652198
)+"#ifdef GRAPHICS"+R(
21662199

2167-
)+"#ifndef FORCE_FIELD"+R(
2200+
)+"#ifndef FORCE_FIELD"+R( // render flags as grid
21682201
)+R(kernel void graphics_flags(const global uchar* flags, const global float* camera, global int* bitmap, global int* zbuffer) {
21692202
)+"#else"+R( // FORCE_FIELD
21702203
)+R(kernel void graphics_flags(const global uchar* flags, const global float* camera, global int* bitmap, global int* zbuffer, const global float* F) {
@@ -2231,7 +2264,62 @@ string opencl_c_container() { return R( // ########################## begin of O
22312264
}
22322265
}
22332266
)+"#endif"+R( // FORCE_FIELD
2234-
}
2267+
}/**/
2268+
2269+
/*)+"#ifndef FORCE_FIELD"+R( // render solid boundaries with marching-cubes
2270+
)+R(kernel void graphics_flags(const global uchar* flags, const global float* camera, global int* bitmap, global int* zbuffer) {
2271+
)+"#else"+R( // FORCE_FIELD
2272+
)+R(kernel void graphics_flags(const global uchar* flags, const global float* camera, global int* bitmap, global int* zbuffer, const global float* F) {
2273+
)+"#endif"+R( // FORCE_FIELD
2274+
const uint n = get_global_id(0);
2275+
if(n>=(uint)def_N||is_halo(n)) return; // don't execute graphics_flags() on halo
2276+
const uint3 xyz = coordinates(n);
2277+
if(xyz.x>=def_Nx-1u||xyz.y>=def_Ny-1u||xyz.z>=def_Nz-1u) return;
2278+
//if(xyz.x==0u||xyz.y==0u||xyz.z==0u||xyz.x>=def_Nx-2u||xyz.y>=def_Ny-2u||xyz.z>=def_Nz-2u) return;
2279+
uint j[8];
2280+
const uint x0 = xyz.x; // cube stencil
2281+
const uint xp = xyz.x+1u;
2282+
const uint y0 = xyz.y *def_Nx;
2283+
const uint yp = (xyz.y+1u)*def_Nx;
2284+
const uint z0 = xyz.z *def_Ny*def_Nx;
2285+
const uint zp = (xyz.z+1u)*def_Ny*def_Nx;
2286+
j[0] = n ; // 000
2287+
j[1] = xp+y0+z0; // +00
2288+
j[2] = xp+y0+zp; // +0+
2289+
j[3] = x0+y0+zp; // 00+
2290+
j[4] = x0+yp+z0; // 0+0
2291+
j[5] = xp+yp+z0; // ++0
2292+
j[6] = xp+yp+zp; // +++
2293+
j[7] = x0+yp+zp; // 0++
2294+
float v[8];
2295+
for(uint i=0u; i<8u; i++) v[i] = (float)((flags[j[i]]&TYPE_BO)==TYPE_S);
2296+
float3 triangles[15]; // maximum of 5 triangles with 3 vertices each
2297+
const uint tn = marching_cubes(v, 0.5f, triangles); // run marching cubes algorithm
2298+
if(tn==0u) return;
2299+
float camera_cache[15]; // cache camera parameters in case the kernel draws more than one shape
2300+
for(uint i=0u; i<15u; i++) camera_cache[i] = camera[i];
2301+
const float3 offset = (float3)((float)xyz.x+0.5f-0.5f*(float)def_Nx, (float)xyz.y+0.5f-0.5f*(float)def_Ny, (float)xyz.z+0.5f-0.5f*(float)def_Nz);
2302+
for(uint i=0u; i<tn; i++) {
2303+
const float3 p0 = triangles[3u*i ]+offset;
2304+
const float3 p1 = triangles[3u*i+1u]+offset;
2305+
const float3 p2 = triangles[3u*i+2u]+offset;
2306+
const float3 p=(p0+p1+p2)/3.0f, normal=cross(p1-p0, p2-p0);
2307+
const int c = lighting(191<<16|191<<8|191, p, normal, camera_cache);
2308+
draw_triangle(p0, p1, p2, c, camera_cache, bitmap, zbuffer);
2309+
}
2310+
)+"#ifdef FORCE_FIELD"+R(
2311+
const uchar flagsn_bo = flags[n]&TYPE_BO;
2312+
const float3 p = position(xyz);
2313+
if(flagsn_bo==TYPE_S) {
2314+
const float3 Fn = def_scale_F*(float3)(F[n], F[def_N+(ulong)n], F[2ul*def_N+(ulong)n]);
2315+
const float Fnl = length(Fn);
2316+
if(Fnl>0.0f) {
2317+
const int c = iron_color(255.0f*Fnl); // color boundaries depending on the force on them
2318+
draw_line(p, p+5.0f*Fn, c, camera_cache, bitmap, zbuffer); // draw colored force vectors
2319+
}
2320+
}
2321+
)+"#endif"+R( // FORCE_FIELD
2322+
}/**/
22352323

22362324
)+R(kernel void graphics_field(const global uchar* flags, const global float* u, const global float* camera, global int* bitmap, global int* zbuffer) {
22372325
const uint n = get_global_id(0);

src/lbm.cpp

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,25 +164,54 @@ uint LBM_Domain::get_velocity_set() const {
164164
return velocity_set;
165165
}
166166

167-
void LBM_Domain::voxelize_mesh_on_device(const Mesh* mesh, const uchar flag) { // voxelize triangle mesh
167+
void LBM_Domain::voxelize_mesh_on_device(const Mesh* mesh, const uchar flag, const float3& rotation_center, const float3& linear_velocity, const float3& rotational_velocity) { // voxelize triangle mesh
168168
Memory<float3> p0(device, mesh->triangle_number, 1u, mesh->p0);
169169
Memory<float3> p1(device, mesh->triangle_number, 1u, mesh->p1);
170170
Memory<float3> p2(device, mesh->triangle_number, 1u, mesh->p2);
171+
Memory<float> bounding_box_and_velocity(device, 16u);
171172
const float x0=mesh->pmin.x, y0=mesh->pmin.y, z0=mesh->pmin.z, x1=mesh->pmax.x, y1=mesh->pmax.y, z1=mesh->pmax.z; // use bounding box of mesh to speed up voxelization
172-
const float M[3] = { (y1-y0)*(z1-z0), (z1-z0)*(x1-x0), (x1-x0)*(y1-y0) };
173-
float Mmin = M[0];
173+
bounding_box_and_velocity[ 0] = as_float(mesh->triangle_number);
174+
bounding_box_and_velocity[ 1] = x0;
175+
bounding_box_and_velocity[ 2] = y0;
176+
bounding_box_and_velocity[ 3] = z0;
177+
bounding_box_and_velocity[ 4] = x1;
178+
bounding_box_and_velocity[ 5] = y1;
179+
bounding_box_and_velocity[ 6] = z1;
180+
bounding_box_and_velocity[ 7] = rotation_center.x;
181+
bounding_box_and_velocity[ 8] = rotation_center.y;
182+
bounding_box_and_velocity[ 9] = rotation_center.z;
183+
bounding_box_and_velocity[10] = linear_velocity.x;
184+
bounding_box_and_velocity[11] = linear_velocity.y;
185+
bounding_box_and_velocity[12] = linear_velocity.z;
186+
bounding_box_and_velocity[13] = rotational_velocity.x;
187+
bounding_box_and_velocity[14] = rotational_velocity.y;
188+
bounding_box_and_velocity[15] = rotational_velocity.z;
174189
uint direction = 0u;
175-
for(uint i=1u; i<3u; i++) {
176-
if(M[i]<Mmin) {
177-
Mmin = M[i];
178-
direction = i; // find direction of minimum bounding-box cross-section area
190+
if(length(rotational_velocity)==0.0f) { // choose direction of minimum bounding-box cross-section area
191+
float v[3] = { (y1-y0)*(z1-z0), (z1-z0)*(x1-x0), (x1-x0)*(y1-y0) };
192+
float vmin = v[0];
193+
for(uint i=1u; i<3u; i++) {
194+
if(v[i]<vmin) {
195+
vmin = v[i];
196+
direction = i;
197+
}
198+
}
199+
} else { // choose direction closest to rotation axis
200+
float v[3] = { rotational_velocity.x, rotational_velocity.y, rotational_velocity.z };
201+
float vmax = v[0];
202+
for(uint i=1u; i<3u; i++) {
203+
if(v[i]>vmax) {
204+
vmax = v[i];
205+
direction = i; // find direction of minimum bounding-box cross-section area
206+
}
179207
}
180208
}
181209
const ulong A[3] = { (ulong)Ny*(ulong)Nz, (ulong)Nz*(ulong)Nx, (ulong)Nx*(ulong)Ny };
182-
Kernel kernel_voxelize_mesh(device, A[direction], "voxelize_mesh", direction, flags, flag, p0, p1, p2, mesh->triangle_number, x0, y0, z0, x1, y1, z1);
210+
Kernel kernel_voxelize_mesh(device, A[direction], "voxelize_mesh", direction, fi, u, flags, t+1ull, flag, p0, p1, p2, bounding_box_and_velocity);
183211
p0.write_to_device();
184212
p1.write_to_device();
185213
p2.write_to_device();
214+
bounding_box_and_velocity.write_to_device();
186215
kernel_voxelize_mesh.run();
187216
}
188217
void LBM_Domain::enqueue_unvoxelize_mesh_on_device(const Mesh* mesh, const uchar flag) { // remove voxelized triangle mesh from LBM grid
@@ -750,16 +779,23 @@ void LBM::write_status(const string& path) { // write LBM status report to a .tx
750779
write_file(filename, status);
751780
}
752781

753-
void LBM::voxelize_mesh_on_device(const Mesh* mesh, const uchar flag) { // voxelize triangle mesh
782+
void LBM::voxelize_mesh_on_device(const Mesh* mesh, const uchar flag, const float3& rotation_center, const float3& linear_velocity, const float3& rotational_velocity) { // voxelize triangle mesh
754783
if(get_D()==1u) {
755-
lbm[0]->voxelize_mesh_on_device(mesh, flag); // if this crashes on Windows, create a TdrDelay 32-bit DWORD with decimal value 300 in Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers
784+
lbm[0]->voxelize_mesh_on_device(mesh, flag, rotation_center, linear_velocity, rotational_velocity); // if this crashes on Windows, create a TdrDelay 32-bit DWORD with decimal value 300 in Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers
756785
} else {
757786
thread* threads=new thread[get_D()]; for(uint d=0u; d<get_D(); d++) threads[d]=thread([=]() {
758-
lbm[d]->voxelize_mesh_on_device(mesh, flag);
787+
lbm[d]->voxelize_mesh_on_device(mesh, flag, rotation_center, linear_velocity, rotational_velocity);
759788
}); for(uint d=0u; d<get_D(); d++) threads[d].join(); delete[] threads;
760789
}
790+
#ifdef MOVING_BOUNDARIES
791+
if(flag==TYPE_S&&(length(linear_velocity)>0.0f||length(rotational_velocity)>0.0f)) update_moving_boundaries();
792+
#endif // MOVING_BOUNDARIES
793+
if(!initialized) {
794+
flags.read_from_device();
795+
u.read_from_device();
796+
}
761797
}
762-
void LBM::unvoxelize_mesh_on_device(const Mesh* mesh, const uchar flag) { // remove voxelized triangle mesh from LBM grid
798+
void LBM::unvoxelize_mesh_on_device(const Mesh* mesh, const uchar flag) { // remove voxelized triangle mesh from LBM grid by removing all flags in mesh bounding box (only required when bounding box size changes during re-voxelization)
763799
for(uint d=0u; d<get_D(); d++) lbm[d]->enqueue_unvoxelize_mesh_on_device(mesh, flag);
764800
for(uint d=0u; d<get_D(); d++) lbm[d]->finish_queue();
765801
}

src/lbm.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class LBM_Domain {
121121
void set_fz(const float fz) { this->fz = fz; } // set global froce per volume
122122
void set_f(const float fx, const float fy, const float fz) { set_fx(fx); set_fy(fy); set_fz(fz); } // set global froce per volume
123123

124-
void voxelize_mesh_on_device(const Mesh* mesh, const uchar flag=TYPE_S); // voxelize mesh
124+
void voxelize_mesh_on_device(const Mesh* mesh, const uchar flag=TYPE_S, const float3& rotation_center=float3(0.0f), const float3& linear_velocity=float3(0.0f), const float3& rotational_velocity=float3(0.0f)); // voxelize mesh
125125
void enqueue_unvoxelize_mesh_on_device(const Mesh* mesh, const uchar flag=TYPE_S); // remove voxelized triangle mesh from LBM grid
126126

127127
#ifdef GRAPHICS
@@ -473,7 +473,7 @@ class LBM {
473473
}
474474
void write_status(const string& path=""); // write LBM status report to a .txt file
475475

476-
void voxelize_mesh_on_device(const Mesh* mesh, const uchar flag=TYPE_S); // voxelize mesh
476+
void voxelize_mesh_on_device(const Mesh* mesh, const uchar flag=TYPE_S, const float3& rotation_center=float3(0.0f), const float3& linear_velocity=float3(0.0f), const float3& rotational_velocity=float3(0.0f)); // voxelize mesh
477477
void unvoxelize_mesh_on_device(const Mesh* mesh, const uchar flag=TYPE_S); // remove voxelized triangle mesh from LBM grid
478478
void voxelize_stl(const string& path, const float3& center, const float3x3& rotation, const float size=-1.0f, const uchar flag=TYPE_S); // read and voxelize binary .stl file
479479
void voxelize_stl(const string& path, const float3x3& rotation, const float size=-1.0f, const uchar flag=TYPE_S); // read and voxelize binary .stl file (place in box center)

0 commit comments

Comments
 (0)