wco_models 3.1.0.325 → 3.4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/production_theme.scss +4 -1
  3. data/app/assets/stylesheets/wco/main.scss +4 -0
  4. data/app/assets/stylesheets/wco/studio.scss +12 -4
  5. data/app/controllers/wco/api/newspartials_controller.rb +1 -1
  6. data/app/controllers/wco/api/newsproducer_controller.rb +5 -0
  7. data/app/controllers/wco/newspartials_controller.rb +1 -0
  8. data/app/models/iro/datapoint.rb +1 -1
  9. data/app/models/iro/option.rb +16 -2
  10. data/app/models/iro/position.rb +4 -6
  11. data/app/models/iro/priceitem.rb +42 -4
  12. data/app/models/iro/purse.rb +2 -0
  13. data/app/models/iro/stock.rb +1 -2
  14. data/app/models/tda/option.rb +4 -9
  15. data/app/models/wco/newspartial.rb +12 -4
  16. data/app/views/wco/api/newsproducer/README_newsproducer.txt +12 -0
  17. data/app/views/wco/api/newsproducer/_scenes.js-bk +20 -0
  18. data/app/views/wco/api/newsproducer/_scenes.js.erb +15 -0
  19. data/app/views/wco/api/newsproducer/{_studio_blue.js → _studio_blue1.js} +4 -1
  20. data/app/views/wco/api/newsproducer/_studio_blue2.js +912 -0
  21. data/app/views/wco/api/newsproducer/_studio_coc.js +769 -0
  22. data/app/views/wco/api/newsproducer/_studio_coc.js-bk +389 -0
  23. data/app/views/wco/api/newsproducer/_studio_coc.js-cannon +677 -0
  24. data/app/views/wco/api/newsproducer/_studio_fps.js +1018 -0
  25. data/app/views/wco/api/newsproducer/_studio_ra2.js +255 -0
  26. data/app/views/wco/api/newsproducer/_touch_controls.js +708 -0
  27. data/app/views/wco/api/newsproducer/_touch_pad.css +200 -0
  28. data/app/views/wco/api/newsproducer/games_fps.html +504 -0
  29. data/app/views/wco/api/newsproducer/{studio_green.html.erb → studio_blue0.html.erb} +1 -1
  30. data/app/views/wco/api/newsproducer/{studio_blue.html.erb → studio_blue1.html.erb} +3 -3
  31. data/app/views/wco/api/newsproducer/studio_blue2.html.erb +146 -0
  32. data/app/views/wco/api/newsproducer/studio_coc.html.erb +95 -0
  33. data/app/views/wco/api/newsproducer/studio_fps.html.erb +199 -0
  34. data/app/views/wco/api/newsproducer/studio_ra2.html.erb +95 -0
  35. data/app/views/wco/newspartials/_show_in_newsvideo.haml +6 -7
  36. data/app/views/wco/newspartials/index.haml-trash +6 -0
  37. data/config/initializers/08_integrations.rb +14 -1
  38. data/config/initializers/08_integrations.rb-example +0 -2
  39. data/config/routes.rb +3 -0
  40. data/lib/shortcuts.rb +1 -1
  41. metadata +23 -7
  42. data/app/views/wco/api/newsproducer/README.txt +0 -2
  43. /data/app/views/wco/api/newsproducer/{_studio_green.js → _studio_blue0.js} +0 -0
