UNPKG

@needle-tools/car-physics

Version:

Car physics for Needle Engine: Create physical cars with ease

704 lines (703 loc) 26.4 kB
import { getParam as W, serializable as a, ParticleSystem as Y, Behaviour as I, getBoundingBox as $, getTempVector as p, Mathf as v, getTempQuaternion as M, Gizmos as R, ParticleSystemBaseBehaviour as J, Rigidbody as V, BoxCollider as L, delayForFrames as X, FrameEvent as Z, EventList as Q, OrbitControls as ee, Camera as K, GameObject as te, findObjectOfType as ie, SmoothFollow as se, Application as re } from "@needle-tools/engine"; import { Object3D as O, Vector2 as q, Quaternion as D, Vector3 as B, Euler as z } from "three"; var j = /* @__PURE__ */ ((n) => (n[n.all = 0] = "all", n[n.rear = 1] = "rear", n[n.front = 2] = "front", n))(j || {}), w = /* @__PURE__ */ ((n) => (n[n.front = 0] = "front", n[n.rear = 1] = "rear", n))(w || {}), ne = Object.defineProperty, x = (n, e, i, r) => { for (var t = void 0, s = n.length - 1, o; s >= 0; s--) (o = n[s]) && (t = o(e, i, t) || t); return t && ne(e, i, t), t; }; const T = W("debugwheels"); class l extends I { /** The wheel index in the car */ get index() { return this._wheelIndex; } wheelModel; axle = w.front; radius = -1; suspensionRestLength = -1; maxSuspensionTravel = -1; suspensionCompression = 3; suspensionRelax = 5; suspensionStiff = -1; maxSuspensionForce = -1; sideFrictionStiffness = 0.7; frictionSlip = new q(1, 20); skidParticle; skidVisualSideThreshold = 5; skidVisualBreakThreshold = 0.1; skidParticleBehaviour; wheelModelRight; wheelModelUp; car; vehicle; _wheelIndex = -1; _activeRadius = -1; _initialQuaternion; async initialize(e, i, r) { this.car = e, this.vehicle = i, this._wheelIndex = r; const t = this.wheelModel || this.gameObject; let s = this.radius; if (s <= 0 && (s = $(t).getSize(p()).y * 0.5), s < 0) { console.error("CarWheel: Radius is invalid, please set it manually or make sure the wheel is attached to a model"); return; } this._activeRadius = Math.max(0.01, s), this._initialQuaternion = t.quaternion.clone(), this.wheelModel?.quaternion.identity(), this.gameObject?.quaternion.identity(); const o = e.gameObject.worldQuaternion.clone(); e.gameObject.worldQuaternion = new D(); const h = new D(); h.copy(e.gameObject.worldQuaternion).multiply(t.worldQuaternion.clone().invert()), e.gameObject.worldQuaternion = o, this.wheelModelUp = new B(0, 1, 0).clone().applyQuaternion(h), this.wheelModelRight = new B(1, 0, 0).clone().applyQuaternion(h); const _ = t.worldPosition, m = this.car.gameObject.worldToLocal(_); m.multiply(this.car.gameObject.worldScale), m.y += this._activeRadius * 0.5; const g = p(0, -1, 0), y = p(-1, 0, 0); let d = this.suspensionRestLength; (!d || d <= 0) && (d = this._activeRadius * 0.5); let f = this.maxSuspensionTravel; (!f || f <= 0) && (f = this._activeRadius * 0.5); let c = this.suspensionStiff; (!c || c <= 0) && (c = 50); let u = this.maxSuspensionForce; (!u || u <= 0) && (u = 1e8), T && console.debug(this.name, { restLength: d, suspensionTravel: f, suspensionStiff: c, maxSupsensionForce: u, radius: this._activeRadius }, this), this.vehicle.addWheel(m, g, y, d, this._activeRadius), this.vehicle.setWheelMaxSuspensionTravel(r, f), this.vehicle.setWheelMaxSuspensionForce(r, u), this.vehicle.setWheelSuspensionStiffness(r, c), this.vehicle.setWheelSuspensionCompression(r, this.suspensionCompression), this.vehicle.setWheelSuspensionRelaxation(r, this.suspensionRelax), this.vehicle.setWheelSideFrictionStiffness(r, this.sideFrictionStiffness), this.vehicle.setWheelFrictionSlip(r, this.frictionSlip.y), this.skidParticle && (this.skidParticleBehaviour = new oe(), this.skidParticle.addBehaviour(this.skidParticleBehaviour)); } applyPhysics(e, i, r) { this.car.carDrive == j.front && this.axle == w.front || this.car.carDrive == j.rear && this.axle == w.rear || this.car.carDrive == j.all || (e = 0), this.vehicle.setWheelEngineForce(this._wheelIndex, e), this.vehicle.setWheelBrake(this._wheelIndex, i), this.axle == w.front && this.vehicle.setWheelSteering(this._wheelIndex, -r); let o = p(this.car.velocity).clampLength(0, 1).dot(this.car.gameObject.worldRight); o = 1 - Math.abs(o); const h = v.lerp(this.frictionSlip.x, this.frictionSlip.y, o); this.vehicle.setWheelFrictionSlip(this._wheelIndex, h); } updateVisuals() { const e = this.wheelModel || this.gameObject, i = this.vehicle.wheelRotation(this._wheelIndex), r = this.vehicle.wheelSteering(this._wheelIndex), t = M().setFromAxisAngle(this.wheelModelUp, r), s = M().setFromAxisAngle(this.wheelModelRight, i), o = t.multiply(s); e.quaternion.copy(o), e.quaternion.multiply(this._initialQuaternion); const h = this.vehicle.wheelContactPoint(this._wheelIndex), _ = this.vehicle.wheelIsInContact(this._wheelIndex), m = p(); if (h && (T && R.DrawWireSphere(h, 0.02, 16777045, 0, !1), m.copy(this.car.gameObject.worldUp).multiplyScalar(this._activeRadius), m.add(h), e.worldPosition = m), this.skidParticleBehaviour) { const g = Math.abs(this.vehicle.wheelSideImpulse(this._wheelIndex) ?? 0), y = Math.abs(this.vehicle.wheelBrake(this._wheelIndex) ?? 0), d = g > this.skidVisualSideThreshold || y > this.skidVisualBreakThreshold, f = _ && h != null && d; if (this.skidParticle && h) { const c = p(h); c.y += this.skidParticle.main.startSize.constant / 4, this.skidParticle.worldPosition = c; } this.skidParticleBehaviour.isSkidding = f; } if (T) { const g = this._activeRadius * 0.1, y = p(this.car.gameObject.worldRight).multiplyScalar(-1); y.applyEuler(new z(0, r, 0)), R.DrawCircle(m, y, this._activeRadius, 255, 0, !1); const d = p(m), f = p(m).add(p(y).multiplyScalar(this._activeRadius)); R.DrawLine(d, f, 16711680, 0, !1), R.DrawSphere(f, g, 16711680, 0, !1); const c = p(this.car.gameObject.worldForward).multiplyScalar(this._activeRadius * 1); c.applyEuler(new z(0, r, 0)); const u = p(m).add(c); R.DrawLine(m, u, 255, 0, !1), R.DrawSphere(u, g, 255, 0, !1); } } } x([ a(O) ], l.prototype, "wheelModel"); x([ a() ], l.prototype, "axle"); x([ a() ], l.prototype, "radius"); x([ a() ], l.prototype, "suspensionRestLength"); x([ a() ], l.prototype, "maxSuspensionTravel"); x([ a() ], l.prototype, "suspensionCompression"); x([ a() ], l.prototype, "suspensionRelax"); x([ a() ], l.prototype, "suspensionStiff"); x([ a() ], l.prototype, "maxSuspensionForce"); x([ a() ], l.prototype, "sideFrictionStiffness"); x([ a(q) ], l.prototype, "frictionSlip"); x([ a(Y) ], l.prototype, "skidParticle"); x([ a() ], l.prototype, "skidVisualSideThreshold"); x([ a() ], l.prototype, "skidVisualBreakThreshold"); class oe extends J { isSkidding = !1; update(e, i) { const r = e; if (this.system.trails?.enabled && r) { this.isSkidding || e.color.setW(0); let t = r.previous?.tail; for (; t && t.hasPrev(); ) { const s = t; s.data ??= {}, s.data.isSkidding === void 0 && (s.data.isSkidding = this.isSkidding), s.data.isSkidding === !1 && t.data.color?.setW(0), t = t.prev; } } } } var ae = Object.defineProperty, C = (n, e, i, r) => { for (var t = void 0, s = n.length - 1, o; s >= 0; s--) (o = n[s]) && (t = o(e, i, t) || t); return t && ae(e, i, t), t; }; const E = W("debugcar"); class b extends I { carDrive = j.all; mass = 500; maxSteer = 40; steerSmoothingFactor = 0.1; accelerationForce = 12; breakForce = 12; topSpeed = 25; wheels = []; /** * Steer the car. -1 is full left, 1 is full right * @param steerAmount -1 to 1 */ steerImpulse(e) { this._steerInput += e, this._steerInput = v.clamp(this._steerInput, -1, 1); } get currentSteer() { return this._currentSteer; } set currentSteer(e) { this._currentSteer = e; } /** * Increase or decrease acceleration * @param accelAmount -1 to 1 where -1 is full brake and 1 is full acceleration */ accelerationImpulse(e) { this._currAcc += e; } /** * This will always apply the break force to the car * @param breakAmount The amount of break force to apply e.g. 1 for full break */ breakImpulse(e) { this._currBreak += e; } /** Rigidbody component */ get rigidbody() { return this._rigidbody; } /** * Rapier Physics Rigidbody (owned by the rigidbody componenti) */ get rapierRigidbody() { return this.context.physics.engine?.getBody(this._rigidbody); } /** * Rapier Physics Vehicle Controller */ get vehicle() { return this._vehicle; } /** * The rigidbody velocity vector of the car in worldspace */ get velocity() { return this._rigidbody?.getVelocity(); } /** * Current vehicle speed */ get currentSpeed() { return this._vehicle?.currentVehicleSpeed() || 0; } /** * Current vehicle speed in km/h */ get currentSpeedInKmh() { return this.currentSpeed * 3.6; } /** * The maximum speed of the car in km/h */ get maxSpeedInKmh() { return this.topSpeed * 3.6; } /** * Current vehicle speed normalized between 0 and 1 where 1 is the top speed */ get currentSpeed01() { return this._vehicle ? this._vehicle.currentVehicleSpeed() / this.topSpeed : 0; } /** * The airtime of the car in seconds */ get airtime() { return this._airtime; } set airtime(e) { this._airtime = e; } _vehicle; _rigidbody; _currentSteer = 0; _currAcc = 0; _currBreak = 0; _steerInput = 0; _airtime = 0; /** @internal */ awake() { if (this._rigidbody || (this._rigidbody = this.gameObject.addComponent(V)), !this.gameObject.getComponentInChildren(L)) { const e = L.add(this.gameObject), i = new O(); i.addComponent(e), this.gameObject.add(i), i.position.copy(e.center), e.center.set(0, 0, 0), e.center.y += e.size.y * 0.1, e.size.x *= 0.85, e.size.y *= 0.7, e.size.z *= 0.85, e.updateProperties(); } } _physicsRoutine; /** @internal */ async onEnable() { if (this.mass <= 0 && (this.mass = 1), this._rigidbody = this.gameObject.getOrAddComponent(V), this._rigidbody.mass = this.mass, this._rigidbody.autoMass = this.mass <= 0, await this.context.physics.engine?.initialize().then(() => X(1)), !this.activeAndEnabled) return; const e = this.context.physics.engine?.world; if (!e) { console.error("[CarPhysics] Physics world not found"); return; } if (!this.rapierRigidbody) { console.error("[CarPhysics] Rigidbody not found"); return; } if (this._vehicle || (this._vehicle = e.createVehicleController(this.rapierRigidbody)), this._vehicle.indexUpAxis = 1, this._vehicle.setIndexForwardAxis = 2, this.wheels.length === 0 && this.wheels.push(...this.gameObject.getComponentsInChildren(l).filter((i) => i.activeAndEnabled)), this.wheels.length <= 0) { console.debug(`[CarPhysics] No wheels found on ${this.gameObject.name}, trying to find them`); const i = he(this); i.length > 0 && (console.debug(`[CarPhysics] Found ${i.length} wheels: ${i.map((r) => `${r.name} (${w[r.axle]})`).join(", ")}`), this.wheels.push(...i)); } this.wheels.length <= 0 && console.warn(`[CarPhysics] No wheels found on ${this.gameObject.name}`), E && console.log(`[CarPhysics] ${this.name} has ${this.wheels.length} wheels:`, this.wheels), this.wheels.forEach((i, r) => { i.initialize(this, this._vehicle, r); }), this._physicsRoutine = this.startCoroutine(this.physicsLoop(), Z.PostPhysicsStep); } /** @internal */ onDisable() { this._vehicle && this.context.physics.engine?.world?.removeVehicleController(this._vehicle), this._vehicle?.free(), this._vehicle = null, this._physicsRoutine && this.stopCoroutine(this._physicsRoutine); } /** @internal */ onBeforeRender() { if (!this._vehicle) return; if (this.steerSmoothingFactor > 0) { const i = this.context.time.deltaTime / this.steerSmoothingFactor; this._currentSteer = v.lerp(this._currentSteer, this._steerInput, v.clamp01(i)); } else this._currentSteer = this._steerInput; this.applyPhysics(), this._steerInput = 0, this._currAcc = 0, this._currBreak = 0; let e = !1; if (this.wheels.forEach((i) => { i.updateVisuals(), e || (e ||= this._vehicle.wheelIsInContact(i.index)); }), e ? this._airtime = 0 : this._airtime += this.context.time.deltaTime, E) { const i = this._vehicle.chassis(), r = i.translation(), t = p(r).add(p(0, 2, 0)), s = `vel: ${this._vehicle.currentVehicleSpeed().toFixed(2)}`; R.DrawLabel(t, s, 0.1, 0, 16777215, 0), this.wheels.forEach((o) => { const h = this._vehicle.wheelChassisConnectionPointCs(o.index); h && R.DrawLine(p(r), p(h).applyQuaternion(i.rotation()).add(r), 255, 0, !1); }); } } teleport(e, i, r = !0) { !this.rapierRigidbody || !this._vehicle || (e && this.rapierRigidbody.setTranslation(e, !0), i && this.rapierRigidbody.setRotation(i, !0), r && this._rigidbody.setVelocity(0, 0, 0)); } *physicsLoop() { for (; ; ) { if (this._vehicle) { const e = this.context.time.deltaTime; this._vehicle?.updateVehicle(e); } yield null; } } applyPhysics() { this._currAcc = v.clamp(this._currAcc, -1, 1); let e = this._currAcc === 0 ? 0.2 : 0, i = 0; const r = this._rigidbody.getVelocity(), t = this._vehicle.currentVehicleSpeed(), s = t > this.topSpeed, o = this.context.time.deltaTime * this.mass * this.currentSpeed01 * 20; this._rigidbody.applyImpulse(p(0, -o, 0)), this._currAcc < 0 && t > 0.05 && r.dot(this.gameObject.worldForward) > 0 && (e = this.breakForce * -this._currAcc), e += Math.max(0, this._currBreak) * this.breakForce, this._currAcc != 0 && !s && (i = this.accelerationForce / this.context.time.deltaTime * this._currAcc); const m = v.lerp(this.maxSteer, this.maxSteer * 0.5, this.currentSpeed01), g = this._currentSteer * m * v.Deg2Rad; this.wheels.forEach((y) => { y.applyPhysics(i, e, g); }); } } C([ a() ], b.prototype, "carDrive"); C([ a() ], b.prototype, "mass"); C([ a() ], b.prototype, "maxSteer"); C([ a() ], b.prototype, "steerSmoothingFactor"); C([ a() ], b.prototype, "accelerationForce"); C([ a() ], b.prototype, "breakForce"); C([ a() ], b.prototype, "topSpeed"); C([ a(l) ], b.prototype, "wheels"); function he(n) { const e = new Array(); if (i(n.gameObject), e.length <= 0) { const r = n.gameObject.worldPosition, t = n.gameObject.worldQuaternion; n.gameObject.worldPosition = new B(), n.gameObject.worldQuaternion = new D(); const s = $(n.gameObject); n.gameObject.worldQuaternion = t, n.gameObject.worldPosition = r; const o = s.max.y - s.min.y, h = Math.max(s.max.x - s.min.x, s.max.z - s.min.z), _ = o / h, g = s.getSize(new B()).length() * 0.1, y = s.min.y; let d = (s.max.x - s.min.x) * 0.1, f = (s.max.z - s.min.z) * 0.1; _ > 1 && (d *= -_ * 1.5, f *= -_ * 1.5); const c = new O(); c.position.set(s.min.x + d, y, s.max.z - f), c.name = "WheelFrontLeft", e.push(c.addComponent(l, { axle: w.front, radius: g })), n.gameObject.add(c); const u = new O(); u.position.set(s.max.x - d, y, s.max.z - f), u.name = "WheelFrontRight", e.push(u.addComponent(l, { axle: w.front, radius: g })), n.gameObject.add(u); const S = new O(); S.position.set(s.min.x + d, y, s.min.z + f), S.name = "WheelRearLeft", e.push(S.addComponent(l, { axle: w.rear, radius: g })), n.gameObject.add(S); const P = new O(); P.position.set(s.max.x - d, y, s.min.z + f), P.name = "WheelRearRight", e.push(P.addComponent(l, { axle: w.rear, radius: g })), n.gameObject.add(P); } return e; function i(r) { for (const t of r.children) { const s = t.name.toLowerCase(); if (s.includes("wheel") && !t.getComponent(l)) { const o = s.includes("front") || s.includes("fl") || s.includes("fr"), h = t.addComponent(l, { axle: o ? w.front : w.rear }); e.push(h); } } for (const t of r.children) { if (e.length > 0) break; t instanceof O && i(t); } } } var ce = Object.defineProperty, F = (n, e, i, r) => { for (var t = void 0, s = n.length - 1, o; s >= 0; s--) (o = n[s]) && (t = o(e, i, t) || t); return t && ce(e, i, t), t; }; class k extends I { carPhysics; autoReset = !0; manualReset = !0; onReset = new Q(); /** * Resets the car to the starting position and orientation */ reset() { this.carPhysics?.teleport(this.posOnStart, this.rotOnStart, !0), this.context.mainCamera.getComponent(ee)?.setCameraTargetPosition(this.camStartPos, !0), this.onReset?.invoke(); } posOnStart; rotOnStart; camStartPos; start() { this.posOnStart = this.gameObject.worldPosition.clone(), this.rotOnStart = this.gameObject.worldQuaternion.clone(), this.camStartPos = this.context.mainCamera.position.clone(); } onEnable() { this.carPhysics ||= this.gameObject.getComponent(b), window.addEventListener("blur", this.onBlur); } onDisable() { window.removeEventListener("blur", this.onBlur); } onBeforeRender() { this.handleInput(), this.manualReset && this.context.input.isKeyDown("r") && this.reset(), this.autoReset && (this.resetWhenRolledOver(), this.resetWhenFallingoff()); } onBlur = (e) => { if (!this.context.application.hasFocus) { const i = navigator.getGamepads()?.[0]; i && i.vibrationActuator?.playEffect("dual-rumble", { startDelay: 0, duration: 0, weakMagnitude: 1, strongMagnitude: 1 }); } }; _lastResetTime = -1; resetWhenFallingoff() { this.carPhysics && this.carPhysics.airtime > 5 && this.context.time.realtimeSinceStartup - this._lastResetTime > 5 && (this._lastResetTime = this.context.time.realtimeSinceStartup, this.reset()); } rolledOverDuration = 0; resetWhenRolledOver() { if (!this.carPhysics) return; const e = this.gameObject.worldUp.dot(p(0, 1, 0)) < 0.65, r = this.carPhysics.rigidbody.getVelocity().length() < 0.1; e && r ? this.rolledOverDuration += this.context.time.deltaTime : this.rolledOverDuration = 0, this.rolledOverDuration > 1 && this.rescueVehicle(); } // TODO: add raycast to determine normal of the surface the car is resetting to async rescueVehicle() { if (!this.carPhysics) return; const e = this.gameObject.worldPosition; e.y += 1; const i = this.gameObject.worldForward; i.y = 0, i.normalize(); const r = M().setFromUnitVectors(p(0, 0, -1), i); this.carPhysics.teleport(e, r); } _lastVehicleVelocity = 0; _lastHeroRumbleTime = -1; _currentSteer = 0; _currentSteerAccum = 0; handleInput() { if (!this.carPhysics?.vehicle) return; let e = 0, i = 0, r = 0; if (this.context.xr) { i += this.context.xr.rightController?.getButton("a-button")?.value || 0, i -= this.context.xr.leftController?.getButton("x-button")?.value || 0; const s = this.context.xr.rightController?.getButton("xr-standard-squeeze")?.value || 0, o = this.context.xr.leftController?.getButton("xr-standard-squeeze")?.value || 0; if (s > 0.5 && o > 0.5) { const h = this.context.xr.leftController.gripPosition.y - this.context.xr.rightController.gripPosition.y; e = v.clamp(h, -2, 2); } } else this.context.input.isKeyPressed("a") || this.context.input.isKeyPressed("ArrowLeft") ? e -= 1 : (this.context.input.isKeyPressed("d") || this.context.input.isKeyPressed("ArrowRight")) && (e += 1), (this.context.input.isKeyPressed("s") || this.context.input.isKeyPressed("ArrowDown")) && (i -= 1), (this.context.input.isKeyPressed("w") || this.context.input.isKeyPressed("ArrowUp")) && (i += 1), this.context.input.isKeyPressed(" ") && (r += 1); const t = navigator.getGamepads()?.[0]; if (t?.connected) { const s = t.axes[0], o = t.axes[1]; if (Math.abs(s) > 0.01) { const c = s < 0 ? -1 : 1; e += Math.pow(s, 2) * c; } Math.abs(o) > 0.01 && (i -= o); const h = t.buttons[0], _ = t.buttons[1], m = t.buttons[6], g = t.buttons[7]; (h.pressed || g.pressed) && (i += 1), (_.pressed || m.pressed) && (i -= 1), t.buttons[2].pressed && this.reset(); const d = this.carPhysics.velocity.length(); if (this.context.time.realtimeSinceStartup - this._lastHeroRumbleTime > 0.3) { d > 0.01 && t.vibrationActuator?.playEffect("dual-rumble", { startDelay: 0, duration: this.context.time.deltaTime, weakMagnitude: 0.1, strongMagnitude: 0.1 }); const c = this.carPhysics.wheels, u = 200; let S = 0; for (const P of c) { const A = this.carPhysics.vehicle.wheelSuspensionForce(P.index); if (A && A < u) { const N = 1 - A / u; S = Math.max(S, N); } } if (S > 0) { const P = Math.pow(S, 2); t.vibrationActuator?.playEffect("dual-rumble", { startDelay: 0, duration: S * 500, weakMagnitude: P * 1, strongMagnitude: P * 1 }); } } if (d) { const c = this._lastVehicleVelocity; this._lastVehicleVelocity = d; const u = c - d; u > 1 && (this._lastHeroRumbleTime = this.context.time.realtimeSinceStartup, t.vibrationActuator?.playEffect("dual-rumble", { startDelay: 0, duration: 150, weakMagnitude: v.clamp01(u / 3), strongMagnitude: v.clamp01(u / 3) })); } } e *= Math.max(0.2, Math.min(1, 2 * Math.abs(this.carPhysics.currentSteer))), this._currentSteer = v.lerp(this._currentSteer, e, this.context.time.deltaTime / 0.12), this.carPhysics.steerImpulse(this._currentSteer), this.carPhysics.breakImpulse(r), this.carPhysics.accelerationImpulse(i); } } F([ a(b) ], k.prototype, "carPhysics"); F([ a() ], k.prototype, "autoReset"); F([ a() ], k.prototype, "manualReset"); F([ a(Q) ], k.prototype, "onReset"); var le = Object.defineProperty, de = (n, e, i, r) => { for (var t = void 0, s = n.length - 1, o; s >= 0; s--) (o = n[s]) && (t = o(e, i, t) || t); return t && le(e, i, t), t; }; class H extends I { carPhysics; steerLeftState = 0; steerRightState = 0; throttleState = 0; breakState = 0; update() { this.throttleInput(), this.steerInput(); } throttleInput() { this.carPhysics?.accelerationImpulse(v.clamp(this.throttleState + this.breakState, -1, 1)); } steerInput() { this.carPhysics?.steerImpulse(v.clamp(this.steerLeftState + this.steerRightState, -1, 1)); } // --- steerLeftPress() { this.steerLeftState = -1; } steerLeftRelease() { this.steerLeftState = 0; } steerRightPress() { this.steerRightState = 1; } steerRightRelease() { this.steerRightState = 0; } throttlePress() { this.throttleState = 1; } throttleRelease() { } brakePress() { this.throttleState = 0, this.breakState = -1; } brakeRelease() { this.breakState = 0; } } de([ a(b) ], H.prototype, "carPhysics"); var ue = Object.defineProperty, U = (n, e, i, r) => { for (var t = void 0, s = n.length - 1, o; s >= 0; s--) (o = n[s]) && (t = o(e, i, t) || t); return t && ue(e, i, t), t; }; class G extends I { cameraRig = null; cars; awake() { this.cars ??= []; } start() { this.cars?.length || (this.cars = [...te.findObjectsOfType(k)]), this.cars.length > 0 && this.selectCarByIndex(0); } onEnable() { this.context.input.addEventListener("keyup", this.onKey), this.context.domElement.addEventListener("click", this.onClick); } onDisable() { this.context.input.removeEventListener("keyup", this.onKey), this.context.domElement.removeEventListener("click", this.onClick); } selectCar(e) { this.cars || (this.cars = []); let i = this.cars.indexOf(e); i === -1 && (this.cars.push(e), i = this.cars.length - 1), this.selectCarByIndex(i); } gamepadButtonDown = !1; update() { const e = navigator.getGamepads()?.[0]; if (e) if (e.buttons?.[3]?.pressed) { if (!this.gamepadButtonDown) { this.gamepadButtonDown = !0; const r = this.cars.find((t) => t.activeAndEnabled); if (r) { const s = ((r ? this.cars.indexOf(r) : -1) + 1) % this.cars.length; this.selectCarByIndex(s); } } } else this.gamepadButtonDown && (this.gamepadButtonDown = !1); } onKey = (e) => { const i = parseInt(e.key) - 1; i >= 0 && i < this.cars.length && this.selectCarByIndex(i); }; onClick = (e) => { if (!this.cars?.length || e instanceof MouseEvent && e.button != 0) return; const i = this.context.physics.raycast(); if (i.length) { const r = i[0]?.object.getComponentInParent(k), t = r ? this.cars.indexOf(r) : -1; t >= 0 && this.selectCarByIndex(t); } }; selectCarByIndex(e) { for (const r of this.cars) r && (r.enabled = !1); const i = this.cars[e]; if (i) { i.enabled = !0; const r = ie(H); r && (r.carPhysics = i.gameObject.getComponentInChildren(b) || void 0); const t = i.gameObject.getComponentInChildren(K); if (t) this.context.setCurrentCamera(t); else if (this.cameraRig) { this.context.setCurrentCamera(this.cameraRig); const s = this.cameraRig.gameObject.getComponentInParent(se); s && (s.target = i.gameObject); } } } } U([ a(K) ], G.prototype, "cameraRig"); U([ a(k) ], G.prototype, "cars"); var pe = Object.defineProperty, me = (n, e, i, r) => { for (var t = void 0, s = n.length - 1, o; s >= 0; s--) (o = n[s]) && (t = o(e, i, t) || t); return t && pe(e, i, t), t; }; class fe extends I { url = "https://stream.laut.fm/gta-classics"; _audio = null; onEnable() { this.url && (this._audio = new Audio(this.url), this._audio.autoplay = !0, re.registerWaitForInteraction(() => { this.enabled && this._audio?.play(); })); } onDisable() { this._audio?.pause(); } } me([ a() ], fe.prototype, "url"); export { w as CarAxle, k as CarController, j as CarDrive, b as CarPhysics, fe as CarRadio, G as CarSelection, l as CarWheel, oe as SkidTrailBehaviour };