Red5 demo

18 views
Skip to first unread message

bowljoman

unread,
Oct 3, 2010, 12:49:33 AM10/3/10
to jinngine
Hello,

I put together a sample using the framework that is integrated into a
red5 flv stream. By subscribing to the stream and interacting with an
amf data service, the connected subscribers can share and interact
with the simulation.

The data is rendered in away3dlite.

http://code.google.com/p/comserver/wiki/Demos#Demo_5

The jinngine seems pretty nice. occasional freezes where an object
wont move until it is hit by another, but all in all, I really like
the way it snapped into place.

Awesome engine.

Thanks!
Andy

mo

unread,
Oct 3, 2010, 5:03:52 AM10/3/10
to jinngine
Hi, glad to hear that you had success integrating jinngine into your
framework :) Sounds really interesting too.

I tried following the link you gave, and I searched a little around
the googlecode page, but I wasn't able to find the demo you mention?

/mo

Andy Shaules

unread,
Oct 3, 2010, 12:51:52 PM10/3/10
to jinn...@googlegroups.com

Hello,

Sorry. The demos for that project are currently not deployed. Only the
source codes, and compiled java webapp are available at this time. You may
find the code at these links familiar. It is taken from the
'CapsuleExample'. I'll run it down. The client side is fairly simple. A user
joins to the red5 server and posts some properties to it's session. The
client begins to play an flv stream. The flv streams contains cue points
which contian the position and rotation values for every object in the
simulation. The client consumes the cuepoints, and for every object data in
the stream, away3d is used to create a 3d shape instance to folow the
animation. This implies that for every client that loads thew flash app, a
shape is added to the sim, and a view of the new shape is created in every
client that is consuming the stream. Basically simple, really.

On the server, when a group is created by the first client consumer to
request a 'simulation stream', the server creates a 'CapsuleExample' sans
rendering engine, and capsules. The CapsuleExample is wrapped by an
IResourceFeed implementation that controls the addition and removal of
bodies and forces. Rather that running the scene.tick() on a display
pipeline, the IResourceFeed uses the Runnable interface at about 30 ticks
per second. at each tick, bodies are added, removed, and bumped by client
presence and input. After all changes to the simulation data are applied,
the scene is 'ticked'. And finally, the simulation data is harvested and
pushed into the FLV stream to the consumers. The consumers can call the
server to 'bump' their respective bodies.

Here is where you may find the jinngine code. If I get a demo server
running, I'll be sure to let you know.

http://code.google.com/p/comserver/source/browse/trunk/demo/demo5/java/demo/WEB-INF/src/comdemo/internal/#internal/feeds

http://code.google.com/p/comserver/source/browse/trunk/demo/demo5/java/demo/WEB-INF/src/comdemo/internal/feeds/Jinngine/CapsuleExample.java

Here is the client code that sets up the client node, away3d renderer, and
reads jinngine events from the flv stream.

http://code.google.com/p/comserver/source/browse/trunk/demo/demo5/flash/comdemo5/src/comdemo5.mxml


Andy

mo

unread,
Oct 3, 2010, 1:34:17 PM10/3/10
to jinngine
This sounds really interesting. I'm really anxious to observe the
result of this, especially if it would be running in a flash client,
directly in a browser? Over the past years, I have thought sporadicly
about how distributed physics should be handled, and whether or not it
would require special considerations inside a physics engine itself.
But I never really had the time to look into that, unfortunately.
Please let us know more about your results and experiences as you
continue your work.

Andy Shaules

unread,
Oct 3, 2010, 2:03:45 PM10/3/10
to jinn...@googlegroups.com
It is running directly in a browser, and although it is distributed data,
the simulation is a singleton and the synchronization between user view is
pretty much flawless. No special handling needed other than normal thread
safe stuff. Using the red5 server makes it simple to distribute.

I'll see If I can host the demo somewhere.

andy

----- Original Message -----
From: "mo" <mor...@silcowitz.dk>
To: "jinngine" <jinn...@googlegroups.com>
Sent: Sunday, October 03, 2010 10:34 AM
Subject: [jinngine] Re: Red5 demo

Andy Shaules

unread,
Oct 3, 2010, 3:09:17 PM10/3/10
to jinn...@googlegroups.com
Here you go...

