* Adding some organization of vehicle type stuff in the ODEPlugin.
* Vehicles do NOT work. This is just organization and a bit of logical code to make doing vehicles easier0.6.5-rc1
parent
a0417f5791
commit
d34d5eb3f7
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Contributors, http://opensimulator.org/
|
||||||
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the OpenSimulator Project nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OpenSim.Region.Physics.Manager
|
||||||
|
{
|
||||||
|
public enum Vehicle : int
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Turns off Vehicle Support
|
||||||
|
/// </summary>
|
||||||
|
TYPE_NONE = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// No Angular motor, High Left right friction, No Hover, Linear Deflection 1, no angular deflection
|
||||||
|
/// no vertical attractor, No banking, Identity rotation frame
|
||||||
|
/// </summary>
|
||||||
|
TYPE_SLED = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Needs Motors to be driven by timer or control events High left/right friction, No angular friction
|
||||||
|
/// Linear Motor wins in a second, decays in 60 seconds. Angular motor wins in a second, decays in 8/10ths of a second
|
||||||
|
/// linear deflection 2 seconds
|
||||||
|
/// Vertical Attractor locked UP
|
||||||
|
/// </summary>
|
||||||
|
TYPE_CAR = 2,
|
||||||
|
TYPE_BOAT = 3,
|
||||||
|
TYPE_AIRPLANE = 4,
|
||||||
|
TYPE_BALLOON = 5,
|
||||||
|
LINEAR_FRICTION_TIMESCALE = 16,
|
||||||
|
/// <summary>
|
||||||
|
/// vector of timescales for exponential decay of angular velocity about three axis
|
||||||
|
/// </summary>
|
||||||
|
ANGULAR_FRICTION_TIMESCALE = 17,
|
||||||
|
/// <summary>
|
||||||
|
/// linear velocity vehicle will try for
|
||||||
|
/// </summary>
|
||||||
|
LINEAR_MOTOR_DIRECTION = 18,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Offset from center of mass where linear motor forces are added
|
||||||
|
/// </summary>
|
||||||
|
LINEAR_MOTOR_OFFSET = 20,
|
||||||
|
/// <summary>
|
||||||
|
/// angular velocity that vehicle will try for
|
||||||
|
/// </summary>
|
||||||
|
ANGULAR_MOTOR_DIRECTION = 19,
|
||||||
|
HOVER_HEIGHT = 24,
|
||||||
|
HOVER_EFFICIENCY = 25,
|
||||||
|
HOVER_TIMESCALE = 26,
|
||||||
|
BUOYANCY = 27,
|
||||||
|
LINEAR_DEFLECTION_EFFICIENCY = 28,
|
||||||
|
LINEAR_DEFLECTION_TIMESCALE = 29,
|
||||||
|
LINEAR_MOTOR_TIMESCALE = 30,
|
||||||
|
LINEAR_MOTOR_DECAY_TIMESCALE = 31,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// slide between 0 and 1
|
||||||
|
/// </summary>
|
||||||
|
ANGULAR_DEFLECTION_EFFICIENCY = 32,
|
||||||
|
ANGULAR_DEFLECTION_TIMESCALE = 33,
|
||||||
|
ANGULAR_MOTOR_TIMESCALE = 34,
|
||||||
|
ANGULAR_MOTOR_DECAY_TIMESCALE = 35,
|
||||||
|
VERTICAL_ATTRACTION_EFFICIENCY = 36,
|
||||||
|
VERTICAL_ATTRACTION_TIMESCALE = 37,
|
||||||
|
BANKING_EFFICIENCY = 38,
|
||||||
|
BANKING_MIX = 39,
|
||||||
|
BANKING_TIMESCALE = 40,
|
||||||
|
REFERENCE_FRAME = 44
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum VehicleFlag
|
||||||
|
{
|
||||||
|
NO_DEFLECTION_UP = 1,
|
||||||
|
LIMIT_ROLL_ONLY = 2,
|
||||||
|
HOVER_WATER_ONLY = 4,
|
||||||
|
HOVER_TERRAIN_ONLY = 8,
|
||||||
|
HOVER_GLOBAL_HEIGHT = 16,
|
||||||
|
HOVER_UP_ONLY = 32,
|
||||||
|
LIMIT_MOTOR_UP = 64,
|
||||||
|
MOUSELOOK_STEER = 128,
|
||||||
|
MOUSELOOK_BANK = 256,
|
||||||
|
CAMERA_DECOUPLED = 512
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -166,10 +166,13 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
|
|
||||||
public volatile bool childPrim = false;
|
public volatile bool childPrim = false;
|
||||||
|
|
||||||
|
private ODEVehicleSettings m_vehicle;
|
||||||
|
|
||||||
public OdePrim(String primName, OdeScene parent_scene, PhysicsVector pos, PhysicsVector size,
|
public OdePrim(String primName, OdeScene parent_scene, PhysicsVector pos, PhysicsVector size,
|
||||||
Quaternion rotation, IMesh mesh, PrimitiveBaseShape pbs, bool pisPhysical, CollisionLocker dode)
|
Quaternion rotation, IMesh mesh, PrimitiveBaseShape pbs, bool pisPhysical, CollisionLocker dode)
|
||||||
{
|
{
|
||||||
_target_velocity = new PhysicsVector(0, 0, 0);
|
_target_velocity = new PhysicsVector(0, 0, 0);
|
||||||
|
m_vehicle = new ODEVehicleSettings();
|
||||||
//gc = GCHandle.Alloc(prim_geom, GCHandleType.Pinned);
|
//gc = GCHandle.Alloc(prim_geom, GCHandleType.Pinned);
|
||||||
ode = dode;
|
ode = dode;
|
||||||
_velocity = new PhysicsVector();
|
_velocity = new PhysicsVector();
|
||||||
|
@ -308,7 +311,10 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
if (!childPrim)
|
if (!childPrim)
|
||||||
{
|
{
|
||||||
if (m_isphysical && Body != IntPtr.Zero)
|
if (m_isphysical && Body != IntPtr.Zero)
|
||||||
|
{
|
||||||
d.BodyEnable(Body);
|
d.BodyEnable(Body);
|
||||||
|
m_vehicle.Enable(Body, _parent_scene);
|
||||||
|
}
|
||||||
|
|
||||||
m_disabled = false;
|
m_disabled = false;
|
||||||
}
|
}
|
||||||
|
@ -319,7 +325,10 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
m_disabled = true;
|
m_disabled = true;
|
||||||
|
|
||||||
if (m_isphysical && Body != IntPtr.Zero)
|
if (m_isphysical && Body != IntPtr.Zero)
|
||||||
|
{
|
||||||
d.BodyDisable(Body);
|
d.BodyDisable(Body);
|
||||||
|
m_vehicle.Disable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableBody()
|
public void enableBody()
|
||||||
|
@ -358,6 +367,10 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
{
|
{
|
||||||
createAMotor(m_angularlock);
|
createAMotor(m_angularlock);
|
||||||
}
|
}
|
||||||
|
if (m_vehicle.Type != Vehicle.TYPE_NONE)
|
||||||
|
{
|
||||||
|
m_vehicle.Enable(Body, _parent_scene);
|
||||||
|
}
|
||||||
|
|
||||||
_parent_scene.addActivePrim(this);
|
_parent_scene.addActivePrim(this);
|
||||||
}
|
}
|
||||||
|
@ -722,7 +735,7 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
if (Body != IntPtr.Zero)
|
if (Body != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
_parent_scene.remActivePrim(this);
|
_parent_scene.remActivePrim(this);
|
||||||
|
m_vehicle.Destroy();
|
||||||
m_collisionCategories &= ~CollisionCategories.Body;
|
m_collisionCategories &= ~CollisionCategories.Body;
|
||||||
m_collisionFlags &= ~(CollisionCategories.Wind | CollisionCategories.Land);
|
m_collisionFlags &= ~(CollisionCategories.Wind | CollisionCategories.Land);
|
||||||
|
|
||||||
|
@ -925,10 +938,16 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
Amotor = IntPtr.Zero;
|
Amotor = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_vehicle.Type != Vehicle.TYPE_NONE)
|
||||||
|
{
|
||||||
|
m_vehicle.Reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Store this for later in case we get turned into a separate body
|
// Store this for later in case we get turned into a separate body
|
||||||
m_angularlock = new PhysicsVector(m_taintAngularLock.X, m_taintAngularLock.Y, m_taintAngularLock.Z);
|
m_angularlock = new PhysicsVector(m_taintAngularLock.X, m_taintAngularLock.Y, m_taintAngularLock.Z);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changelink(float timestep)
|
private void changelink(float timestep)
|
||||||
|
@ -1113,7 +1132,7 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
createAMotor(m_angularlock);
|
createAMotor(m_angularlock);
|
||||||
}
|
}
|
||||||
d.BodySetPosition(Body, Position.X, Position.Y, Position.Z);
|
d.BodySetPosition(Body, Position.X, Position.Y, Position.Z);
|
||||||
|
m_vehicle.Enable(Body, _parent_scene);
|
||||||
_parent_scene.addActivePrim(this);
|
_parent_scene.addActivePrim(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1706,6 +1725,8 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
fy = nmin;
|
fy = nmin;
|
||||||
d.BodyAddForce(Body, fx, fy, fz);
|
d.BodyAddForce(Body, fx, fy, fz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_vehicle.Step(timestep);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1816,11 +1837,7 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
|
|
||||||
public void changesize(float timestamp)
|
public void changesize(float timestamp)
|
||||||
{
|
{
|
||||||
//if (!_parent_scene.geom_name_map.ContainsKey(prim_geom))
|
|
||||||
//{
|
|
||||||
// m_taintsize = _size;
|
|
||||||
//return;
|
|
||||||
//}
|
|
||||||
string oldname = _parent_scene.geom_name_map[prim_geom];
|
string oldname = _parent_scene.geom_name_map[prim_geom];
|
||||||
|
|
||||||
if (_size.X <= 0) _size.X = 0.01f;
|
if (_size.X <= 0) _size.X = 0.01f;
|
||||||
|
@ -1915,177 +1932,7 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
m_taintsize = _size;
|
m_taintsize = _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
//public void changesize(float timestamp)
|
|
||||||
//{
|
|
||||||
// //if (!_parent_scene.geom_name_map.ContainsKey(prim_geom))
|
|
||||||
// //{
|
|
||||||
// // m_taintsize = _size;
|
|
||||||
// //return;
|
|
||||||
// //}
|
|
||||||
// string oldname = _parent_scene.geom_name_map[prim_geom];
|
|
||||||
|
|
||||||
// if (_size.X <= 0) _size.X = 0.01f;
|
|
||||||
// if (_size.Y <= 0) _size.Y = 0.01f;
|
|
||||||
// if (_size.Z <= 0) _size.Z = 0.01f;
|
|
||||||
|
|
||||||
// // Cleanup of old prim geometry
|
|
||||||
// if (_mesh != null)
|
|
||||||
// {
|
|
||||||
// // Cleanup meshing here
|
|
||||||
// }
|
|
||||||
// //kill body to rebuild
|
|
||||||
// if (IsPhysical && Body != (IntPtr) 0)
|
|
||||||
// {
|
|
||||||
// disableBody();
|
|
||||||
// }
|
|
||||||
// if (d.SpaceQuery(m_targetSpace, prim_geom))
|
|
||||||
// {
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// d.SpaceRemove(m_targetSpace, prim_geom);
|
|
||||||
// }
|
|
||||||
// d.GeomDestroy(prim_geom);
|
|
||||||
// prim_geom = (IntPtr)0;
|
|
||||||
// // we don't need to do space calculation because the client sends a position update also.
|
|
||||||
|
|
||||||
// // Construction of new prim
|
|
||||||
// if (_parent_scene.needsMeshing(_pbs))
|
|
||||||
// {
|
|
||||||
// float meshlod = _parent_scene.meshSculptLOD;
|
|
||||||
|
|
||||||
// if (IsPhysical)
|
|
||||||
// meshlod = _parent_scene.MeshSculptphysicalLOD;
|
|
||||||
// // Don't need to re-enable body.. it's done in SetMesh
|
|
||||||
// IMesh mesh = _parent_scene.mesher.CreateMesh(oldname, _pbs, _size, meshlod, IsPhysical);
|
|
||||||
// // createmesh returns null when it's a shape that isn't a cube.
|
|
||||||
// if (mesh != null)
|
|
||||||
// {
|
|
||||||
// setMesh(_parent_scene, mesh);
|
|
||||||
// d.GeomSetPosition(prim_geom, _position.X, _position.Y, _position.Z);
|
|
||||||
// d.Quaternion myrot = new d.Quaternion();
|
|
||||||
// myrot.W = _orientation.w;
|
|
||||||
// myrot.X = _orientation.X;
|
|
||||||
// myrot.Y = _orientation.Y;
|
|
||||||
// myrot.Z = _orientation.Z;
|
|
||||||
// d.GeomSetQuaternion(prim_geom, ref myrot);
|
|
||||||
|
|
||||||
// //d.GeomBoxSetLengths(prim_geom, _size.X, _size.Y, _size.Z);
|
|
||||||
// if (IsPhysical && Body == (IntPtr)0)
|
|
||||||
// {
|
|
||||||
// // Re creates body on size.
|
|
||||||
// // EnableBody also does setMass()
|
|
||||||
// enableBody();
|
|
||||||
// d.BodyEnable(Body);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// if (_pbs.ProfileShape == ProfileShape.HalfCircle && _pbs.PathCurve == (byte)Extrusion.Curve1)
|
|
||||||
// {
|
|
||||||
// if (_size.X == _size.Y && _size.Y == _size.Z && _size.X == _size.Z)
|
|
||||||
// {
|
|
||||||
// if (((_size.X / 2f) > 0f) && ((_size.X / 2f) < 1000))
|
|
||||||
// {
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// SetGeom(d.CreateSphere(m_targetSpace, _size.X / 2));
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// m_log.Info("[PHYSICS]: Failed to load a sphere bad size");
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// SetGeom(d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// SetGeom(d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// //else if (_pbs.ProfileShape == ProfileShape.Circle && _pbs.PathCurve == (byte)Extrusion.Straight)
|
|
||||||
// //{
|
|
||||||
// //Cyllinder
|
|
||||||
// //if (_size.X == _size.Y)
|
|
||||||
// //{
|
|
||||||
// // prim_geom = d.CreateCylinder(m_targetSpace, _size.X / 2, _size.Z);
|
|
||||||
// //}
|
|
||||||
// //else
|
|
||||||
// //{
|
|
||||||
// //prim_geom = d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z);
|
|
||||||
// //}
|
|
||||||
// //}
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// SetGeom(prim_geom = d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z));
|
|
||||||
// }
|
|
||||||
// //prim_geom = d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z);
|
|
||||||
// d.GeomSetPosition(prim_geom, _position.X, _position.Y, _position.Z);
|
|
||||||
// d.Quaternion myrot = new d.Quaternion();
|
|
||||||
// myrot.W = _orientation.w;
|
|
||||||
// myrot.X = _orientation.X;
|
|
||||||
// myrot.Y = _orientation.Y;
|
|
||||||
// myrot.Z = _orientation.Z;
|
|
||||||
// d.GeomSetQuaternion(prim_geom, ref myrot);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// if (_pbs.ProfileShape == ProfileShape.HalfCircle && _pbs.PathCurve == (byte)Extrusion.Curve1)
|
|
||||||
// {
|
|
||||||
// if (_size.X == _size.Y && _size.Y == _size.Z && _size.X == _size.Z)
|
|
||||||
// {
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// SetGeom(d.CreateSphere(m_targetSpace, _size.X / 2));
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// SetGeom(d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// //else if (_pbs.ProfileShape == ProfileShape.Circle && _pbs.PathCurve == (byte)Extrusion.Straight)
|
|
||||||
// //{
|
|
||||||
// //Cyllinder
|
|
||||||
// //if (_size.X == _size.Y)
|
|
||||||
// //{
|
|
||||||
// //prim_geom = d.CreateCylinder(m_targetSpace, _size.X / 2, _size.Z);
|
|
||||||
// //}
|
|
||||||
// //else
|
|
||||||
// //{
|
|
||||||
// //prim_geom = d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z);
|
|
||||||
// //}
|
|
||||||
// //}
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// _parent_scene.waitForSpaceUnlock(m_targetSpace);
|
|
||||||
// SetGeom(d.CreateBox(m_targetSpace, _size.X, _size.Y, _size.Z));
|
|
||||||
// }
|
|
||||||
// d.GeomSetPosition(prim_geom, _position.X, _position.Y, _position.Z);
|
|
||||||
// d.Quaternion myrot = new d.Quaternion();
|
|
||||||
// myrot.W = _orientation.w;
|
|
||||||
// myrot.X = _orientation.X;
|
|
||||||
// myrot.Y = _orientation.Y;
|
|
||||||
// myrot.Z = _orientation.Z;
|
|
||||||
// d.GeomSetQuaternion(prim_geom, ref myrot);
|
|
||||||
|
|
||||||
// //d.GeomBoxSetLengths(prim_geom, _size.X, _size.Y, _size.Z);
|
|
||||||
// if (IsPhysical && Body == (IntPtr) 0)
|
|
||||||
// {
|
|
||||||
// // Re creates body on size.
|
|
||||||
// // EnableBody also does setMass()
|
|
||||||
// enableBody();
|
|
||||||
// d.BodyEnable(Body);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _parent_scene.geom_name_map[prim_geom] = oldname;
|
|
||||||
|
|
||||||
// changeSelectedStatus(timestamp);
|
|
||||||
|
|
||||||
// resetCollisionAccounting();
|
|
||||||
// m_taintsize = _size;
|
|
||||||
//}
|
|
||||||
|
|
||||||
public void changefloatonwater(float timestep)
|
public void changefloatonwater(float timestep)
|
||||||
{
|
{
|
||||||
|
@ -2380,23 +2227,23 @@ namespace OpenSim.Region.Physics.OdePlugin
|
||||||
|
|
||||||
public override int VehicleType
|
public override int VehicleType
|
||||||
{
|
{
|
||||||
get { return 0; }
|
get { return (int)m_vehicle.Type; }
|
||||||
set { return; }
|
set { m_vehicle.ProcessTypeChange((Vehicle)value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void VehicleFloatParam(int param, float value)
|
public override void VehicleFloatParam(int param, float value)
|
||||||
{
|
{
|
||||||
|
m_vehicle.ProcessFloatVehicleParam((Vehicle) param, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void VehicleVectorParam(int param, PhysicsVector value)
|
public override void VehicleVectorParam(int param, PhysicsVector value)
|
||||||
{
|
{
|
||||||
|
m_vehicle.ProcessVectorVehicleParam((Vehicle) param, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void VehicleRotationParam(int param, Quaternion rotation)
|
public override void VehicleRotationParam(int param, Quaternion rotation)
|
||||||
{
|
{
|
||||||
|
m_vehicle.ProcessRotationVehicleParam((Vehicle) param, rotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetVolumeDetect(int param)
|
public override void SetVolumeDetect(int param)
|
||||||
|
|
|
@ -0,0 +1,392 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Contributors, http://opensimulator.org/
|
||||||
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the OpenSimulator Project nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using log4net;
|
||||||
|
using OpenMetaverse;
|
||||||
|
using Ode.NET;
|
||||||
|
using OpenSim.Framework;
|
||||||
|
using OpenSim.Region.Physics.Manager;
|
||||||
|
|
||||||
|
namespace OpenSim.Region.Physics.OdePlugin
|
||||||
|
{
|
||||||
|
public class ODEVehicleSettings
|
||||||
|
{
|
||||||
|
public Vehicle Type
|
||||||
|
{
|
||||||
|
get { return m_type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vehicle m_type = Vehicle.TYPE_NONE;
|
||||||
|
private OdeScene m_parentScene = null;
|
||||||
|
private IntPtr m_body = IntPtr.Zero;
|
||||||
|
private IntPtr m_jointGroup = IntPtr.Zero;
|
||||||
|
private IntPtr m_aMotor = IntPtr.Zero;
|
||||||
|
private IntPtr m_lMotor = IntPtr.Zero;
|
||||||
|
|
||||||
|
// Vehicle properties
|
||||||
|
private Quaternion m_referenceFrame = Quaternion.Identity;
|
||||||
|
private Vector3 m_angularFrictionTimescale = Vector3.Zero;
|
||||||
|
private Vector3 m_angularMotorDirection = Vector3.Zero;
|
||||||
|
private Vector3 m_linearFrictionTimescale = Vector3.Zero;
|
||||||
|
private Vector3 m_linearMotorDirection = Vector3.Zero;
|
||||||
|
private Vector3 m_linearMotorOffset = Vector3.Zero;
|
||||||
|
private float m_angularDeflectionEfficiency = 0;
|
||||||
|
private float m_angularDeflectionTimescale = 0;
|
||||||
|
private float m_angularMotorDecayTimescale = 0;
|
||||||
|
private float m_angularMotorTimescale = 0;
|
||||||
|
private float m_bankingEfficiency = 0;
|
||||||
|
private float m_bankingMix = 0;
|
||||||
|
private float m_bankingTimescale = 0;
|
||||||
|
private float m_buoyancy = 0;
|
||||||
|
private float m_hoverHeight = 0;
|
||||||
|
private float m_hoverEfficiency = 0;
|
||||||
|
private float m_hoverTimescale = 0;
|
||||||
|
private float m_linearDeflectionEfficiency = 0;
|
||||||
|
private float m_linearDeflectionTimescale = 0;
|
||||||
|
private float m_linearMotorDecayTimescale = 0;
|
||||||
|
private float m_linearMotorTimescale = 0;
|
||||||
|
private float m_verticalAttractionEfficiency = 0;
|
||||||
|
private float m_verticalAttractionTimescale = 0;
|
||||||
|
private VehicleFlag m_flags = (VehicleFlag) 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
internal void ProcessFloatVehicleParam(Vehicle pParam, float pValue)
|
||||||
|
{
|
||||||
|
switch (pParam)
|
||||||
|
{
|
||||||
|
case Vehicle.ANGULAR_DEFLECTION_EFFICIENCY:
|
||||||
|
m_angularDeflectionEfficiency = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.ANGULAR_DEFLECTION_TIMESCALE:
|
||||||
|
m_angularDeflectionTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.ANGULAR_MOTOR_DECAY_TIMESCALE:
|
||||||
|
m_angularMotorDecayTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.ANGULAR_MOTOR_TIMESCALE:
|
||||||
|
m_angularMotorTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.BANKING_EFFICIENCY:
|
||||||
|
m_bankingEfficiency = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.BANKING_MIX:
|
||||||
|
m_bankingMix = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.BANKING_TIMESCALE:
|
||||||
|
m_bankingTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.BUOYANCY:
|
||||||
|
m_buoyancy = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.HOVER_EFFICIENCY:
|
||||||
|
m_hoverEfficiency = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.HOVER_HEIGHT:
|
||||||
|
m_hoverHeight = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.HOVER_TIMESCALE:
|
||||||
|
m_hoverTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_DEFLECTION_EFFICIENCY:
|
||||||
|
m_linearDeflectionEfficiency = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_DEFLECTION_TIMESCALE:
|
||||||
|
m_linearDeflectionTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_MOTOR_DECAY_TIMESCALE:
|
||||||
|
m_linearMotorDecayTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_MOTOR_TIMESCALE:
|
||||||
|
m_linearMotorTimescale = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.VERTICAL_ATTRACTION_EFFICIENCY:
|
||||||
|
m_verticalAttractionEfficiency = pValue;
|
||||||
|
break;
|
||||||
|
case Vehicle.VERTICAL_ATTRACTION_TIMESCALE:
|
||||||
|
m_verticalAttractionTimescale = pValue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// These are vector properties but the engine lets you use a single float value to
|
||||||
|
// set all of the components to the same value
|
||||||
|
case Vehicle.ANGULAR_FRICTION_TIMESCALE:
|
||||||
|
m_angularFrictionTimescale = new Vector3(pValue, pValue, pValue);
|
||||||
|
break;
|
||||||
|
case Vehicle.ANGULAR_MOTOR_DIRECTION:
|
||||||
|
m_angularMotorDirection = new Vector3(pValue, pValue, pValue);
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_FRICTION_TIMESCALE:
|
||||||
|
m_linearFrictionTimescale = new Vector3(pValue, pValue, pValue);
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_MOTOR_DIRECTION:
|
||||||
|
m_linearMotorDirection = new Vector3(pValue, pValue, pValue);
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_MOTOR_OFFSET:
|
||||||
|
m_linearMotorOffset = new Vector3(pValue, pValue, pValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessVectorVehicleParam(Vehicle pParam, PhysicsVector pValue)
|
||||||
|
{
|
||||||
|
switch (pParam)
|
||||||
|
{
|
||||||
|
case Vehicle.ANGULAR_FRICTION_TIMESCALE:
|
||||||
|
m_angularFrictionTimescale = new Vector3(pValue.X, pValue.Y, pValue.Z);
|
||||||
|
break;
|
||||||
|
case Vehicle.ANGULAR_MOTOR_DIRECTION:
|
||||||
|
m_angularMotorDirection = new Vector3(pValue.X, pValue.Y, pValue.Z);
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_FRICTION_TIMESCALE:
|
||||||
|
m_linearFrictionTimescale = new Vector3(pValue.X, pValue.Y, pValue.Z);
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_MOTOR_DIRECTION:
|
||||||
|
m_linearMotorDirection = new Vector3(pValue.X, pValue.Y, pValue.Z);
|
||||||
|
break;
|
||||||
|
case Vehicle.LINEAR_MOTOR_OFFSET:
|
||||||
|
m_linearMotorOffset = new Vector3(pValue.X, pValue.Y, pValue.Z);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessRotationVehicleParam(Vehicle pParam, Quaternion pValue)
|
||||||
|
{
|
||||||
|
switch (pParam)
|
||||||
|
{
|
||||||
|
case Vehicle.REFERENCE_FRAME:
|
||||||
|
m_referenceFrame = pValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ProcessTypeChange(Vehicle pType)
|
||||||
|
{
|
||||||
|
if (m_type == Vehicle.TYPE_NONE && pType != Vehicle.TYPE_NONE)
|
||||||
|
{
|
||||||
|
// Activate whatever it is
|
||||||
|
SetDefaultsForType(pType);
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
else if (m_type != Vehicle.TYPE_NONE && pType != Vehicle.TYPE_NONE )
|
||||||
|
{
|
||||||
|
// Set properties
|
||||||
|
SetDefaultsForType(pType);
|
||||||
|
// then reset
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
else if (m_type != Vehicle.TYPE_NONE && pType == Vehicle.TYPE_NONE)
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Disable()
|
||||||
|
{
|
||||||
|
if (m_body == IntPtr.Zero || m_type == Vehicle.TYPE_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Enable(IntPtr pBody, OdeScene pParentScene)
|
||||||
|
{
|
||||||
|
if (pBody == IntPtr.Zero || m_type == Vehicle.TYPE_NONE)
|
||||||
|
return;
|
||||||
|
m_body = pBody;
|
||||||
|
m_parentScene = pParentScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Reset()
|
||||||
|
{
|
||||||
|
if (m_body == IntPtr.Zero || m_type == Vehicle.TYPE_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Destroy()
|
||||||
|
{
|
||||||
|
if (m_body == IntPtr.Zero || m_type == Vehicle.TYPE_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Step(float pTimestep)
|
||||||
|
{
|
||||||
|
if (m_body == IntPtr.Zero || m_type == Vehicle.TYPE_NONE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetDefaultsForType(Vehicle pType)
|
||||||
|
{
|
||||||
|
switch (pType)
|
||||||
|
{
|
||||||
|
case Vehicle.TYPE_SLED:
|
||||||
|
m_linearFrictionTimescale = new Vector3(30, 1, 1000);
|
||||||
|
m_angularFrictionTimescale = new Vector3(1000, 1000, 1000);
|
||||||
|
m_linearMotorDirection = Vector3.Zero;
|
||||||
|
m_linearMotorTimescale = 1000;
|
||||||
|
m_linearMotorDecayTimescale = 120;
|
||||||
|
m_angularMotorDirection = Vector3.Zero;
|
||||||
|
m_angularMotorTimescale = 1000;
|
||||||
|
m_angularMotorDecayTimescale = 120;
|
||||||
|
m_hoverHeight = 0;
|
||||||
|
m_hoverEfficiency = 10;
|
||||||
|
m_hoverTimescale = 10;
|
||||||
|
m_buoyancy = 0;
|
||||||
|
m_linearDeflectionEfficiency = 1;
|
||||||
|
m_linearDeflectionTimescale = 1;
|
||||||
|
m_angularDeflectionEfficiency = 1;
|
||||||
|
m_angularDeflectionTimescale = 1000;
|
||||||
|
m_bankingEfficiency = 0;
|
||||||
|
m_bankingMix = 1;
|
||||||
|
m_bankingTimescale = 10;
|
||||||
|
m_referenceFrame = Quaternion.Identity;
|
||||||
|
m_flags &=
|
||||||
|
~(VehicleFlag.HOVER_WATER_ONLY | VehicleFlag.HOVER_TERRAIN_ONLY |
|
||||||
|
VehicleFlag.HOVER_GLOBAL_HEIGHT | VehicleFlag.HOVER_UP_ONLY);
|
||||||
|
m_flags |= (VehicleFlag.NO_DEFLECTION_UP | VehicleFlag.LIMIT_ROLL_ONLY | VehicleFlag.LIMIT_MOTOR_UP);
|
||||||
|
break;
|
||||||
|
case Vehicle.TYPE_CAR:
|
||||||
|
m_linearFrictionTimescale = new Vector3(100, 2, 1000);
|
||||||
|
m_angularFrictionTimescale = new Vector3(1000, 1000, 1000);
|
||||||
|
m_linearMotorDirection = Vector3.Zero;
|
||||||
|
m_linearMotorTimescale = 1;
|
||||||
|
m_linearMotorDecayTimescale = 60;
|
||||||
|
m_angularMotorDirection = Vector3.Zero;
|
||||||
|
m_angularMotorTimescale = 1;
|
||||||
|
m_angularMotorDecayTimescale = 0.8f;
|
||||||
|
m_hoverHeight = 0;
|
||||||
|
m_hoverEfficiency = 0;
|
||||||
|
m_hoverTimescale = 1000;
|
||||||
|
m_buoyancy = 0;
|
||||||
|
m_linearDeflectionEfficiency = 1;
|
||||||
|
m_linearDeflectionTimescale = 2;
|
||||||
|
m_angularDeflectionEfficiency = 0;
|
||||||
|
m_angularDeflectionTimescale = 10;
|
||||||
|
m_verticalAttractionEfficiency = 1;
|
||||||
|
m_verticalAttractionTimescale = 10;
|
||||||
|
m_bankingEfficiency = -0.2f;
|
||||||
|
m_bankingMix = 1;
|
||||||
|
m_bankingTimescale = 1;
|
||||||
|
m_referenceFrame = Quaternion.Identity;
|
||||||
|
m_flags &= ~(VehicleFlag.HOVER_WATER_ONLY | VehicleFlag.HOVER_TERRAIN_ONLY | VehicleFlag.HOVER_GLOBAL_HEIGHT);
|
||||||
|
m_flags |= (VehicleFlag.NO_DEFLECTION_UP | VehicleFlag.LIMIT_ROLL_ONLY | VehicleFlag.HOVER_UP_ONLY |
|
||||||
|
VehicleFlag.LIMIT_MOTOR_UP);
|
||||||
|
break;
|
||||||
|
case Vehicle.TYPE_BOAT:
|
||||||
|
m_linearFrictionTimescale = new Vector3(10, 3, 2);
|
||||||
|
m_angularFrictionTimescale = new Vector3(10,10,10);
|
||||||
|
m_linearMotorDirection = Vector3.Zero;
|
||||||
|
m_linearMotorTimescale = 5;
|
||||||
|
m_linearMotorDecayTimescale = 60;
|
||||||
|
m_angularMotorDirection = Vector3.Zero;
|
||||||
|
m_angularMotorTimescale = 4;
|
||||||
|
m_angularMotorDecayTimescale = 4;
|
||||||
|
m_hoverHeight = 0;
|
||||||
|
m_hoverEfficiency = 0.5f;
|
||||||
|
m_hoverTimescale = 2;
|
||||||
|
m_buoyancy = 1;
|
||||||
|
m_linearDeflectionEfficiency = 0.5f;
|
||||||
|
m_linearDeflectionTimescale = 3;
|
||||||
|
m_angularDeflectionEfficiency = 0.5f;
|
||||||
|
m_angularDeflectionTimescale = 5;
|
||||||
|
m_verticalAttractionEfficiency = 0.5f;
|
||||||
|
m_verticalAttractionTimescale = 5;
|
||||||
|
m_bankingEfficiency = -0.3f;
|
||||||
|
m_bankingMix = 0.8f;
|
||||||
|
m_bankingTimescale = 1;
|
||||||
|
m_referenceFrame = Quaternion.Identity;
|
||||||
|
m_flags &= ~( VehicleFlag.HOVER_TERRAIN_ONLY | VehicleFlag.LIMIT_ROLL_ONLY | VehicleFlag.HOVER_GLOBAL_HEIGHT);
|
||||||
|
m_flags |= (VehicleFlag.NO_DEFLECTION_UP | VehicleFlag.HOVER_WATER_ONLY | VehicleFlag.HOVER_UP_ONLY |
|
||||||
|
VehicleFlag.LIMIT_MOTOR_UP);
|
||||||
|
break;
|
||||||
|
case Vehicle.TYPE_AIRPLANE:
|
||||||
|
m_linearFrictionTimescale = new Vector3(200, 10, 5);
|
||||||
|
m_angularFrictionTimescale = new Vector3(20, 20, 20);
|
||||||
|
m_linearMotorDirection = Vector3.Zero;
|
||||||
|
m_linearMotorTimescale = 2;
|
||||||
|
m_linearMotorDecayTimescale = 60;
|
||||||
|
m_angularMotorDirection = Vector3.Zero;
|
||||||
|
m_angularMotorTimescale = 4;
|
||||||
|
m_angularMotorDecayTimescale = 4;
|
||||||
|
m_hoverHeight = 0;
|
||||||
|
m_hoverEfficiency = 0.5f;
|
||||||
|
m_hoverTimescale = 1000;
|
||||||
|
m_buoyancy = 0;
|
||||||
|
m_linearDeflectionEfficiency = 0.5f;
|
||||||
|
m_linearDeflectionTimescale = 3;
|
||||||
|
m_angularDeflectionEfficiency = 1;
|
||||||
|
m_angularDeflectionTimescale = 2;
|
||||||
|
m_verticalAttractionEfficiency = 0.9f;
|
||||||
|
m_verticalAttractionTimescale = 2;
|
||||||
|
m_bankingEfficiency = 1;
|
||||||
|
m_bankingMix = 0.7f;
|
||||||
|
m_bankingTimescale = 2;
|
||||||
|
m_referenceFrame = Quaternion.Identity;
|
||||||
|
m_flags &= ~(VehicleFlag.NO_DEFLECTION_UP | VehicleFlag.HOVER_WATER_ONLY | VehicleFlag.HOVER_TERRAIN_ONLY |
|
||||||
|
VehicleFlag.HOVER_GLOBAL_HEIGHT | VehicleFlag.HOVER_UP_ONLY | VehicleFlag.LIMIT_MOTOR_UP);
|
||||||
|
m_flags |= (VehicleFlag.LIMIT_ROLL_ONLY);
|
||||||
|
break;
|
||||||
|
case Vehicle.TYPE_BALLOON:
|
||||||
|
m_linearFrictionTimescale = new Vector3(5, 5, 5);
|
||||||
|
m_angularFrictionTimescale = new Vector3(10, 10, 10);
|
||||||
|
m_linearMotorDirection = Vector3.Zero;
|
||||||
|
m_linearMotorTimescale = 5;
|
||||||
|
m_linearMotorDecayTimescale = 60;
|
||||||
|
m_angularMotorDirection = Vector3.Zero;
|
||||||
|
m_angularMotorTimescale = 6;
|
||||||
|
m_angularMotorDecayTimescale = 10;
|
||||||
|
m_hoverHeight = 5;
|
||||||
|
m_hoverEfficiency = 0.8f;
|
||||||
|
m_hoverTimescale = 10;
|
||||||
|
m_buoyancy = 1;
|
||||||
|
m_linearDeflectionEfficiency = 0;
|
||||||
|
m_linearDeflectionTimescale = 5;
|
||||||
|
m_angularDeflectionEfficiency = 0;
|
||||||
|
m_angularDeflectionTimescale = 5;
|
||||||
|
m_verticalAttractionEfficiency = 1;
|
||||||
|
m_verticalAttractionTimescale = 1000;
|
||||||
|
m_bankingEfficiency = 0;
|
||||||
|
m_bankingMix = 0.7f;
|
||||||
|
m_bankingTimescale = 5;
|
||||||
|
m_referenceFrame = Quaternion.Identity;
|
||||||
|
m_flags = (VehicleFlag)0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue