.:: Bots United ::.

.:: Bots United ::. (http://forums.bots-united.com/index.php)
-   General Bot Coding (http://forums.bots-united.com/forumdisplay.php?f=24)
-   -   BSP file: a couple of question linked to geometry (http://forums.bots-united.com/showthread.php?t=2338)

evy 22-07-2004 01:23

BSP file: a couple of question linked to geometry
 
Hello,

In short, I'm helping Stefan with his Realbot. I'm specially working on a automatic translation of BSP map file into the waypoints file used by Realbot. It is a mixed approach between Navmesh (merci Pierre-Marie) and usual waypoint.

As I graduated a long time ago, a lot of my mathematics has disappeared from my brain :( Hence, a couple of questions:

1) how can we compute the area (in square unit) of a polygon (i.e. an area defined by multiple points in the same plane) ? Splitting the face into multiple triangles seems a little too much.

2) how can we check whether a point is inside a polygon ? I know about the mathematical fact that all egdes defines a 360 degree when inside, but, again looks heavy to compute

3) as my utility is running as standalone, it does not have access to the HL engine :'( , hence the TraceHull function is not available :'( I used Botman TraceLine but how can we emulate the TraceHull ?

3.1) what are the 'sizes' of point_hull, head_hull, human_hull ? It looks like the shape of a human player when standing is 16x16x76 and when duckinc 16x16x36, is it correct ?

3.2) how can we handle the de_aztec bridge, the utility cannot waypoint it probably because it cannot 'feel' the ground as the brigde has holes :'(

Thanks in advance for any piece of information

-eric

PS: for this interested, the utility is not rocket science and is based on Pierre-Marie's ideas of navmesh combined with a lot of algorithms & code from Botman. The code is in the realbot CVS repository under Bsp2Rbn directory.

sfx1999 22-07-2004 01:50

Re: BSP file: a couple of question linked to geometry
 
Quote:

Originally Posted by evy
1) how can we compute the area (in square unit) of a polygon (i.e. an area defined by multiple points in the same plane) ? Splitting the face into multiple triangles seems a little too much.

This is very problematic. You could probably use another equation for rectangles, but what if you end up getting a trapezoid? You might as well just split them into triangles.

Quote:

2) how can we check whether a point is inside a polygon ? I know about the mathematical fact that all egdes defines a 360 degree when inside, but, again looks heavy to compute
Generate a plane for the polygon and then check to see if it is in that plane. after that you can check to see if it is within the polygon.

Quote:

3) as my utility is running as standalone, it does not have access to the HL engine :'( , hence the TraceHull function is not available :'( I used Botman TraceLine but how can we emulate the TraceHull ?
What does TraceHull do? I might be able to help you if I knew.

Quote:

3.1) what are the 'sizes' of point_hull, head_hull, human_hull ? It looks like the shape of a human player when standing is 16x16x76 and when duckinc 16x16x36, is it correct ?
I believe it is 32x72x32, 72 units tall, 32 units wide, and 32 units deep.
Crouching it is 32x36x32.

Quote:

3.2) how can we handle the de_aztec bridge, the utility cannot waypoint it probably because it cannot 'feel' the ground as the brigde has holes :'(
My guess is you would get a simulated person to walk across it and see if they could. Also, the bridge may have a clip brush in between. You could also just put a point on anything the bot can stand on and test if they can get across it.

Whistler 22-07-2004 02:08

Re: BSP file: a couple of question linked to geometry
 
Take a look at Quake1 engine source code for the TraceHull code.

It can be found at http://www.idsoftware.com, at the Technolody Download page.

Look at the SV_Move() function and what it called.

PS, I think the HL routine is identical as the Quake1 routine.

sfx1999 22-07-2004 06:43

Re: BSP file: a couple of question linked to geometry
 
If you use Quake code you will have to comply with the terms of the GPL.

Pierre-Marie Baty 22-07-2004 16:50

Re: BSP file: a couple of question linked to geometry
 
Quote:

Originally Posted by evy
1) how can we compute the area (in square unit) of a polygon (i.e. an area defined by multiple points in the same plane) ? Splitting the face into multiple triangles seems a little too much.