get it while it lasts. Multi user demo is live.

http://74.217.67.51:5080/demo/


----- Original Message -----
From: "mo" <mor...@silcowitz.dk>
To: "jinngine" <jinn...@googlegroups.com>
Sent: Sunday, October 03, 2010 10:34 AM
Subject: [jinngine] Re: Red5 demo

Morten Silcowitz

unread,
Oct 3, 2010, 3:34:09 PM10/3/10
to jinn...@googlegroups.com
Cool, Thanks :) I observed that the spheres seem to roll around in a
non-intuitive way. I guess that is caused by the orientation
interpolation issue that you mentioned? I was able to bounce my
sphere around by pressing the bump button. I experienced a delay of
about half a second. This is probably expected since my ping time is
relatively large. (I pasted the ping trace at the end of the message).
Are you planning to create some specific application due to this
work, or are you working on a general framework for network based
interactive applications?

mo@mo-laptop:~$ ping 74.217.67.51
PING 74.217.67.51 (74.217.67.51) 56(84) bytes of data.
64 bytes from 74.217.67.51: icmp_seq=1 ttl=51 time=199 ms
64 bytes from 74.217.67.51: icmp_seq=2 ttl=51 time=198 ms
64 bytes from 74.217.67.51: icmp_seq=3 ttl=51 time=200 ms
--- 74.217.67.51 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 198.856/199.662/200.737/0.791 ms

Andy Shaules

unread,
Oct 3, 2010, 4:22:41 PM10/3/10
to jinn...@googlegroups.com
Hello,

I was basically demonstrating how you could apply other libraries into the
comserver framework, and especially, how to broadcast interactive time-based
data.
The comserver framework, based on cuepoints in an flv stream, implies
time-based and time-regulated data delivery which lends itself well to a
physics sim.

At 200ms one way ping, you may experience a larger lag for certain. The red5
server does it's best to respect buffer length an lag time in it's core
framework.

Andy

Andy Shaules

unread,
Oct 5, 2010, 4:33:18 PM10/5/10
to jinn...@googlegroups.com
I messed around a bit more but it seemed most of what I tried to do failed.
I removed the 'bump' velocity in the z direction to help it appear correct
longer...It take a full day or two to put my head into such an advanced math
frame of mind, and I just dont have it in me currently.

Without having the time to sit down and look at the differences of the data
and render, I'll have to leave it as it is for now. It would be awesome if
you had the time to add in euler extraction for users like me.

The other thing that came out during testing I would mention is how objects
freeze at small velocites, and especially the 'box' Body.

What I mean is that at a certain point, the body will not reflect velocity
changes input by the 'bump'. When another body finally collides with the
frozen body, the frozen body will reflect the accumulated velocity 'bumps'
in a massive singular burst.

Yeah, if you could find the time in the future to add euler 'get' function,
or a utility that is compliant with your quanternions that would be awesome.
The next easy step for me would be to add a 'game' element to the
simulation. The engine was so easy to work with that I expect I will be
drawn back to it.

Andy

p.s. I was using the away3d lite version which does not contain the
quanternion class.

----- Original Message -----
From: "Morten Silcowitz" <mor...@silcowitz.dk>
To: <jinn...@googlegroups.com>
Sent: Sunday, October 03, 2010 12:34 PM
Subject: Re: [jinngine] Re: Red5 demo

mo

unread,
Oct 6, 2010, 3:41:39 AM10/6/10
to jinngine
> Without having the time to sit down and look at the differences of the data
> and render, I'll have to leave it as it is for now. It would be awesome if
> you had the time to add in euler extraction for users like me.

You could try and paste this method into you code. It was simply
ported from the equclidianspace.com site. However, it uses the
convention Y,Z,X. I'm not sure if that is standard really. Thats one
of the reasons why euler angles is a terrible rotation
parametrization. But give it a go and let me know how it works out!

/**
* Convert unit quaternion into Euler angles, taking singularities
into account.
* this code is ported from Martin John Baker's website,
* http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
* The produced euler angles have the apply order Y,Z,X
* @param q1 Unit quaternion representing rotation transform
* @param euler Vector3 to contain the euler angles
*/
public static final void quaternionToEuler(final Quaternion q1, final
Vector3 euler) {
double heading, attitude, bank;
double test = q1.v.x*q1.v.y + q1.v.z*q1.s;
if (test > 0.5-1e-7) { // singularity at north pole
heading = 2 * Math.atan2(q1.v.x,q1.s);
attitude = Math.PI/2;
bank = 0;
} else if (test < -0.5+1e-7) { // singularity at south pole
heading = -2 * Math.atan2(q1.v.x,q1.s);
attitude = - Math.PI/2;
bank = 0;
} else {
double sqx = q1.v.x*q1.v.x;
double sqy = q1.v.y*q1.v.y;
double sqz = q1.v.z*q1.v.z;
heading = Math.atan2(2*q1.v.y*q1.s-2*q1.v.x*q1.v.z , 1 - 2*sqy -
2*sqz);
attitude = Math.asin(2*test);
bank = Math.atan2(2*q1.v.x*q1.s-2*q1.v.y*q1.v.z , 1 - 2*sqx -
2*sqz);
}
// return values
euler.x = heading;
euler.y = attitude;
euler.z = bank;
}


> The other thing that came out during testing I would mention is how objects
> freeze at small velocites, and especially the 'box' Body.

It is the deactivation that starts. You can turn it off by using
"DisabledDeactivationPolicy" when you create the scene:

scene = new DefaultScene(new SAP2(), new
NonsmoothNonlinearConjugateGradient(44), new
DisabledDeactivationPolicy());

However, this would mean that the server will spend cycles calculating
motion of resting objects forever. It is better to apply your
interaction in another way, see below

> ... When another body finally collides with the
> frozen body, the frozen body will reflect the accumulated velocity 'bumps'
> in a massive singular burst.

This happens because you apply your changes directly to the velocities
of the bodies. This is actually not the intended way of modeling. You
should use a Force or a Constraint to create the interaction. In your
case, I think you can easily do what you want using ImpulseForce.
Simply create an ImpulseForce for each of your bodies, and add it in
the same way as the gravity force. Then you can apply your desired
impulse (which is what you want, because an impulse is per definition
an instant change in velocity) by using ImpulseForce.setMagnitude().
In every timestep, the magnitude is reset to zero, so the impulse will
only be applied once for each call to setMagnitude() (per timestep)
See

http://code.google.com/p/jinngine/source/browse/trunk/jinngine/src/jinngine/physics/force/ImpulseForce.java

When you do it this way, your bodies will automatically be re-
activated, and the burst-issue you describe will not happen.

Let me know how it goes

-mo


Andy Shaules

unread,
Oct 6, 2010, 1:00:29 PM10/6/10
to jinn...@googlegroups.com
Hey,

thanks! Im going to plug them in real quick and see what happens. Most of
what you said confirmed what I suspected about my usage of the velocity API.

I'll let you know how it goes.

Andy


----- Original Message -----
From: "mo" <mor...@silcowitz.dk>
To: "jinngine" <jinn...@googlegroups.com>
Sent: Wednesday, October 06, 2010 12:41 AM
Subject: [jinngine] Re: Red5 demo

Andy Shaules

unread,
Oct 6, 2010, 2:07:26 PM10/6/10
to jinn...@googlegroups.com
The euler code is soooooooo close! But not quite correct. Im goina see if I
can get it sussed out.

----- Original Message -----
From: "mo" <mor...@silcowitz.dk>
To: "jinngine" <jinn...@googlegroups.com>
Sent: Wednesday, October 06, 2010 12:41 AM
Subject: [jinngine] Re: Red5 demo

Andy Shaules

unread,
Oct 6, 2010, 5:42:59 PM10/6/10
to jinn...@googlegroups.com
Here lies the inherant problem

atitude = ± π/2 rads

I would never get a complete rotation, only ocilation.

Andy


----- Original Message -----
From: "mo" <mor...@silcowitz.dk>
To: "jinngine" <jinn...@googlegroups.com>
Sent: Wednesday, October 06, 2010 12:41 AM
Subject: [jinngine] Re: Red5 demo

Morten Silcowitz

unread,
Oct 6, 2010, 5:53:56 PM10/6/10
to jinn...@googlegroups.com
Yes, it should run in [-pi,pi].... hmmm. So perhaps your best option
is to upgrade to a newer version of away3d, so you can simply plug in
the quaternion and/or the rotation matrix directly?

What version are you using now?

mo

Andy Shaules

unread,
Oct 6, 2010, 6:05:39 PM10/6/10
to jinn...@googlegroups.com
Im using the newest branch. the 'lite' version. It uses this:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/geom/Matrix3D.html

Generally the flash 3d libs dont have the 'order of rotation' issue when
setting rotations , however I could be wrong about away3dlite.


----- Original Message -----
From: "Morten Silcowitz" <mor...@silcowitz.dk>
To: <jinn...@googlegroups.com>
Sent: Wednesday, October 06, 2010 2:53 PM
Subject: Re: [jinngine] Re: Red5 demo

Andy Shaules

unread,
Oct 6, 2010, 6:09:00 PM10/6/10
to jinn...@googlegroups.com
this looks good here... investigating

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/geom/Matrix3D.html#recompose%28%29


----- Original Message -----
From: "Morten Silcowitz" <mor...@silcowitz.dk>
To: <jinn...@googlegroups.com>
Sent: Wednesday, October 06, 2010 2:53 PM
Subject: Re: [jinngine] Re: Red5 demo

Morten Silcowitz

unread,
Oct 7, 2010, 8:38:33 AM10/7/10
to jinn...@googlegroups.com
Looks like you should be able to just pass the quaternion directly
into this "recompose" function.

Seems that their Vector3D type supports a forth element, Vector3D.w.
So, i guess something like this should do it?

Vector3D orientation;
orientation.x = q.v.x;
orientation.y = q.v.y;
orientation.z = q.v.z;
orientation.w = q.s;

Andy Shaules

unread,
Oct 7, 2010, 11:32:09 AM10/7/10
to jinn...@googlegroups.com
I tried and it didnt work.

Maybe Im doing it wrong, but the shape wouldnt move at all.

I see the issue about producing eulers from a rotation matrix, but flash
does not have the problem. You can apply rotation in any order and the
result will be the same. The rotations do not affect each other.

I tried to make the native matrix change with your quanternions but
something doesnt work.

Becuse all translations of quanternions to eulers do not return ranges of at
least +-180 degrees or 0-360 degrees it makes them all useless.

I spent all day yesterday trying to get an accurate rendering and gave up.

The lack of euler rotation angles of your physical bodies makes it very
difficult for a non-math expert to use the libratry

I may have another chunk of time this weekend to look again, but for now, Im
busted and burnt out, trying to get a simple full rotation value.

Other than that, I'd love to keep adding to my experiment.

Andy

>> atitude = οΏ½ οΏ½/2 rads

Andy Shaules

unread,
Oct 7, 2010, 12:43:25 PM10/7/10
to jinn...@googlegroups.com

I think I got it figured out. The trouble is that if I requested qunternions
and then set the x,y,x value from the jinngine position vector, it would
break it.

So I set the eulers to the position, then decompose as qunternion and add in
the qunternion values from jinngin.

So far I think it is correct, but its hard to tell after testing so few
collisions.

Ill see about redeploying the demo with all the recommended changes.

Here is the qunternion code for flash incase anyone else needs it.

Use 'sprite.tranform.matrix3D' field of the display object.

Thanks again. You may want to add in an 'euler get' still, but I think my
issue was solved.

var o:Sphere= objects[obj.id].shape;


o.z=obj.z ;

o.y= obj.y * -1;

o.x=obj.x ;


var v:Vector.<Vector3D> =
o.transform.matrix3D.decompose(Orientation3D.QUATERNION);

Vector3D(v[1]).x=obj.qX * -1;

Vector3D(v[1]).y=obj.qY ;

Vector3D(v[1]).z=obj.qZ * -1;

Vector3D(v[1]).w=obj.qW;


o.transform.matrix3D.recompose(v,Orientation3D.QUATERNION);


----- Original Message -----
From: "Morten Silcowitz" <mor...@silcowitz.dk>
To: <jinn...@googlegroups.com>

Sent: Thursday, October 07, 2010 5:38 AM

>> atitude = οΏ½ οΏ½/2 rads

Morten Silcowitz

unread,
Oct 7, 2010, 1:07:50 PM10/7/10
to jinn...@googlegroups.com
Yes I know that stuff like this can be pretty tricky. I would like to
add a euler angle conversion to jinngine, but I have to check up on
the theory first, to be sure that it will work exactly as expected.
That will take a bit of time.

> Maybe Im doing it wrong, but the shape wouldnt move at all.

Sounds weird. If your numbers were getting through, you would at least
expect something garbled to show up

> I see the issue about producing eulers from a rotation matrix, but flash
> does not have the problem. You can apply rotation in any order and the
> result will be the same. The rotations do not affect each other.

I'm not sure that this is true. In general, the order in which you
apply rotations does mater. Some cases, you may get the same result
even if you permute the order, but not in all cases...

> Becuse all translations of quanternions to eulers do not return ranges of at
> least +-180 degrees or  0-360 degrees it makes them all useless.

The thing is, when 2 of the parameters are in the range [-Pi,Pi], then
you only need [-Pi/2,Pi/2] for the last parameter to have the whole
rotation space covered... So I speculate that this is not directly the
cause of your problem. I think it has to do with the angles being in
the wrong order somehow. Like I pointed out earlier, the rotation
order of the euclideanspace.com example, the order is [Y,Z,X]. If this
is not the order you need, it will surely not work when you plug it
in. And you most likely cannot just switch the order around, because
that has to be done inside the conversion too. The example on wiki

http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles

has the order X,Y,Z, which is most likely the one you want. I'll try
to throw that into some code right now and see if I can make that
work...

> The lack of euler rotation angles of your physical bodies makes it very
> difficult for a non-math expert to use the libratry

Yeah, I can see that for sure :) I'll look into it :) Basically,
plugging in the rotation matrix from Body.state.rotation, or the
quaternion Body.state.orientation, should possible. If you have access
to the rotation matrix in your flash library, it should be possible to
simply copy all 9 values from Body.state.rotation into that matrix.

But that cannot be done, if they only allow you to access the rotation
by means of specifying Euler angles..

Andy Shaules

unread,
Oct 7, 2010, 1:20:07 PM10/7/10
to jinn...@googlegroups.com

Yes, about the order. Flash maintains independant matrix for each plane of
rotation, so it is as if when rotating one plane, the other planes are
always figured as zero.

You probaly are correct about how adobe computes the values in the end,
however, from an API user stand point where we can change the rotation by
euler angles, there is no difference in the final rotation of the object
based on which I change first.

There are also many of the compound translations available in these 3d flash
libs that do follow those rules of order to get in and out of a transform,
but the top level main rotation planes are handled under the hood, so we can
apply any value magnatude in any order.

Im glad I found the correct way to tunnel the data in finally. And it
appears to use the least amount of client or additional server side math. A
nice suprise!

Andy


----- Original Message -----
From: "Morten Silcowitz" <mor...@silcowitz.dk>
To: <jinn...@googlegroups.com>
Sent: Thursday, October 07, 2010 10:07 AM
Subject: Re: [jinngine] Re: Red5 demo

Andy Shaules

unread,
Oct 7, 2010, 2:03:08 PM10/7/10
to jinn...@googlegroups.com
Ok, I can confirm the flash code I'm using is working great with your Q's

Thanks again!

I'll redeploy the demo and update the svn today some time.

Andy

----- Original Message -----
From: "Morten Silcowitz" <mor...@silcowitz.dk>
To: <jinn...@googlegroups.com>
Sent: Thursday, October 07, 2010 10:07 AM
Subject: Re: [jinngine] Re: Red5 demo

Morten Silcowitz

unread,
Oct 7, 2010, 2:48:25 PM10/7/10
to jinn...@googlegroups.com
By reading your description, I'm not really sure I understand what
exactly you are doing in your code. If it works however, thats just
fine then :). If you are interested, I toke a little time out to
implement the conversion method lined out on wiki, which uses the XYZ
order (see below). This conversion does NOT handle the singularity
problems, because I simply couldn't get my head around how to compute
the 3. parameter in those cases. I hope i'll figure that out soon.
Anyway, this method should produce right-looking results when you plug
it in, except for some single cases. If not, well, then we have some
more confusion :)

/**
* Convert unit quaternion into Euler angles, in the order XYZ. The
* singular points is NOT taken into account.


* @param q1 Unit quaternion representing rotation transform
* @param euler Vector3 to contain the euler angles
*/
public static final void quaternionToEuler(final Quaternion q1, final
Vector3 euler) {

double phi=0, theta=0, psi=0;
if (Math.abs(q1.s*q1.v.y-q1.v.z*q1.v.x)<0.5) {


double sqx = q1.v.x*q1.v.x;
double sqy = q1.v.y*q1.v.y;
double sqz = q1.v.z*q1.v.z;

phi = Math.atan2(2*(q1.s*q1.v.x-q1.v.y*q1.v.z) , 1 - 2*sqx - 2*sqy);
theta = Math.asin(2*q1.s*q1.v.y-q1.v.z*q1.v.x);
psi = Math.atan2(2*(q1.s*q1.v.z-q1.v.x*q1.v.y) , 1 - 2*sqy - 2*sqz);
}
// return values
euler.x = phi;
euler.y = theta;
euler.z = psi;
}

2010/10/7 Andy Shaules <bowl...@gmail.com>:

>>> atitude = ± π/2 rads

Andy Shaules

unread,
Oct 7, 2010, 3:19:58 PM10/7/10
to jinn...@googlegroups.com
Thanks,

Here is the working sim using the impulse force and quaternion rotations.

http://74.217.67.51:5080/demo/

Andy

>>> atitude = οΏ½ οΏ½/2 rads

mo

unread,
Oct 8, 2010, 4:17:22 PM10/8/10
to jinngine
Alright, I have talked to an associate professor here at DIKU
(University of Copenhagen CS department), I'm now pretty confident
that the following method does the job of converting a unit quaternion
into XYZ ordered Euler angles. The method is similar to the other
methods posted previously, but it takes further advantage of the atan2
function, so that special cases are implicitly handled. The code goes
here

/**
* Calculate the XYZ Euler angles from the unit quaternion q.
*
* The euler angles, [phi,theta,psi], will reflect the rotation
obtained by
* the rotation matrix R(q) = Rz(psi)Ry(theta)Rx(phi), where R(q) is
the
* rotation matrix computed directly from the unit quaternion q, and
Rx(phi) is
* the rotation matrix that rotates phi radians about the x-axis,
etc.
*/
public static final void quaternionToEuler(final Quaternion q, final
Vector3 euler) {
// partially calculate the rotation matrix
final double m11 = 1-2*(q.v.y*q.v.y+q.v.z*q.v.z); // final double m12
= 2*q1.v.x*q1.v.y-2*q1.s*q1.v.z; final double m13 = 2*q1.s*q1.v.y
+2*q1.v.x*q1.v.z;
final double m21 = 2*q.v.x*q.v.y+2*q.s*q.v.z; // final double m22 =
1-2*(q1.v.x*q1.v.x+q1.v.z*q1.v.z); final double m23 = -2*q1.s*q1.v.x
+2*q1.v.y*q1.v.z;
final double m31 = -2*q.s*q.v.y+2*q.v.x*q.v.z; final double m32 =
2*q.s*q.v.x+2*q.v.y*q.v.z; final double m33 = 1-2*(q.v.x*q.v.x
+q.v.y*q.v.y);

// calculate the euler angles using the atan2() function
final double phi = Math.atan2( m32, m33 );
final double theta = Math.atan2( -m31, m32*Math.sin(phi)
+m33*Math.cos(phi));
final double psi = Math.atan2( m21, m11 );

// return result
euler.assign(phi,theta,psi);
}

This method will be included in the svn and in a coming release, at
some point...

Andy Shaules

unread,
Oct 8, 2010, 4:46:32 PM10/8/10
to jinn...@googlegroups.com
Excellent!

I'm glad I wasnt alone at having to devote a full day to the issue. And Im
even more glad that your quaternion works in the native adobe quaternion
compose method.

Awesome!

Andy

----- Original Message -----
From: "mo" <mor...@silcowitz.dk>
To: "jinngine" <jinn...@googlegroups.com>
Sent: Friday, October 08, 2010 1:17 PM
Subject: [jinngine] Re: Red5 demo

Reply all
Reply to author
Forward
0 new messages