| 1 | /**************************************************************************/ | 
| 2 | /*  godot_hinge_joint_3d.cpp                                              */ | 
| 3 | /**************************************************************************/ | 
| 4 | /*                         This file is part of:                          */ | 
| 5 | /*                             GODOT ENGINE                               */ | 
| 6 | /*                        https://godotengine.org                         */ | 
| 7 | /**************************************************************************/ | 
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | 
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */ | 
| 10 | /*                                                                        */ | 
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining  */ | 
| 12 | /* a copy of this software and associated documentation files (the        */ | 
| 13 | /* "Software"), to deal in the Software without restriction, including    */ | 
| 14 | /* without limitation the rights to use, copy, modify, merge, publish,    */ | 
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to     */ | 
| 16 | /* permit persons to whom the Software is furnished to do so, subject to  */ | 
| 17 | /* the following conditions:                                              */ | 
| 18 | /*                                                                        */ | 
| 19 | /* The above copyright notice and this permission notice shall be         */ | 
| 20 | /* included in all copies or substantial portions of the Software.        */ | 
| 21 | /*                                                                        */ | 
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */ | 
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */ | 
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | 
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */ | 
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */ | 
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */ | 
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */ | 
| 29 | /**************************************************************************/ | 
| 30 |  | 
| 31 | /* | 
| 32 | Adapted to Godot from the Bullet library. | 
| 33 | */ | 
| 34 |  | 
| 35 | /* | 
| 36 | Bullet Continuous Collision Detection and Physics Library | 
| 37 | Copyright (c) 2003-2006 Erwin Coumans  http://continuousphysics.com/Bullet/ | 
| 38 |  | 
| 39 | This software is provided 'as-is', without any express or implied warranty. | 
| 40 | In no event will the authors be held liable for any damages arising from the use of this software. | 
| 41 | Permission is granted to anyone to use this software for any purpose, | 
| 42 | including commercial applications, and to alter it and redistribute it freely, | 
| 43 | subject to the following restrictions: | 
| 44 |  | 
| 45 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. | 
| 46 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. | 
| 47 | 3. This notice may not be removed or altered from any source distribution. | 
| 48 | */ | 
| 49 |  | 
| 50 | #include "godot_hinge_joint_3d.h" | 
| 51 |  | 
| 52 | GodotHingeJoint3D::GodotHingeJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameA, const Transform3D &frameB) : | 
| 53 | 		GodotJoint3D(_arr, 2) { | 
| 54 | 	A = rbA; | 
| 55 | 	B = rbB; | 
| 56 |  | 
| 57 | 	m_rbAFrame = frameA; | 
| 58 | 	m_rbBFrame = frameB; | 
| 59 | 	// flip axis | 
| 60 | 	m_rbBFrame.basis[0][2] *= real_t(-1.); | 
| 61 | 	m_rbBFrame.basis[1][2] *= real_t(-1.); | 
| 62 | 	m_rbBFrame.basis[2][2] *= real_t(-1.); | 
| 63 |  | 
| 64 | 	A->add_constraint(this, 0); | 
| 65 | 	B->add_constraint(this, 1); | 
| 66 | } | 
| 67 |  | 
| 68 | GodotHingeJoint3D::GodotHingeJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Vector3 &pivotInA, const Vector3 &pivotInB, | 
| 69 | 		const Vector3 &axisInA, const Vector3 &axisInB) : | 
| 70 | 		GodotJoint3D(_arr, 2) { | 
| 71 | 	A = rbA; | 
| 72 | 	B = rbB; | 
| 73 |  | 
| 74 | 	m_rbAFrame.origin = pivotInA; | 
| 75 |  | 
| 76 | 	// since no frame is given, assume this to be zero angle and just pick rb transform axis | 
| 77 | 	Vector3 rbAxisA1 = rbA->get_transform().basis.get_column(0); | 
| 78 |  | 
| 79 | 	Vector3 rbAxisA2; | 
| 80 | 	real_t projection = axisInA.dot(rbAxisA1); | 
| 81 | 	if (projection >= 1.0f - CMP_EPSILON) { | 
| 82 | 		rbAxisA1 = -rbA->get_transform().basis.get_column(2); | 
| 83 | 		rbAxisA2 = rbA->get_transform().basis.get_column(1); | 
| 84 | 	} else if (projection <= -1.0f + CMP_EPSILON) { | 
| 85 | 		rbAxisA1 = rbA->get_transform().basis.get_column(2); | 
| 86 | 		rbAxisA2 = rbA->get_transform().basis.get_column(1); | 
| 87 | 	} else { | 
| 88 | 		rbAxisA2 = axisInA.cross(rbAxisA1); | 
| 89 | 		rbAxisA1 = rbAxisA2.cross(axisInA); | 
| 90 | 	} | 
| 91 |  | 
| 92 | 	m_rbAFrame.basis = Basis(rbAxisA1.x, rbAxisA2.x, axisInA.x, | 
| 93 | 			rbAxisA1.y, rbAxisA2.y, axisInA.y, | 
| 94 | 			rbAxisA1.z, rbAxisA2.z, axisInA.z); | 
| 95 |  | 
| 96 | 	Quaternion rotationArc = Quaternion(axisInA, axisInB); | 
| 97 | 	Vector3 rbAxisB1 = rotationArc.xform(rbAxisA1); | 
| 98 | 	Vector3 rbAxisB2 = axisInB.cross(rbAxisB1); | 
| 99 |  | 
| 100 | 	m_rbBFrame.origin = pivotInB; | 
| 101 | 	m_rbBFrame.basis = Basis(rbAxisB1.x, rbAxisB2.x, -axisInB.x, | 
| 102 | 			rbAxisB1.y, rbAxisB2.y, -axisInB.y, | 
| 103 | 			rbAxisB1.z, rbAxisB2.z, -axisInB.z); | 
| 104 |  | 
| 105 | 	A->add_constraint(this, 0); | 
| 106 | 	B->add_constraint(this, 1); | 
| 107 | } | 
| 108 |  | 
| 109 | bool GodotHingeJoint3D::setup(real_t p_step) { | 
| 110 | 	dynamic_A = (A->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); | 
| 111 | 	dynamic_B = (B->get_mode() > PhysicsServer3D::BODY_MODE_KINEMATIC); | 
| 112 |  | 
| 113 | 	if (!dynamic_A && !dynamic_B) { | 
| 114 | 		return false; | 
| 115 | 	} | 
| 116 |  | 
| 117 | 	m_appliedImpulse = real_t(0.); | 
| 118 |  | 
| 119 | 	if (!m_angularOnly) { | 
| 120 | 		Vector3 pivotAInW = A->get_transform().xform(m_rbAFrame.origin); | 
| 121 | 		Vector3 pivotBInW = B->get_transform().xform(m_rbBFrame.origin); | 
| 122 | 		Vector3 relPos = pivotBInW - pivotAInW; | 
| 123 |  | 
| 124 | 		Vector3 normal[3]; | 
| 125 | 		if (Math::is_zero_approx(relPos.length_squared())) { | 
| 126 | 			normal[0] = Vector3(real_t(1.0), 0, 0); | 
| 127 | 		} else { | 
| 128 | 			normal[0] = relPos.normalized(); | 
| 129 | 		} | 
| 130 |  | 
| 131 | 		plane_space(normal[0], normal[1], normal[2]); | 
| 132 |  | 
| 133 | 		for (int i = 0; i < 3; i++) { | 
| 134 | 			memnew_placement( | 
| 135 | 					&m_jac[i], | 
| 136 | 					GodotJacobianEntry3D( | 
| 137 | 							A->get_principal_inertia_axes().transposed(), | 
| 138 | 							B->get_principal_inertia_axes().transposed(), | 
| 139 | 							pivotAInW - A->get_transform().origin - A->get_center_of_mass(), | 
| 140 | 							pivotBInW - B->get_transform().origin - B->get_center_of_mass(), | 
| 141 | 							normal[i], | 
| 142 | 							A->get_inv_inertia(), | 
| 143 | 							A->get_inv_mass(), | 
| 144 | 							B->get_inv_inertia(), | 
| 145 | 							B->get_inv_mass())); | 
| 146 | 		} | 
| 147 | 	} | 
| 148 |  | 
| 149 | 	//calculate two perpendicular jointAxis, orthogonal to hingeAxis | 
| 150 | 	//these two jointAxis require equal angular velocities for both bodies | 
| 151 |  | 
| 152 | 	//this is unused for now, it's a todo | 
| 153 | 	Vector3 jointAxis0local; | 
| 154 | 	Vector3 jointAxis1local; | 
| 155 |  | 
| 156 | 	plane_space(m_rbAFrame.basis.get_column(2), jointAxis0local, jointAxis1local); | 
| 157 |  | 
| 158 | 	Vector3 jointAxis0 = A->get_transform().basis.xform(jointAxis0local); | 
| 159 | 	Vector3 jointAxis1 = A->get_transform().basis.xform(jointAxis1local); | 
| 160 | 	Vector3 hingeAxisWorld = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(2)); | 
| 161 |  | 
| 162 | 	memnew_placement( | 
| 163 | 			&m_jacAng[0], | 
| 164 | 			GodotJacobianEntry3D( | 
| 165 | 					jointAxis0, | 
| 166 | 					A->get_principal_inertia_axes().transposed(), | 
| 167 | 					B->get_principal_inertia_axes().transposed(), | 
| 168 | 					A->get_inv_inertia(), | 
| 169 | 					B->get_inv_inertia())); | 
| 170 |  | 
| 171 | 	memnew_placement( | 
| 172 | 			&m_jacAng[1], | 
| 173 | 			GodotJacobianEntry3D( | 
| 174 | 					jointAxis1, | 
| 175 | 					A->get_principal_inertia_axes().transposed(), | 
| 176 | 					B->get_principal_inertia_axes().transposed(), | 
| 177 | 					A->get_inv_inertia(), | 
| 178 | 					B->get_inv_inertia())); | 
| 179 |  | 
| 180 | 	memnew_placement( | 
| 181 | 			&m_jacAng[2], | 
| 182 | 			GodotJacobianEntry3D( | 
| 183 | 					hingeAxisWorld, | 
| 184 | 					A->get_principal_inertia_axes().transposed(), | 
| 185 | 					B->get_principal_inertia_axes().transposed(), | 
| 186 | 					A->get_inv_inertia(), | 
| 187 | 					B->get_inv_inertia())); | 
| 188 |  | 
| 189 | 	// Compute limit information | 
| 190 | 	real_t hingeAngle = get_hinge_angle(); | 
| 191 |  | 
| 192 | 	//set bias, sign, clear accumulator | 
| 193 | 	m_correction = real_t(0.); | 
| 194 | 	m_limitSign = real_t(0.); | 
| 195 | 	m_solveLimit = false; | 
| 196 | 	m_accLimitImpulse = real_t(0.); | 
| 197 |  | 
| 198 | 	if (m_useLimit && m_lowerLimit <= m_upperLimit) { | 
| 199 | 		if (hingeAngle <= m_lowerLimit) { | 
| 200 | 			m_correction = (m_lowerLimit - hingeAngle); | 
| 201 | 			m_limitSign = 1.0f; | 
| 202 | 			m_solveLimit = true; | 
| 203 | 		} else if (hingeAngle >= m_upperLimit) { | 
| 204 | 			m_correction = m_upperLimit - hingeAngle; | 
| 205 | 			m_limitSign = -1.0f; | 
| 206 | 			m_solveLimit = true; | 
| 207 | 		} | 
| 208 | 	} | 
| 209 |  | 
| 210 | 	//Compute K = J*W*J' for hinge axis | 
| 211 | 	Vector3 axisA = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(2)); | 
| 212 | 	m_kHinge = 1.0f / (A->compute_angular_impulse_denominator(axisA) + B->compute_angular_impulse_denominator(axisA)); | 
| 213 |  | 
| 214 | 	return true; | 
| 215 | } | 
| 216 |  | 
| 217 | void GodotHingeJoint3D::solve(real_t p_step) { | 
| 218 | 	Vector3 pivotAInW = A->get_transform().xform(m_rbAFrame.origin); | 
| 219 | 	Vector3 pivotBInW = B->get_transform().xform(m_rbBFrame.origin); | 
| 220 |  | 
| 221 | 	//real_t tau = real_t(0.3); | 
| 222 |  | 
| 223 | 	//linear part | 
| 224 | 	if (!m_angularOnly) { | 
| 225 | 		Vector3 rel_pos1 = pivotAInW - A->get_transform().origin; | 
| 226 | 		Vector3 rel_pos2 = pivotBInW - B->get_transform().origin; | 
| 227 |  | 
| 228 | 		Vector3 vel1 = A->get_velocity_in_local_point(rel_pos1); | 
| 229 | 		Vector3 vel2 = B->get_velocity_in_local_point(rel_pos2); | 
| 230 | 		Vector3 vel = vel1 - vel2; | 
| 231 |  | 
| 232 | 		for (int i = 0; i < 3; i++) { | 
| 233 | 			const Vector3 &normal = m_jac[i].m_linearJointAxis; | 
| 234 | 			real_t jacDiagABInv = real_t(1.) / m_jac[i].getDiagonal(); | 
| 235 |  | 
| 236 | 			real_t rel_vel; | 
| 237 | 			rel_vel = normal.dot(vel); | 
| 238 | 			//positional error (zeroth order error) | 
| 239 | 			real_t depth = -(pivotAInW - pivotBInW).dot(normal); //this is the error projected on the normal | 
| 240 | 			real_t impulse = depth * tau / p_step * jacDiagABInv - rel_vel * jacDiagABInv; | 
| 241 | 			m_appliedImpulse += impulse; | 
| 242 | 			Vector3 impulse_vector = normal * impulse; | 
| 243 | 			if (dynamic_A) { | 
| 244 | 				A->apply_impulse(impulse_vector, pivotAInW - A->get_transform().origin); | 
| 245 | 			} | 
| 246 | 			if (dynamic_B) { | 
| 247 | 				B->apply_impulse(-impulse_vector, pivotBInW - B->get_transform().origin); | 
| 248 | 			} | 
| 249 | 		} | 
| 250 | 	} | 
| 251 |  | 
| 252 | 	{ | 
| 253 | 		///solve angular part | 
| 254 |  | 
| 255 | 		// get axes in world space | 
| 256 | 		Vector3 axisA = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(2)); | 
| 257 | 		Vector3 axisB = B->get_transform().basis.xform(m_rbBFrame.basis.get_column(2)); | 
| 258 |  | 
| 259 | 		const Vector3 &angVelA = A->get_angular_velocity(); | 
| 260 | 		const Vector3 &angVelB = B->get_angular_velocity(); | 
| 261 |  | 
| 262 | 		Vector3 angVelAroundHingeAxisA = axisA * axisA.dot(angVelA); | 
| 263 | 		Vector3 angVelAroundHingeAxisB = axisB * axisB.dot(angVelB); | 
| 264 |  | 
| 265 | 		Vector3 angAorthog = angVelA - angVelAroundHingeAxisA; | 
| 266 | 		Vector3 angBorthog = angVelB - angVelAroundHingeAxisB; | 
| 267 | 		Vector3 velrelOrthog = angAorthog - angBorthog; | 
| 268 | 		{ | 
| 269 | 			//solve orthogonal angular velocity correction | 
| 270 | 			real_t relaxation = real_t(1.); | 
| 271 | 			real_t len = velrelOrthog.length(); | 
| 272 | 			if (len > real_t(0.00001)) { | 
| 273 | 				Vector3 normal = velrelOrthog.normalized(); | 
| 274 | 				real_t denom = A->compute_angular_impulse_denominator(normal) + | 
| 275 | 						B->compute_angular_impulse_denominator(normal); | 
| 276 | 				// scale for mass and relaxation | 
| 277 | 				velrelOrthog *= (real_t(1.) / denom) * m_relaxationFactor; | 
| 278 | 			} | 
| 279 |  | 
| 280 | 			//solve angular positional correction | 
| 281 | 			Vector3 angularError = -axisA.cross(axisB) * (real_t(1.) / p_step); | 
| 282 | 			real_t len2 = angularError.length(); | 
| 283 | 			if (len2 > real_t(0.00001)) { | 
| 284 | 				Vector3 normal2 = angularError.normalized(); | 
| 285 | 				real_t denom2 = A->compute_angular_impulse_denominator(normal2) + | 
| 286 | 						B->compute_angular_impulse_denominator(normal2); | 
| 287 | 				angularError *= (real_t(1.) / denom2) * relaxation; | 
| 288 | 			} | 
| 289 |  | 
| 290 | 			if (dynamic_A) { | 
| 291 | 				A->apply_torque_impulse(-velrelOrthog + angularError); | 
| 292 | 			} | 
| 293 | 			if (dynamic_B) { | 
| 294 | 				B->apply_torque_impulse(velrelOrthog - angularError); | 
| 295 | 			} | 
| 296 |  | 
| 297 | 			// solve limit | 
| 298 | 			if (m_solveLimit) { | 
| 299 | 				real_t amplitude = ((angVelB - angVelA).dot(axisA) * m_relaxationFactor + m_correction * (real_t(1.) / p_step) * m_biasFactor) * m_limitSign; | 
| 300 |  | 
| 301 | 				real_t impulseMag = amplitude * m_kHinge; | 
| 302 |  | 
| 303 | 				// Clamp the accumulated impulse | 
| 304 | 				real_t temp = m_accLimitImpulse; | 
| 305 | 				m_accLimitImpulse = MAX(m_accLimitImpulse + impulseMag, real_t(0)); | 
| 306 | 				impulseMag = m_accLimitImpulse - temp; | 
| 307 |  | 
| 308 | 				Vector3 impulse = axisA * impulseMag * m_limitSign; | 
| 309 | 				if (dynamic_A) { | 
| 310 | 					A->apply_torque_impulse(impulse); | 
| 311 | 				} | 
| 312 | 				if (dynamic_B) { | 
| 313 | 					B->apply_torque_impulse(-impulse); | 
| 314 | 				} | 
| 315 | 			} | 
| 316 | 		} | 
| 317 |  | 
| 318 | 		//apply motor | 
| 319 | 		if (m_enableAngularMotor) { | 
| 320 | 			//todo: add limits too | 
| 321 | 			Vector3 angularLimit(0, 0, 0); | 
| 322 |  | 
| 323 | 			Vector3 velrel = angVelAroundHingeAxisA - angVelAroundHingeAxisB; | 
| 324 | 			real_t projRelVel = velrel.dot(axisA); | 
| 325 |  | 
| 326 | 			real_t desiredMotorVel = m_motorTargetVelocity; | 
| 327 | 			real_t motor_relvel = desiredMotorVel - projRelVel; | 
| 328 |  | 
| 329 | 			real_t unclippedMotorImpulse = m_kHinge * motor_relvel; | 
| 330 | 			//todo: should clip against accumulated impulse | 
| 331 | 			real_t clippedMotorImpulse = unclippedMotorImpulse > m_maxMotorImpulse ? m_maxMotorImpulse : unclippedMotorImpulse; | 
| 332 | 			clippedMotorImpulse = clippedMotorImpulse < -m_maxMotorImpulse ? -m_maxMotorImpulse : clippedMotorImpulse; | 
| 333 | 			Vector3 motorImp = clippedMotorImpulse * axisA; | 
| 334 |  | 
| 335 | 			if (dynamic_A) { | 
| 336 | 				A->apply_torque_impulse(motorImp + angularLimit); | 
| 337 | 			} | 
| 338 | 			if (dynamic_B) { | 
| 339 | 				B->apply_torque_impulse(-motorImp - angularLimit); | 
| 340 | 			} | 
| 341 | 		} | 
| 342 | 	} | 
| 343 | } | 
| 344 |  | 
| 345 | /* | 
| 346 | void	HingeJointSW::updateRHS(real_t	timeStep) | 
| 347 | { | 
| 348 | 	(void)timeStep; | 
| 349 | } | 
| 350 |  | 
| 351 | */ | 
| 352 |  | 
| 353 | real_t GodotHingeJoint3D::get_hinge_angle() { | 
| 354 | 	const Vector3 refAxis0 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(0)); | 
| 355 | 	const Vector3 refAxis1 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(1)); | 
| 356 | 	const Vector3 swingAxis = B->get_transform().basis.xform(m_rbBFrame.basis.get_column(1)); | 
| 357 |  | 
| 358 | 	return atan2fast(swingAxis.dot(refAxis0), swingAxis.dot(refAxis1)); | 
| 359 | } | 
| 360 |  | 
| 361 | void GodotHingeJoint3D::set_param(PhysicsServer3D::HingeJointParam p_param, real_t p_value) { | 
| 362 | 	switch (p_param) { | 
| 363 | 		case PhysicsServer3D::HINGE_JOINT_BIAS: | 
| 364 | 			tau = p_value; | 
| 365 | 			break; | 
| 366 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_UPPER: | 
| 367 | 			m_upperLimit = p_value; | 
| 368 | 			break; | 
| 369 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_LOWER: | 
| 370 | 			m_lowerLimit = p_value; | 
| 371 | 			break; | 
| 372 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_BIAS: | 
| 373 | 			m_biasFactor = p_value; | 
| 374 | 			break; | 
| 375 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_SOFTNESS: | 
| 376 | 			m_limitSoftness = p_value; | 
| 377 | 			break; | 
| 378 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_RELAXATION: | 
| 379 | 			m_relaxationFactor = p_value; | 
| 380 | 			break; | 
| 381 | 		case PhysicsServer3D::HINGE_JOINT_MOTOR_TARGET_VELOCITY: | 
| 382 | 			m_motorTargetVelocity = p_value; | 
| 383 | 			break; | 
| 384 | 		case PhysicsServer3D::HINGE_JOINT_MOTOR_MAX_IMPULSE: | 
| 385 | 			m_maxMotorImpulse = p_value; | 
| 386 | 			break; | 
| 387 | 		case PhysicsServer3D::HINGE_JOINT_MAX: | 
| 388 | 			break; // Can't happen, but silences warning | 
| 389 | 	} | 
| 390 | } | 
| 391 |  | 
| 392 | real_t GodotHingeJoint3D::get_param(PhysicsServer3D::HingeJointParam p_param) const { | 
| 393 | 	switch (p_param) { | 
| 394 | 		case PhysicsServer3D::HINGE_JOINT_BIAS: | 
| 395 | 			return tau; | 
| 396 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_UPPER: | 
| 397 | 			return m_upperLimit; | 
| 398 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_LOWER: | 
| 399 | 			return m_lowerLimit; | 
| 400 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_BIAS: | 
| 401 | 			return m_biasFactor; | 
| 402 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_SOFTNESS: | 
| 403 | 			return m_limitSoftness; | 
| 404 | 		case PhysicsServer3D::HINGE_JOINT_LIMIT_RELAXATION: | 
| 405 | 			return m_relaxationFactor; | 
| 406 | 		case PhysicsServer3D::HINGE_JOINT_MOTOR_TARGET_VELOCITY: | 
| 407 | 			return m_motorTargetVelocity; | 
| 408 | 		case PhysicsServer3D::HINGE_JOINT_MOTOR_MAX_IMPULSE: | 
| 409 | 			return m_maxMotorImpulse; | 
| 410 | 		case PhysicsServer3D::HINGE_JOINT_MAX: | 
| 411 | 			break; // Can't happen, but silences warning | 
| 412 | 	} | 
| 413 |  | 
| 414 | 	return 0; | 
| 415 | } | 
| 416 |  | 
| 417 | void GodotHingeJoint3D::set_flag(PhysicsServer3D::HingeJointFlag p_flag, bool p_value) { | 
| 418 | 	switch (p_flag) { | 
| 419 | 		case PhysicsServer3D::HINGE_JOINT_FLAG_USE_LIMIT: | 
| 420 | 			m_useLimit = p_value; | 
| 421 | 			break; | 
| 422 | 		case PhysicsServer3D::HINGE_JOINT_FLAG_ENABLE_MOTOR: | 
| 423 | 			m_enableAngularMotor = p_value; | 
| 424 | 			break; | 
| 425 | 		case PhysicsServer3D::HINGE_JOINT_FLAG_MAX: | 
| 426 | 			break; // Can't happen, but silences warning | 
| 427 | 	} | 
| 428 | } | 
| 429 |  | 
| 430 | bool GodotHingeJoint3D::get_flag(PhysicsServer3D::HingeJointFlag p_flag) const { | 
| 431 | 	switch (p_flag) { | 
| 432 | 		case PhysicsServer3D::HINGE_JOINT_FLAG_USE_LIMIT: | 
| 433 | 			return m_useLimit; | 
| 434 | 		case PhysicsServer3D::HINGE_JOINT_FLAG_ENABLE_MOTOR: | 
| 435 | 			return m_enableAngularMotor; | 
| 436 | 		case PhysicsServer3D::HINGE_JOINT_FLAG_MAX: | 
| 437 | 			break; // Can't happen, but silences warning | 
| 438 | 	} | 
| 439 |  | 
| 440 | 	return false; | 
| 441 | } | 
| 442 |  |