I don't know any other solution than splitting it into triangles, indeed... that's not hard to do, enumerate the corners successively, take 3 successive ones, they form a triangle, compute the area, remove the middle corner of the 3 from the corners list and do it again and again until you end up with only 2 corners, that's when you've covered the face entirely.

Quote:

2) how can we check whether a point is inside a polygon ? I know about the mathematical fact that all egdes defines a 360 degree when inside, but, again looks heavy to compute
I don't know any better method. That's the one I am using in my bot code.

Quote:

3) as my utility is running as standalone, it does not have access to the HL engine :'( , hence the TraceHull function is not available :'( I used Botman TraceLine but how can we emulate the TraceHull ?
It *is* possible to use TraceHull without the HL engine because a TraceHull is a version of TraceLine that works in a "fattened" world. Since there are 3 hulls (or is it 4 ?) in this game, 3 chunks of the map geometry are stored in the BSP file. The first one corresponds to TraceLine (which is in fact TraceHull+point_hull), and the successive ones correspond to each of the TraceHulls.

Quote:

3.1) what are the 'sizes' of point_hull, head_hull, human_hull ? It looks like the shape of a human player when standing is 16x16x76 and when duckinc 16x16x36, is it correct ?
point_hull == traceline
head_hull == ducking player
human_hull == standing player

Quote:

3.2) how can we handle the de_aztec bridge, the utility cannot waypoint it probably because it cannot 'feel' the ground as the brigde has holes :'(
using a TraceHull :)
But if you don't want to do that (as I do), then you consider that the bridge is straight, and that there is a direct connection from one end of the bridge to the other. It's as if the bot was jumping off a cliff to land on another, except that here it won't fall but cross a bridge (even if the bot doesn't "feel" it that won't prevent it to cross it successfully).

evy 22-07-2004 17:32

Re: BSP file: a couple of question linked to geometry
 
Pierre-Marie,

First thanks for the information (as well as sfx1999, Whistler).

Code:

It *is* possible to use TraceHull without the HL engine because a TraceHull is a version of TraceLine that works in a "fattened" world.
Do you mean that there is a TraceHull function (or a similar one) in the HLDSK? I grepped through the HLDSK and it looked (but I can be wrong) that TraceHull is actually implemented in the HL engine itself (as it is called through a pointer of a function).

Did I miss something ? I would be delighted =)

Code:

Since there are 3 hulls (or is it 4 ?) in this game, 3 chunks of the map geometry are stored in the BSP file. The first one corresponds to TraceLine (which is in fact TraceHull+point_hull), and the successive ones correspond to each of the TraceHulls.
Not sure that I understand you correctly... Are there really three copies of the map in the BSP file? And one for each hull ? (assuming then that the blocks have been widened/fattened with ketchup & fries in order to have less space among them).

Thanks again

Merci beaucoup

-eric

PS: PM, funny that my brother in law has exactly the same first name as you :D

PS2: for de_aztec bridge, I made further test. It seems linked to the use of the Botman TraceLine: it never detects the bridge itself, I'm afraid that the bridge is not a worldspawn block but rather a func_wall entity... So, I'll need to extend TraceLine to handle func_wall entities :'(

Whistler 23-07-2004 01:17

Re: BSP file: a couple of question linked to geometry
 
"If you use Quake code you will have to comply with the terms of the GPL."
...and if you use Metamod code (i.e., make the bot as a metamod plugin) or botman's BSP tools code you also will have to comply with the GNU GPL. (and is it so bad ?)

@evy: the bridge in de_aztec is func_illusionary. and it even does not block tracelines in the HL engine.
and there's no implemention of traceline/hull in HLSDK btw, I think what he means is to make one yourself in the code, and you just need to thicken the walls and use traceline.

evy 23-07-2004 08:07

Re: BSP file: a couple of question linked to geometry
 
Quote:

Originally Posted by Whistler
"If you use Quake code you will have to comply with the terms of the GPL."
...and if you use Metamod code (i.e., make the bot as a metamod plugin) or botman's BSP tools code you also will have to comply with the GNU GPL. (and is it so bad ?).

Of course not ;) It was always my intention to make the code public (which is already the case as everyone can access it in the realbot CVS repository).

I'll have to put the GPLize the comments in every source file :( (no clue how to do it -- I'll look in another GPL package) but this will be done.

Quote:

Originally Posted by Whistler
@evy: the bridge in de_aztec is func_illusionary. and it even does not block tracelines in the HL engine.
and there's no implemention of traceline/hull in HLSDK btw, I think what he means is to make one yourself in the code, and you just need to thicken the walls and use traceline.

Found the func_illusionary last night as well =) Working now!

Thickening the walls looks more complex than shooting a couple of TraceLine even if less efficient. I do not really understand the node structure of a BSP file and I do not want to re-invent a MAP compiler 8)

THANKS all for the help

-eric

Pierre-Marie Baty 23-07-2004 14:44

Re: BSP file: a couple of question linked to geometry
 
Quote:

Originally Posted by evy
Not sure that I understand you correctly... Are there really three copies of the map in the BSP file? And one for each hull ? (assuming then that the blocks have been widened/fattened with ketchup & fries in order to have less space among them).

Exactly. Given the amount of ketchup & fries (point_hull, head_hull and human_hull) you will trace a "bigger" line in the world, which is exactly the same thing as if you were tracing the same line in three "fatter" worlds. Storing 3 maps in one, that's the concept the ID programmers invented to find out easily the limits of where a player can move and where it can not.

TraceLine and TraceHull are the same thing, they call the same function in the engine: SV_RecursiveHullTrace(). Look in any of the Quake's source code to find out how this function works and how to make yours work with HL BSP files.

evy 23-07-2004 22:14

Re: BSP file: a couple of question linked to geometry
 
Quote:

Exactly. Given the amount of ketchup & fries (point_hull, head_hull and human_hull) you will trace a "bigger" line in the world, which is exactly the same thing as if you were tracing the same line in three "fatter" worlds. Storing 3 maps in one, that's the concept the ID programmers invented to find out easily the limits of where a player can move and where it can not
.

Pierre-Marie,

You were right: by digging in the code and by using my crystal ball ;) :

- BSP contains information about 4 hulls: visibility (=point_hull), human, head and large
- hull0 = pointhull starts on nodes 0 (i.e. the nodes 'lump') for worldspawn
- other hulls start on model->headnode[hullNumber] and use clipnodes (i.e. the clipnodes 'lump') instead of nodes

The algorithm to parse the node tree is the same (I used first Botman's TraceLine which is easier to understand).:)

Thanks for everyone's help

-eric

Whistler 24-07-2004 01:19

Re: BSP file: a couple of question linked to geometry
 
s/"SV_RecursiveHullTrace()"/"SV_RecursiveHullCheck()"

however actually the quake engine doesn't call this function directly, it just use that "SV_Move()" function in the code.

evy 24-07-2004 08:41

Re: BSP file: a couple of question linked to geometry
 
Quote:

Originally Posted by Whistler
s/"SV_RecursiveHullTrace()"/"SV_RecursiveHullCheck()"

Should actually be s/"SV_RecursiveHullTrace()"/"SV_RecursiveHullCheck()"/

;)

Sorry, could not resist:D

BTW, nice to see people using sed on the forum

It took me some time to find the typo in the original post:D

Immortal_BLG 17-01-2010 03:25

Re: BSP file: a couple of question linked to geometry
 
Hello people! I need create TraceLine() function which can't "pass" thru objects on a map which player entity can't pass thru too.

I've read that such objects (invisible, but shootable from any weapons, impervious to any entities other than "player" entities and non-blocking TraceLine () function) - they are called clip-brushes (as I understood - this is the part of a worldspawn entity), but how they can be found on the map, I don't know :(

If anyone knows anything about this, please help me.

P.S. I have met such objects on a map 3d_aim_nuke (for example, there impossible to get to the CT's spawn points).

SamPlay 18-01-2010 22:44

Re: BSP file: a couple of question linked to geometry
 
HI,
I am not sure I understand your problem; I would think that using the proper bsp tree with the traceline/tracehull function provided by the HL SDK should help.
The trace functions in my Bspview have been coded starting from quake, but the tracehull provided in my code is not as accurate as the one available in the hlsdk. I do NOT use my code of my Bspview trace for tracehulls; in other words , the source code of my bot uses only hlsdk functions.
Endly, remember that for collision checks, the first ( word) bsp tree is useless; if you want to use directly bsp tree in your code, you must select one among the three others, as they represent a different geometry than the first one ( ie if I remember well, they include blocking brushes/faces which are not included in the word bsptree because this one is used for drawing).
PS sorry for the unanswered email, but I had ( and still have!) a problem with it.

SamPlay 18-01-2010 22:45

Re: BSP file: a couple of question linked to geometry
 
HI,
I am not sure I understand your problem; I would think that using the proper bsp tree with the traceline/tracehull function provided by the HL SDK should help.
The trace functions in my Bspview have been coded starting from quake, but the tracehull provided in my code is not as accurate as the one available in the hlsdk. I do NOT use my code of my Bspview trace for tracehulls; in other words , the source code of my bot uses only hlsdk functions.
Endly, remember that for collision checks, the first ( world) bsp tree is useless; if you want to use directly bsp tree in your code, you must select one among the three others, as they represent a different geometry than the first one ( ie if I remember well, they include blocking brushes/faces which are not included in the world bsptree because this one is used for drawing).
PS sorry for the unanswered email, but I had ( and still have!) a problem with it.

Immortal_BLG 19-01-2010 07:22

Re: BSP file: a couple of question linked to geometry
 
I need a function TraceLine and desirable TraceHull(which can gets BBox mins and maxs), which can not "pass" where the player can not pass, ie for example: through func_illusionary (HLSDK TraceLine passes), but through such entities like the func_pushable entity - passed (HLSDK TraceLine NOT passes), as player will be able to push this entity. Since with entities I can do this: at the time of generation navmesh set pev-> solid at SOLID_NOT or SOLID_BSP depending on - should it be that Trace(Line/Hull) "passed" through the object or not. But with objects, as I described in the my previous post - so do not do. These functions I need only to generate navmesh and all. In general I need to function as playermove_t:: PM_PlayerTrace.

So now I need to find At least some information about the hidden areas on the map through which HLSDK TraceLine passes, and the player not.

Please help! :wacko:

P.S. Can you upload somewhere the latest source code of your BSP View (if there at filebase older versions) and the source code of your bot please. :P

SamPlay 19-01-2010 21:22

Re: BSP file: a couple of question linked to geometry
 
Hi,
please find herafter the updated version of ( my) Bspview.It has some bugs
in the display, that is why I did not upload it to the filebase; I do not intend to correct those bugs soon.
I think it has interesting improvements, especially visualization of all the bsptrees of the map ( hit F11, then cycle through F12).
As regards the source code of my bot, it is far from complete, so I do not intend to give it in the near future.
Besides, if you do not know already, the old and abandonned Racc-ai bot has a navmesh system which , as far as I remember , worked.
...
SORRY, UPLOADING DOES NOT WORK! Send me your e-mail so that I can e-mail it to you.

Immortal_BLG 23-01-2010 09:54

Re: BSP file: a couple of question linked to geometry
 
HAHAHAHAHHA! It turns out that the function SV_RecursiveHullCheck (about which here said) can "find" clip-brushes by the clip-nodes.
hence the conclusion - I'm stupid.
Sorry all.

Immortal_BLG 23-01-2010 10:00

Re: BSP file: a couple of question linked to geometry
 
HAHAHAHAHHA! It turns out that the function SV_RecursiveHullCheck (about which here said) can "find" clip-brushes by the clip-nodes.
hence the conclusion - I'm stupid.
Sorry all.

Immortal_BLG 26-01-2010 10:41

Re: BSP file: a couple of question linked to geometry
 
Somebody can tell to me, whether it is possible to make, that the function SV_RecursiveHullCheck is traced a LINE instead of the HULL. (This function of is necessary to me that it works by clip-nodes - as I want, but as I have understood(maybe not true) by the clip-nodes can be determined only HULL collisions)

And one more question: can me someone explain what a difference between:
Code:

if (plane->type < 3)
        d = p[plane->type] - plane->dist;
else
        d = DotProduct (plane->normal, p) - plane->dist;

and
Code:

d = DotProduct (plane->normal, p) - plane->dist;
I noticed that in HL do not use the first case, only in the Quake - why?

Please help me....

Immortal_BLG 20-08-2010 13:21

Re: BSP file: a couple of question linked to geometry
 
Hello!

Is it possible to correctly shift planes?
If so - how to do this?
I need to shift the clip-planes so that their distances was the same distances of the point-hull planes.

I do so:
Code:

const Math::Vector3D hullSizes[Hull_Total][2u] =
{
        /* Mins */                                /* Maxs */
        {Math::Vector3D (  0.0f,  0.0f,  0.0f), Math::Vector3D ( 0.0f,  0.0f,  0.0f)},        // Hull_Point (0x0x0)
        {Math::Vector3D (-16.0f, -16.0f, -36.0f), Math::Vector3D (16.0f, 16.0f, 36.0f)},        // Hull_Human (32x32x72)
        {Math::Vector3D (-32.0f, -32.0f, -32.0f), Math::Vector3D (32.0f, 32.0f, 32.0f)},        // Hull_Large (64x64x64)
        {Math::Vector3D (-16.0f, -16.0f, -18.0f), Math::Vector3D (16.0f, 16.0f, 18.0f)}                // Hull_Head  (32x32x36)
};

const Math::Vector3D offset
(
        plane.normal.x >= 0.0f ? hullSizes[hullIndex][0u].x : hullSizes[hullIndex][1u].x,
        plane.normal.y >= 0.0f ? hullSizes[hullIndex][0u].y : hullSizes[hullIndex][1u].y,
        plane.normal.z >= 0.0f ? hullSizes[hullIndex][0u].z : hullSizes[hullIndex][1u].z
);

plane.distance -= Dot (offset, plane.normal);

but this won't work for some planes (for example floor) - this conversely shifts plane to opposite side.

Please help.

SamPlay 21-08-2010 10:52

Re: BSP file: a couple of question linked to geometry
 
My guess is that what you implicitely want is to shift FACES.
Shifting their supporting planes does not work if the face and its plane have opposite normals, which cannot be always avoided.
I think it is not possible to get a proper thickenning of the world based on PLANES alone.
You may want to have a look at Zoner's Half-life tools ( qcsg and qbsp i believe) to see how they do it.
But actually, the right way to do that is by performing a tweaked Minkowsky sum of the hull and the map.

Immortal_BLG 21-08-2010 12:02

Re: BSP file: a couple of question linked to geometry
 
Hello SamPlay!!
Thank you for helping!

Quote:

My guess is that what you implicitely want is to shift FACES.
No. I want to shift clip-planes. My goal point-trace(not trace hull) trough clip-nodes. Now with clip-nodes I can trace only hulls (human, large and head).

Quote:

But actually, the right way to do that is by performing a tweaked Minkowsky sum of the hull and the map.
Sorry stupid me, but can you give some links to examples in C++ of Minkowski sum.

The Storm 21-08-2010 12:27

Re: BSP file: a couple of question linked to geometry
 
Quick google: http://mathworld.wolfram.com/MinkowskiSum.html :)

Immortal_BLG 21-08-2010 13:19

Re: BSP file: a couple of question linked to geometry
 
I read it but did not understand how this relates to the clip-planes - sorry :(

The Storm 22-08-2010 12:29

Re: BSP file: a couple of question linked to geometry
 
Me too. :D

SamPlay 22-08-2010 18:45

Re: BSP file: a couple of question linked to geometry
 
How does Minkowski sum relate to collision? : google for"Minkowski sum bsp".
Almost all references on the first page are interesting.
I am using it to (re)create bsp's for hulls (obviously..) different than the "point-hull".
After thought: maybe "minkowski sum collision" would be better...

SamPlay 22-08-2010 19:17

Re: BSP file: a couple of question linked to geometry
 
Quote:

Originally Posted by Immortal_BLG (Post 62031)
Hello!

Is it possible to correctly shift planes?
If so - how to do this?
I need to shift the clip-planes so that their distances was the same distances of the point-hull planes.

I do so:
...
but this won't work for some planes (for example floor) - this conversely shifts plane to opposite side.

Please help.

To be sure I understand what you are looking for:
1. the point-hull bsp provided in the bsp file, as opposed to the 3 others bsps ( human, head,..) is used in HL for drawing, not for movement( ie collision detection); so some clipping ( blocking) faces are not taken into account when building the point-hull bsp tree because they are not drawn.
I assume you want a COLLISION bsp tree for the point and not a DRAWING bsp tree; so you use one of the other bsp trees as a starting point ( because it takes clipping faces into account), and "moving back" its (clipping) planes so that the "thickness" resulting from the hull is reduced to zero, reducing implicitely the hull to a point.
Is that right?
If it is right, I do not see how you can guarantee that you will get all the original planes.

As regard your code, I cannot see how the code itself can be right in some cases and wrong in some others.
I mentionned FACES instead of planes because in general the normal of a clipping plane alone cannot tell if the SOLID ( or move-blocking if you wish) side is in front of the plane or in its back. On the contrary, the "contents" data of a face always describes what is in its back ( but the face normal maybe equal or opposite to its supporting plane one).
My conclusion is that your code is right ( providing plane.distance has the right signing!), but you make an assumption on the meaning of the plane normal relative the SOLID side which is sometimes right,sometimes wrong.

EDIT I forgot: your code is ok in principle but there is a tweak in the way the bsps are build: the planes are actually shifted horizontally by a CONSTANT value, as if the hulls were vertical CYLINDERS.

Immortal_BLG 23-08-2010 02:28

Re: BSP file: a couple of question linked to geometry
 
Quote:

google for"Minkowski sum bsp"
I have found your site in search results (the fourth under the account) - so, I am bad with it have familiarized, since knew about it long before have learned about Minkowski :(

Quote:

Is that right?
Yes - that's what I want :)

Thanks for the detailed response!

Code:

                //add the offset non-axial plane to the expanded hull
        VectorCopy(current_plane->origin, origin);
        VectorCopy(current_plane->normal, normal);

                //old code multiplied offset by normal -- this led to post-csg "sticky" walls where a
                //slope met an axial plane from the next brush since the offset from the slope would be less
                //than the full offset for the axial plane -- the discontinuity also contributes to increased
                //clipnodes.  If the normal is zero along an axis, shifting the origin in that direction won't
                //change the plane number, so I don't explicitly test that case.  The old method is still used if
                //preciseclip is turned off to allow backward compatability -- some of the improperly beveled edges
                //grow using the new origins, and might cause additional problems.

                if((g_texinfo[current_face->texinfo].flags & TEX_BEVEL))
                {
                        //don't adjust origin - we'll correct g_texinfo's flags in a later step
                }
                else if(g_cliptype == clip_legacy || (g_cliptype == clip_precise && (normal[2] > FLOOR_Z)) || g_cliptype == clip_normalized)
                {
                        if(normal[0])
                        { origin[0] += normal[0] * (normal[0] > 0 ? g_hull_size[hullnum][1][0] : -g_hull_size[hullnum][0][0]); }
                        if(normal[1])
                        { origin[1] += normal[1] * (normal[1] > 0 ? g_hull_size[hullnum][1][1] : -g_hull_size[hullnum][0][1]); }
                        if(normal[2])
                        { origin[2] += normal[2] * (normal[2] > 0 ? g_hull_size[hullnum][1][2] : -g_hull_size[hullnum][0][2]); }
                }
                else
                {
                        origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0];
                        origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1];
                        origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2];
                }

                AddHullPlane(hull,normal,origin,false);

- From zhlt hlcsg brush.cpp ExpandBrush() function.

Maybe is possible to find 'origin'(also known as point0 for computation hull plane normal) from code above by some equation - if possible? :)

SamPlay 23-08-2010 09:58

Re: BSP file: a couple of question linked to geometry
 
well if you do
'origin[0]-=' instead of 'origin[0]+=' , you should get what you are looking for.
Now that I understand your goal, Minkowski sum will not help you! Zhlt should have use it instead of their code you show, and the problems mentioned in the top comment of zhlt code would not haved occurred ( when creating and displaying faces from non-pointhull bsps, it really looks silly!).
Also, forget about the EDIT ( about 'vertical CYLINDER'), it is a mistake.

Immortal_BLG 23-08-2010 12:37

Re: BSP file: a couple of question linked to geometry
 
well... left only to find out how to calculate this origin.... :(

SamPlay 23-08-2010 18:39

Re: BSP file: a couple of question linked to geometry
 
Sorry, I did not understand
origin can be any point lying on the plane, which you can find using the plane equation ( for example by setting x and y and computing z for a non vertical plane).


All times are GMT +2. The time now is 06:17.

Powered by vBulletin® Version 3.8.2
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.