KeyframeTrack.js 9.4 KB
Newer Older
B
Ben Houston 已提交
1 2
/**
 *
3 4
 * A timed sequence of keyframes for a specific property.
 *
J
jackcaron 已提交
5
 *
B
Ben Houston 已提交
6 7
 * @author Ben Houston / http://clara.io/
 * @author David Sarno / http://lighthaus.us/
8
 * @author tschw
B
Ben Houston 已提交
9
 */
10

11
THREE.KeyframeTrack = function ( name, times, values, interpolation ) {
12

T
tschw 已提交
13
	if( name === undefined ) throw new Error( "track name is undefined" );
14

15
	if( times === undefined || times.length === 0 ) {
16 17 18 19

		throw new Error( "no keyframes in track named " + name );

	}
20

21
	this.name = name;
22

23 24 25 26
	this.times = THREE.AnimationUtils.convertArray( times, this.TimeBufferType );
	this.values = THREE.AnimationUtils.convertArray( values, this.ValueBufferType );

	this.setInterpolation( interpolation || this.DefaultInterpolation );
27

28
	this.validate();
29
	this.optimize();
30

31 32 33 34
};

THREE.KeyframeTrack.prototype = {

B
Ben Houston 已提交
35
	constructor: THREE.KeyframeTrack,
36

T
tschw 已提交
37 38
	TimeBufferType: Float32Array,
	ValueBufferType: Float32Array,
39

40
	DefaultInterpolation: THREE.InterpolateLinear,
41

42
	InterpolantFactoryMethodDiscrete: function( result ) {
43

44 45 46 47 48 49 50 51 52 53 54 55 56 57
		return new THREE.DiscreteInterpolant(
				this.times, this.values, this.getValueSize(), result );

	},

	InterpolantFactoryMethodLinear: function( result ) {

		return new THREE.LinearInterpolant(
				this.times, this.values, this.getValueSize(), result );

	},

	InterpolantFactoryMethodSmooth: function( result ) {

58
		return new THREE.CubicInterpolant(
59 60 61 62 63 64 65
				this.times, this.values, this.getValueSize(), result );

	},

	setInterpolation: function( interpolation ) {

		var factoryMethod = undefined;
66

67
		switch ( interpolation ) {
68

69
			case THREE.InterpolateDiscrete:
70

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
				factoryMethod = this.InterpolantFactoryMethodDiscrete;

				break;

			case THREE.InterpolateLinear:

				factoryMethod = this.InterpolantFactoryMethodLinear;

				break;

			case THREE.InterpolateSmooth:

				factoryMethod = this.InterpolantFactoryMethodSmooth;

				break;
86 87 88

		}

89
		if ( factoryMethod === undefined ) {
90

91 92
			var message = "unsupported interpolation for " +
					this.ValueTypeName + " keyframe track named " + this.name;
93

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
			if ( this.createInterpolant === undefined ) {

				// fall back to default, unless the default itself is messed up
				if ( interpolation !== this.DefaultInterpolation ) {

					this.setInterpolation( this.DefaultInterpolation );

				} else {

					throw new Error( message ); // fatal, in this case

				}

			}

			console.warn( message );
			return;
111 112 113

		}

114
		this.createInterpolant = factoryMethod;
115

116
	},
J
jackcaron 已提交
117

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
	getInterpolation: function() {

		switch ( this.createInterpolant ) {

			case this.InterpolantFactoryMethodDiscrete:

				return THREE.InterpolateDiscrete;

			case this.InterpolantFactoryMethodLinear:

				return THREE.InterpolateLinear;

			case this.InterpolantFactoryMethodSmooth:

				return THREE.InterpolateSmooth;
133 134

		}
135

136
	},
137

138 139 140
	getValueSize: function() {

		return this.values.length / this.times.length;
141

142
	},
143

144 145 146
	// move all keyframes either forwards or backwards in time
	shift: function( timeOffset ) {

T
tschw 已提交
147
		if( timeOffset !== 0.0 ) {
148

149 150 151 152 153 154
			var times = this.times;

			for( var i = 0, n = times.length; i !== n; ++ i ) {

				times[ i ] += timeOffset;

155 156
			}

157 158
		}

159 160
		return this;

161 162
	},

163 164 165
	// scale all keyframe times by a factor (useful for frame <-> seconds conversions)
	scale: function( timeScale ) {

T
tschw 已提交
166
		if( timeScale !== 1.0 ) {
167

168 169 170 171 172 173
			var times = this.times;

			for( var i = 0, n = times.length; i !== n; ++ i ) {

				times[ i ] *= timeScale;

174 175 176 177 178 179 180 181 182
			}

		}

		return this;

	},

	// removes keyframes before and after animation without changing any values within the range [startTime, endTime].
183
	// IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
184 185 186 187
	trim: function( startTime, endTime ) {

		var times = this.times;
		var nKeys = times.length;
J
jackcaron 已提交
188

189
		var firstKeysToRemove = 0;
190 191 192 193
		for ( var i = 1; i !== nKeys; ++ i ) {

			if ( times[i] <= startTime ) ++ firstKeysToRemove;

194
		}
J
jackcaron 已提交
195

196
		var lastKeysToRemove = 0;
197 198 199 200 201
		for ( var i = nKeys - 2; i !== 0; -- i ) {

			if ( times[i] >= endTime ) ++ lastKeysToRemove;
			else break;

202
		}
J
jackcaron 已提交
203

204
		// remove last keys first because it doesn't affect the position of the first keys (the otherway around doesn't work as easily)
205
		if( ( firstKeysToRemove + lastKeysToRemove ) !== 0 ) {
206

207 208
			var from = firstKeysToRemove;
			var to = nKeys - lastKeysToRemove - firstKeysToRemove;
209

210 211
			var stride = this.getValueSize();

212
			this.times = THREE.AnimationUtils.arraySlice( times, from, to );
213

214 215
			var values = this.values;
			this.values = THREE.AnimationUtils.arraySlice( values, from * stride, to * stride );
216

217
		}
218

219
		return this;
220

221
	},
222 223 224 225

	// ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
	validate: function() {

226
		var valid = true;
227

228 229 230 231 232
		var valueSize = this.getValueSize();
		if ( valueSize - Math.floor( valueSize ) !== 0 ) {

			console.error( "invalid value size in track", this );
			valid = false;
233 234 235

		}

236 237
		var times = this.times,
			values = this.values,
238

239
			nKeys = times.length;
240 241

		if( nKeys === 0 ) {
242

243 244
			console.error( "track is empty", this );
			valid = false;
245

246 247
		}

248 249 250 251 252
		var prevTime = null;

		for( var i = 0; i !== nKeys; i ++ ) {

			var currTime = times[ i ];
253

T
tschw 已提交
254
			if ( typeof currTime === 'number' && isNaN( currTime ) ) {
255

256 257 258
				console.error( "time is not a valid number", this, i, currTime );
				valid = false;
				break;
259

260 261
			}

262
			if( prevTime !== null && prevTime > currTime ) {
263

264 265 266
				console.error( "out of order keys", this, i, currTime, prevTime );
				valid = false;
				break;
267

268 269
			}

270 271 272
			prevTime = currTime;

		}
273

274
		if ( values !== undefined ) {
275

276
			if ( THREE.AnimationUtils.isTypedArray( values ) ) {
277

278
				for ( var i = 0, n = values.length; i !== n; ++ i ) {
279

280
					var value = values[ i ];
281

T
tschw 已提交
282
					if ( isNaN( value ) ) {
283

284 285 286
						console.error( "value is not a valid number", this, i, value );
						valid = false;
						break;
287

288
					}
289

290
				}
291

292
			}
293 294 295

		}

296
		return valid;
297

298 299
	},

300 301
	// removes equivalent sequential keys as common in morph target sequences
	// (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
302 303
	optimize: function() {

304 305 306
		var times = this.times,
			values = this.values,
			stride = this.getValueSize(),
307

308
			writeIndex = 1;
309

310
		for( var i = 1, n = times.length - 1; i <= n; ++ i ) {
311

312
			var keep = false;
B
Ben Houston 已提交
313

314 315
			var time = times[ i ];
			var timeNext = times[ i + 1 ];
B
Ben Houston 已提交
316

317 318 319
			// remove adjacent keyframes scheduled at the same time

			if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) {
320

321
				// remove unnecessary keyframes same as their neighbors
322 323 324
				var offset = i * stride,
					offsetP = offset - stride,
					offsetN = offset + stride;
B
Ben Houston 已提交
325

326 327 328 329 330 331 332 333 334 335 336 337 338
				for ( var j = 0; j !== stride; ++ j ) {

					var value = values[ offset + j ];

					if ( value !== values[ offsetP + j ] ||
							value !== values[ offsetN + j ] ) {

						keep = true;
						break;

					}

				}
339

B
Ben Houston 已提交
340
			}
341

342 343 344 345 346 347 348 349
			// in-place compaction

			if ( keep ) {

				if ( i !== writeIndex ) {

					times[ writeIndex ] = times[ i ];

350 351
					var readOffset = i * stride,
						writeOffset = writeIndex * stride;
352 353 354 355 356 357 358 359 360 361 362 363 364

					for ( var j = 0; j !== stride; ++ j ) {

						values[ writeOffset + j ] = values[ readOffset + j ];

					}


				}

				++ writeIndex;

			}
365

366 367
		}

368 369 370 371 372 373
		if ( writeIndex !== times.length ) {

			this.times = THREE.AnimationUtils.arraySlice( times, 0, writeIndex );
			this.values = THREE.AnimationUtils.arraySlice( values, 0, writeIndex * stride );

		}
374

375 376
		return this;

377
	}
