stable-ish multichunk solver
All checks were successful
studiorailgun/fluid-sim/pipeline/head This commit looks good
All checks were successful
studiorailgun/fluid-sim/pipeline/head This commit looks good
This commit is contained in:
parent
d4d5d2c94e
commit
7a1da2d29c
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@
|
||||
/shared-folder
|
||||
/shared-folder/**
|
||||
/src/main/c/lib/**
|
||||
/chunks
|
||||
9
compare.sh
Normal file
9
compare.sh
Normal file
@ -0,0 +1,9 @@
|
||||
search_dir=./chunks
|
||||
for entry in "$search_dir"/*
|
||||
do
|
||||
if ! diff "/c/Users/satellite/Documents/fluid-sim/$entry" "/c/Users/satellite/Documents/fluid-sim-the-golden-code/$entry" > /dev/null;
|
||||
then
|
||||
echo "“$entry differs"
|
||||
fi
|
||||
done
|
||||
echo "done!"
|
||||
26
optimizationideas.md
Normal file
26
optimizationideas.md
Normal file
@ -0,0 +1,26 @@
|
||||
'sleep' chunks if there are no massive velocity swings for ~100 frames
|
||||
- unsleep as soon as new density or velocity is added
|
||||
|
||||
VVV These two may not work because the projection phase currently iterates over all cells with avx instructions
|
||||
it also may generally not work as fast moving water basically works by having empty cells query area that already has water (so maybe no sleeping air)
|
||||
'sleep' individual cells that are full of water and fully surrounded by water
|
||||
'sleep' terrain cells
|
||||
'sleep' "inactive" cells
|
||||
- When reading data back into java, while iterating over each cell check if they've changed value
|
||||
- If not, add 1 to an accumulating array alongside the other ones (d, u, etc)
|
||||
- In C, if the accumulating array is >100 or some threshold, skip the cell
|
||||
- If a force gets added java side that is greater than some threshold, set all accumulating array to 0
|
||||
|
||||
multigrid method for projection code
|
||||
|
||||
intelligent neighbor awakening --
|
||||
If a neighbor is asleep (say a neighbor full of air above this cell),
|
||||
do not simulate that neighbor at all
|
||||
Instead poll the currently awake cell for large velocity spikes in the direction of the neighbor
|
||||
If there is a large spike, awake the neighbor cell
|
||||
if a neighbor is awoken, the simulator should go back through and simulate for that chunk as well (idk if this is possible)
|
||||
|
||||
|
||||
use avx bitmasking for handling terrain in projection code ???
|
||||
|
||||
|
||||
@ -15,9 +15,13 @@
|
||||
|
||||
#define DIM 18
|
||||
#define LINEARSOLVERTIMES 20
|
||||
#define SAVE_STEPS 0
|
||||
#define REALLY_SMALL_VALUE 0.00001
|
||||
|
||||
#define DIFFUSION_CONSTANT 0.000001
|
||||
#define VISCOSITY_CONSTANT 0.000001
|
||||
#define DIFFUSION_CONSTANT 0.00001
|
||||
#define VISCOSITY_CONSTANT 0.00001
|
||||
|
||||
char fileNameBuff[50];
|
||||
|
||||
/**
|
||||
* A chunk
|
||||
@ -44,6 +48,8 @@ Chunk ** chunks = NULL;
|
||||
//jni help:
|
||||
//https://stackoverflow.com/questions/39823375/clarification-about-getfieldid
|
||||
|
||||
void saveStep(float * values, const char * name);
|
||||
|
||||
JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
JNIEnv * env,
|
||||
jclass class,
|
||||
@ -139,6 +145,12 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
}
|
||||
|
||||
// printf("%p\n",chunks[0].d);
|
||||
saveStep(chunks[0]->u[CENTER_LOC], "./chunks/beginU");
|
||||
saveStep(chunks[0]->v[CENTER_LOC], "./chunks/beginV");
|
||||
saveStep(chunks[0]->w[CENTER_LOC], "./chunks/beginW");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/beginU0");
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/beginV0");
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], "./chunks/beginW0");
|
||||
|
||||
//solve chunk mask
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
@ -157,6 +169,12 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
VISCOSITY_CONSTANT,
|
||||
timestep
|
||||
);
|
||||
saveStep(currentChunk->u[CENTER_LOC], "./chunks/addSrcU");
|
||||
saveStep(currentChunk->v[CENTER_LOC], "./chunks/addSrcV");
|
||||
saveStep(currentChunk->w[CENTER_LOC], "./chunks/addSrcW");
|
||||
saveStep(currentChunk->u0[CENTER_LOC], "./chunks/addSrcU0");
|
||||
saveStep(currentChunk->v0[CENTER_LOC], "./chunks/addSrcV0");
|
||||
saveStep(currentChunk->w0[CENTER_LOC], "./chunks/addSrcW0");
|
||||
}
|
||||
//swap all vector fields
|
||||
{
|
||||
@ -194,6 +212,16 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
copyNeighborsRaw(DIM,chunkMask,0,3,currentChunk->w0);
|
||||
}
|
||||
}
|
||||
saveStep(chunks[0]->u[CENTER_LOC], "./chunks/swapU");
|
||||
saveStep(chunks[0]->v[CENTER_LOC], "./chunks/swapV");
|
||||
saveStep(chunks[0]->w[CENTER_LOC], "./chunks/swapW");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/swapU0");
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/swapV0");
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], "./chunks/swapW0");
|
||||
// printf("after swap vecs u\n");
|
||||
// printLayer(chunks[0]->u[CENTER_LOC],targetLayer);
|
||||
// printf("after swap vecs u0\n");
|
||||
// printLayer(chunks[0]->u0[CENTER_LOC],targetLayer);
|
||||
//solve vector diffusion
|
||||
{
|
||||
for(int l = 0; l < LINEARSOLVERTIMES; l++){
|
||||
@ -203,6 +231,20 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
Java_electrosphere_FluidSim_solveVectorDiffuse(DIM,chunkMask,currentChunk->u,currentChunk->v,currentChunk->w,currentChunk->u0,currentChunk->v0,currentChunk->w0,DIFFUSION_CONSTANT,VISCOSITY_CONSTANT,timestep);
|
||||
}
|
||||
if(SAVE_STEPS){
|
||||
sprintf(fileNameBuff, "./chunks/diffuseUStep%dx", l);
|
||||
saveStep(chunks[0]->u[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseUStep%dx0", l);
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseVStep%dx", l);
|
||||
saveStep(chunks[0]->v[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseVStep%dx0", l);
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseWStep%dx", l);
|
||||
saveStep(chunks[0]->w[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseWStep%dx0", l);
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], fileNameBuff);
|
||||
}
|
||||
//update array for vectors
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
Chunk * currentChunk = chunks[i];
|
||||
@ -210,23 +252,49 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,2,currentChunk->v);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,3,currentChunk->w);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,1,currentChunk->u);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,2,currentChunk->v);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,3,currentChunk->w);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u0);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,2,currentChunk->v0);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,3,currentChunk->w0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->u);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->v);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->w);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->u0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->v0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->w0);
|
||||
}
|
||||
if(SAVE_STEPS){
|
||||
sprintf(fileNameBuff, "./chunks/diffuseUStep%dxBnd", l);
|
||||
saveStep(chunks[0]->u[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseUStep%dx0Bnd", l);
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseVStep%dxBnd", l);
|
||||
saveStep(chunks[0]->v[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseVStep%dx0Bnd", l);
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseWStep%dxBnd", l);
|
||||
saveStep(chunks[0]->w[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/diffuseWStep%dx0Bnd", l);
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], fileNameBuff);
|
||||
}
|
||||
}
|
||||
}
|
||||
saveStep(chunks[0]->u[CENTER_LOC], "./chunks/diffuseU");
|
||||
saveStep(chunks[0]->v[CENTER_LOC], "./chunks/diffuseV");
|
||||
saveStep(chunks[0]->w[CENTER_LOC], "./chunks/diffuseW");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/diffuseU0");
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/diffuseV0");
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], "./chunks/diffuseW0");
|
||||
//solve projection
|
||||
{
|
||||
//update array for vectors
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
Chunk * currentChunk = chunks[i];
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,2,currentChunk->v);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,3,currentChunk->w);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u0);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,2,currentChunk->v0);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,2,currentChunk->v);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,3,currentChunk->w);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u0);
|
||||
// setBoundsToNeighborsRaw(DIM,chunkMask,2,currentChunk->v0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,1,currentChunk->u);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,2,currentChunk->v);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,3,currentChunk->w);
|
||||
@ -237,17 +305,25 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
Chunk * currentChunk = chunks[i];
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
Java_electrosphere_FluidSim_setupProjection(DIM,chunkMask,currentChunk->u,currentChunk->v,currentChunk->w,currentChunk->u0,currentChunk->v0,currentChunk->w0,DIFFUSION_CONSTANT,VISCOSITY_CONSTANT,timestep);
|
||||
Java_electrosphere_FluidSim_setupProjection(DIM,chunkMask,currentChunk->u,currentChunk->v,currentChunk->w,currentChunk->u0,currentChunk->v0,DIFFUSION_CONSTANT,VISCOSITY_CONSTANT,timestep);
|
||||
}
|
||||
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/setupProj1Div");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/setupProj1P");
|
||||
|
||||
//update array for vectors
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
Chunk * currentChunk = chunks[i];
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u0);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,2,currentChunk->v0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,1,currentChunk->u0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,2,currentChunk->v0);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,0,currentChunk->u0);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,0,currentChunk->v0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->u0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->v0);
|
||||
}
|
||||
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/setupProj1DivBnd");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/setupProj1PBnd");
|
||||
|
||||
//samples u0, v0
|
||||
//sets u0
|
||||
//these should have just been mirrored in the above
|
||||
@ -259,11 +335,23 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
Java_electrosphere_FluidSim_solveProjection(DIM,chunkMask,currentChunk->u,currentChunk->v,currentChunk->w,currentChunk->u0,currentChunk->v0,currentChunk->w0,DIFFUSION_CONSTANT,VISCOSITY_CONSTANT,timestep);
|
||||
}
|
||||
if(SAVE_STEPS){
|
||||
sprintf(fileNameBuff, "./chunks/proj1Step%dx", l);
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/proj1Step%dx0", l);
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], fileNameBuff);
|
||||
}
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
Chunk * currentChunk = chunks[i];
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,1,currentChunk->u0);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,0,currentChunk->u0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->u0);
|
||||
}
|
||||
if(SAVE_STEPS){
|
||||
sprintf(fileNameBuff, "./chunks/proj1Step%dxBnd", l);
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], fileNameBuff);
|
||||
sprintf(fileNameBuff, "./chunks/proj1Step%dx0Bnd", l);
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], fileNameBuff);
|
||||
}
|
||||
}
|
||||
//samples u,v,w,u0
|
||||
@ -274,6 +362,11 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
Java_electrosphere_FluidSim_finalizeProjection(DIM,chunkMask,currentChunk->u,currentChunk->v,currentChunk->w,currentChunk->u0,currentChunk->v0,currentChunk->w0,DIFFUSION_CONSTANT,VISCOSITY_CONSTANT,timestep);
|
||||
}
|
||||
|
||||
saveStep(chunks[0]->u[CENTER_LOC], "./chunks/finalizeProj1U");
|
||||
saveStep(chunks[0]->v[CENTER_LOC], "./chunks/finalizeProj1V");
|
||||
saveStep(chunks[0]->w[CENTER_LOC], "./chunks/finalizeProj1W");
|
||||
// exit(0);
|
||||
//set boundaries a final time for u,v,w
|
||||
//...
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
@ -293,6 +386,13 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
copyNeighborsRaw(DIM,chunkMask,0,3,currentChunk->w0);
|
||||
}
|
||||
}
|
||||
saveStep(chunks[0]->u[CENTER_LOC], "./chunks/projU");
|
||||
saveStep(chunks[0]->v[CENTER_LOC], "./chunks/projV");
|
||||
saveStep(chunks[0]->w[CENTER_LOC], "./chunks/projW");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/projU0");
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/projV0");
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], "./chunks/projW0");
|
||||
// exit(0);
|
||||
//swap all vector fields
|
||||
{
|
||||
//swap vector fields
|
||||
@ -329,6 +429,12 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
copyNeighborsRaw(DIM,chunkMask,0,3,currentChunk->w0);
|
||||
}
|
||||
}
|
||||
saveStep(chunks[0]->u[CENTER_LOC], "./chunks/swap2U");
|
||||
saveStep(chunks[0]->v[CENTER_LOC], "./chunks/swap2V");
|
||||
saveStep(chunks[0]->w[CENTER_LOC], "./chunks/swap2W");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/swap2U0");
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/swap2V0");
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], "./chunks/swap2W0");
|
||||
//advect vectors across boundaries
|
||||
{
|
||||
//update border arrs
|
||||
@ -366,6 +472,12 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
copyNeighborsRaw(DIM,chunkMask,0,3,currentChunk->w);
|
||||
}
|
||||
}
|
||||
saveStep(chunks[0]->u[CENTER_LOC], "./chunks/advectU");
|
||||
saveStep(chunks[0]->v[CENTER_LOC], "./chunks/advectV");
|
||||
saveStep(chunks[0]->w[CENTER_LOC], "./chunks/advectW");
|
||||
saveStep(chunks[0]->u0[CENTER_LOC], "./chunks/advectU0");
|
||||
saveStep(chunks[0]->v0[CENTER_LOC], "./chunks/advectV0");
|
||||
saveStep(chunks[0]->w0[CENTER_LOC], "./chunks/advectW0");
|
||||
//solve projection
|
||||
{
|
||||
//update array for vectors
|
||||
@ -387,7 +499,7 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
Chunk * currentChunk = chunks[i];
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
Java_electrosphere_FluidSim_setupProjection(DIM,chunkMask,currentChunk->u,currentChunk->v,currentChunk->w,currentChunk->u0,currentChunk->v0,currentChunk->w0,DIFFUSION_CONSTANT,VISCOSITY_CONSTANT,timestep);
|
||||
Java_electrosphere_FluidSim_setupProjection(DIM,chunkMask,currentChunk->u,currentChunk->v,currentChunk->w,currentChunk->u0,currentChunk->v0,DIFFUSION_CONSTANT,VISCOSITY_CONSTANT,timestep);
|
||||
}
|
||||
//update array for vectors
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
@ -412,8 +524,8 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
for(int i = 0; i < numChunks; i++){
|
||||
Chunk * currentChunk = chunks[i];
|
||||
chunkMask = currentChunk->chunkMask;
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,1,currentChunk->u0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,1,currentChunk->u0);
|
||||
setBoundsToNeighborsRaw(DIM,chunkMask,0,currentChunk->u0);
|
||||
copyNeighborsRaw(DIM,chunkMask,0,0,currentChunk->u0);
|
||||
}
|
||||
}
|
||||
//samples u,v,w,u0
|
||||
@ -535,3 +647,33 @@ JNIEXPORT void JNICALL Java_electrosphere_FluidSim_simulate(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void saveStep(float * values, const char * name){
|
||||
if(SAVE_STEPS){
|
||||
FILE *fp;
|
||||
int N = DIM;
|
||||
|
||||
// ... fill the array somehow ...
|
||||
|
||||
fp = fopen(name, "w");
|
||||
// check for error here
|
||||
|
||||
for(int x = 0; x < DIM; x++){
|
||||
for(int y = 0; y < DIM; y++){
|
||||
for(int z = 0; z < DIM; z++){
|
||||
float val = values[IX(x,y,z)];
|
||||
if(val < REALLY_SMALL_VALUE && val > -REALLY_SMALL_VALUE){
|
||||
val = 0;
|
||||
}
|
||||
fprintf(fp, "%f\t", val);
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
@ -10,13 +10,13 @@ extern "C" {
|
||||
#undef electrosphere_FluidSim_DIM
|
||||
#define electrosphere_FluidSim_DIM 18L
|
||||
#undef electrosphere_FluidSim_DIFFUSION_CONSTANT
|
||||
#define electrosphere_FluidSim_DIFFUSION_CONSTANT 0.0f
|
||||
#define electrosphere_FluidSim_DIFFUSION_CONSTANT 1.0E-5f
|
||||
#undef electrosphere_FluidSim_VISCOSITY_CONSTANT
|
||||
#define electrosphere_FluidSim_VISCOSITY_CONSTANT 0.0f
|
||||
#define electrosphere_FluidSim_VISCOSITY_CONSTANT 1.0E-5f
|
||||
#undef electrosphere_FluidSim_LINEARSOLVERTIMES
|
||||
#define electrosphere_FluidSim_LINEARSOLVERTIMES 20L
|
||||
#undef electrosphere_FluidSim_GRAVITY
|
||||
#define electrosphere_FluidSim_GRAVITY -100.0f
|
||||
#define electrosphere_FluidSim_GRAVITY -1000.0f
|
||||
/*
|
||||
* Class: electrosphere_FluidSim
|
||||
* Method: simulate
|
||||
|
||||
@ -33,7 +33,17 @@ void Java_electrosphere_FluidSim_solveVectorDiffuse
|
||||
* Signature: (II[Ljava/nio/ByteBuffer;[Ljava/nio/ByteBuffer;[Ljava/nio/ByteBuffer;[Ljava/nio/ByteBuffer;[Ljava/nio/ByteBuffer;[Ljava/nio/ByteBuffer;FFF)V
|
||||
*/
|
||||
void Java_electrosphere_FluidSim_setupProjection
|
||||
(int, int, float **, float **, float **, float **, float **, float **, float, float, float);
|
||||
(
|
||||
int N,
|
||||
int chunk_mask,
|
||||
float ** ur,
|
||||
float ** vr,
|
||||
float ** wr,
|
||||
float ** pr,
|
||||
float ** divr,
|
||||
float DIFFUSION_CONST,
|
||||
float VISCOSITY_CONST,
|
||||
float dt);
|
||||
|
||||
/*
|
||||
* Class: electrosphere_FluidSim
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
#define SET_BOUND_IGNORE 0
|
||||
#define SET_BOUND_USE_NEIGHBOR 1
|
||||
|
||||
#define LINEARSOLVERTIMES 20
|
||||
|
||||
void add_source(int N, float * x, float * s, float dt);
|
||||
void advect(uint32_t chunk_mask, int N, int b, float ** jrd, float ** jrd0, float * u, float * v, float * w, float dt);
|
||||
|
||||
@ -162,30 +164,27 @@ void Java_electrosphere_FluidSim_setupProjection
|
||||
(
|
||||
int N,
|
||||
int chunk_mask,
|
||||
float ** jru,
|
||||
float ** jrv,
|
||||
float ** jrw,
|
||||
float ** jru0,
|
||||
float ** jrv0,
|
||||
float ** jrw0,
|
||||
float ** ur,
|
||||
float ** vr,
|
||||
float ** wr,
|
||||
float ** pr,
|
||||
float ** divr,
|
||||
float DIFFUSION_CONST,
|
||||
float VISCOSITY_CONST,
|
||||
float dt){
|
||||
int i, j, k;
|
||||
|
||||
__m256 xVector = _mm256_set1_ps(N);
|
||||
__m256 yVector = _mm256_set1_ps(N);
|
||||
__m256 zVector = _mm256_set1_ps(N);
|
||||
__m256 nVector = _mm256_set1_ps(N);
|
||||
__m256 constScalar = _mm256_set1_ps(-1.0/3.0);
|
||||
__m256 zeroVec = _mm256_set1_ps(0);
|
||||
__m256 vector, vector2, vector3;
|
||||
|
||||
float * u = GET_ARR_RAW(env,jru,CENTER_LOC);
|
||||
float * v = GET_ARR_RAW(env,jrv,CENTER_LOC);
|
||||
float * w = GET_ARR_RAW(env,jrw,CENTER_LOC);
|
||||
float * u = GET_ARR_RAW(env,ur,CENTER_LOC);
|
||||
float * v = GET_ARR_RAW(env,vr,CENTER_LOC);
|
||||
float * w = GET_ARR_RAW(env,wr,CENTER_LOC);
|
||||
|
||||
float * p = GET_ARR_RAW(env,jru0,CENTER_LOC);
|
||||
float * div = GET_ARR_RAW(env,jrv0,CENTER_LOC);
|
||||
float * p = GET_ARR_RAW(env,pr,CENTER_LOC);
|
||||
float * div = GET_ARR_RAW(env,divr,CENTER_LOC);
|
||||
|
||||
float scalar = 1.0/3.0;
|
||||
float h = 1.0/N;
|
||||
@ -199,15 +198,15 @@ void Java_electrosphere_FluidSim_setupProjection
|
||||
//first part
|
||||
vector = _mm256_loadu_ps(&u[IX(i+1,j,k)]);
|
||||
vector = _mm256_sub_ps(vector,_mm256_loadu_ps(&u[IX(i-1,j,k)]));
|
||||
vector = _mm256_div_ps(vector,xVector);
|
||||
vector = _mm256_div_ps(vector,nVector);
|
||||
//second part
|
||||
vector2 = _mm256_loadu_ps(&v[IX(i,j+1,k)]);
|
||||
vector2 = _mm256_sub_ps(vector2,_mm256_loadu_ps(&v[IX(i,j-1,k)]));
|
||||
vector2 = _mm256_div_ps(vector2,yVector);
|
||||
vector2 = _mm256_div_ps(vector2,nVector);
|
||||
//third part
|
||||
vector3 = _mm256_loadu_ps(&w[IX(i,j,k+1)]);
|
||||
vector3 = _mm256_sub_ps(vector3,_mm256_loadu_ps(&w[IX(i,j,k-1)]));
|
||||
vector3 = _mm256_div_ps(vector3,zVector);
|
||||
vector3 = _mm256_div_ps(vector3,nVector);
|
||||
//multiply and finalize
|
||||
vector = _mm256_add_ps(vector,_mm256_add_ps(vector2,vector3));
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
@ -221,15 +220,15 @@ void Java_electrosphere_FluidSim_setupProjection
|
||||
//first part
|
||||
vector = _mm256_loadu_ps(&u[IX(i+1,j,k)]);
|
||||
vector = _mm256_sub_ps(vector,_mm256_loadu_ps(&u[IX(i-1,j,k)]));
|
||||
vector = _mm256_div_ps(vector,xVector);
|
||||
vector = _mm256_div_ps(vector,nVector);
|
||||
//second part
|
||||
vector2 = _mm256_loadu_ps(&v[IX(i,j+1,k)]);
|
||||
vector2 = _mm256_sub_ps(vector2,_mm256_loadu_ps(&v[IX(i,j-1,k)]));
|
||||
vector2 = _mm256_div_ps(vector2,yVector);
|
||||
vector2 = _mm256_div_ps(vector2,nVector);
|
||||
//third part
|
||||
vector3 = _mm256_loadu_ps(&w[IX(i,j,k+1)]);
|
||||
vector3 = _mm256_sub_ps(vector3,_mm256_loadu_ps(&w[IX(i,j,k-1)]));
|
||||
vector3 = _mm256_div_ps(vector3,zVector);
|
||||
vector3 = _mm256_div_ps(vector3,nVector);
|
||||
//multiply and finalize
|
||||
vector = _mm256_add_ps(vector,_mm256_add_ps(vector2,vector3));
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
@ -285,7 +284,7 @@ void Java_electrosphere_FluidSim_solveProjection
|
||||
vector = _mm256_add_ps(vector,_mm256_loadu_ps(&p[IX(i,j+1,k)]));
|
||||
vector = _mm256_add_ps(vector,_mm256_loadu_ps(&p[IX(i,j,k-1)]));
|
||||
vector = _mm256_add_ps(vector,_mm256_loadu_ps(&p[IX(i,j,k+1)]));
|
||||
// vector = _mm256_mul_ps(vector,aScalar);
|
||||
vector = _mm256_mul_ps(vector,aScalar);
|
||||
vector = _mm256_add_ps(vector,_mm256_loadu_ps(&div[IX(i,j,k)]));
|
||||
vector = _mm256_div_ps(vector,cScalar);
|
||||
_mm256_storeu_ps(&p[IX(i,j,k)],vector);
|
||||
@ -320,10 +319,7 @@ void Java_electrosphere_FluidSim_finalizeProjection
|
||||
float VISCOSITY_CONST,
|
||||
float dt){
|
||||
int i, j, k;
|
||||
// __m256 constScalar = _mm256_set1_ps(0.5f*N);
|
||||
__m256 xScalar = _mm256_set1_ps(0.5*N);
|
||||
__m256 yScalar = _mm256_set1_ps(0.5*N);
|
||||
__m256 zScalar = _mm256_set1_ps(0.5*N);
|
||||
__m256 constScalar = _mm256_set1_ps(0.5f*N);
|
||||
__m256 vector, vector2, vector3;
|
||||
|
||||
float * u = GET_ARR_RAW(env,jru,CENTER_LOC);
|
||||
@ -333,8 +329,6 @@ void Java_electrosphere_FluidSim_finalizeProjection
|
||||
float * p = GET_ARR_RAW(env,jru0,CENTER_LOC);
|
||||
float * div = GET_ARR_RAW(env,jrv0,CENTER_LOC);
|
||||
|
||||
float h = 1.0 / N;
|
||||
|
||||
for ( k=1 ; k<N-1 ; k++ ) {
|
||||
for ( j=1 ; j<N-1 ; j++ ) {
|
||||
//
|
||||
@ -344,14 +338,14 @@ void Java_electrosphere_FluidSim_finalizeProjection
|
||||
vector = _mm256_loadu_ps(&p[IX(1+1,j,k)]);
|
||||
vector2 = _mm256_loadu_ps(&p[IX(1-1,j,k)]);
|
||||
vector = _mm256_sub_ps(vector,vector2);
|
||||
vector = _mm256_mul_ps(vector,xScalar);
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
vector = _mm256_sub_ps(_mm256_loadu_ps(&u[IX(1,j,k)]),vector);
|
||||
_mm256_storeu_ps(&u[IX(1,j,k)],vector);
|
||||
//upper
|
||||
vector = _mm256_loadu_ps(&p[IX(9+1,j,k)]);
|
||||
vector2 = _mm256_loadu_ps(&p[IX(9-1,j,k)]);
|
||||
vector = _mm256_sub_ps(vector,vector2);
|
||||
vector = _mm256_mul_ps(vector,xScalar);
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
vector = _mm256_sub_ps(_mm256_loadu_ps(&u[IX(9,j,k)]),vector);
|
||||
_mm256_storeu_ps(&u[IX(9,j,k)],vector);
|
||||
//
|
||||
@ -361,14 +355,14 @@ void Java_electrosphere_FluidSim_finalizeProjection
|
||||
vector = _mm256_loadu_ps(&p[IX(1,j+1,k)]);
|
||||
vector2 = _mm256_loadu_ps(&p[IX(1,j-1,k)]);
|
||||
vector = _mm256_sub_ps(vector,vector2);
|
||||
vector = _mm256_mul_ps(vector,yScalar);
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
vector = _mm256_sub_ps(_mm256_loadu_ps(&v[IX(1,j,k)]),vector);
|
||||
_mm256_storeu_ps(&v[IX(1,j,k)],vector);
|
||||
//upper
|
||||
vector = _mm256_loadu_ps(&p[IX(9,j+1,k)]);
|
||||
vector2 = _mm256_loadu_ps(&p[IX(9,j-1,k)]);
|
||||
vector = _mm256_sub_ps(vector,vector2);
|
||||
vector = _mm256_mul_ps(vector,yScalar);
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
vector = _mm256_sub_ps(_mm256_loadu_ps(&v[IX(9,j,k)]),vector);
|
||||
_mm256_storeu_ps(&v[IX(9,j,k)],vector);
|
||||
//
|
||||
@ -378,21 +372,16 @@ void Java_electrosphere_FluidSim_finalizeProjection
|
||||
vector = _mm256_loadu_ps(&p[IX(1,j,k+1)]);
|
||||
vector2 = _mm256_loadu_ps(&p[IX(1,j,k-1)]);
|
||||
vector = _mm256_sub_ps(vector,vector2);
|
||||
vector = _mm256_mul_ps(vector,zScalar);
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
vector = _mm256_sub_ps(_mm256_loadu_ps(&w[IX(1,j,k)]),vector);
|
||||
_mm256_storeu_ps(&w[IX(1,j,k)],vector);
|
||||
//upper
|
||||
vector = _mm256_loadu_ps(&p[IX(9,j,k+1)]);
|
||||
vector2 = _mm256_loadu_ps(&p[IX(9,j,k-1)]);
|
||||
vector = _mm256_sub_ps(vector,vector2);
|
||||
vector = _mm256_mul_ps(vector,zScalar);
|
||||
vector = _mm256_mul_ps(vector,constScalar);
|
||||
vector = _mm256_sub_ps(_mm256_loadu_ps(&w[IX(9,j,k)]),vector);
|
||||
_mm256_storeu_ps(&w[IX(9,j,k)],vector);
|
||||
// for(i = 1; i < N-1; i++){
|
||||
// u[IX(i,j,k)] = u[IX(i,j,k)] - 0.5 * (p[IX(i+1,j,k)] - p[IX(i-1,j,k)]) / h;
|
||||
// v[IX(i,j,k)] = v[IX(i,j,k)] - 0.5 * (p[IX(i,j+1,k)] - p[IX(i,j-1,k)]) / h;
|
||||
// w[IX(i,j,k)] = w[IX(i,j,k)] - 0.5 * (p[IX(i,j,k+1)] - p[IX(i,j,k-1)]) / h;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -670,19 +659,22 @@ void setBoundsToNeighborsRaw
|
||||
int DIM = N;
|
||||
float * target = GET_ARR_RAW(env,neighborArray,CENTER_LOC);
|
||||
float * source;
|
||||
for(int x=1; x < DIM-1; x++){
|
||||
for(int y = 1; y < DIM-1; y++){
|
||||
target[IX(0,x,y)] = vector_dir==BOUND_DIR_U ? -target[IX(1,x,y)] : target[IX(1,x,y)];
|
||||
}
|
||||
}
|
||||
for(int x=1; x < DIM-1; x++){
|
||||
for(int y = 1; y < DIM-1; y++){
|
||||
target[IX(DIM-1,x,y)] = vector_dir==BOUND_DIR_U ? -target[IX(DIM-2,x,y)] : target[IX(DIM-2,x,y)];
|
||||
}
|
||||
}
|
||||
// for(int x=1; x < DIM-1; x++){
|
||||
// for(int y = 1; y < DIM-1; y++){
|
||||
// target[IX(0,x,y)] = vector_dir==BOUND_DIR_U ? -target[IX(1,x,y)] : target[IX(1,x,y)];
|
||||
// }
|
||||
// }
|
||||
// for(int x=1; x < DIM-1; x++){
|
||||
// for(int y = 1; y < DIM-1; y++){
|
||||
// target[IX(DIM-1,x,y)] = vector_dir==BOUND_DIR_U ? -target[IX(DIM-2,x,y)] : target[IX(DIM-2,x,y)];
|
||||
// }
|
||||
// }
|
||||
//set the faces bounds
|
||||
for(int x=1; x < DIM-1; x++){
|
||||
for(int y = 1; y < DIM-1; y++){
|
||||
//((x)+(DIM)*(y) + (DIM)*(DIM)*(z))
|
||||
target[IX(0,x,y)] = vector_dir==BOUND_DIR_U ? -target[IX(1,x,y)] : target[IX(1,x,y)];
|
||||
target[IX(DIM-1,x,y)] = vector_dir==BOUND_DIR_U ? -target[IX(DIM-2,x,y)] : target[IX(DIM-2,x,y)];
|
||||
target[IX(x,0,y)] = vector_dir==BOUND_DIR_V ? -target[IX(x,1,y)] : target[IX(x,1,y)];
|
||||
target[IX(x,DIM-1,y)] = vector_dir==BOUND_DIR_V ? -target[IX(x,DIM-2,y)] : target[IX(x,DIM-2,y)];
|
||||
target[IX(x,y,0)] = vector_dir==BOUND_DIR_W ? -target[IX(x,y,1)] : target[IX(x,y,1)];
|
||||
|
||||
@ -73,12 +73,12 @@ public class FluidSim {
|
||||
public int chunkMask = 0;
|
||||
|
||||
|
||||
static final float DIFFUSION_CONSTANT = 0.0f;
|
||||
static final float VISCOSITY_CONSTANT = 0.0f;
|
||||
static final float DIFFUSION_CONSTANT = 0.00001f;
|
||||
static final float VISCOSITY_CONSTANT = 0.00001f;
|
||||
|
||||
static final int LINEARSOLVERTIMES = 20;
|
||||
|
||||
static final float GRAVITY = -100f;
|
||||
static final float GRAVITY = -1000f;
|
||||
|
||||
public void setup(Vector3i offset){
|
||||
//allocate buffers for this chunk
|
||||
@ -111,45 +111,47 @@ public class FluidSim {
|
||||
for(int i = 0; i < DIM; i++){
|
||||
for(int j = 0; j < DIM; j++){
|
||||
for(int k = 0; k < DIM; k++){
|
||||
if(offset.x == 0 && offset.y == 0 && offset.z == 0){
|
||||
// if(offset.x == 0 && offset.y == 0 && offset.z == 0){
|
||||
if(
|
||||
Math.abs(16 - i) < 5 &&
|
||||
Math.abs(j) < 5 &&
|
||||
Math.abs(16 - k) < 5 &&
|
||||
i < 17 && i > 0 &&
|
||||
j < 17 && j > 0 &&
|
||||
k < 17 && k > 0
|
||||
Math.abs(16 - i) < 4 &&
|
||||
Math.abs(8 - j) < 4 &&
|
||||
Math.abs(10 - k) < 4
|
||||
// &&
|
||||
// i < 17 && i > 0 &&
|
||||
// j < 17 && j > 0 &&
|
||||
// k < 17 && k > 0
|
||||
){
|
||||
xf.put(1);
|
||||
uf.put(50);
|
||||
vf.put(0);
|
||||
wf.put(0);
|
||||
} else {
|
||||
uf.put(0.1f);
|
||||
vf.put(-1);
|
||||
wf.put(0.1f);
|
||||
}
|
||||
else {
|
||||
xf.put(0);
|
||||
uf.put(0);
|
||||
vf.put(0);
|
||||
wf.put(0);
|
||||
}
|
||||
} else {
|
||||
if(
|
||||
Math.abs(0 - i) < 5 &&
|
||||
Math.abs(j) < 5 &&
|
||||
Math.abs(0 - k) < 5 &&
|
||||
i < 17 && i > 0 &&
|
||||
j < 17 && j > 0 &&
|
||||
k < 17 && k > 0
|
||||
){
|
||||
// xf.put(1);
|
||||
// uf.put(50);
|
||||
// } else {
|
||||
// if(
|
||||
// Math.abs(0 - i) < 5 &&
|
||||
// Math.abs(j) < 5 &&
|
||||
// Math.abs(0 - k) < 5 &&
|
||||
// i < 17 && i > 0 &&
|
||||
// j < 17 && j > 0 &&
|
||||
// k < 17 && k > 0
|
||||
// ){
|
||||
// // xf.put(1);
|
||||
// // uf.put(50);
|
||||
// // vf.put(0);
|
||||
// // wf.put(rand.nextFloat() * 0.1f);
|
||||
// } else {
|
||||
// xf.put(0);
|
||||
// uf.put(0);
|
||||
// vf.put(0);
|
||||
// wf.put(rand.nextFloat() * 0.1f);
|
||||
} else {
|
||||
xf.put(0);
|
||||
uf.put(0);
|
||||
vf.put(0);
|
||||
wf.put(0);
|
||||
}
|
||||
}
|
||||
// wf.put(0);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -160,9 +162,9 @@ public class FluidSim {
|
||||
static double lastTime = 0;
|
||||
public static void simChunks(FluidSim[][][] simArray, int step, float timestep){
|
||||
|
||||
simArray[0][1][2].density0ArrayView[IX(10,10,3)] = 3.0f;
|
||||
simArray[2][1][2].density0ArrayView[IX(10,10,3)] = 3.0f;
|
||||
simArray[2][1][0].density0ArrayView[IX(10,10,3)] = 3.0f;
|
||||
// simArray[0][1][2].density0ArrayView[IX(10,10,3)] = 3.0f;
|
||||
// simArray[2][1][2].density0ArrayView[IX(10,10,3)] = 3.0f;
|
||||
// simArray[2][1][0].density0ArrayView[IX(10,10,3)] = 3.0f;
|
||||
|
||||
List<FluidSim> chunksToSim = new LinkedList<FluidSim>();
|
||||
//
|
||||
@ -189,7 +191,7 @@ public class FluidSim {
|
||||
lastTime = GLFW.glfwGetTime();
|
||||
//
|
||||
//simulate
|
||||
simulateWrapper(chunksToSim,timestep);
|
||||
simulateWrapper(chunksToSim,0.01f);
|
||||
//clock
|
||||
time = time + (GLFW.glfwGetTime() - lastTime);
|
||||
i++;
|
||||
@ -198,8 +200,6 @@ public class FluidSim {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
//Read out of buffers back into accessible arrays
|
||||
for(int x = 0; x < simArray.length; x++){
|
||||
@ -212,6 +212,25 @@ public class FluidSim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(Main.endStep == 0){
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
static void printLayer(FluidSim[][][] simArray, int step, int layer){
|
||||
if(step == 0){
|
||||
for(int x = 0; x < DIM; x++){
|
||||
for(int y = 0; y < DIM; y++){
|
||||
System.out.printf("%.2f\t",simArray[0][0][0].uArrayView[IX(x,layer,y)]);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static double sumAllDensity(FluidSim[][][] simArray){
|
||||
|
||||
@ -17,7 +17,8 @@ import electrosphere.render.Mesh;
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
|
||||
static boolean render = true;
|
||||
public static int endStep = -1;
|
||||
|
||||
|
||||
public static void main(String args[]){
|
||||
@ -31,15 +32,20 @@ public class Main {
|
||||
|
||||
try {
|
||||
|
||||
if(render){
|
||||
GLFWContext.init();
|
||||
|
||||
//init shader program
|
||||
Mesh.initShaderProgram();
|
||||
}
|
||||
|
||||
|
||||
FluidSim[][][] simArray = initFluidSim(dim,vdim,dim);
|
||||
|
||||
Mesh[][][] meshArray = initMeshes(dim,vdim,dim,simArray);
|
||||
Mesh[][][] meshArray = null;
|
||||
if(render){
|
||||
meshArray = initMeshes(dim,vdim,dim,simArray);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -60,6 +66,7 @@ public class Main {
|
||||
//
|
||||
//Remesh
|
||||
//
|
||||
if(render){
|
||||
for(int x = 0; x < simArray.length; x++){
|
||||
for(int y = 0; y < simArray[0].length; y++){
|
||||
for(int z = 0; z < simArray[0][0].length; z++){
|
||||
@ -67,9 +74,12 @@ public class Main {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
time = time + (System.currentTimeMillis() - lastTime);
|
||||
//redraw
|
||||
if(render){
|
||||
GLFWContext.redraw(meshArray);
|
||||
}
|
||||
i++;
|
||||
if(i == 100){
|
||||
System.out.println("overall time: " + time / 100.0);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user