wco_models 3.1.0.325 → 3.1.0.326

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,912 @@
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 > 0) {
8
+ window[c] = a
9
+ }
10
+ console.log(`+++ ${b}:`, a) // eslint-disable-line no-console
11
+ }
12
+
13
+ const MODELS_ROOT = 'https://localhost/vendor/models';
14
+ // const AVATARS_ROOT = 'https://cdn.jsdelivr.net/gh/wasya-co/ishlib3js@0.3.0/public/vendor/models/avatars';
15
+ const AVATARS_ROOT = 'https://localhost/vendor/models/avatars';
16
+ const avatars = {
17
+ brunette: { type: 'F', url: `${AVATARS_ROOT}/brunette/model.glb` },
18
+ cara: { type: 'F', url: `${AVATARS_ROOT}/cara/model.glb` }, // short hair
19
+ eve: { type: 'F', url: `${AVATARS_ROOT}/eve/model.glb` }, // poison green
20
+
21
+ denis: { type: 'M', url: `${AVATARS_ROOT}/denis/model.glb` }, // beard
22
+ gregor: { type: 'M', url: `${AVATARS_ROOT}/gregor/model.glb` }, // gray shirt
23
+ }
24
+ const AVATAR_STOR = 'avatar'
25
+ $('select.avatar').each((_idx, el) => {
26
+ const $select = $(el)
27
+ $.each(avatars, function(name, url) {
28
+ $('<option>', {
29
+ value: name,
30
+ text: name,
31
+ }).appendTo($select)
32
+ })
33
+ try {
34
+ const saved = localStorage.getItem(`${AVATAR_STOR}-${$select.data('uid')}`)
35
+ if (saved && avatars[saved]) $select.val(saved)
36
+ } catch (error) {
37
+ console.log(error)
38
+ }
39
+ })
40
+
41
+ let scenes = {
42
+ skybox_1: {
43
+ url: `${MODELS_ROOT}/scenes/000mb skybox_1/scene.glb`,
44
+ height: 30,
45
+ },
46
+ studio_tron: {
47
+ height: 10,
48
+ url: `${MODELS_ROOT}/scenes/003mb studio_tron/scene.glb`,
49
+ },
50
+ room_1: {
51
+ height: 6,
52
+ url: `${MODELS_ROOT}/scenes/000mb room-1/scene.glb`,
53
+ },
54
+ studio_blue: {
55
+ height: 4.3,
56
+ url: `${MODELS_ROOT}/scenes/000mb studio_blue/scene.glb`,
57
+ },
58
+ purple_stage: {
59
+ height: 10,
60
+ url: `${MODELS_ROOT}/scenes/000mb purple_stage/scene.glb`,
61
+ },
62
+ red_stage: {
63
+ height: 10,
64
+ url: `${MODELS_ROOT}/scenes/000mb red_stage/scene.glb`,
65
+ },
66
+ // skybox_1: `${MODELS_ROOT}/scenes//scene.glb`,
67
+
68
+ newsroom_green: `${MODELS_ROOT}/scenes/001mb newsroom_green/scene.glb`,
69
+ rick_and_morty_garage: `${MODELS_ROOT}/scenes/003mb rick-and-morty-garage/scene.glb`,
70
+ }
71
+ const SCENE_STOR = 'studio'
72
+ $.each(scenes, (name) => {
73
+ $('<option>', { value: name, text: name }).appendTo($('select.studio'))
74
+ })
75
+ try {
76
+ const saved = localStorage.getItem( SCENE_STOR )
77
+ if (saved && scenes[saved]) $('select.studio').val(saved)
78
+ } catch (error) {
79
+ console.log(error)
80
+ }
81
+
82
+ let scene_url = scenes[$('select.studio').val()] || scenes.rick_and_morty_garage
83
+
84
+ let gestures = {
85
+ talking_1: (avatar) => `${MODELS_ROOT}/animation-library/feminine/fbx/expression/${avatar.body}_Talking_Variations_001.fbx`,
86
+ talking_4: (avatar) => `${MODELS_ROOT}/animation-library/feminine/fbx/expression/${avatar.body}_Talking_Variations_004.fbx`,
87
+ walk_1: { speed: 2.0, url: (a) => `${MODELS_ROOT}/animation-library/feminine/fbx/locomotion/${a.body}_Walk_001.fbx` },
88
+ walk_2: { speed: 2.0, url: (a) => `${MODELS_ROOT}/animation-library/feminine/fbx/locomotion/${a.body}_Walk_002.fbx` },
89
+
90
+ sitting: (a) => `${MODELS_ROOT}/animations/Sitting.fbx`,
91
+ sit_to_stand: (a) => `${MODELS_ROOT}/animations/Sit To Stand.fbx`,
92
+ stand_to_sit: (a) => `${MODELS_ROOT}/animations/Stand To Sit.fbx`,
93
+
94
+ entering_car: (a) => `${MODELS_ROOT}/animations/Entering Car.fbx`,
95
+ slow_run: { speed: 1.1, url: (a) => `${MODELS_ROOT}/animations/Slow Run.fbx`, }
96
+ };
97
+ $.each(gestures, (name) => {
98
+ $('<option>', { value: name, text: name }).appendTo($('select.gestures'))
99
+ })
100
+
101
+ let u_positions = {
102
+ '1': [0, 0, 0],
103
+ '2': [1, 0, 0],
104
+ '3': [-1, 0, 0],
105
+ }
106
+
107
+
108
+
109
+ import * as THREE from 'three'
110
+
111
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
112
+ import { FBXLoader } from 'three/addons/loaders/FBXLoader.js'
113
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
114
+
115
+ import { TalkingHead } from "talkinghead"
116
+
117
+ const params = new URLSearchParams(window.location.search)
118
+
119
+ let config = {
120
+ sampleRate: 8000,
121
+ mood: 'neutral',
122
+ gain: 0.5,
123
+ lipsyncType: "visemes",
124
+ lipsyncLang: "en",
125
+ waitForAudioChunks: false,
126
+ enableMetrics: false,
127
+ };
128
+
129
+ const onMetrics = (which) => {
130
+ logg(which, 'onMetrics')
131
+ }
132
+
133
+ const onSubtitles = (which) => {
134
+ logg(which, 'onSubtitles')
135
+ }
136
+ const streamOpts = {
137
+ gain: config.gain,
138
+ lipsyncLang: config.lipsyncLang,
139
+ lipsyncType: config.lipsyncType,
140
+ metrics: config.enableMetrics ? { enabled: true, intervalHz: config.metricsInterval } : { enabled: false },
141
+ mood: config.mood,
142
+ sampleRate: config.sampleRate,
143
+ waitForAudioChunks: config.waitForAudioChunks,
144
+ }
145
+
146
+
147
+ let width = 854
148
+ let height = 480
149
+ let slug = '<ccapture>'
150
+ const fps = 30
151
+
152
+ let frame = 0
153
+ let lastAnimTime = 0
154
+ let capturing = false
155
+ let captureFrame = 0
156
+ let captureTotal = 0
157
+ const api_key = params.get('api_key')
158
+ const api_secret = params.get('api_secret')
159
+ const wco_origin = params.get('wco_origin')
160
+ const newspartial_id = params.get('newspartial_id')
161
+ let totalFrames
162
+
163
+ let camera, camera_wide, camera_1, camera_2, camera_3
164
+ let controls, renderer, scene
165
+ let head, head_1, head_2, head_3
166
+ let faceTarget = new THREE.Vector3()
167
+ const cameraTargets = {
168
+ wide: new THREE.Vector3(0.5, 1.2, 0),
169
+ av_1: new THREE.Vector3(0, 1.5, 0),
170
+ av_2: new THREE.Vector3(1, 1.5, 0),
171
+ av_3: new THREE.Vector3(-1, 1.5, 0),
172
+ }
173
+ let cameraTransition = null
174
+ const CAMERA_BLEND_MS = 900
175
+ let chunkedInput = null
176
+ const lights = {}
177
+ const LIGHT_CTRL_KEY = 'wco.studio_blue.light-ctrl'
178
+ const HEAD_CTRL_KEY = 'wco.studio_blue.head-ctrl'
179
+ const CAMERA_CTRL_KEY = 'wco.studio_blue.camera-ctrl'
180
+ var capturer = new CCapture( { format: 'webm', framerate: fps } )
181
+ const gltfLoader = new GLTFLoader()
182
+ const fbxLoader = new FBXLoader()
183
+ const loading = document.getElementById('loading')
184
+ const status = document.getElementById('status')
185
+
186
+ /*
187
+ **/
188
+ function point_camera_at_face(cam_id, _head) {
189
+ const leftEye = new THREE.Vector3()
190
+ const rightEye = new THREE.Vector3()
191
+ _head.objectLeftEye.getWorldPosition(leftEye)
192
+ _head.objectRightEye.getWorldPosition(rightEye)
193
+ faceTarget.copy(leftEye).add(rightEye).multiplyScalar(0.5)
194
+
195
+ const camPos = new THREE.Vector3(0, 0, 2)
196
+ camPos.applyQuaternion(_head.armature.quaternion)
197
+ camPos.add(faceTarget)
198
+ const _camera = cameras_fn(cam_id)
199
+ _camera.position.copy(camPos)
200
+ _camera.lookAt(faceTarget)
201
+ cameraTargets[cam_id].copy(faceTarget)
202
+ }
203
+
204
+ /*
205
+ **/
206
+ function put_feet_at_origin(_head) {
207
+ // Put feet at the origin
208
+ _head.armature.updateMatrixWorld(true)
209
+ const leftToe = new THREE.Vector3()
210
+ const rightToe = new THREE.Vector3()
211
+ _head.objectLeftToeBase.getWorldPosition(leftToe)
212
+ _head.objectRightToeBase.getWorldPosition(rightToe)
213
+ _head.armature.position.set(
214
+ -(leftToe.x + rightToe.x) / 2,
215
+ -Math.min(leftToe.y, rightToe.y),
216
+ -(leftToe.z + rightToe.z) / 2
217
+ )
218
+ _head.armature.updateMatrixWorld(true)
219
+ }
220
+
221
+ /*
222
+ **/
223
+ function rescale(model, config) {
224
+ const box = new THREE.Box3().setFromObject(model)
225
+ const size = box.getSize(new THREE.Vector3())
226
+ const currentHeight = size.y
227
+ const scale = config.height / currentHeight
228
+ model.scale.setScalar(scale)
229
+ }
230
+
231
+ function scene_cfg(s) {
232
+ if (typeof s === 'string') return { url: s, height: 3.3 }
233
+ return { url: s.url, height: s.height || 3.3 }
234
+ }
235
+
236
+ function set_shadows(root, cast, receive) {
237
+ root.traverse((obj) => {
238
+ if (obj.isMesh) {
239
+ obj.castShadow = cast
240
+ obj.receiveShadow = receive
241
+ }
242
+ })
243
+ }
244
+
245
+ /*
246
+ **/
247
+ function setup_light(scene) {
248
+ lights.ambientLight = new THREE.AmbientLight( 0xffffff, 0.6 )
249
+ scene.add( lights.ambientLight )
250
+
251
+ lights.hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444, 1.2 )
252
+ lights.hemiLight.position.set( 0, 20, 0 )
253
+ scene.add( lights.hemiLight )
254
+
255
+ lights.keyLight = new THREE.DirectionalLight( 0xffffff, 2.5 )
256
+ lights.keyLight.position.set( 5, 10, 7 )
257
+ lights.keyLight.castShadow = true
258
+ lights.keyLight.shadow.mapSize.set( 2048, 2048 )
259
+ lights.keyLight.shadow.camera.near = 0.5
260
+ lights.keyLight.shadow.camera.far = 40
261
+ lights.keyLight.shadow.camera.left = -10
262
+ lights.keyLight.shadow.camera.right = 10
263
+ lights.keyLight.shadow.camera.top = 10
264
+ lights.keyLight.shadow.camera.bottom = -10
265
+ lights.keyLight.shadow.bias = -0.0001
266
+ scene.add( lights.keyLight )
267
+
268
+ lights.fillLight = new THREE.DirectionalLight( 0xffffff, 1.2 )
269
+ lights.fillLight.position.set( -5, 4, -2 )
270
+ scene.add( lights.fillLight )
271
+
272
+ lights.rimLight = new THREE.DirectionalLight( 0xffffff, 0.8 )
273
+ lights.rimLight.position.set( 0, 6, -8 )
274
+ scene.add( lights.rimLight )
275
+
276
+ restoreLightControls()
277
+ syncLightsFromControls()
278
+ }
279
+
280
+ function restoreLightControls() {
281
+ try {
282
+ const saved = JSON.parse(localStorage.getItem(LIGHT_CTRL_KEY) || 'null')
283
+ if (!saved || typeof saved !== 'object') return
284
+ document.querySelectorAll('input.light-ctrl').forEach((input) => {
285
+ if (Object.prototype.hasOwnProperty.call(saved, input.name)) {
286
+ input.checked = !!saved[input.name]
287
+ }
288
+ })
289
+ } catch (error) {
290
+ console.log(error)
291
+ }
292
+ }
293
+
294
+ function persistLightControls() {
295
+ const saved = {}
296
+ document.querySelectorAll('input.light-ctrl').forEach((input) => {
297
+ saved[input.name] = input.checked
298
+ })
299
+ localStorage.setItem(LIGHT_CTRL_KEY, JSON.stringify(saved))
300
+ }
301
+
302
+ function restoreHeadControl() {
303
+ try {
304
+ const saved = localStorage.getItem(HEAD_CTRL_KEY)
305
+ if (!saved) return
306
+ const input = document.querySelector(`input[name=head][value="${saved}"]`)
307
+ if (input) input.checked = true
308
+ } catch (error) {
309
+ console.log(error)
310
+ }
311
+ }
312
+
313
+ function persistHeadControl(id) {
314
+ localStorage.setItem(HEAD_CTRL_KEY, String(id))
315
+ }
316
+
317
+ function restoreCameraControl() {
318
+ try {
319
+ const saved = localStorage.getItem(CAMERA_CTRL_KEY)
320
+ if (!saved) return
321
+ const input = document.querySelector(`input[name=camera][value="${saved}"]`)
322
+ if (input) input.checked = true
323
+ } catch (error) {
324
+ console.log(error)
325
+ }
326
+ }
327
+
328
+ function persistCameraControl(id) {
329
+ localStorage.setItem(CAMERA_CTRL_KEY, String(id))
330
+ }
331
+
332
+ function syncLightsFromControls() {
333
+ document.querySelectorAll('input.light-ctrl').forEach((input) => {
334
+ const light = lights[input.name]
335
+ if (light) light.visible = input.checked
336
+ })
337
+ persistLightControls()
338
+ }
339
+
340
+ function sync_shadows() {
341
+ const on = $('#is_shadows').prop('checked')
342
+ if (lights.keyLight) lights.keyLight.castShadow = on
343
+ }
344
+
345
+ function gesture_url(g, avatar) {
346
+ if (typeof g === 'function') return g(avatar)
347
+ const u = g && g.url
348
+ return typeof u === 'function' ? u(avatar) : u
349
+ }
350
+
351
+ function update_walk(this_head, dt) {
352
+ if (!this_head || !this_head.speed || !this_head.mixer || !this_head.armature) return
353
+ const dir = new THREE.Vector3()
354
+ this_head.armature.getWorldDirection(dir)
355
+ dir.y = 0
356
+ if (dir.lengthSq() === 0) return
357
+ dir.normalize()
358
+ this_head.armature.position.addScaledVector(dir, this_head.speed * dt / 1000)
359
+ }
360
+
361
+ // function play_animation(config) {
362
+ // logg(config, 'my play_animation()')
363
+ // let this_head = heads_fn()[config.avatar_id]
364
+ // if (this_head) this_head.playAnimation(geastures[config.animation_name](config))
365
+ // }
366
+
367
+ function cut_to(key) {
368
+ const radio = document.querySelector(`input[name=camera][value="${key}"]`)
369
+ if (radio) radio.checked = true
370
+ setActiveCamera(key, true)
371
+ }
372
+
373
+ function move_camera(config) {
374
+ const fromKey = String(config.from)
375
+ const toKey = String(config.to)
376
+ const duration = Number(config.duration) > 0 ? Number(config.duration) : CAMERA_BLEND_MS
377
+ const src = cameras_fn(fromKey)
378
+ const dest = cameras_fn(toKey)
379
+ const startTarget = (cameraTargets[fromKey] || cameraTargets.wide).clone()
380
+ const destTarget = (cameraTargets[toKey] || cameraTargets.wide).clone()
381
+
382
+ persistCameraControl(toKey)
383
+ const radio = document.querySelector(`input[name=camera][value="${toKey}"]`)
384
+ if (radio) radio.checked = true
385
+
386
+ ;[camera_wide, camera_1, camera_2, camera_3, camera].forEach((cam) => {
387
+ if (cam) {
388
+ cam.aspect = width / height
389
+ cam.updateProjectionMatrix()
390
+ }
391
+ })
392
+
393
+ src.updateMatrixWorld()
394
+ dest.updateMatrixWorld()
395
+
396
+ camera.position.copy(src.position)
397
+ camera.quaternion.copy(src.quaternion)
398
+ camera.fov = src.fov
399
+ camera.near = dest.near
400
+ camera.far = dest.far
401
+ camera.updateProjectionMatrix()
402
+ faceTarget.copy(startTarget)
403
+ if (controls) {
404
+ controls.target.copy(startTarget)
405
+ controls.enabled = false
406
+ }
407
+
408
+ cameraTransition = {
409
+ startPos: src.position.clone(),
410
+ startQuat: src.quaternion.clone(),
411
+ startFov: src.fov,
412
+ startTarget: startTarget,
413
+ endPos: dest.position.clone(),
414
+ endQuat: dest.quaternion.clone(),
415
+ endFov: dest.fov,
416
+ endTarget: destTarget,
417
+ elapsed: 0,
418
+ duration: duration,
419
+ }
420
+ }
421
+
422
+ // window.play_animation = play_animation
423
+ window.move_camera = move_camera
424
+ window.cut_to = cut_to
425
+
426
+ let eventsIndex = 0
427
+ function tickEvents(elapsedMs) {
428
+ if (!chunkedInput || !chunkedInput.ftimes || !chunkedInput.fns) return
429
+ while (eventsIndex < chunkedInput.ftimes.length && elapsedMs >= chunkedInput.ftimes[eventsIndex]) {
430
+ const fn = chunkedInput.fns[eventsIndex]
431
+ logg(fn, 'fn')
432
+ eventsIndex++
433
+ eval(fn)
434
+ }
435
+ }
436
+
437
+
438
+
439
+ /*
440
+ **/
441
+ async function init() {
442
+ if (newspartial_id) {
443
+ try {
444
+ const url = wco_origin + '/wco/api/newspartials/' + newspartial_id + '/config.json?api_key=' + api_key + '&api_secret=' + api_secret
445
+ chunkedInput = await fetch(url).then(r => r.json())
446
+ logg(chunkedInput, 'chunkedInput')
447
+
448
+ const last = chunkedInput.wtimes.length-1
449
+ const duration_ms = chunkedInput.wtimes[last] + chunkedInput.wdurations[last]
450
+ totalFrames = duration_ms/1000*fps
451
+ width = chunkedInput.w_px
452
+ height = chunkedInput.h_px
453
+ slug = chunkedInput.slug
454
+ } catch (error) {
455
+ console.log(error)
456
+ chunkedInput = null
457
+ totalFrames = undefined
458
+ }
459
+ }
460
+
461
+ scene = new THREE.Scene()
462
+ scene.background = new THREE.Color(0x333333) // new THREE.Color(0xd3d3d3)
463
+
464
+ /* fov — Camera frustum vertical field of view.
465
+ * aspect — Camera frustum aspect ratio.
466
+ * near — Camera frustum near plane.
467
+ * far — Camera frustum far plane.
468
+ **/
469
+ camera_1 = new THREE.PerspectiveCamera( 10, width/height, 0.1, 1000 )
470
+ camera_1.position.set( 0, 1.6, 4 )
471
+ camera_1.lookAt( 0, 1.5, 0 )
472
+
473
+ camera_2 = new THREE.PerspectiveCamera( 10, width/height, 0.1, 1000 )
474
+ camera_2.position.set( 2, 1.6, 4 )
475
+ camera_2.lookAt( 1, 1.5, 0 )
476
+
477
+ camera_3 = new THREE.PerspectiveCamera( 10, width/height, 0.1, 1000 )
478
+ camera_3.position.set( -2, 1.6, 4 )
479
+ camera_3.lookAt( -1, 1.5, 0 )
480
+
481
+ camera_wide = new THREE.PerspectiveCamera( 25, width/height, 0.1, 1000 )
482
+ camera_wide.position.set( -2, 2.2, 8 )
483
+ camera_wide.lookAt( 0.5, 1.2, 0 )
484
+
485
+ camera = new THREE.PerspectiveCamera( 10, width/height, 0.1, 1000 )
486
+ camera.position.copy( camera_wide.position )
487
+ camera.quaternion.copy( camera_wide.quaternion )
488
+ camera.fov = camera_wide.fov
489
+ camera.updateProjectionMatrix()
490
+
491
+ setup_light(scene)
492
+
493
+ const grid = new THREE.GridHelper(10, 10, 0x888888, 0xbbbbbb)
494
+ grid.position.y = 0
495
+ scene.add(grid)
496
+
497
+
498
+ let studio
499
+ async function load_studio(s) {
500
+ const cfg = scene_cfg(s)
501
+ if (studio && studio.parent) studio.parent.remove(studio)
502
+ studio = (await gltfLoader.loadAsync(cfg.url)).scene
503
+ rescale(studio, { height: cfg.height })
504
+ set_shadows(studio, false, true)
505
+ scene.add(studio)
506
+ }
507
+ await load_studio(scene_url)
508
+ $('select.studio').on('change', async function() {
509
+ const name = $(this).val()
510
+ if (!scenes[name]) return
511
+ localStorage.setItem(SCENE_STOR, name)
512
+ scene_url = scenes[name]
513
+ loading.textContent = 'Loading...'
514
+ try {
515
+ await load_studio(scene_url)
516
+ loading.textContent = 'loaded'
517
+ } catch (error) {
518
+ console.log(error)
519
+ loading.textContent = error.toString()
520
+ }
521
+ })
522
+
523
+
524
+ // head = new TalkingHead( document.getElementById('avatar'), {
525
+ // avatarOnly: true,
526
+ // avatarOnlyScene: scene,
527
+ // avatarOnlyCamera: camera,
528
+ // lipsyncModules: ["en"],
529
+ // dracoEnabled: true,
530
+ // })
531
+ head_1 = new TalkingHead( document.getElementById('avatar_1'), {
532
+ avatarOnly: true,
533
+ avatarOnlyScene: scene,
534
+ avatarOnlyCamera: camera_1,
535
+ lipsyncModules: ["en"],
536
+ dracoEnabled: true,
537
+ })
538
+ head_2 = new TalkingHead( document.getElementById('avatar_2'), {
539
+ avatarOnly: true,
540
+ avatarOnlyScene: scene,
541
+ avatarOnlyCamera: camera_1,
542
+ lipsyncModules: ["en"],
543
+ dracoEnabled: true,
544
+ })
545
+ head_3 = new TalkingHead( document.getElementById('avatar_3'), {
546
+ avatarOnly: true,
547
+ avatarOnlyScene: scene,
548
+ avatarOnlyCamera: camera_1,
549
+ lipsyncModules: ["en"],
550
+ dracoEnabled: true,
551
+ })
552
+
553
+
554
+
555
+
556
+ $('select.avatar').on('change', async function() {
557
+ const uid = String($(this).data('uid'))
558
+ const thisHead = heads_fn(uid)
559
+ const val = $(this).val()
560
+ localStorage.setItem(`${AVATAR_STOR}-${uid}`, val || '')
561
+ if (!thisHead) return
562
+ if (thisHead.armature && thisHead.armature.parent) thisHead.armature.parent.remove(thisHead.armature)
563
+ const avatar = avatars[val]
564
+ if (!avatar || !avatar.url) return
565
+ loading.textContent = 'Loading...'
566
+ try {
567
+ if (thisHead.armature && thisHead.armature.parent) thisHead.armature.parent.remove(thisHead.armature)
568
+ await thisHead.showAvatar({
569
+ url: avatar.url,
570
+ body: avatar.type,
571
+ avatarMood: 'neutral',
572
+ lipsyncLang: 'en'
573
+ }, (ev) => {
574
+ if ( ev.lengthComputable ) {
575
+ let val = Math.min(100,Math.round(ev.loaded/ev.total * 100 ))
576
+ loading.textContent = "Loading " + val + "%"
577
+ }
578
+ })
579
+ thisHead.armature.rotation.set(0, 0, 0)
580
+ set_shadows(thisHead.armature, true, false)
581
+ scene.add(thisHead.armature)
582
+ put_feet_at_origin(thisHead)
583
+ thisHead.armature.position.add(new THREE.Vector3(...u_positions[uid]))
584
+ point_camera_at_face(`av_${uid}`, thisHead)
585
+ await thisHead.streamStart(streamOpts, () => {}, () => {}, onSubtitles, onMetrics)
586
+ loading.textContent = 'loaded'
587
+ } catch (error) {
588
+ console.log(error)
589
+ loading.textContent = error.toString()
590
+ }
591
+ })
592
+ $('select.avatar').trigger('change')
593
+
594
+
595
+
596
+ //
597
+
598
+ renderer = new THREE.WebGLRenderer( {
599
+ alpha: false,
600
+ antialias: true,
601
+ } )
602
+ renderer.setClearColor(0xd3d3d3, 1)
603
+ renderer.setPixelRatio( 1 ); // window.devicePixelRatio )
604
+ renderer.setSize( width, height )
605
+ renderer.toneMapping = THREE.ACESFilmicToneMapping
606
+ renderer.toneMappingExposure = 1
607
+ renderer.outputColorSpace = THREE.SRGBColorSpace
608
+ renderer.shadowMap.enabled = true
609
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap
610
+ document.getElementById('rotatingC').appendChild( renderer.domElement )
611
+
612
+ //
613
+
614
+ controls = new OrbitControls( camera, renderer.domElement )
615
+ controls.enableDamping = true
616
+ controls.enableRotate = true
617
+ controls.enablePan = true
618
+ controls.enableZoom = true
619
+ controls.minDistance = 0.5
620
+ controls.maxDistance = 100
621
+ controls.target.copy(faceTarget)
622
+ controls.autoRotate = false
623
+ controls.update()
624
+
625
+ restoreHeadControl()
626
+ document.querySelectorAll('input[name=head]').forEach((input) => {
627
+ input.addEventListener('change', () => {
628
+ if (!input.checked) return
629
+ setActiveHead(input.value)
630
+ })
631
+ })
632
+ const selectedHead = document.querySelector('input[name=head]:checked')
633
+ setActiveHead(selectedHead ? selectedHead.value : '1')
634
+
635
+ restoreCameraControl()
636
+ document.querySelectorAll('input[name=camera]').forEach((input) => {
637
+ input.addEventListener('change', () => {
638
+ if (!input.checked) return
639
+ setActiveCamera(input.value)
640
+ })
641
+ })
642
+ const selected = document.querySelector('input[name=camera]:checked')
643
+ setActiveCamera(selected ? selected.value : 'wide', true)
644
+
645
+ document.querySelectorAll('input.light-ctrl').forEach((input) => {
646
+ input.addEventListener('change', syncLightsFromControls)
647
+ })
648
+ syncLightsFromControls()
649
+ $('#is_shadows').on('change', sync_shadows)
650
+ sync_shadows()
651
+
652
+ //
653
+
654
+ window.addEventListener( 'resize', onWindowResize )
655
+
656
+
657
+ // Keep rendering so OrbitControls drag/damping stay live
658
+ lastAnimTime = performance.now()
659
+ renderer.setAnimationLoop(animate)
660
+ }
661
+
662
+ let semafore = false
663
+ document.addEventListener('DOMContentLoaded', async function(e) {
664
+ if (!semafore) {
665
+ semafore = true
666
+ await init()
667
+ }
668
+ })
669
+
670
+ function cameras_fn(which) {
671
+ switch (which) {
672
+ case 'wide':
673
+ return camera_wide
674
+ case 'av_1':
675
+ return camera_1
676
+ case 'av_2':
677
+ return camera_2
678
+ case 'av_3':
679
+ return camera_3
680
+ default:
681
+ logg('fpq - cameras_fn default - this should never happen')
682
+ return camera_wide
683
+ }
684
+ }
685
+
686
+ function heads_fn(which) {
687
+ switch (which) {
688
+ case '1':
689
+ return head_1
690
+ break;
691
+ case '2':
692
+ return head_2
693
+ break;
694
+ case '3':
695
+ return head_3
696
+ break;
697
+ default:
698
+ logg('fpp - heads_fn default - this should never happen')
699
+ return head_1
700
+ }
701
+ }
702
+
703
+ function setActiveHead(id) {
704
+ head = heads_fn(id)
705
+ persistHeadControl(id)
706
+ }
707
+
708
+ function easeInOutCubic(t) {
709
+ return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
710
+ }
711
+
712
+ function setActiveCamera(id, instant = false) {
713
+ const key = String(id)
714
+ persistCameraControl(key)
715
+ const dest = cameras_fn(key)
716
+ const destTarget = (cameraTargets[key] || cameraTargets.wide).clone()
717
+ ;[camera_wide, camera_1, camera_2, camera_3, camera].forEach((cam) => {
718
+ if (cam) {
719
+ cam.aspect = width / height
720
+ cam.updateProjectionMatrix()
721
+ }
722
+ })
723
+
724
+ dest.updateMatrixWorld()
725
+
726
+ const endPos = dest.position.clone()
727
+ const endQuat = dest.quaternion.clone()
728
+
729
+ if (instant || !controls) {
730
+ camera.position.copy(endPos)
731
+ camera.quaternion.copy(endQuat)
732
+ camera.fov = dest.fov
733
+ camera.near = dest.near
734
+ camera.far = dest.far
735
+ camera.updateProjectionMatrix()
736
+ faceTarget.copy(destTarget)
737
+ if (controls) {
738
+ controls.target.copy(destTarget)
739
+ controls.update()
740
+ }
741
+ cameraTransition = null
742
+ return
743
+ }
744
+
745
+ cameraTransition = {
746
+ startPos: camera.position.clone(),
747
+ startQuat: camera.quaternion.clone(),
748
+ startFov: camera.fov,
749
+ startTarget: controls.target.clone(),
750
+ endPos: endPos,
751
+ endQuat: endQuat,
752
+ endFov: dest.fov,
753
+ endTarget: destTarget,
754
+ elapsed: 0,
755
+ duration: CAMERA_BLEND_MS,
756
+ }
757
+ controls.enabled = false
758
+ }
759
+
760
+ function updateCameraTransition(dt) {
761
+ if (!cameraTransition) return
762
+ cameraTransition.elapsed += dt
763
+ const u = Math.min(1, cameraTransition.elapsed / cameraTransition.duration)
764
+ const e = easeInOutCubic(u)
765
+ const tr = cameraTransition
766
+ camera.position.lerpVectors(tr.startPos, tr.endPos, e)
767
+ camera.quaternion.slerpQuaternions(tr.startQuat, tr.endQuat, e)
768
+ camera.fov = tr.startFov + (tr.endFov - tr.startFov) * e
769
+ camera.updateProjectionMatrix()
770
+ if (controls) {
771
+ controls.target.lerpVectors(tr.startTarget, tr.endTarget, e)
772
+ faceTarget.copy(controls.target)
773
+ } else {
774
+ faceTarget.lerpVectors(tr.startTarget, tr.endTarget, e)
775
+ }
776
+ if (u >= 1) {
777
+ cameraTransition = null
778
+ if (!capturing && controls) {
779
+ controls.enabled = true
780
+ controls.update()
781
+ }
782
+ }
783
+ }
784
+
785
+ function onWindowResize() {
786
+ ;[camera_wide, camera_1, camera_2, camera_3].forEach((cam) => {
787
+ if (cam) {
788
+ cam.aspect = width / height
789
+ cam.updateProjectionMatrix()
790
+ }
791
+ })
792
+ renderer.setSize( width, height )
793
+ render()
794
+ }
795
+
796
+ function render() {
797
+ renderer.render( scene, camera )
798
+ }
799
+
800
+ function animate() {
801
+ let dt
802
+ if (capturing) {
803
+ dt = 1000 / fps
804
+ } else {
805
+ const now = performance.now()
806
+ dt = lastAnimTime ? (now - lastAnimTime) : (1000 / fps)
807
+ lastAnimTime = now
808
+ if (dt > 100) dt = 100
809
+ }
810
+
811
+ if (capturing) tickEvents(captureFrame * dt)
812
+
813
+ if (head_1) head_1.animate(dt)
814
+ if (head_2) head_2.animate(dt)
815
+ if (head_3) head_3.animate(dt)
816
+ update_walk(head_1, dt)
817
+ update_walk(head_2, dt)
818
+ update_walk(head_3, dt)
819
+ updateCameraTransition(dt)
820
+ if (!cameraTransition && !capturing) controls.update()
821
+ renderer.render( scene, camera )
822
+
823
+ if (capturing) {
824
+ capturer.capture( renderer.domElement )
825
+ captureFrame++
826
+ if (captureFrame >= captureTotal) {
827
+ finishCapture()
828
+ }
829
+ }
830
+ }
831
+
832
+ async function startSpeakCapture() {
833
+ if (capturing) return
834
+ if (!head) return
835
+ if (!chunkedInput) return
836
+
837
+ const last = chunkedInput.wtimes.length - 1
838
+ const duration_ms = chunkedInput.wtimes[last] + chunkedInput.wdurations[last]
839
+ captureTotal = Math.max(1, Math.ceil(duration_ms / 1000 * fps))
840
+ captureFrame = 0
841
+ eventsIndex = 0
842
+ capturing = true
843
+ cameraTransition = null
844
+ if (controls) controls.enabled = false
845
+
846
+ capturer = new CCapture( { format: 'webm', framerate: fps } )
847
+ capturer.start()
848
+ logg('capturer.start')
849
+ status.textContent = 'capturing'
850
+ head.streamAudio(chunkedInput)
851
+ }
852
+
853
+ function finishCapture() {
854
+ capturing = false
855
+ capturer.stop()
856
+ logg('capturer.stop')
857
+ lastAnimTime = performance.now()
858
+ if (controls) controls.enabled = true
859
+
860
+ capturer.save((blob) => {
861
+ logg(blob, 'blob')
862
+ renderer.domElement.toBlob((thumb) => {
863
+ logg(thumb, 'thumb')
864
+ const form = new FormData()
865
+ form.append('video', blob, 'lips.webm')
866
+ form.append('name', slug)
867
+ form.append('thumb', thumb)
868
+ if (newspartial_id) form.append('newspartial_id', newspartial_id)
869
+
870
+ const url = wco_origin
871
+ ? (wco_origin + '/wco/api/videos/?api_key=' + api_key + '&api_secret=' + api_secret)
872
+ : '/wco/api/videos/'
873
+ fetch(url, {
874
+ method: 'POST',
875
+ body: form,
876
+ }).then(() => {
877
+ status.textContent = 'finished'
878
+ }).catch((error) => {
879
+ console.log(error)
880
+ status.textContent = 'save failed'
881
+ })
882
+ })
883
+ })
884
+ }
885
+
886
+
887
+ // Speak when clicked
888
+ $('#speak').on('click', async () => {
889
+ try {
890
+ await startSpeakCapture()
891
+ } catch (error) {
892
+ console.log(error)
893
+ capturing = false
894
+ lastAnimTime = performance.now()
895
+ if (controls) controls.enabled = true
896
+ }
897
+ })
898
+
899
+ $('#jesture').on('click', async () => {
900
+ try {
901
+ if (!head) return
902
+ const name = $('select.gestures').val()
903
+ const g = gestures[name]
904
+ const _anim = gesture_url(g, head.avatar)
905
+ head.speed = (g && typeof g === 'object' && g.speed) ? g.speed : 0
906
+ head.playAnimation( _anim )
907
+ } catch (error) {
908
+ console.log(error)
909
+ }
910
+ })
911
+
912
+ console.log('+++ loaded wco_models :: newsproducer :: studio_blue.js')