378

379
};
380

381
// Static methods:
382

383
Object.assign( THREE.KeyframeTrack, {
384

385 386
	// Serialization (in static context, because of constructor invocation
	// and automatic invocation of .toJSON):
387

388
	parse: function( json ) {
389

T
tschw 已提交
390
		if( json.type === undefined ) {
391

392
			throw new Error( "track type undefined, can not parse" );
393

394
		}
395

T
tschw 已提交
396
		var trackType = THREE.KeyframeTrack._getTrackTypeForValueTypeName( json.type );
397

T
tschw 已提交
398
		if ( json.times === undefined ) {
399

400
			console.warn( "legacy JSON format detected, converting" );
401

402
			var times = [], values = [];
403

T
tschw 已提交
404
			THREE.AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
405

T
tschw 已提交
406 407
			json.times = times;
			json.values = values;
408

409
		}
410

411 412
		// derived classes can define a static parse method
		if ( trackType.parse !== undefined ) {
413

414
			return trackType.parse( json );
415

416
		} else {
417

418 419
			// by default, we asssume a constructor compatible with the base
			return new trackType(
T
tschw 已提交
420
					json.name, json.times, json.values, json.interpolation );
421 422

		}
423

424
	},
425

426
	toJSON: function( track ) {
427

428
		var trackType = track.constructor;
429

430
		var json;
431

432 433
		// derived classes can define a static toJSON method
		if ( trackType.toJSON !== undefined ) {
434

435
			json = trackType.toJSON( track );
436

437
		} else {
438

439 440
			// by default, we assume the data can be serialized as-is
			json = {
441

442 443 444 445 446
				'name': track.name,
				'times': THREE.AnimationUtils.convertArray( track.times, Array ),
				'values': THREE.AnimationUtils.convertArray( track.values, Array )

			};
447

448
			var interpolation = track.getInterpolation();
449

450
			if ( interpolation !== track.DefaultInterpolation ) {
451

T
tschw 已提交
452
				json.interpolation = interpolation;
453 454

			}
455 456 457

		}

T
tschw 已提交
458
		json.type = track.ValueTypeName; // mandatory
459

460
		return json;
461

462
	},
463

T
tschw 已提交
464
	_getTrackTypeForValueTypeName: function( typeName ) {
465

466
		switch( typeName.toLowerCase() ) {
467

468 469 470 471 472
			case "scalar":
			case "double":
			case "float":
			case "number":
			case "integer":
473

474
				return THREE.NumberKeyframeTrack;
475

476 477 478 479
			case "vector":
			case "vector2":
			case "vector3":
			case "vector4":
480

481
				return THREE.VectorKeyframeTrack;
482

483
			case "color":
484

485
				return THREE.ColorKeyframeTrack;
486

487
			case "quaternion":
488

489
				return THREE.QuaternionKeyframeTrack;
490

491 492
			case "bool":
			case "boolean":
493

494
				return THREE.BooleanKeyframeTrack;
495

496
			case "string":
497

498
				return THREE.StringKeyframeTrack;
499

500
		};
501

502
		throw new Error( "Unsupported typeName: " + typeName );
503

504
	}
505

506
} );