@@ -0,0 +1,677 @@
1
+
2
+ import * as THREE from 'three'
3
+
4
+ import Stats from 'three/addons/libs/stats.module.js'
5
+
6
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
7
+
8
+ import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
9
+
10
+ import * as CANNON from 'cannon-es'
11
+ import CannonDebugger from 'cannon-es-debugger'
12
+
13
+ const timer = new THREE.Timer()
14
+ timer.connect( document )
15
+
16
+ const scene = new THREE.Scene()
17
+ scene.background = new THREE.Color( 0x88ccee )
18
+ scene.fog = new THREE.Fog( 0x88ccee, 0, 50 )
19
+
20
+ const frustumSize = 10
21
+ const camera = new THREE.OrthographicCamera(
22
+ frustumSize * window.innerWidth / window.innerHeight / - 2,
23
+ frustumSize * window.innerWidth / window.innerHeight / 2,
24
+ frustumSize / 2,
25
+ frustumSize / - 2,
26
+ -100,
27
+ 1000
28
+ )
29
+ camera.rotation.order = 'YXZ'
30
+
31
+ const fillLight1 = new THREE.HemisphereLight( 0x8dc1de, 0x00668d, 1.5 )
32
+ fillLight1.position.set( 2, 1, 1 )
33
+ scene.add( fillLight1 )
34
+
35
+ const directionalLight = new THREE.DirectionalLight( 0xffffff, 2.5 )
36
+ directionalLight.position.set( - 5, 25, - 1 )
37
+ directionalLight.castShadow = true
38
+ directionalLight.shadow.camera.near = 0.01
39
+ directionalLight.shadow.camera.far = 500
40
+ directionalLight.shadow.camera.right = 30
41
+ directionalLight.shadow.camera.left = - 30
42
+ directionalLight.shadow.camera.top = 30
43
+ directionalLight.shadow.camera.bottom = - 30
44
+ directionalLight.shadow.mapSize.width = 1024
45
+ directionalLight.shadow.mapSize.height = 1024
46
+ directionalLight.shadow.radius = 4
47
+ directionalLight.shadow.bias = - 0.00006
48
+ scene.add( directionalLight )
49
+
50
+ const container = document.getElementById( 'rotatingC' )
51
+
52
+ const renderer = new THREE.WebGLRenderer( { antialias: true } )
53
+ renderer.setPixelRatio( window.devicePixelRatio )
54
+ renderer.setSize( window.innerWidth, window.innerHeight )
55
+ renderer.setAnimationLoop( animate )
56
+ renderer.shadowMap.enabled = true
57
+ renderer.shadowMap.type = THREE.VSMShadowMap
58
+ renderer.toneMapping = THREE.ACESFilmicToneMapping
59
+ container.appendChild( renderer.domElement )
60
+
61
+ const stats = new Stats()
62
+ stats.domElement.style.position = 'absolute'
63
+ stats.domElement.style.top = '0px'
64
+ container.appendChild( stats.domElement )
65
+
66
+ const GRAVITY = 16
67
+
68
+ const VEHICLE_SPEED = 4
69
+ const VEHICLE_REVERSE_SPEED = 3
70
+ const VEHICLE_ACCELERATION = 2.5
71
+ const VEHICLE_BRAKE = 5
72
+ const VEHICLE_TURN_SPEED = 2.2
73
+ const VEHICLE_MASS = 80
74
+ const VEHICLE_DRIVE_FORCE = VEHICLE_MASS * VEHICLE_ACCELERATION
75
+ const VEHICLE_BRAKE_FORCE = VEHICLE_MASS * VEHICLE_BRAKE
76
+ const VEHICLE_TURN_TORQUE = 55
77
+ const VEHICLE_UPRIGHT_STRENGTH = 14
78
+
79
+ const NUM_SPHERES = 100
80
+ const SPHERE_RADIUS = 0.2
81
+
82
+ const sphereGeometry = new THREE.IcosahedronGeometry( SPHERE_RADIUS, 5 )
83
+ const sphereMaterial = new THREE.MeshLambertMaterial( { color: 0xdede8d } )
84
+
85
+ const world = new CANNON.World()
86
+ world.gravity.set( 0, - GRAVITY, 0 )
87
+ world.broadphase = new CANNON.NaiveBroadphase()
88
+ world.allowSleep = true
89
+ world.solver.iterations = 15
90
+ world.defaultContactMaterial.friction = 0.4
91
+ world.defaultContactMaterial.restitution = 0.1
92
+
93
+ const groundMaterial = new CANNON.Material( 'ground' )
94
+ const vehicleMaterial = new CANNON.Material( 'vehicle' )
95
+ const playerMaterial = new CANNON.Material( 'player' )
96
+ const ballMaterial = new CANNON.Material( 'ball' )
97
+
98
+ world.addContactMaterial( new CANNON.ContactMaterial( groundMaterial, vehicleMaterial, {
99
+ friction: 0.3,
100
+ restitution: 0.05
101
+ } ) )
102
+ world.addContactMaterial( new CANNON.ContactMaterial( groundMaterial, playerMaterial, {
103
+ friction: 0.1,
104
+ restitution: 0
105
+ } ) )
106
+ world.addContactMaterial( new CANNON.ContactMaterial( groundMaterial, ballMaterial, {
107
+ friction: 0.4,
108
+ restitution: 0.6
109
+ } ) )
110
+ world.addContactMaterial( new CANNON.ContactMaterial( vehicleMaterial, ballMaterial, {
111
+ friction: 0.3,
112
+ restitution: 0.4
113
+ } ) )
114
+
115
+ const cannonDebugger = new CannonDebugger( scene, world )
116
+ let debugPhysics = false
117
+
118
+ let worldReady = false
119
+
120
+ let vehicle = null
121
+ let vehicleBody = null
122
+ const vehicleForward = new THREE.Vector3()
123
+ const vehicleSize = new THREE.Vector3()
124
+ const vehicleRoofLocal = new CANNON.Vec3( 0, 1, 0 )
125
+ const vehicleWorldUp = new CANNON.Vec3()
126
+ const vehicleRighting = new CANNON.Vec3()
127
+ const vehicleWallNormal = new CANNON.Vec3()
128
+ const vehicleRayFrom = new CANNON.Vec3()
129
+ const vehicleRayTo = new CANNON.Vec3()
130
+ const vehicleRayResult = new CANNON.RaycastResult()
131
+ const worldUpVec = new CANNON.Vec3( 0, 1, 0 )
132
+ const vehicleInvQuat = new CANNON.Quaternion()
133
+ let vehicleHeading = 0
134
+ let vehicleSpeed = 0
135
+
136
+ const playerBody = new CANNON.Body( {
137
+ mass: 5,
138
+ material: playerMaterial,
139
+ linearDamping: 0.15,
140
+ fixedRotation: true
141
+ } )
142
+ playerBody.addShape( new CANNON.Sphere( 0.35 ) )
143
+ playerBody.position.set( 0, 1, 0 )
144
+ playerBody.updateMassProperties()
145
+ world.addBody( playerBody )
146
+
147
+ const playerVelocity = new THREE.Vector3()
148
+ const playerDirection = new THREE.Vector3()
149
+
150
+ let playerOnFloor = false
151
+ let mouseTime = 0
152
+
153
+ const keyStates = {}
154
+
155
+ const vector1 = new THREE.Vector3()
156
+
157
+ const spheres = []
158
+ let sphereIdx = 0
159
+
160
+ for ( let i = 0; i < NUM_SPHERES; i ++ ) {
161
+
162
+ const mesh = new THREE.Mesh( sphereGeometry, sphereMaterial )
163
+ mesh.castShadow = true
164
+ mesh.receiveShadow = true
165
+ scene.add( mesh )
166
+
167
+ const body = new CANNON.Body( {
168
+ mass: 0.4,
169
+ material: ballMaterial,
170
+ linearDamping: 0.2,
171
+ angularDamping: 0.2
172
+ } )
173
+ body.addShape( new CANNON.Sphere( SPHERE_RADIUS ) )
174
+ body.position.set( 0, -100, 0 )
175
+ body.sleep()
176
+ world.addBody( body )
177
+
178
+ spheres.push( { mesh: mesh, body: body } )
179
+
180
+ }
181
+
182
+ const vehicle_url = 'https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.3.0/public/vendor/models/vehicles/000mb car-mazda-miata/model.glb'
183
+ const vehicle_config = {
184
+ height: 0.5,
185
+ }
186
+
187
+ document.addEventListener( 'keydown', ( event ) => {
188
+
189
+ keyStates[ event.code ] = true
190
+
191
+ } )
192
+
193
+ document.addEventListener( 'keyup', ( event ) => {
194
+
195
+ keyStates[ event.code ] = false
196
+
197
+ } )
198
+
199
+ container.addEventListener( 'mousedown', () => {
200
+
201
+ document.body.requestPointerLock()
202
+ mouseTime = performance.now()
203
+
204
+ } )
205
+
206
+ document.addEventListener( 'mouseup', () => {
207
+
208
+ if ( document.pointerLockElement !== null ) throwBall()
209
+
210
+ } )
211
+
212
+ document.body.addEventListener( 'mousemove', ( event ) => {
213
+
214
+ if ( document.pointerLockElement === document.body ) {
215
+
216
+ camera.rotation.y -= event.movementX / 500
217
+ camera.rotation.x -= event.movementY / 500
218
+
219
+ }
220
+
221
+ } )
222
+
223
+ window.addEventListener( 'resize', onWindowResize )
224
+
225
+ function onWindowResize() {
226
+
227
+ const aspect = window.innerWidth / window.innerHeight
228
+ camera.left = frustumSize * aspect / - 2
229
+ camera.right = frustumSize * aspect / 2
230
+ camera.top = frustumSize / 2
231
+ camera.bottom = frustumSize / - 2
232
+ camera.updateProjectionMatrix()
233
+
234
+ renderer.setSize( window.innerWidth, window.innerHeight )
235
+
236
+ }
237
+
238
+ function throwBall() {
239
+
240
+ const sphere = spheres[ sphereIdx ]
241
+
242
+ camera.getWorldDirection( playerDirection )
243
+
244
+ sphere.body.wakeUp()
245
+ sphere.body.position.set(
246
+ playerBody.position.x + playerDirection.x * 0.8,
247
+ playerBody.position.y + 0.3 + playerDirection.y * 0.8,
248
+ playerBody.position.z + playerDirection.z * 0.8
249
+ )
250
+
251
+ const impulse = 15 + 30 * ( 1 - Math.exp( ( mouseTime - performance.now() ) * 0.001 ) )
252
+
253
+ sphere.body.velocity.set(
254
+ playerDirection.x * impulse + playerBody.velocity.x,
255
+ playerDirection.y * impulse + playerBody.velocity.y,
256
+ playerDirection.z * impulse + playerBody.velocity.z
257
+ )
258
+ sphere.body.angularVelocity.set( 0, 0, 0 )
259
+
260
+ sphereIdx = ( sphereIdx + 1 ) % spheres.length
261
+
262
+ }
263
+
264
+ function bodyHasFloorContact( body ) {
265
+
266
+ for ( let i = 0; i < world.contacts.length; i ++ ) {
267
+
268
+ const contact = world.contacts[ i ]
269
+ if ( contact.bi !== body && contact.bj !== body ) continue
270
+
271
+ const ny = contact.bi === body ? contact.ni.y : - contact.ni.y
272
+ if ( ny > 0.4 ) return true
273
+
274
+ }
275
+
276
+ return false
277
+
278
+ }
279
+
280
+ function updatePlayer( deltaTime ) {
281
+
282
+ playerOnFloor = bodyHasFloorContact( playerBody )
283
+
284
+ const speedDelta = deltaTime * ( playerOnFloor ? 25 : 8 )
285
+
286
+ if ( keyStates[ 'KeyW' ] ) {
287
+
288
+ playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) )
289
+
290
+ }
291
+
292
+ if ( keyStates[ 'KeyS' ] ) {
293
+
294
+ playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) )
295
+
296
+ }
297
+
298
+ playerVelocity.x *= Math.exp( - 4 * deltaTime )
299
+ playerVelocity.z *= Math.exp( - 4 * deltaTime )
300
+
301
+ playerBody.velocity.x = playerVelocity.x
302
+ playerBody.velocity.z = playerVelocity.z
303
+
304
+ if ( playerOnFloor && keyStates[ 'Space' ] ) {
305
+
306
+ playerBody.velocity.y = 8
307
+ playerOnFloor = false
308
+
309
+ }
310
+
311
+ if ( ! vehicle ) {
312
+
313
+ camera.position.set( playerBody.position.x, playerBody.position.y + 0.4, playerBody.position.z )
314
+
315
+ }
316
+
317
+ }
318
+
319
+ function getForwardVector() {
320
+
321
+ camera.getWorldDirection( playerDirection )
322
+ playerDirection.y = 0
323
+ playerDirection.normalize()
324
+
325
+ return playerDirection
326
+
327
+ }
328
+
329
+ function rescale( model, config ) {
330
+
331
+ const box = new THREE.Box3().setFromObject( model )
332
+ const size = box.getSize( new THREE.Vector3() )
333
+ const scale = config.height / size.y
334
+ model.scale.setScalar( scale )
335
+
336
+ }
337
+
338
+ function addTrimeshFromObject( object ) {
339
+
340
+ object.updateWorldMatrix( true, true )
341
+
342
+ object.traverse( child => {
343
+
344
+ if ( ! child.isMesh || ! child.geometry ) return
345
+
346
+ const geometry = child.geometry.index ? child.geometry.toNonIndexed() : child.geometry
347
+ const position = geometry.attributes.position
348
+ const vertices = []
349
+ const indices = []
350
+ const point = new THREE.Vector3()
351
+
352
+ for ( let i = 0; i < position.count; i ++ ) {
353
+
354
+ point.fromBufferAttribute( position, i ).applyMatrix4( child.matrixWorld )
355
+ vertices.push( point.x, point.y, point.z )
356
+
357
+ }
358
+
359
+ for ( let i = 0; i < position.count; i += 3 ) {
360
+
361
+ indices.push( i, i + 1, i + 2 )
362
+
363
+ }
364
+
365
+ if ( indices.length === 0 ) return
366
+
367
+ const trimesh = new CANNON.Trimesh( vertices, indices )
368
+ trimesh.updateTree()
369
+ const body = new CANNON.Body( { mass: 0, material: groundMaterial } )
370
+ body.addShape( trimesh )
371
+ body.updateBoundingRadius()
372
+ world.addBody( body )
373
+
374
+ } )
375
+
376
+ }
377
+
378
+ function createVehicleBody() {
379
+
380
+ if ( ! vehicle || vehicleBody || ! worldReady ) return
381
+
382
+ vehicle.updateMatrixWorld( true )
383
+ const box = new THREE.Box3().setFromObject( vehicle )
384
+ box.getSize( vehicleSize )
385
+ const center = box.getCenter( vector1 )
386
+ const offset = center.clone().sub( vehicle.position )
387
+ const radius = Math.max( Math.min( vehicleSize.x, vehicleSize.y, vehicleSize.z ) * 0.42, 0.12 )
388
+ const hx = Math.max( vehicleSize.x * 0.5 - radius, 0 )
389
+ const hz = Math.max( vehicleSize.z * 0.5 - radius, 0 )
390
+
391
+ vehicleBody = new CANNON.Body( {
392
+ mass: VEHICLE_MASS,
393
+ material: vehicleMaterial,
394
+ linearDamping: 0.01,
395
+ angularDamping: 0.2,
396
+ allowSleep: false,
397
+ collisionFilterGroup: 2,
398
+ collisionFilterMask: 1
399
+ } )
400
+
401
+ const sphere = new CANNON.Sphere( radius )
402
+ const points = [
403
+ [ offset.x, offset.y, offset.z ],
404
+ [ offset.x + hx, offset.y, offset.z + hz ],
405
+ [ offset.x + hx, offset.y, offset.z - hz ],
406
+ [ offset.x - hx, offset.y, offset.z + hz ],
407
+ [ offset.x - hx, offset.y, offset.z - hz ]
408
+ ]
409
+
410
+ for ( let i = 0; i < points.length; i ++ ) {
411
+
412
+ vehicleBody.addShape( sphere, new CANNON.Vec3( points[ i ][ 0 ], points[ i ][ 1 ], points[ i ][ 2 ] ) )
413
+
414
+ }
415
+
416
+ vehicleBody.position.set( vehicle.position.x, vehicle.position.y, vehicle.position.z )
417
+ vehicleBody.quaternion.set(
418
+ vehicle.quaternion.x,
419
+ vehicle.quaternion.y,
420
+ vehicle.quaternion.z,
421
+ vehicle.quaternion.w
422
+ )
423
+ vehicleInvQuat.copy( vehicleBody.quaternion ).inverse()
424
+ vehicleInvQuat.vmult( worldUpVec, vehicleRoofLocal )
425
+ world.addBody( vehicleBody )
426
+
427
+ }
428
+
429
+ const loader = new GLTFLoader()
430
+ const scene_url = 'https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.3.0/public/vendor/models/scenes/000mb%20collision-world/collision-world.glb'
431
+
432
+ loader.load( vehicle_url, ( gltf ) => {
433
+
434
+ vehicle = gltf.scene
435
+ rescale( vehicle, vehicle_config )
436
+
437
+ vehicle.traverse( child => {
438
+
439
+ if ( child.isMesh ) {
440
+
441
+ child.castShadow = true
442
+ child.receiveShadow = true
443
+
444
+ }
445
+
446
+ } )
447
+
448
+ const box = new THREE.Box3().setFromObject( vehicle )
449
+ vehicle.position.y += 5 - box.min.y
450
+ scene.add( vehicle )
451
+ createVehicleBody()
452
+
453
+ } )
454
+
455
+ loader.load( scene_url, ( gltf ) => {
456
+
457
+ scene.add( gltf.scene )
458
+ addTrimeshFromObject( gltf.scene )
459
+ worldReady = true
460
+ createVehicleBody()
461
+
462
+ gltf.scene.traverse( child => {
463
+
464
+ if ( child.isMesh ) {
465
+
466
+ child.castShadow = true
467
+ child.receiveShadow = true
468
+
469
+ if ( child.material.map ) {
470
+
471
+ child.material.map.anisotropy = 4
472
+
473
+ }
474
+
475
+ }
476
+
477
+ } )
478
+
479
+ const gui = new GUI( { width: 200 } )
480
+ gui.add( { debug: false }, 'debug' )
481
+ .onChange( function ( value ) {
482
+
483
+ debugPhysics = value
484
+
485
+ } )
486
+
487
+ } )
488
+
489
+ function keepVehicleUpright( deltaTime ) {
490
+
491
+ if ( ! vehicleBody ) return
492
+
493
+ vehicleBody.quaternion.vmult( vehicleRoofLocal, vehicleWorldUp )
494
+ vehicleRighting.copy( vehicleWorldUp ).cross( worldUpVec )
495
+ vehicleBody.angularVelocity.x += vehicleRighting.x * VEHICLE_UPRIGHT_STRENGTH * deltaTime
496
+ vehicleBody.angularVelocity.z += vehicleRighting.z * VEHICLE_UPRIGHT_STRENGTH * deltaTime
497
+ vehicleBody.angularVelocity.x *= 0.9
498
+ vehicleBody.angularVelocity.z *= 0.9
499
+
500
+ }
501
+
502
+ function wallAhead( dir_x, dir_z, distance ) {
503
+
504
+ const side_x = - dir_z
505
+ const side_z = dir_x
506
+ const y = vehicleBody.position.y + 0.22
507
+ const sides = [ 0, 0.18, -0.18 ]
508
+
509
+ for ( let i = 0; i < sides.length; i ++ ) {
510
+
511
+ vehicleRayFrom.set(
512
+ vehicleBody.position.x + side_x * sides[ i ],
513
+ y,
514
+ vehicleBody.position.z + side_z * sides[ i ]
515
+ )
516
+ vehicleRayTo.set(
517
+ vehicleRayFrom.x + dir_x * distance,
518
+ y,
519
+ vehicleRayFrom.z + dir_z * distance
520
+ )
521
+ vehicleRayResult.reset()
522
+ const hit = world.raycastClosest( vehicleRayFrom, vehicleRayTo, {
523
+ skipBackfaces: true,
524
+ collisionFilterMask: 1
525
+ }, vehicleRayResult )
526
+
527
+ if ( hit && vehicleRayResult.body !== vehicleBody && Math.abs( vehicleRayResult.hitNormalWorld.y ) < 0.55 ) {
528
+
529
+ return true
530
+
531
+ }
532
+
533
+ }
534
+
535
+ return false
536
+
537
+ }
538
+
539
+ function updateVehicleInput( deltaTime ) {
540
+
541
+ if ( ! vehicleBody ) return
542
+
543
+ const holding_a = !! keyStates[ 'KeyA' ]
544
+ const holding_d = !! keyStates[ 'KeyD' ]
545
+ const braking = holding_a && holding_d
546
+
547
+ if ( holding_a && ! holding_d ) {
548
+
549
+ vehicleHeading += VEHICLE_TURN_SPEED * deltaTime
550
+
551
+ } else if ( holding_d && ! holding_a ) {
552
+
553
+ vehicleHeading -= VEHICLE_TURN_SPEED * deltaTime
554
+
555
+ }
556
+
557
+ if ( braking ) {
558
+
559
+ vehicleSpeed -= VEHICLE_BRAKE * deltaTime
560
+ if ( vehicleSpeed < - VEHICLE_REVERSE_SPEED ) vehicleSpeed = - VEHICLE_REVERSE_SPEED
561
+
562
+ } else {
563
+
564
+ vehicleSpeed += VEHICLE_ACCELERATION * deltaTime
565
+ if ( vehicleSpeed > VEHICLE_SPEED ) vehicleSpeed = VEHICLE_SPEED
566
+
567
+ }
568
+
569
+ }
570
+
571
+ function applyVehicleDrive( deltaTime ) {
572
+
573
+ if ( ! vehicleBody ) return
574
+
575
+ const holding_a = !! keyStates[ 'KeyA' ]
576
+ const holding_d = !! keyStates[ 'KeyD' ]
577
+ const forward_x = - Math.sin( vehicleHeading )
578
+ const forward_z = - Math.cos( vehicleHeading )
579
+ const move_x = forward_x * vehicleSpeed * deltaTime
580
+ const move_z = forward_z * vehicleSpeed * deltaTime
581
+ const travel = Math.hypot( move_x, move_z )
582
+ const dir_x = travel > 1e-8 ? move_x / travel : forward_x
583
+ const dir_z = travel > 1e-8 ? move_z / travel : forward_z
584
+ const bumper = Math.max( vehicleSize.x, vehicleSize.z, 0.4 ) * 0.5
585
+
586
+ vehicleBody.wakeUp()
587
+
588
+ if ( travel > 0 && ! wallAhead( dir_x, dir_z, bumper + travel ) ) {
589
+
590
+ vehicleBody.position.x += move_x
591
+ vehicleBody.position.z += move_z
592
+
593
+ }
594
+
595
+ vehicleBody.velocity.x = forward_x * vehicleSpeed
596
+ vehicleBody.velocity.z = forward_z * vehicleSpeed
597
+ vehicleBody.angularVelocity.y = ( holding_a && ! holding_d ? VEHICLE_TURN_SPEED : 0 ) + ( holding_d && ! holding_a ? - VEHICLE_TURN_SPEED : 0 )
598
+
599
+ }
600
+
601
+ function syncPhysicsMeshes() {
602
+
603
+ if ( vehicle && vehicleBody ) {
604
+
605
+ vehicle.position.copy( vehicleBody.position )
606
+ vehicle.quaternion.copy( vehicleBody.quaternion )
607
+
608
+ }
609
+
610
+ for ( const sphere of spheres ) {
611
+
612
+ sphere.mesh.position.copy( sphere.body.position )
613
+ sphere.mesh.quaternion.copy( sphere.body.quaternion )
614
+
615
+ }
616
+
617
+ }
618
+
619
+ function followVehiclePosition() {
620
+
621
+ if ( ! vehicle ) return
622
+
623
+ camera.position.copy( vehicle.position )
624
+
625
+ }
626
+
627
+ function teleportPlayerIfOob() {
628
+
629
+ if ( camera.position.y <= - 25 ) {
630
+
631
+ playerBody.position.set( 0, 1, 0 )
632
+ playerBody.velocity.set( 0, 0, 0 )
633
+ playerVelocity.set( 0, 0, 0 )
634
+
635
+ if ( ! vehicle ) {
636
+
637
+ camera.position.set( 0, 1.4, 0 )
638
+ camera.rotation.set( 0, 0, 0 )
639
+
640
+ }
641
+
642
+ }
643
+
644
+ }
645
+
646
+ function animate() {
647
+
648
+ timer.update()
649
+
650
+ const deltaTime = Math.min( 0.05, timer.getDelta() )
651
+
652
+ updatePlayer( deltaTime )
653
+ keepVehicleUpright( deltaTime )
654
+ updateVehicleInput( deltaTime )
655
+
656
+ if ( worldReady ) {
657
+
658
+ world.step( 1 / 90, deltaTime, 8 )
659
+
660
+ }
661
+
662
+ applyVehicleDrive( deltaTime )
663
+ keepVehicleUpright( deltaTime )
664
+ syncPhysicsMeshes()
665
+ followVehiclePosition()
666
+ teleportPlayerIfOob()
667
+
668
+ if ( debugPhysics ) {
669
+
670
+ cannonDebugger.update()
671
+
672
+ }
673
+
674
+ renderer.render( scene, camera )
675
+ stats.update()
676
+
677
+ }