Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
S
SWE
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Gaurav Kukreja
SWE
Commits
847292cd
Commit
847292cd
authored
Oct 12, 2012
by
Sebastian Rettenberger
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add asagi in opengl version
parent
e2c1995c
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
142 additions
and
47 deletions
+142
-47
SConstruct
SConstruct
+4
-0
SWE_Block.cpp
src/SWE_Block.cpp
+7
-4
SWE_Block.hh
src/SWE_Block.hh
+4
-2
SWE_BlockCUDA.cu
src/SWE_BlockCUDA.cu
+2
-2
SWE_BlockCUDA.hh
src/SWE_BlockCUDA.hh
+2
-1
SWE_WavePropagationBlockCuda.cu
src/SWE_WavePropagationBlockCuda.cu
+3
-3
SWE_WavePropagationBlockCuda.hh
src/SWE_WavePropagationBlockCuda.hh
+2
-3
swe_opengl.cpp
src/examples/swe_opengl.cpp
+0
-1
camera.cpp
src/opengl/camera.cpp
+25
-15
camera.h
src/opengl/camera.h
+6
-1
controller.cpp
src/opengl/controller.cpp
+68
-9
controller.h
src/opengl/controller.h
+9
-1
simulation.cu
src/opengl/simulation.cu
+8
-2
visualization.cpp
src/opengl/visualization.cpp
+1
-2
SWE_simple_scenarios.h
src/scenarios/SWE_simple_scenarios.h
+1
-1
No files found.
SConstruct
View file @
847292cd
...
...
@@ -86,6 +86,8 @@ vars.AddVariables(
BoolVariable
(
'asagi'
,
'use ASAGI'
,
False
),
PathVariable
(
'asagiInputDir'
,
'location of netcdf input files'
,
''
,
PathVariable
.
PathAccept
),
EnumVariable
(
'solver'
,
'Riemann solver'
,
'augrie'
,
allowed_values
=
(
'rusanov'
,
'fwave'
,
'augrie'
,
'hybrid'
)
),
...
...
@@ -241,6 +243,8 @@ if env['asagi'] == True:
if
'netCDFDir'
in
env
:
env
.
Append
(
LIBPATH
=
[
env
[
'netCDFDir'
]
+
'/lib'
])
env
.
Append
(
RPATH
=
[
os
.
path
.
join
(
env
[
'netCDFDir'
],
'lib'
)])
if
'asagiInputDir'
in
env
:
env
.
Append
(
CPPFLAGS
=
[
'
\'
-DASAGI_INPUT_DIR="'
+
env
[
'asagiInputDir'
]
+
'"
\'
'
])
# xml runtime parameters
if
env
[
'xmlRuntime'
]
==
True
:
#TODO
...
...
src/SWE_Block.cpp
View file @
847292cd
...
...
@@ -3,6 +3,7 @@
* This file is part of SWE.
*
* @author Michael Bader, Kaveh Rahnema, Tobias Schnabel
* @author Sebastian Rettenberger (rettenbs AT in.tum.de, http://www5.in.tum.de/wiki/index.php/Sebastian_Rettenberger,_M.Sc.)
*
* @section LICENSE
*
...
...
@@ -56,9 +57,8 @@
* generated.
*
*/
SWE_Block
::
SWE_Block
(
float
_offsetX
,
float
_offsetY
)
:
h
(
nx
+
2
,
ny
+
2
),
hu
(
nx
+
2
,
ny
+
2
),
hv
(
nx
+
2
,
ny
+
2
),
b
(
nx
+
2
,
ny
+
2
),
offsetX
(
_offsetX
),
offsetY
(
_offsetY
)
SWE_Block
::
SWE_Block
()
:
h
(
nx
+
2
,
ny
+
2
),
hu
(
nx
+
2
,
ny
+
2
),
hv
(
nx
+
2
,
ny
+
2
),
b
(
nx
+
2
,
ny
+
2
)
{
// set WALL as default boundary condition
for
(
int
i
=
0
;
i
<
4
;
i
++
)
{
...
...
@@ -91,8 +91,11 @@ SWE_Block::~SWE_Block() {
* @param i_scenario scenario, which is used during the setup.
* @param i_multipleBlocks are the multiple SWE_blocks?
*/
void
SWE_Block
::
initScenario
(
SWE_Scenario
&
i_scenario
,
void
SWE_Block
::
initScenario
(
float
_offsetX
,
float
_offsetY
,
SWE_Scenario
&
i_scenario
,
const
bool
i_multipleBlocks
)
{
offsetX
=
_offsetX
;
offsetY
=
_offsetY
;
// initialize water height and discharge
for
(
int
i
=
1
;
i
<=
nx
;
i
++
)
...
...
src/SWE_Block.hh
View file @
847292cd
...
...
@@ -3,6 +3,7 @@
* This file is part of SWE.
*
* @author Michael Bader, Kaveh Rahnema, Tobias Schnabel
* @author Sebastian Rettenberger (rettenbs AT in.tum.de, http://www5.in.tum.de/wiki/index.php/Sebastian_Rettenberger,_M.Sc.)
*
* @section LICENSE
*
...
...
@@ -118,7 +119,8 @@ class SWE_Block {
public
:
// object methods
/// initialise unknowns to a specific scenario:
void
initScenario
(
SWE_Scenario
&
i_scenario
,
const
bool
i_multipleBlocks
=
false
);
void
initScenario
(
float
_offsetX
,
float
_offsetY
,
SWE_Scenario
&
i_scenario
,
const
bool
i_multipleBlocks
=
false
);
// set unknowns
/// set the water height to a uniform value
void
setWaterHeight
(
float
_h
);
...
...
@@ -229,7 +231,7 @@ class SWE_Block {
protected
:
// Constructor und Destructor
SWE_Block
(
float
_offsetX
,
float
_offsetY
);
SWE_Block
();
virtual
~
SWE_Block
();
// synchronisation Methods
...
...
src/SWE_BlockCUDA.cu
View file @
847292cd
...
...
@@ -3,6 +3,7 @@
* This file is part of SWE.
*
* @author Michael Bader, Kaveh Rahnema, Tobias Schnabel
* @author Sebastian Rettenberger (rettenbs AT in.tum.de, http://www5.in.tum.de/wiki/index.php/Sebastian_Rettenberger,_M.Sc.)
*
* @section LICENSE
*
...
...
@@ -86,8 +87,7 @@ void tryCUDA(cudaError_t err, const char *msg)
* @param _offsetY offset in y-direction.
* @param i_cudaDevice ID of the CUDA-device, which should be used.
*/
SWE_BlockCUDA::SWE_BlockCUDA(float _offsetX, float _offsetY, const int i_cudaDevice )
: SWE_Block(_offsetX,_offsetY)
SWE_BlockCUDA::SWE_BlockCUDA(const int i_cudaDevice )
{
s_sweLogger.setProcessRank(i_cudaDevice);
...
...
src/SWE_BlockCUDA.hh
View file @
847292cd
...
...
@@ -3,6 +3,7 @@
* This file is part of SWE.
*
* @author Michael Bader, Kaveh Rahnema, Tobias Schnabel
* @author Sebastian Rettenberger (rettenbs AT in.tum.de, http://www5.in.tum.de/wiki/index.php/Sebastian_Rettenberger,_M.Sc.)
*
* @section LICENSE
*
...
...
@@ -53,7 +54,7 @@ class SWE_BlockCUDA : public SWE_Block {
public
:
// Constructor und Destructor
SWE_BlockCUDA
(
float
_offsetX
=
0
,
float
_offsetY
=
0
,
const
int
i_cudaDevice
=
0
);
SWE_BlockCUDA
(
const
int
i_cudaDevice
=
0
);
virtual
~
SWE_BlockCUDA
();
// object methods
...
...
src/SWE_WavePropagationBlockCuda.cu
View file @
847292cd
...
...
@@ -95,9 +95,9 @@ static tools::Logger s_sweLogger;
* @param i_offsetY spatial offset of the offset in y-direction.
* @param i_cudaDevice ID of the CUDA-device, which should be used.
*/
SWE_WavePropagationBlockCuda::SWE_WavePropagationBlockCuda(
const float i_offsetX,
const float i_offsetY,
const int i_cudaDevice ): SWE_BlockCUDA(i_offsetX, i_offsetY, i_cudaDevice)
{
SWE_WavePropagationBlockCuda::SWE_WavePropagationBlockCuda(
const int i_cudaDevice )
: SWE_BlockCUDA(i_cudaDevice)
{
// compute the size of one 1D net-update array.
int sizeOfNetUpdates = (nx+1)*(ny+1)*sizeof(float);
...
...
src/SWE_WavePropagationBlockCuda.hh
View file @
847292cd
...
...
@@ -3,6 +3,7 @@
* This file is part of SWE.
*
* @author Alexander Breuer (breuera AT in.tum.de, http://www5.in.tum.de/wiki/index.php/Dipl.-Math._Alexander_Breuer)
* @author Sebastian Rettenberger (rettenbs AT in.tum.de, http://www5.in.tum.de/wiki/index.php/Sebastian_Rettenberger,_M.Sc.)
*
* @section LICENSE
*
...
...
@@ -64,9 +65,7 @@ class SWE_WavePropagationBlockCuda: public SWE_BlockCUDA {
public
:
// constructor of SWE_WavePropagationBlockCuda
SWE_WavePropagationBlockCuda
(
const
float
i_offsetX
=
0
,
const
float
i_offsetY
=
0
,
const
int
i_cudaDevice
=
0
);
SWE_WavePropagationBlockCuda
(
const
int
i_cudaDevice
=
0
);
// destructor of SWE_WavePropagationBlockCuda
~
SWE_WavePropagationBlockCuda
();
...
...
src/examples/swe_opengl.cpp
View file @
847292cd
...
...
@@ -98,7 +98,6 @@ int main(int argc, char *argv[])
// use splashing pool scenario ...
SWE_SplashingPoolScenarioVisInfo
*
newScene
=
new
SWE_SplashingPoolScenarioVisInfo
();
scene
=
newScene
;
visInfo
=
newScene
;
};
// define grid size and initial time step
...
...
src/opengl/camera.cpp
View file @
847292cd
...
...
@@ -2,6 +2,7 @@
// This file is part of SWE_CUDA (see file SWE_Block.cu for details).
//
// Copyright (C) 2010,2011 Tobias Schnabel
// Copyright (C) 2012 Sebastian Rettenberger
//
// SWE_CUDA is free software: you can redristribute it and/or modify
// it under the terms of the GNU General Public License as published by
...
...
@@ -25,22 +26,14 @@
@param window_title title of the current window
*/
Camera
::
Camera
(
float
view_distance
,
const
char
*
window_title
)
{
Camera
::
Camera
(
float
_view_distance
,
const
char
*
window_title
)
:
view_distance
(
_view_distance
)
{
// Initialize member variables
cameraX
=
-
0.3
f
;
cameraY
=
1.0
f
;
cameraZ
=
2.0
f
;
objectX
=
0.0
f
;
objectY
=
0.0
f
;
objectZ
=
0.0
f
;
angleX
=
0.0
f
;
angleY
=
0.0
f
;
zoomfactor
=
0.85
f
*
view_distance
;
win_title
=
window_title
;
frames
=
0
;
lastTime
=
0
;
oldMouseX
=
0
;
oldMouseY
=
0
;
reset
();
// Reset framebuffer
glClear
(
GL_COLOR_BUFFER_BIT
|
GL_DEPTH_BUFFER_BIT
);
...
...
@@ -75,6 +68,23 @@ void Camera::setCamera() {
glLightfv
(
GL_LIGHT0
,
GL_POSITION
,
LightPosition
);
}
void
Camera
::
reset
()
{
cameraX
=
-
0.3
f
;
cameraY
=
1.0
f
;
cameraZ
=
2.0
f
;
objectX
=
0.0
f
;
objectY
=
0.0
f
;
objectZ
=
0.0
f
;
angleX
=
0.0
f
;
angleY
=
0.0
f
;
zoomfactor
=
0.85
f
*
view_distance
;
frames
=
0
;
lastTime
=
0
;
oldMouseX
=
0
;
oldMouseY
=
0
;
}
/**
Rotate camera
...
...
src/opengl/camera.h
View file @
847292cd
...
...
@@ -4,6 +4,7 @@
// This file is part of SWE_CUDA (see file SWE_Block.cu for details).
//
// Copyright (C) 2010,2011 Tobias Schnabel
// Copyright (C) 2012 Sebastian Rettenberger
//
// SWE_CUDA is free software: you can redristribute it and/or modify
// it under the terms of the GNU General Public License as published by
...
...
@@ -28,6 +29,8 @@ public:
void
setCamera
();
// Change viewing properties
void
reset
();
void
orient
(
float
angX
,
float
angY
);
void
zoomIn
(
float
scaleFactor
);
void
zoomOut
(
float
scaleFactor
);
...
...
@@ -38,6 +41,8 @@ public:
void
displayImage
();
private
:
float
view_distance
;
// Position of the camera
float
cameraX
;
float
cameraY
;
...
...
src/opengl/controller.cpp
View file @
847292cd
...
...
@@ -2,6 +2,7 @@
// This file is part of SWE_CUDA (see file SWE_Block.cu for details).
//
// Copyright (C) 2010,2011 Tobias Schnabel
// Copyright (C) 2012 Sebastian Rettenberger
//
// SWE_CUDA is free software: you can redristribute it and/or modify
// it under the terms of the GNU General Public License as published by
...
...
@@ -19,6 +20,9 @@
#include "controller.h"
#include "../scenarios/SWE_simple_scenarios_vis.h"
#ifdef ASAGI
#include "../scenarios/SWE_AsagiScenario.hpp"
#endif // ASAGI
/**
Constructor
...
...
@@ -34,6 +38,16 @@ Controller::Controller(Simulation* sim, Visualization* vis) {
done
=
false
;
paused
=
false
;
allowStep
=
false
;
// No scenario loaded
memset
(
scenarios
,
0
,
SCENARIO_COUNT
*
sizeof
(
SWE_Scenario
*
));
}
Controller
::~
Controller
()
{
// Delete scenarios
for
(
int
i
=
0
;
i
<
SCENARIO_COUNT
;
i
++
)
delete
scenarios
;
}
/**
...
...
@@ -67,7 +81,6 @@ bool Controller::handleEvents() {
visualization
->
camera
->
zoomOut
(
1.2
f
);
}
else
if
(
event
.
button
.
button
==
SDL_BUTTON_RIGHT
)
{
visualization
->
camera
->
startPanning
(
event
.
button
.
x
,
event
.
button
.
y
);
}
break
;
case
SDL_MOUSEBUTTONUP
:
// Scroll wheel up
...
...
@@ -75,6 +88,9 @@ bool Controller::handleEvents() {
// Zoom in
visualization
->
camera
->
zoomIn
(
1.15
f
);
}
}
else
if
(
event
.
button
.
button
==
SDL_BUTTON_MIDDLE
)
{
visualization
->
camera
->
reset
();
}
break
;
case
SDL_ACTIVEEVENT
:
// Don't draw anything if we're getting minimized
...
...
@@ -121,6 +137,8 @@ bool Controller::isPaused() {
Process single keyboard event
@param *keysym pointer to the sdl keyevent structure
@todo Refector!!
*/
bool
Controller
::
handleKeyPress
(
SDL_keysym
*
keysym
)
{
...
...
@@ -154,16 +172,18 @@ bool Controller::handleKeyPress( SDL_keysym *keysym) {
// Load scenario 1
{
allowStep
=
paused
;
SWE_RadialDamBreakScenarioVisInfo
*
newScene
=
new
SWE_RadialDamBreakScenarioVisInfo
();
SWE_VisInfo
*
visInfo
=
newScene
;
if
(
scenarios
[
0
]
==
0
)
scenarios
[
0
]
=
new
SWE_RadialDamBreakScenario
();
SWE_Scenario
*
newScene
=
scenarios
[
0
];
// define grid size and initial time step
float
dx
=
(
newScene
->
getBoundaryPos
(
BND_RIGHT
)
-
newScene
->
getBoundaryPos
(
BND_LEFT
)
)
/
SWE_Block
::
getNx
();
float
dy
=
(
newScene
->
getBoundaryPos
(
BND_TOP
)
-
newScene
->
getBoundaryPos
(
BND_BOTTOM
)
)
/
SWE_Block
::
getNy
();
SWE_Block
::
initGridData
(
SWE_Block
::
getNx
(),
SWE_Block
::
getNy
(),
dx
,
dy
);
simulation
->
loadNewScenario
(
newScene
,
visInfo
);
simulation
->
loadNewScenario
(
newScene
,
NULL
);
visualization
->
updateBathymetryVBO
(
simulation
);
}
break
;
...
...
@@ -171,7 +191,12 @@ bool Controller::handleKeyPress( SDL_keysym *keysym) {
// Load scenario 2
{
allowStep
=
paused
;
SWE_Scenario
*
newScene
=
new
SWE_BathymetryDamBreakScenario
;
if
(
scenarios
[
1
]
==
0
)
scenarios
[
1
]
=
new
SWE_BathymetryDamBreakScenario
();
SWE_Scenario
*
newScene
=
scenarios
[
1
];
// define grid size and initial time step
float
dx
=
(
newScene
->
getBoundaryPos
(
BND_RIGHT
)
-
newScene
->
getBoundaryPos
(
BND_LEFT
)
)
/
SWE_Block
::
getNx
();
float
dy
=
(
newScene
->
getBoundaryPos
(
BND_TOP
)
-
newScene
->
getBoundaryPos
(
BND_BOTTOM
)
)
/
SWE_Block
::
getNy
();
...
...
@@ -185,18 +210,52 @@ bool Controller::handleKeyPress( SDL_keysym *keysym) {
// Load scenario 3
{
allowStep
=
paused
;
SWE_SplashingPoolScenarioVisInfo
*
newScene
=
new
SWE_SplashingPoolScenarioVisInfo
;
if
(
scenarios
[
2
]
==
0
)
scenarios
[
2
]
=
new
SWE_SplashingPoolScenarioVisInfo
();
SWE_Scenario
*
newScene
=
scenarios
[
2
];
// define grid size and initial time step
float
dx
=
(
newScene
->
getBoundaryPos
(
BND_RIGHT
)
-
newScene
->
getBoundaryPos
(
BND_LEFT
)
)
/
SWE_Block
::
getNx
();
float
dy
=
(
newScene
->
getBoundaryPos
(
BND_TOP
)
-
newScene
->
getBoundaryPos
(
BND_BOTTOM
)
)
/
SWE_Block
::
getNy
();
SWE_Block
::
initGridData
(
SWE_Block
::
getNx
(),
SWE_Block
::
getNy
(),
dx
,
dy
);
simulation
->
loadNewScenario
(
newScene
,
newScene
);
simulation
->
loadNewScenario
(
newScene
,
NULL
);
visualization
->
updateBathymetryVBO
(
simulation
);
}
break
;
#ifdef ASAGI
case
SDLK_4
:
// Load scenario 3
{
allowStep
=
paused
;
if
(
scenarios
[
3
]
==
0
)
{
//simulation area
float
simulationArea
[
4
];
simulationArea
[
0
]
=
-
450000
;
simulationArea
[
1
]
=
6450000
;
simulationArea
[
2
]
=
-
2450000
;
simulationArea
[
3
]
=
1450000
;
scenarios
[
3
]
=
new
SWE_AsagiScenario
(
ASAGI_INPUT_DIR
"tohoku_gebco_ucsb3_500m_hawaii_bath.nc"
,
ASAGI_INPUT_DIR
"tohoku_gebco_ucsb3_500m_hawaii_displ.nc"
,
(
float
)
28800.
,
simulationArea
);
}
SWE_Scenario
*
newScene
=
scenarios
[
3
];
// define grid size and initial time step
float
dx
=
(
newScene
->
getBoundaryPos
(
BND_RIGHT
)
-
newScene
->
getBoundaryPos
(
BND_LEFT
)
)
/
SWE_Block
::
getNx
();
float
dy
=
(
newScene
->
getBoundaryPos
(
BND_TOP
)
-
newScene
->
getBoundaryPos
(
BND_BOTTOM
)
)
/
SWE_Block
::
getNy
();
SWE_Block
::
initGridData
(
SWE_Block
::
getNx
(),
SWE_Block
::
getNy
(),
dx
,
dy
);
simulation
->
loadNewScenario
(
newScene
,
NULL
);
visualization
->
updateBathymetryVBO
(
simulation
);
}
break
;
#endif // ASAGI
default
:
break
;
}
...
...
src/opengl/controller.h
View file @
847292cd
...
...
@@ -4,6 +4,7 @@
// This file is part of SWE_CUDA (see file SWE_Block.cu for details).
//
// Copyright (C) 2010,2011 Tobias Schnabel
// Copyright (C) 2012 Sebastian Rettenberger
//
// SWE_CUDA is free software: you can redristribute it and/or modify
// it under the terms of the GNU General Public License as published by
...
...
@@ -22,10 +23,15 @@
#include "simulation.h"
#include "visualization.h"
/** The number of different scenarios */
#define SCENARIO_COUNT 4
class
Controller
{
public
:
Controller
(
Simulation
*
sim
,
Visualization
*
vis
);
virtual
~
Controller
();
// Process new events
bool
handleEvents
();
...
...
@@ -44,6 +50,8 @@ private:
Simulation
*
simulation
;
Visualization
*
visualization
;
SWE_Scenario
*
scenarios
[
SCENARIO_COUNT
];
// Handle keyboard events
bool
handleKeyPress
(
SDL_keysym
*
keysym
);
};
...
...
src/opengl/simulation.cu
View file @
847292cd
...
...
@@ -66,7 +66,6 @@ Simulation::~Simulation () {
}
void Simulation::loadNewScenario(SWE_Scenario* scene, SWE_VisInfo* visInfo) {
delete myScenario;
myScenario = scene;
curTime = 0.0f;
isFirstStep = 1;
...
...
@@ -133,7 +132,14 @@ void Simulation::runCuda(struct cudaGraphicsResource **vbo_resource, struct cuda
*/
void Simulation::initBoundaries(SWE_Scenario* scene) {
std::cout << "Init Scenario\n" << flush;
splash->initScenario(*scene);
float l_originX, l_originY;
// get the origin from the scenario
l_originX = scene->getBoundaryPos(BND_LEFT);
l_originY = scene->getBoundaryPos(BND_BOTTOM);
splash->initScenario(l_originX, l_originY, *scene);
splash->setGhostLayer();
}
...
...
src/opengl/visualization.cpp
View file @
847292cd
...
...
@@ -2,6 +2,7 @@
// This file is part of SWE_CUDA (see file SWE_Block.cu for details).
//
// Copyright (C) 2010,2011 Tobias Schnabel
// Copyright (C) 2012 Sebastian Rettenberger
//
// SWE_CUDA is free software: you can redristribute it and/or modify
// it under the terms of the GNU General Public License as published by
...
...
@@ -541,8 +542,6 @@ void Visualization::createVertexVBO(GLuint* vboID, int size, struct cudaGraphics
void
Visualization
::
createIndicesVBO
(
GLuint
*
vboID
,
int
xsize
,
int
ysize
)
{
// Create an array describing the vertex indices to be drawn
xsize
=
xsize
;
ysize
=
ysize
;
int
noVertices
=
(
xsize
-
1
)
*
(
ysize
-
1
)
*
6
;
if
((
xsize
<
1
)
||
(
ysize
<
1
))
{
...
...
src/scenarios/SWE_simple_scenarios.h
View file @
847292cd
...
...
@@ -147,7 +147,7 @@ class SWE_SplashingPoolScenario : public SWE_Scenario {
};
float
getWaterHeight
(
float
x
,
float
y
)
{
return
260
.
0
f
+
(
1
.
0
f
-
(
x
+
y
)
/
95
0
);
return
260
.
0
f
+
(
1
.
0
f
-
(
x
+
y
)
/
20
0
);
};
virtual
float
endSimulation
()
{
return
(
float
)
15
;
};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment