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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/production_theme.scss +4 -1
- data/app/assets/stylesheets/wco/main.scss +4 -0
- data/app/assets/stylesheets/wco/studio.scss +12 -4
- data/app/controllers/wco/api/newspartials_controller.rb +1 -1
- data/app/controllers/wco/api/newsproducer_controller.rb +5 -0
- data/app/controllers/wco/newspartials_controller.rb +1 -0
- data/app/models/iro/datapoint.rb +1 -1
- data/app/models/iro/option.rb +16 -2
- data/app/models/iro/position.rb +4 -6
- data/app/models/iro/priceitem.rb +42 -4
- data/app/models/iro/purse.rb +2 -0
- data/app/models/iro/stock.rb +1 -2
- data/app/models/tda/option.rb +4 -9
- data/app/models/wco/newspartial.rb +12 -4
- data/app/views/wco/api/newsproducer/README_newsproducer.txt +12 -0
- data/app/views/wco/api/newsproducer/_scenes.js-bk +20 -0
- data/app/views/wco/api/newsproducer/_scenes.js.erb +15 -0
- data/app/views/wco/api/newsproducer/{_studio_blue.js → _studio_blue1.js} +4 -1
- data/app/views/wco/api/newsproducer/_studio_blue2.js +912 -0
- data/app/views/wco/api/newsproducer/_studio_coc.js +769 -0
- data/app/views/wco/api/newsproducer/_studio_coc.js-bk +389 -0
- data/app/views/wco/api/newsproducer/_studio_coc.js-cannon +677 -0
- data/app/views/wco/api/newsproducer/_studio_fps.js +1018 -0
- data/app/views/wco/api/newsproducer/_studio_ra2.js +255 -0
- data/app/views/wco/api/newsproducer/_touch_controls.js +708 -0
- data/app/views/wco/api/newsproducer/_touch_pad.css +200 -0
- data/app/views/wco/api/newsproducer/games_fps.html +504 -0
- data/app/views/wco/api/newsproducer/{studio_green.html.erb → studio_blue0.html.erb} +1 -1
- data/app/views/wco/api/newsproducer/{studio_blue.html.erb → studio_blue1.html.erb} +3 -3
- data/app/views/wco/api/newsproducer/studio_blue2.html.erb +146 -0
- data/app/views/wco/api/newsproducer/studio_coc.html.erb +95 -0
- data/app/views/wco/api/newsproducer/studio_fps.html.erb +199 -0
- data/app/views/wco/api/newsproducer/studio_ra2.html.erb +95 -0
- data/app/views/wco/newspartials/_show_in_newsvideo.haml +6 -7
- data/app/views/wco/newspartials/index.haml-trash +6 -0
- data/config/initializers/08_integrations.rb +14 -1
- data/config/initializers/08_integrations.rb-example +0 -2
- data/config/routes.rb +3 -0
- data/lib/shortcuts.rb +1 -1
- metadata +23 -7
- data/app/views/wco/api/newsproducer/README.txt +0 -2
- /data/app/views/wco/api/newsproducer/{_studio_green.js → _studio_blue0.js} +0 -0
|
@@ -0,0 +1,1018 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
**/
|
|
4
|
+
const logg = (a, b="", c=null) => {
|
|
5
|
+
if ('undefined' === typeof window) { return }
|
|
6
|
+
c = "string" === typeof c ? c : b.replace(/\W/g, '')
|
|
7
|
+
if (c.length) { window[c] = a }
|
|
8
|
+
console.log(`+++ ${b}:`, a) // eslint-disable-line no-console
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
document.addEventListener("keydown", e => {
|
|
12
|
+
if (e.code === "Space" && !["INPUT", "TEXTAREA"].includes(e.target.tagName)) {
|
|
13
|
+
e.preventDefault()
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// const MODELS_ROOT = '/vendor/models'
|
|
18
|
+
// const MODELS_ROOT = "https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.4.0/public/vendor/models"
|
|
19
|
+
const SCENE_STOR = 'studio'
|
|
20
|
+
const scenes = await fetch('https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.4.3/public/vendor/models/scenes/index.json').then(r => r.json())
|
|
21
|
+
$.each(scenes, (name) => {
|
|
22
|
+
$('<option>', { value: name, text: name }).appendTo($('select.studio'))
|
|
23
|
+
})
|
|
24
|
+
try {
|
|
25
|
+
const saved = localStorage.getItem( SCENE_STOR )
|
|
26
|
+
if (saved && scenes[saved]) $('select.studio').val(saved)
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.log(error)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let scene_url = scenes[$('select.studio').val()] || scenes.collision_world
|
|
32
|
+
let specsheet = null
|
|
33
|
+
|
|
34
|
+
let width = 50 // 854
|
|
35
|
+
let height = 50 // 480
|
|
36
|
+
function is_mobile() {
|
|
37
|
+
return window.matchMedia('(max-width: 768px)').matches || /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent)
|
|
38
|
+
}
|
|
39
|
+
function size_canvas() {
|
|
40
|
+
if (is_mobile()) {
|
|
41
|
+
width = Math.round(window.innerWidth * 0.8)
|
|
42
|
+
height = Math.round(window.innerHeight * 0.8)
|
|
43
|
+
} else {
|
|
44
|
+
width = 854
|
|
45
|
+
height = 480
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
size_canvas()
|
|
49
|
+
let slug = '<ccapture>'
|
|
50
|
+
const fps = 30
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
import * as THREE from 'three';
|
|
54
|
+
|
|
55
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
56
|
+
|
|
57
|
+
import { Octree } from 'three/addons/math/Octree.js';
|
|
58
|
+
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
|
|
59
|
+
|
|
60
|
+
import { Capsule } from 'three/addons/math/Capsule.js';
|
|
61
|
+
import { TransformControls } from 'three/addons/controls/TransformControls.js';
|
|
62
|
+
|
|
63
|
+
window.THREE = THREE
|
|
64
|
+
const TouchControls = window.TouchControls
|
|
65
|
+
const MovementPad = window.MovementPad
|
|
66
|
+
const RotationPad = window.RotationPad
|
|
67
|
+
|
|
68
|
+
const timer = new THREE.Timer();
|
|
69
|
+
timer.connect( document );
|
|
70
|
+
|
|
71
|
+
const scene = new THREE.Scene();
|
|
72
|
+
scene.background = new THREE.Color( 0x88ccee );
|
|
73
|
+
scene.fog = new THREE.Fog( 0x88ccee, 0, 50 );
|
|
74
|
+
|
|
75
|
+
const camera = new THREE.PerspectiveCamera( 70, width / height, 0.1, 1000 );
|
|
76
|
+
camera.rotation.order = 'YXZ';
|
|
77
|
+
|
|
78
|
+
const fillLight1 = new THREE.HemisphereLight( 0x8dc1de, 0x00668d, 1.5 );
|
|
79
|
+
fillLight1.position.set( 2, 1, 1 );
|
|
80
|
+
scene.add( fillLight1 );
|
|
81
|
+
|
|
82
|
+
const directionalLight = new THREE.DirectionalLight( 0xffffff, 2.5 );
|
|
83
|
+
directionalLight.position.set( - 5, 25, - 1 );
|
|
84
|
+
directionalLight.castShadow = true;
|
|
85
|
+
directionalLight.shadow.camera.near = 0.01;
|
|
86
|
+
directionalLight.shadow.camera.far = 500;
|
|
87
|
+
directionalLight.shadow.camera.right = 30;
|
|
88
|
+
directionalLight.shadow.camera.left = - 30;
|
|
89
|
+
directionalLight.shadow.camera.top = 30;
|
|
90
|
+
directionalLight.shadow.camera.bottom = - 30;
|
|
91
|
+
directionalLight.shadow.mapSize.width = 1024;
|
|
92
|
+
directionalLight.shadow.mapSize.height = 1024;
|
|
93
|
+
directionalLight.shadow.radius = 4;
|
|
94
|
+
directionalLight.shadow.bias = - 0.00006;
|
|
95
|
+
scene.add( directionalLight );
|
|
96
|
+
|
|
97
|
+
const container = document.getElementById( 'rotatingC' );
|
|
98
|
+
container.style.touchAction = 'none'
|
|
99
|
+
container.style.width = width + 'px'
|
|
100
|
+
container.style.height = height + 'px'
|
|
101
|
+
|
|
102
|
+
const renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
103
|
+
renderer.setPixelRatio( window.devicePixelRatio );
|
|
104
|
+
renderer.setSize( width, height );
|
|
105
|
+
renderer.setAnimationLoop( animate );
|
|
106
|
+
renderer.shadowMap.enabled = true;
|
|
107
|
+
renderer.shadowMap.type = THREE.VSMShadowMap;
|
|
108
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
109
|
+
renderer.domElement.style.touchAction = 'none'
|
|
110
|
+
container.appendChild( renderer.domElement );
|
|
111
|
+
|
|
112
|
+
let touch_controls = null
|
|
113
|
+
|
|
114
|
+
const GRAVITY = 30
|
|
115
|
+
let walk_speed = 12
|
|
116
|
+
const AIR_SPEED_FACTOR = 0.32
|
|
117
|
+
let strafe_factor = 0.5
|
|
118
|
+
let strafe_deadzone = 0.35
|
|
119
|
+
|
|
120
|
+
const NUM_SPHERES = 100;
|
|
121
|
+
const SPHERE_RADIUS = 0.2;
|
|
122
|
+
|
|
123
|
+
const STEPS_PER_FRAME = 5;
|
|
124
|
+
|
|
125
|
+
const sphereGeometry = new THREE.IcosahedronGeometry( SPHERE_RADIUS, 5 );
|
|
126
|
+
const sphereMaterial = new THREE.MeshLambertMaterial( { color: 0xdede8d } );
|
|
127
|
+
|
|
128
|
+
const spheres = [];
|
|
129
|
+
let sphereIdx = 0;
|
|
130
|
+
|
|
131
|
+
for ( let i = 0; i < NUM_SPHERES; i ++ ) {
|
|
132
|
+
|
|
133
|
+
const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
|
|
134
|
+
sphere.castShadow = true;
|
|
135
|
+
sphere.receiveShadow = true;
|
|
136
|
+
|
|
137
|
+
scene.add( sphere );
|
|
138
|
+
|
|
139
|
+
spheres.push( {
|
|
140
|
+
mesh: sphere,
|
|
141
|
+
collider: new THREE.Sphere( new THREE.Vector3( 0, - 100, 0 ), SPHERE_RADIUS ),
|
|
142
|
+
velocity: new THREE.Vector3()
|
|
143
|
+
} );
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function weapons_on() {
|
|
148
|
+
return $('input[name=weaponCtrl]').is(':checked')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function set_weapons( on ) {
|
|
152
|
+
spheres.forEach( sphere => {
|
|
153
|
+
sphere.mesh.visible = on
|
|
154
|
+
if ( !on ) {
|
|
155
|
+
sphere.collider.center.set( 0, -100, 0 )
|
|
156
|
+
sphere.velocity.set( 0, 0, 0 )
|
|
157
|
+
sphere.mesh.position.copy( sphere.collider.center )
|
|
158
|
+
}
|
|
159
|
+
} )
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let worldOctree = new Octree()
|
|
163
|
+
let octree_helper = null
|
|
164
|
+
let studio = null
|
|
165
|
+
|
|
166
|
+
const playerCollider = new Capsule( new THREE.Vector3( 0, 0.35, 0 ), new THREE.Vector3( 0, 1, 0 ), 0.35 );
|
|
167
|
+
|
|
168
|
+
const playerVelocity = new THREE.Vector3();
|
|
169
|
+
const playerDirection = new THREE.Vector3();
|
|
170
|
+
|
|
171
|
+
let playerOnFloor = false;
|
|
172
|
+
let mouseTime = 0;
|
|
173
|
+
|
|
174
|
+
const keyStates = {};
|
|
175
|
+
|
|
176
|
+
const vector1 = new THREE.Vector3();
|
|
177
|
+
const vector2 = new THREE.Vector3();
|
|
178
|
+
const vector3 = new THREE.Vector3();
|
|
179
|
+
|
|
180
|
+
const DOOR_W = 0.9
|
|
181
|
+
const DOOR_H = 2.1
|
|
182
|
+
const DOOR_D = 0.1
|
|
183
|
+
const door_color = new THREE.Color( 0x8a6a4a )
|
|
184
|
+
const door_aim_color = new THREE.Color( 0xffff00 )
|
|
185
|
+
const door_geo = new THREE.BoxGeometry( DOOR_W, DOOR_H, DOOR_D )
|
|
186
|
+
const door_label_geo = new THREE.PlaneGeometry( DOOR_W, DOOR_H )
|
|
187
|
+
const doors = []
|
|
188
|
+
let door_inside = null
|
|
189
|
+
let last_created_door = null
|
|
190
|
+
|
|
191
|
+
function wrap_text( ctx, text, max_width ) {
|
|
192
|
+
const words = String( text || '' ).replace( /_/g, ' ' ).split( /\s+/ ).filter( Boolean )
|
|
193
|
+
const lines = []
|
|
194
|
+
let line = ''
|
|
195
|
+
words.forEach( word => {
|
|
196
|
+
const test = line ? ( line + ' ' + word ) : word
|
|
197
|
+
if ( ctx.measureText( test ).width <= max_width ) {
|
|
198
|
+
line = test
|
|
199
|
+
} else {
|
|
200
|
+
if ( line ) lines.push( line )
|
|
201
|
+
if ( ctx.measureText( word ).width <= max_width ) {
|
|
202
|
+
line = word
|
|
203
|
+
} else {
|
|
204
|
+
let chunk = ''
|
|
205
|
+
for ( let i = 0; i < word.length; i++ ) {
|
|
206
|
+
const next = chunk + word[i]
|
|
207
|
+
if ( ctx.measureText( next ).width <= max_width ) {
|
|
208
|
+
chunk = next
|
|
209
|
+
} else {
|
|
210
|
+
if ( chunk ) lines.push( chunk )
|
|
211
|
+
chunk = word[i]
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
line = chunk
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} )
|
|
218
|
+
if ( line ) lines.push( line )
|
|
219
|
+
return lines
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function door_label_texture( text ) {
|
|
223
|
+
const canvas = document.createElement( 'canvas' )
|
|
224
|
+
canvas.width = 512
|
|
225
|
+
canvas.height = Math.round( 512 * DOOR_H / DOOR_W )
|
|
226
|
+
const ctx = canvas.getContext( '2d' )
|
|
227
|
+
const pad = 36
|
|
228
|
+
const font_size = 72
|
|
229
|
+
ctx.fillStyle = '#ffffff'
|
|
230
|
+
ctx.font = 'bold ' + font_size + 'px sans-serif'
|
|
231
|
+
ctx.textAlign = 'center'
|
|
232
|
+
ctx.textBaseline = 'middle'
|
|
233
|
+
const lines = wrap_text( ctx, text, canvas.width - pad * 2 )
|
|
234
|
+
const line_h = font_size * 1.15
|
|
235
|
+
const start_y = canvas.height / 2 - ( ( lines.length - 1 ) * line_h ) / 2
|
|
236
|
+
lines.forEach( ( line, i ) => {
|
|
237
|
+
ctx.fillText( line, canvas.width / 2, start_y + i * line_h )
|
|
238
|
+
} )
|
|
239
|
+
const tex = new THREE.CanvasTexture( canvas )
|
|
240
|
+
tex.needsUpdate = true
|
|
241
|
+
return tex
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function make_door( marker ) {
|
|
245
|
+
const name = marker.label || marker.name
|
|
246
|
+
const pos = marker.position || {}
|
|
247
|
+
const rot = marker.rotation || {}
|
|
248
|
+
const mat = new THREE.MeshLambertMaterial( { color: door_color } )
|
|
249
|
+
const mesh = new THREE.Mesh( door_geo, mat )
|
|
250
|
+
mesh.position.set( pos.x || 0, pos.y || 0, pos.z || 0 )
|
|
251
|
+
mesh.rotation.set(
|
|
252
|
+
THREE.MathUtils.degToRad(rot.x || 0),
|
|
253
|
+
THREE.MathUtils.degToRad(rot.y || 0),
|
|
254
|
+
THREE.MathUtils.degToRad(rot.z || 0)
|
|
255
|
+
)
|
|
256
|
+
mesh.castShadow = true
|
|
257
|
+
mesh.receiveShadow = true
|
|
258
|
+
mesh.userData.scene_name = name
|
|
259
|
+
mesh.userData.door_mat = mat
|
|
260
|
+
|
|
261
|
+
const label_mat = new THREE.MeshBasicMaterial( {
|
|
262
|
+
map: door_label_texture( name ),
|
|
263
|
+
transparent: true,
|
|
264
|
+
side: THREE.DoubleSide,
|
|
265
|
+
depthWrite: false
|
|
266
|
+
} )
|
|
267
|
+
const front = new THREE.Mesh( door_label_geo, label_mat )
|
|
268
|
+
front.position.set( 0, 0, DOOR_D / 2 + 0.006 )
|
|
269
|
+
mesh.add( front )
|
|
270
|
+
const back = new THREE.Mesh( door_label_geo, label_mat )
|
|
271
|
+
back.position.set( 0, 0, -( DOOR_D / 2 + 0.006 ) )
|
|
272
|
+
back.rotation.y = Math.PI
|
|
273
|
+
mesh.add( back )
|
|
274
|
+
|
|
275
|
+
scene.add( mesh )
|
|
276
|
+
doors.push( mesh )
|
|
277
|
+
return mesh
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function clear_doors() {
|
|
281
|
+
if ( door_helper ) {
|
|
282
|
+
door_helper.detach()
|
|
283
|
+
if ( door_helper_root ) door_helper_root.visible = false
|
|
284
|
+
}
|
|
285
|
+
doors.forEach( d => {
|
|
286
|
+
if ( d.parent ) d.parent.remove( d )
|
|
287
|
+
} )
|
|
288
|
+
doors.length = 0
|
|
289
|
+
door_inside = null
|
|
290
|
+
last_created_door = null
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function place_doors_from_specsheet() {
|
|
294
|
+
clear_doors()
|
|
295
|
+
const markers = specsheet && specsheet.markers
|
|
296
|
+
if ( !markers || !markers.length ) return
|
|
297
|
+
markers.forEach( make_door )
|
|
298
|
+
if ( door_helper_on() ) set_door_helper( true )
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let door_helper = null
|
|
302
|
+
let door_helper_root = null
|
|
303
|
+
|
|
304
|
+
function door_helper_on() {
|
|
305
|
+
return $('input[name=doorHelper]').is(':checked') || $('input[name=doorRotate]').is(':checked')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function door_helper_mode() {
|
|
309
|
+
if ($('input[name=doorRotate]').is(':checked')) return 'rotate'
|
|
310
|
+
if ($('input[name=doorHelper]').is(':checked')) return 'translate'
|
|
311
|
+
return null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function log_door_position() {
|
|
315
|
+
const obj = door_helper && door_helper.object
|
|
316
|
+
if ( !obj ) return
|
|
317
|
+
logg( {
|
|
318
|
+
x: obj.position.x,
|
|
319
|
+
y: obj.position.y,
|
|
320
|
+
z: obj.position.z,
|
|
321
|
+
name: obj.userData.scene_name
|
|
322
|
+
}, 'door position' )
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function log_door_rotation() {
|
|
326
|
+
const obj = door_helper && door_helper.object
|
|
327
|
+
if ( !obj ) return
|
|
328
|
+
logg( {
|
|
329
|
+
x: THREE.MathUtils.radToDeg(obj.rotation.x),
|
|
330
|
+
y: THREE.MathUtils.radToDeg(obj.rotation.y),
|
|
331
|
+
z: THREE.MathUtils.radToDeg(obj.rotation.z),
|
|
332
|
+
name: obj.userData.scene_name
|
|
333
|
+
}, 'door rotation' )
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function log_door_transform() {
|
|
337
|
+
if ( door_helper_mode() === 'rotate' ) log_door_rotation()
|
|
338
|
+
else log_door_position()
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function ensure_door_helper() {
|
|
342
|
+
if (door_helper) return
|
|
343
|
+
door_helper = new TransformControls(camera, renderer.domElement)
|
|
344
|
+
door_helper.addEventListener('objectChange', log_door_transform)
|
|
345
|
+
door_helper_root = door_helper.getHelper ? door_helper.getHelper() : door_helper
|
|
346
|
+
scene.add(door_helper_root)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function set_door_helper(on) {
|
|
350
|
+
if (on) {
|
|
351
|
+
if (document.pointerLockElement) document.exitPointerLock()
|
|
352
|
+
ensure_door_helper()
|
|
353
|
+
const target = last_created_door || doors[doors.length - 1]
|
|
354
|
+
if (!target) {
|
|
355
|
+
door_helper.detach()
|
|
356
|
+
door_helper.enabled = false
|
|
357
|
+
if (door_helper_root) door_helper_root.visible = false
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
door_helper.enabled = true
|
|
361
|
+
door_helper.setMode(door_helper_mode() || 'translate')
|
|
362
|
+
door_helper.attach(target)
|
|
363
|
+
if (door_helper_root) door_helper_root.visible = true
|
|
364
|
+
log_door_transform()
|
|
365
|
+
} else if (door_helper) {
|
|
366
|
+
door_helper.detach()
|
|
367
|
+
door_helper.enabled = false
|
|
368
|
+
if (door_helper_root) door_helper_root.visible = false
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
$('input[name=doorHelper]').on('change', function() {
|
|
373
|
+
if (this.checked) $('input[name=doorRotate]').prop('checked', false)
|
|
374
|
+
set_door_helper(door_helper_on())
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
$('input[name=doorRotate]').on('change', function() {
|
|
378
|
+
if (this.checked) $('input[name=doorHelper]').prop('checked', false)
|
|
379
|
+
set_door_helper(door_helper_on())
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
$('#createDoor').on('click', function() {
|
|
383
|
+
last_created_door = make_door({ name: '', position: { x: 0, y: 0, z: 0 } })
|
|
384
|
+
if (door_helper_on()) {
|
|
385
|
+
if (document.pointerLockElement) document.exitPointerLock()
|
|
386
|
+
ensure_door_helper()
|
|
387
|
+
door_helper.enabled = true
|
|
388
|
+
door_helper.setMode(door_helper_mode() || 'translate')
|
|
389
|
+
door_helper.attach(last_created_door)
|
|
390
|
+
if (door_helper_root) door_helper_root.visible = true
|
|
391
|
+
log_door_transform()
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const aim_ray = new THREE.Raycaster()
|
|
396
|
+
const aim_dir = new THREE.Vector3()
|
|
397
|
+
const door_box = new THREE.Box3()
|
|
398
|
+
const player_box = new THREE.Box3()
|
|
399
|
+
let studio_loading = false
|
|
400
|
+
|
|
401
|
+
function player_in_door( mesh ) {
|
|
402
|
+
mesh.updateMatrixWorld(true)
|
|
403
|
+
door_box.setFromObject(mesh)
|
|
404
|
+
door_box.expandByScalar(0.3)
|
|
405
|
+
player_box.makeEmpty()
|
|
406
|
+
player_box.expandByPoint(playerCollider.start)
|
|
407
|
+
player_box.expandByPoint(playerCollider.end)
|
|
408
|
+
player_box.expandByScalar(playerCollider.radius)
|
|
409
|
+
return player_box.intersectsBox(door_box)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function check_door_portal() {
|
|
413
|
+
const hit = doors.find( d => player_in_door( d ) )
|
|
414
|
+
if ( hit && hit !== door_inside && !studio_loading ) {
|
|
415
|
+
const dest = hit.userData.scene_name
|
|
416
|
+
if ( dest && scenes[dest] && $('select.studio').val() !== dest ) {
|
|
417
|
+
$('select.studio').val( dest )
|
|
418
|
+
select_studio( dest )
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
door_inside = hit || null
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function door_from_hit( obj ) {
|
|
425
|
+
while ( obj ) {
|
|
426
|
+
if ( doors.indexOf( obj ) !== -1 ) return obj
|
|
427
|
+
obj = obj.parent
|
|
428
|
+
}
|
|
429
|
+
return null
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function update_door_aim() {
|
|
433
|
+
camera.getWorldDirection( aim_dir )
|
|
434
|
+
aim_ray.set( camera.position, aim_dir )
|
|
435
|
+
const hits = aim_ray.intersectObjects( doors, true )
|
|
436
|
+
const aimed = hits.length ? door_from_hit( hits[0].object ) : null
|
|
437
|
+
doors.forEach( d => {
|
|
438
|
+
const mat = d.userData.door_mat
|
|
439
|
+
if ( d === aimed ) {
|
|
440
|
+
mat.color.copy( door_aim_color )
|
|
441
|
+
mat.emissive.setHex( 0x333300 )
|
|
442
|
+
} else {
|
|
443
|
+
mat.color.copy( door_color )
|
|
444
|
+
mat.emissive.setHex( 0x000000 )
|
|
445
|
+
}
|
|
446
|
+
} )
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
document.addEventListener( 'keydown', ( event ) => {
|
|
450
|
+
|
|
451
|
+
keyStates[ event.code ] = true;
|
|
452
|
+
|
|
453
|
+
} );
|
|
454
|
+
|
|
455
|
+
document.addEventListener( 'keyup', ( event ) => {
|
|
456
|
+
|
|
457
|
+
keyStates[ event.code ] = false;
|
|
458
|
+
|
|
459
|
+
} );
|
|
460
|
+
|
|
461
|
+
container.addEventListener( 'mousedown', () => {
|
|
462
|
+
|
|
463
|
+
if ( current_ctrl_type() !== 'fps' ) return
|
|
464
|
+
if ( door_helper_on() ) return
|
|
465
|
+
document.body.requestPointerLock();
|
|
466
|
+
|
|
467
|
+
mouseTime = performance.now();
|
|
468
|
+
|
|
469
|
+
} );
|
|
470
|
+
|
|
471
|
+
document.addEventListener( 'mouseup', () => {
|
|
472
|
+
|
|
473
|
+
if ( current_ctrl_type() !== 'fps' ) return
|
|
474
|
+
if ( door_helper_on() ) return
|
|
475
|
+
if ( document.pointerLockElement !== null ) throwBall();
|
|
476
|
+
|
|
477
|
+
} );
|
|
478
|
+
|
|
479
|
+
document.body.addEventListener( 'mousemove', ( event ) => {
|
|
480
|
+
|
|
481
|
+
if ( current_ctrl_type() !== 'fps' ) return
|
|
482
|
+
if ( document.pointerLockElement === document.body ) {
|
|
483
|
+
|
|
484
|
+
camera.rotation.y -= event.movementX / 500;
|
|
485
|
+
camera.rotation.x -= event.movementY / 500;
|
|
486
|
+
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
} );
|
|
490
|
+
|
|
491
|
+
window.addEventListener( 'resize', onWindowResize );
|
|
492
|
+
|
|
493
|
+
function onWindowResize() {
|
|
494
|
+
|
|
495
|
+
size_canvas()
|
|
496
|
+
container.style.width = width + 'px'
|
|
497
|
+
container.style.height = height + 'px'
|
|
498
|
+
camera.aspect = width / height
|
|
499
|
+
camera.updateProjectionMatrix()
|
|
500
|
+
renderer.setSize( width, height )
|
|
501
|
+
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function throwBall() {
|
|
505
|
+
|
|
506
|
+
if ( !weapons_on() ) return
|
|
507
|
+
|
|
508
|
+
const sphere = spheres[ sphereIdx ];
|
|
509
|
+
|
|
510
|
+
camera.getWorldDirection( playerDirection );
|
|
511
|
+
|
|
512
|
+
sphere.collider.center.copy( playerCollider.end ).addScaledVector( playerDirection, playerCollider.radius * 1.5 );
|
|
513
|
+
|
|
514
|
+
// throw the ball with more force if we hold the button longer, and if we move forward
|
|
515
|
+
|
|
516
|
+
const impulse = 15 + 30 * ( 1 - Math.exp( ( mouseTime - performance.now() ) * 0.001 ) );
|
|
517
|
+
|
|
518
|
+
sphere.velocity.copy( playerDirection ).multiplyScalar( impulse );
|
|
519
|
+
sphere.velocity.addScaledVector( playerVelocity, 2 );
|
|
520
|
+
|
|
521
|
+
sphereIdx = ( sphereIdx + 1 ) % spheres.length;
|
|
522
|
+
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function playerCollisions() {
|
|
526
|
+
|
|
527
|
+
const result = worldOctree.capsuleIntersect( playerCollider );
|
|
528
|
+
|
|
529
|
+
playerOnFloor = false;
|
|
530
|
+
|
|
531
|
+
if ( result ) {
|
|
532
|
+
|
|
533
|
+
// determine if the surface we bumped into is something we can stand on
|
|
534
|
+
|
|
535
|
+
playerOnFloor = result.normal.y >= 0.15; // allow slopes up to ~81° but ignore sheer vertical walls
|
|
536
|
+
|
|
537
|
+
if ( ! playerOnFloor ) {
|
|
538
|
+
|
|
539
|
+
playerVelocity.addScaledVector( result.normal, - result.normal.dot( playerVelocity ) );
|
|
540
|
+
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if ( result.depth >= 1e-10 ) {
|
|
544
|
+
|
|
545
|
+
playerCollider.translate( result.normal.multiplyScalar( result.depth ) );
|
|
546
|
+
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function updatePlayer( deltaTime ) {
|
|
554
|
+
|
|
555
|
+
let damping = Math.exp( - 4 * deltaTime ) - 1;
|
|
556
|
+
|
|
557
|
+
if ( ! playerOnFloor ) {
|
|
558
|
+
|
|
559
|
+
playerVelocity.y -= GRAVITY * deltaTime;
|
|
560
|
+
|
|
561
|
+
// small air resistance
|
|
562
|
+
damping *= 0.1;
|
|
563
|
+
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
playerVelocity.addScaledVector( playerVelocity, damping );
|
|
567
|
+
|
|
568
|
+
const deltaPosition = playerVelocity.clone().multiplyScalar( deltaTime );
|
|
569
|
+
playerCollider.translate( deltaPosition );
|
|
570
|
+
|
|
571
|
+
playerCollisions();
|
|
572
|
+
|
|
573
|
+
camera.position.copy( playerCollider.end );
|
|
574
|
+
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function playerSphereCollision( sphere ) {
|
|
578
|
+
|
|
579
|
+
const center = vector1.addVectors( playerCollider.start, playerCollider.end ).multiplyScalar( 0.5 );
|
|
580
|
+
|
|
581
|
+
const sphere_center = sphere.collider.center;
|
|
582
|
+
|
|
583
|
+
const r = playerCollider.radius + sphere.collider.radius;
|
|
584
|
+
const r2 = r * r;
|
|
585
|
+
|
|
586
|
+
// approximation: player = 3 spheres
|
|
587
|
+
|
|
588
|
+
for ( const point of [ playerCollider.start, playerCollider.end, center ] ) {
|
|
589
|
+
|
|
590
|
+
const d2 = point.distanceToSquared( sphere_center );
|
|
591
|
+
|
|
592
|
+
if ( d2 < r2 ) {
|
|
593
|
+
|
|
594
|
+
const normal = vector1.subVectors( point, sphere_center ).normalize();
|
|
595
|
+
const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( playerVelocity ) );
|
|
596
|
+
const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( sphere.velocity ) );
|
|
597
|
+
|
|
598
|
+
playerVelocity.add( v2 ).sub( v1 );
|
|
599
|
+
sphere.velocity.add( v1 ).sub( v2 );
|
|
600
|
+
|
|
601
|
+
const d = ( r - Math.sqrt( d2 ) ) / 2;
|
|
602
|
+
sphere_center.addScaledVector( normal, - d );
|
|
603
|
+
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function spheresCollisions() {
|
|
611
|
+
|
|
612
|
+
for ( let i = 0, length = spheres.length; i < length; i ++ ) {
|
|
613
|
+
|
|
614
|
+
const s1 = spheres[ i ];
|
|
615
|
+
|
|
616
|
+
for ( let j = i + 1; j < length; j ++ ) {
|
|
617
|
+
|
|
618
|
+
const s2 = spheres[ j ];
|
|
619
|
+
|
|
620
|
+
const d2 = s1.collider.center.distanceToSquared( s2.collider.center );
|
|
621
|
+
const r = s1.collider.radius + s2.collider.radius;
|
|
622
|
+
const r2 = r * r;
|
|
623
|
+
|
|
624
|
+
if ( d2 < r2 ) {
|
|
625
|
+
|
|
626
|
+
const normal = vector1.subVectors( s1.collider.center, s2.collider.center ).normalize();
|
|
627
|
+
const v1 = vector2.copy( normal ).multiplyScalar( normal.dot( s1.velocity ) );
|
|
628
|
+
const v2 = vector3.copy( normal ).multiplyScalar( normal.dot( s2.velocity ) );
|
|
629
|
+
|
|
630
|
+
s1.velocity.add( v2 ).sub( v1 );
|
|
631
|
+
s2.velocity.add( v1 ).sub( v2 );
|
|
632
|
+
|
|
633
|
+
const d = ( r - Math.sqrt( d2 ) ) / 2;
|
|
634
|
+
|
|
635
|
+
s1.collider.center.addScaledVector( normal, d );
|
|
636
|
+
s2.collider.center.addScaledVector( normal, - d );
|
|
637
|
+
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function updateSpheres( deltaTime ) {
|
|
647
|
+
|
|
648
|
+
if ( !weapons_on() ) return
|
|
649
|
+
|
|
650
|
+
spheres.forEach( sphere => {
|
|
651
|
+
|
|
652
|
+
sphere.collider.center.addScaledVector( sphere.velocity, deltaTime );
|
|
653
|
+
|
|
654
|
+
const result = worldOctree.sphereIntersect( sphere.collider );
|
|
655
|
+
|
|
656
|
+
if ( result ) {
|
|
657
|
+
|
|
658
|
+
sphere.velocity.addScaledVector( result.normal, - result.normal.dot( sphere.velocity ) * 1.5 );
|
|
659
|
+
sphere.collider.center.add( result.normal.multiplyScalar( result.depth ) );
|
|
660
|
+
|
|
661
|
+
} else {
|
|
662
|
+
|
|
663
|
+
sphere.velocity.y -= GRAVITY * deltaTime;
|
|
664
|
+
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const damping = Math.exp( - 1.5 * deltaTime ) - 1;
|
|
668
|
+
sphere.velocity.addScaledVector( sphere.velocity, damping );
|
|
669
|
+
|
|
670
|
+
playerSphereCollision( sphere );
|
|
671
|
+
|
|
672
|
+
} );
|
|
673
|
+
|
|
674
|
+
spheresCollisions();
|
|
675
|
+
|
|
676
|
+
for ( const sphere of spheres ) {
|
|
677
|
+
|
|
678
|
+
sphere.mesh.position.copy( sphere.collider.center );
|
|
679
|
+
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function getForwardVector() {
|
|
685
|
+
|
|
686
|
+
camera.getWorldDirection( playerDirection );
|
|
687
|
+
playerDirection.y = 0;
|
|
688
|
+
playerDirection.normalize();
|
|
689
|
+
|
|
690
|
+
return playerDirection;
|
|
691
|
+
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function getSideVector() {
|
|
695
|
+
|
|
696
|
+
camera.getWorldDirection( playerDirection );
|
|
697
|
+
playerDirection.y = 0;
|
|
698
|
+
playerDirection.normalize();
|
|
699
|
+
playerDirection.cross( camera.up );
|
|
700
|
+
|
|
701
|
+
return playerDirection;
|
|
702
|
+
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function controls( deltaTime ) {
|
|
706
|
+
|
|
707
|
+
// gives a bit of air control
|
|
708
|
+
const speedDelta = deltaTime * ( playerOnFloor ? walk_speed : walk_speed * AIR_SPEED_FACTOR )
|
|
709
|
+
|
|
710
|
+
if ( keyStates[ 'KeyW' ] ) {
|
|
711
|
+
|
|
712
|
+
playerVelocity.add( getForwardVector().multiplyScalar( speedDelta ) );
|
|
713
|
+
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if ( keyStates[ 'KeyS' ] ) {
|
|
717
|
+
|
|
718
|
+
playerVelocity.add( getForwardVector().multiplyScalar( - speedDelta ) );
|
|
719
|
+
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if ( keyStates[ 'KeyA' ] ) {
|
|
723
|
+
|
|
724
|
+
playerVelocity.add( getSideVector().multiplyScalar( - speedDelta ) );
|
|
725
|
+
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if ( keyStates[ 'KeyD' ] ) {
|
|
729
|
+
|
|
730
|
+
playerVelocity.add( getSideVector().multiplyScalar( speedDelta ) );
|
|
731
|
+
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if ( playerOnFloor ) {
|
|
735
|
+
|
|
736
|
+
if ( keyStates[ 'Space' ] ) {
|
|
737
|
+
|
|
738
|
+
playerVelocity.y = 15;
|
|
739
|
+
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function rescale(model, config) {
|
|
747
|
+
const box = new THREE.Box3().setFromObject(model)
|
|
748
|
+
const size = box.getSize(new THREE.Vector3())
|
|
749
|
+
const currentHeight = size.y
|
|
750
|
+
const scale = config.height / currentHeight
|
|
751
|
+
model.scale.setScalar(scale)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function scene_cfg(s) {
|
|
755
|
+
logg(s, 'scene_cfg()')
|
|
756
|
+
|
|
757
|
+
if (typeof s === 'string') {
|
|
758
|
+
return { url: s } // , height: 3.3 }
|
|
759
|
+
}
|
|
760
|
+
if (typeof s.url === 'string') {
|
|
761
|
+
return { ...s, url: s.url }
|
|
762
|
+
}
|
|
763
|
+
logg(s, 'out')
|
|
764
|
+
return s
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function apply_studio_mesh(root) {
|
|
768
|
+
root.traverse(child => {
|
|
769
|
+
if (child.isMesh) {
|
|
770
|
+
child.castShadow = true
|
|
771
|
+
child.receiveShadow = true
|
|
772
|
+
if (child.material && child.material.map) {
|
|
773
|
+
child.material.map.anisotropy = 4
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
})
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function reset_player() {
|
|
780
|
+
playerCollider.start.set(0, 0.35, 0)
|
|
781
|
+
playerCollider.end.set(0, 1, 0)
|
|
782
|
+
playerCollider.radius = 0.35
|
|
783
|
+
playerVelocity.set(0, 0, 0)
|
|
784
|
+
camera.position.copy(playerCollider.end)
|
|
785
|
+
camera.rotation.set(0, 0, 0)
|
|
786
|
+
if (touch_controls) {
|
|
787
|
+
touch_controls.setPosition(0, 1, 0)
|
|
788
|
+
touch_controls.setRotation(0, 0)
|
|
789
|
+
}
|
|
790
|
+
door_inside = doors.find( d => player_in_door( d ) ) || null
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const loader = new GLTFLoader()
|
|
794
|
+
|
|
795
|
+
function specsheet_url(glb_url) {
|
|
796
|
+
return glb_url.replace(/[^/]+$/, 'specsheet.json')
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async function load_specsheet(glb_url) {
|
|
800
|
+
specsheet = null
|
|
801
|
+
try {
|
|
802
|
+
const res = await fetch(specsheet_url(glb_url))
|
|
803
|
+
if (!res.ok) return
|
|
804
|
+
specsheet = await res.json()
|
|
805
|
+
logg(specsheet, 'specsheet')
|
|
806
|
+
} catch (error) {
|
|
807
|
+
console.log(error)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async function load_studio(s) {
|
|
812
|
+
const cfg = scene_cfg(s)
|
|
813
|
+
if (studio && studio.parent) studio.parent.remove(studio)
|
|
814
|
+
if (octree_helper && octree_helper.parent) octree_helper.parent.remove(octree_helper)
|
|
815
|
+
const gltf = await loader.loadAsync(cfg.url)
|
|
816
|
+
await load_specsheet(cfg.url)
|
|
817
|
+
studio = gltf.scene
|
|
818
|
+
if (cfg.height) rescale(studio, { height: cfg.height })
|
|
819
|
+
apply_studio_mesh(studio)
|
|
820
|
+
const pos = specsheet && specsheet.position
|
|
821
|
+
if (pos) studio.position.set(pos.x || 0, pos.y || 0, pos.z || 0)
|
|
822
|
+
scene.add(studio)
|
|
823
|
+
worldOctree = new Octree()
|
|
824
|
+
worldOctree.fromGraphNode(studio)
|
|
825
|
+
octree_helper = new OctreeHelper(worldOctree)
|
|
826
|
+
octree_helper.visible = false
|
|
827
|
+
scene.add(octree_helper)
|
|
828
|
+
place_doors_from_specsheet()
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async function select_studio(name) {
|
|
832
|
+
if (!scenes[name] || studio_loading) return
|
|
833
|
+
studio_loading = true
|
|
834
|
+
localStorage.setItem(SCENE_STOR, name)
|
|
835
|
+
scene_url = scenes[name]
|
|
836
|
+
$('#status').text('Loading...')
|
|
837
|
+
$('#loading').text('Loading...')
|
|
838
|
+
try {
|
|
839
|
+
await load_studio(scene_url)
|
|
840
|
+
reset_player()
|
|
841
|
+
$('#status').text('loaded')
|
|
842
|
+
$('#loading').text('loaded')
|
|
843
|
+
} catch (error) {
|
|
844
|
+
console.log(error)
|
|
845
|
+
$('#status').text(error.toString())
|
|
846
|
+
$('#loading').text(error.toString())
|
|
847
|
+
}
|
|
848
|
+
studio_loading = false
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
$('select.studio').on('change', function() {
|
|
852
|
+
select_studio($(this).val())
|
|
853
|
+
})
|
|
854
|
+
const initial_studio = $('select.studio').val() || 'collision_world'
|
|
855
|
+
if (!$('select.studio').val()) $('select.studio').val(initial_studio)
|
|
856
|
+
select_studio(initial_studio)
|
|
857
|
+
|
|
858
|
+
function teleportPlayerIfOob() {
|
|
859
|
+
|
|
860
|
+
if ( camera.position.y <= - 25 ) {
|
|
861
|
+
|
|
862
|
+
playerCollider.start.set( 0, 0.35, 0 );
|
|
863
|
+
playerCollider.end.set( 0, 1, 0 );
|
|
864
|
+
playerCollider.radius = 0.35;
|
|
865
|
+
camera.position.copy( playerCollider.end );
|
|
866
|
+
camera.rotation.set( 0, 0, 0 );
|
|
867
|
+
if ( touch_controls ) {
|
|
868
|
+
touch_controls.setPosition( 0, 1, 0 )
|
|
869
|
+
touch_controls.setRotation( 0, 0 )
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function touch_pad_controls( deltaTime ) {
|
|
877
|
+
|
|
878
|
+
const speedDelta = deltaTime * ( playerOnFloor ? walk_speed : walk_speed * AIR_SPEED_FACTOR )
|
|
879
|
+
const fwd = touch_controls.stick_y
|
|
880
|
+
let side = touch_controls.stick_x
|
|
881
|
+
if ( Math.abs( side ) < strafe_deadzone ) side = 0
|
|
882
|
+
side *= strafe_factor
|
|
883
|
+
|
|
884
|
+
if ( fwd ) {
|
|
885
|
+
playerVelocity.add( getForwardVector().multiplyScalar( speedDelta * fwd ) )
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if ( side ) {
|
|
889
|
+
playerVelocity.add( getSideVector().multiplyScalar( speedDelta * side ) )
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
function animate() {
|
|
896
|
+
|
|
897
|
+
timer.update();
|
|
898
|
+
|
|
899
|
+
const deltaTime = Math.min( 0.05, timer.getDelta() ) / STEPS_PER_FRAME;
|
|
900
|
+
|
|
901
|
+
// we look for collisions in substeps to mitigate the risk of
|
|
902
|
+
// an object traversing another too quickly for detection.
|
|
903
|
+
|
|
904
|
+
for ( let i = 0; i < STEPS_PER_FRAME; i ++ ) {
|
|
905
|
+
|
|
906
|
+
if ( current_ctrl_type() === 'touch' && touch_controls ) {
|
|
907
|
+
touch_pad_controls( deltaTime )
|
|
908
|
+
} else {
|
|
909
|
+
controls( deltaTime )
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
updatePlayer( deltaTime )
|
|
913
|
+
check_door_portal()
|
|
914
|
+
teleportPlayerIfOob()
|
|
915
|
+
updateSpheres( deltaTime )
|
|
916
|
+
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if ( current_ctrl_type() === 'touch' && touch_controls ) {
|
|
920
|
+
touch_controls.setPosition( camera.position.x, camera.position.y, camera.position.z )
|
|
921
|
+
apply_touch_pose()
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
update_door_aim()
|
|
925
|
+
renderer.render( scene, camera );
|
|
926
|
+
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
$('#fullScreen').on('click', () => {
|
|
930
|
+
document.body.requestFullscreen()
|
|
931
|
+
})
|
|
932
|
+
|
|
933
|
+
const CTRL_TYPE_STOR = 'ctrl-type'
|
|
934
|
+
|
|
935
|
+
function current_ctrl_type() {
|
|
936
|
+
return $('input[name=ctrl-type]:checked').val() || 'fps'
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function apply_touch_pose() {
|
|
940
|
+
const holder = touch_controls.fpsBody.getObjectByName( 'cameraHolder' )
|
|
941
|
+
camera.rotation.set( holder.rotation.x, touch_controls.fpsBody.rotation.y, 0 )
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function enable_touch_controls() {
|
|
945
|
+
if ( document.pointerLockElement ) document.exitPointerLock()
|
|
946
|
+
|
|
947
|
+
const pos = new THREE.Vector3()
|
|
948
|
+
camera.getWorldPosition( pos )
|
|
949
|
+
if ( pos.y < 0.2 ) pos.copy( playerCollider.end )
|
|
950
|
+
const rx = camera.rotation.x
|
|
951
|
+
const ry = camera.rotation.y
|
|
952
|
+
|
|
953
|
+
if ( !touch_controls ) {
|
|
954
|
+
camera.position.set( 0, 0, 0 )
|
|
955
|
+
camera.rotation.set( 0, 0, 0 )
|
|
956
|
+
touch_controls = new TouchControls( $( container ), camera, {
|
|
957
|
+
speedFactor: 0.08,
|
|
958
|
+
delta: 1,
|
|
959
|
+
rotationFactor: 0.002,
|
|
960
|
+
maxPitch: 55,
|
|
961
|
+
hitTest: false,
|
|
962
|
+
hitTestDistance: 1
|
|
963
|
+
} )
|
|
964
|
+
if ( camera.parent ) camera.parent.remove( camera )
|
|
965
|
+
touch_controls.addToScene( scene )
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
touch_controls.enabled = true
|
|
969
|
+
touch_controls.setPosition( pos.x, pos.y, pos.z )
|
|
970
|
+
touch_controls.setRotation( rx, ry )
|
|
971
|
+
camera.position.copy( pos )
|
|
972
|
+
playerCollider.end.copy( pos )
|
|
973
|
+
playerCollider.start.set( pos.x, pos.y - 0.65, pos.z )
|
|
974
|
+
apply_touch_pose()
|
|
975
|
+
$( '.movement-pad' ).show()
|
|
976
|
+
$( '.rotation-pad' ).removeClass('is-active').css('display', '')
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function disable_touch_controls() {
|
|
980
|
+
if ( !touch_controls ) return
|
|
981
|
+
touch_controls.enabled = false
|
|
982
|
+
$( '.movement-pad' ).hide()
|
|
983
|
+
$( '.rotation-pad' ).removeClass('is-active').css('display', '')
|
|
984
|
+
playerCollider.end.copy( camera.position )
|
|
985
|
+
playerCollider.start.set( camera.position.x, camera.position.y - 0.65, camera.position.z )
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function set_ctrl_type( type ) {
|
|
989
|
+
if ( type === 'touch' ) enable_touch_controls()
|
|
990
|
+
else disable_touch_controls()
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
try {
|
|
994
|
+
const saved = localStorage.getItem(CTRL_TYPE_STOR)
|
|
995
|
+
if (saved) $(`input[name=ctrl-type][value="${saved}"]`).prop('checked', true)
|
|
996
|
+
} catch (error) {
|
|
997
|
+
console.log(error)
|
|
998
|
+
}
|
|
999
|
+
$('input[name=ctrl-type]').on('change', function() {
|
|
1000
|
+
if (!this.checked) return
|
|
1001
|
+
localStorage.setItem(CTRL_TYPE_STOR, this.value)
|
|
1002
|
+
set_ctrl_type(this.value)
|
|
1003
|
+
})
|
|
1004
|
+
set_ctrl_type(current_ctrl_type())
|
|
1005
|
+
|
|
1006
|
+
const WEAPON_CTRL_STOR = 'weaponCtrl'
|
|
1007
|
+
try {
|
|
1008
|
+
const saved = localStorage.getItem(WEAPON_CTRL_STOR)
|
|
1009
|
+
if (saved !== null) $('input[name=weaponCtrl]').prop('checked', saved === 'on')
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
console.log(error)
|
|
1012
|
+
}
|
|
1013
|
+
$('input[name=weaponCtrl]').on('change', function() {
|
|
1014
|
+
localStorage.setItem(WEAPON_CTRL_STOR, this.checked ? 'on' : 'off')
|
|
1015
|
+
set_weapons(this.checked)
|
|
1016
|
+
})
|
|
1017
|
+
set_weapons(weapons_on())
|
|
1018
|
+
set_weapons(weapons_on())
|