提交 82439389 编写于 作者: R Ricardo Cabello

Merge pull request #5997 from zz85/catmullrom

Add CatmullRomCurve3 which supports catmullrom, centripetal & chordal
/*
* @author zz85 / https://github.com/zz85
* Running this will allow you to drag three.js objects around the screen.
*/
THREE.DragControls = function( _camera, _objects, _domElement ) {
var _projector = new THREE.Projector();
var _raycaster = new THREE.Raycaster();
var _mouse = new THREE.Vector3(),
_offset = new THREE.Vector3();
var _selected, _hovered;
var p3subp1 = new THREE.Vector3();
var targetposition = new THREE.Vector3();
var zerovector = new THREE.Vector3();
this.enabled = false;
/* Custom Event Handling */
var _listeners = {
};
var me = this;
this.on = function( event, handler ) {
if ( !_listeners[ event ] ) _listeners[ event ] = [];
_listeners[ event ].push( handler );
return me;
};
this.off = function( event, handler ) {
var l = _listeners[ event ];
if ( !l ) return me;
if ( l.indexOf( handler ) > -1 ) {
l.splice( handler, 1 );
}
return me;
};
var notify = function( event, data, member ) {
var l = _listeners[ event ];
if ( !l ) return;
if ( !member ) {
for ( var i = 0; i < l.length; i ++ ) {
l[ i ]( data );
}
}
};
this.setObjects = function( objects ) {
if ( objects instanceof THREE.Scene ) {
_objects = objects.children;
} else {
_objects = objects;
}
};
this.setObjects( _objects );
this.activate = function() {
_domElement.addEventListener( 'mousemove', onDocumentMouseMove, false );
_domElement.addEventListener( 'mousedown', onDocumentMouseDown, false );
_domElement.addEventListener( 'mouseup', onDocumentMouseUp, false );
};
this.deactivate = function() {
_domElement.removeEventListener( 'mousemove', onDocumentMouseMove, false );
_domElement.removeEventListener( 'mousedown', onDocumentMouseDown, false );
_domElement.removeEventListener( 'mouseup', onDocumentMouseUp, false );
};
this.activate();
function onDocumentMouseMove( event ) {
event.preventDefault();
_mouse.x = ( event.clientX / _domElement.width ) * 2 - 1;
_mouse.y = -( event.clientY / _domElement.height ) * 2 + 1;
_raycaster.setFromCamera( _mouse, _camera );
var ray = _raycaster.ray;
if ( _selected && me.enabled ) {
var normal = _selected.normal;
// I found this article useful about plane-line intersections
// http://paulbourke.net/geometry/planeline/
var denom = normal.dot( ray.direction );
if ( denom == 0 ) {
// bail
console.log( 'no or infinite solutions' );
return;
}
var num = normal.dot( p3subp1.copy( _selected.point ).sub( ray.origin ) );
var u = num / denom;
targetposition.copy( ray.direction ).multiplyScalar( u ).add( ray.origin ).sub( _offset );
// _selected.object.position.copy(targetposition);
var xLock, yLock, zLock = false;
var moveX, moveY, moveZ;
if ( xLock ) {
moveX = true;
moveY = false;
moveZ = false;
} else if ( yLock ) {
moveX = false;
moveY = true;
moveZ = false;
} else {
moveX = moveY = moveZ = true;
}
// Reverse Matrix?
if ( moveX ) _selected.object.position.x = targetposition.x;
if ( moveY ) _selected.object.position.y = targetposition.y;
if ( moveZ ) _selected.object.position.z = targetposition.z;
notify( 'drag', _selected );
return;
}
_raycaster.setFromCamera( _mouse, _camera );
var intersects = _raycaster.intersectObjects( _objects );
if ( intersects.length > 0 ) {
_domElement.style.cursor = 'pointer';
_hovered = intersects[ 0 ];
notify( 'hoveron', _hovered );
} else {
notify( 'hoveroff', _hovered );
_hovered = null;
_domElement.style.cursor = 'auto';
}
}
function onDocumentMouseDown( event ) {
event.preventDefault();
_mouse.x = ( event.clientX / _domElement.width ) * 2 - 1;
_mouse.y = -( event.clientY / _domElement.height ) * 2 + 1;
_raycaster.setFromCamera( _mouse, _camera );
var intersects = _raycaster.intersectObjects( _objects );
var ray = _raycaster.ray;
var normal = ray.direction; // normal ray to the camera position
if ( intersects.length > 0 ) {
_selected = intersects[ 0 ];
_selected.ray = ray;
_selected.normal = normal ;
_offset.copy( _selected.point ).sub( _selected.object.position );
_domElement.style.cursor = 'move';
notify( 'dragstart', _selected );
}
}
function onDocumentMouseUp( event ) {
event.preventDefault();
if ( _selected ) {
notify( 'dragend', _selected );
_selected = null;
}
_domElement.style.cursor = 'auto';
}
}
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
} }
var randomSpline = new THREE.SplineCurve3( randomPoints ); var randomSpline = new THREE.CatmullRomCurve3( randomPoints );
// //
......
...@@ -16,11 +16,6 @@ ...@@ -16,11 +16,6 @@
<body> <body>
<script src="../build/three.min.js"></script> <script src="../build/three.min.js"></script>
<!--
<script src="../src/extras/core/Curve.js"></script>
<script src="../src/extras/geometries/TubeGeometry.js"></script>
<script src="../src/extras/helpers/CameraHelper.js"></script>
-->
<!-- where curves formulas are defined --> <!-- where curves formulas are defined -->
<script src="js/CurveExtras.js"></script> <script src="js/CurveExtras.js"></script>
...@@ -48,7 +43,7 @@ ...@@ -48,7 +43,7 @@
var normal = new THREE.Vector3(); var normal = new THREE.Vector3();
var pipeSpline = new THREE.SplineCurve3([ var pipeSpline = new THREE.CatmullRomCurve3([
new THREE.Vector3(0, 10, -10), new THREE.Vector3(10, 0, -10), new THREE.Vector3(20, 0, 0), new THREE.Vector3(30, 0, 10), new THREE.Vector3(30, 0, 20), new THREE.Vector3(20, 0, 30), new THREE.Vector3(10, 0, 30), new THREE.Vector3(0, 0, 30), new THREE.Vector3(-10, 10, 30), new THREE.Vector3(-10, 20, 30), new THREE.Vector3(0, 30, 30), new THREE.Vector3(10, 30, 30), new THREE.Vector3(20, 30, 15), new THREE.Vector3(10, 30, 10), new THREE.Vector3(0, 30, 10), new THREE.Vector3(-10, 20, 10), new THREE.Vector3(-10, 10, 10), new THREE.Vector3(0, 0, 10), new THREE.Vector3(10, -10, 10), new THREE.Vector3(20, -15, 10), new THREE.Vector3(30, -15, 10), new THREE.Vector3(40, -15, 10), new THREE.Vector3(50, -15, 10), new THREE.Vector3(60, 0, 10), new THREE.Vector3(70, 0, 0), new THREE.Vector3(80, 0, 0), new THREE.Vector3(90, 0, 0), new THREE.Vector3(100, 0, 0)]); new THREE.Vector3(0, 10, -10), new THREE.Vector3(10, 0, -10), new THREE.Vector3(20, 0, 0), new THREE.Vector3(30, 0, 10), new THREE.Vector3(30, 0, 20), new THREE.Vector3(20, 0, 30), new THREE.Vector3(10, 0, 30), new THREE.Vector3(0, 0, 30), new THREE.Vector3(-10, 10, 30), new THREE.Vector3(-10, 20, 30), new THREE.Vector3(0, 30, 30), new THREE.Vector3(10, 30, 30), new THREE.Vector3(20, 30, 15), new THREE.Vector3(10, 30, 10), new THREE.Vector3(0, 30, 10), new THREE.Vector3(-10, 20, 10), new THREE.Vector3(-10, 10, 10), new THREE.Vector3(0, 0, 10), new THREE.Vector3(10, -10, 10), new THREE.Vector3(20, -15, 10), new THREE.Vector3(30, -15, 10), new THREE.Vector3(40, -15, 10), new THREE.Vector3(50, -15, 10), new THREE.Vector3(60, 0, 10), new THREE.Vector3(70, 0, 0), new THREE.Vector3(80, 0, 0), new THREE.Vector3(90, 0, 0), new THREE.Vector3(100, 0, 0)]);
var sampleClosedSpline = new THREE.ClosedSplineCurve3([ var sampleClosedSpline = new THREE.ClosedSplineCurve3([
......
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - geometry - catmull spline editor</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="../build/three.min.js"></script>
<script src="js/controls/OrbitControls.js"></script>
<script src="js/controls/TransformControls.js"></script>
<script src="js/renderers/Projector.js"></script>
<script src="js/controls/DragControls.js"></script>
<script src="js/libs/stats.min.js"></script>
<script>
String.prototype.format = function () {
var str = this;
for ( var i = 0; i < arguments.length; i ++ ) {
str = str.replace( '{' + i + '}', arguments[ i ] );
}
return str;
}
var container, stats;
var camera, scene, renderer;
var splineHelperObjects = [],
splineOutline;
var splinePointsLength = 4;
var positions = [];
var options;
var geometry = new THREE.BoxGeometry( 20, 20, 20 );
var ARC_SEGMENTS = 200;
var splineMesh;
var splines = {
};
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.z = 1000;
scene.add( camera );
scene.add( new THREE.AmbientLight( 0xf0f0f0 ) );
var light = new THREE.SpotLight( 0xffffff, 1.5 );
light.position.set( 0, 1500, 200 );
light.castShadow = true;
light.shadowCameraNear = 200;
light.shadowCameraFar = camera.far;
light.shadowCameraFov = 70;
light.shadowBias = -0.000222;
light.shadowDarkness = 0.25;
light.shadowMapWidth = 1024;
light.shadowMapHeight = 1024;
scene.add( light );
spotlight = light;
var planeGeometry = new THREE.PlaneGeometry( 2000, 2000, 20, 20 );
planeGeometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
var planeMaterials = [ new THREE.MeshBasicMaterial( {
color: 0xeeeeee,
opacity: 0.5
} ), new THREE.MeshBasicMaterial( {
color: 0x405040,
wireframe: true,
opacity: 0.2,
transparent: true
} ) ];
var plane = THREE.SceneUtils.createMultiMaterialObject( planeGeometry, planeMaterials );
plane.position.y = -200;
plane.children[ 0 ].castShadow = false;
plane.children[ 0 ].receiveShadow = true;
scene.add( plane );
var axis = new THREE.AxisHelper();
axis.position.set( -500, -500, -500 );
scene.add( axis );
renderer = new THREE.WebGLRenderer( {
antialias: true
} );
renderer.setClearColor( 0xf0f0f0 );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMapEnabled = true;
renderer.shadowMapSoft = true;
container.appendChild( renderer.domElement );
var info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = 'catmull-rom rom spline comparisions';
options = document.createElement( 'div' );
options.style.position = 'absolute';
options.style.top = '30px';
options.style.width = '100%';
options.style.textAlign = 'center';
options.innerHTML = 'Points: <input type="button" onclick="addPoint();" value="+" />\
<input type="button" onclick="removePoint();" value="-" />\
<input type="button" onclick="exportSpline();" value="Export" /><br />\
<input type="checkbox" id="uniform" checked /> <label for="uniform">Uniform Catmull-rom</label> <input type="range" id="tension" onchange="splines.uniform.tension = tension.value;updateSplineOutline();" min=0 max=1 step=0.01 value=0.5 /> <span id="tension_value" /></span> <br />\
<input type="checkbox" id="centripetal" checked /> Centripetal Catmull-rom<br />\
<input type="checkbox" id="chordal" checked /> Chordal Catmull-rom<br />';
container.appendChild( info );
container.appendChild( options );
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
// Controls
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.damping = 0.2;
controls.addEventListener( 'change', render );
transformControl = new THREE.TransformControls( camera, renderer.domElement );
transformControl.addEventListener( 'change', render );
scene.add( transformControl );
// Hiding transform situation is a little in a mess :()
transformControl.addEventListener( 'change', function( e ) {
cancelHideTransorm();
} );
transformControl.addEventListener( 'mouseDown', function( e ) {
cancelHideTransorm();
} );
transformControl.addEventListener( 'mouseUp', function( e ) {
delayHideTransform();
} );
transformControl.addEventListener( 'objectChange', function( e ) {
updateSplineOutline();
} );
var dragcontrols = new THREE.DragControls( camera, splineHelperObjects, renderer.domElement ); //
dragcontrols.on( 'hoveron', function( e ) {
transformControl.attach( e.object );
cancelHideTransorm(); // *
} )
dragcontrols.on( 'hoveroff', function( e ) {
if ( e ) delayHideTransform();
} )
controls.addEventListener( 'start', function() {
cancelHideTransorm();
} );
controls.addEventListener( 'end', function() {
delayHideTransform();
} );
var hiding;
function delayHideTransform() {
cancelHideTransorm();
hideTransform();
}
function hideTransform() {
hiding = setTimeout( function() {
transformControl.detach( transformControl.object );
}, 2500 )
}
function cancelHideTransorm() {
if ( hiding ) clearTimeout( hiding );
}
/*******
* Curves
*********/
var i;
for ( i = 0; i < splinePointsLength; i ++ ) {
addSplineObject( positions[ i ] );
}
positions = [];
for ( i = 0; i < splinePointsLength; i ++ ) {
positions.push( splineHelperObjects[ i ].position );
}
var geometry = new THREE.Geometry();
for ( var i = 0; i < ARC_SEGMENTS; i ++ ) {
geometry.vertices.push( new THREE.Vector3() );
}
var curve;
curve = new THREE.CatmullRomCurve3( positions );
curve.type = 'catmullrom';
curve.mesh = new THREE.Line( geometry.clone(), new THREE.LineBasicMaterial( {
color: 0xff0000,
opacity: 0.35,
linewidth: 2
} ) );
splines.uniform = curve;
curve = new THREE.CatmullRomCurve3( positions );
curve.type = 'centripetal';
curve.mesh = new THREE.Line( geometry.clone(), new THREE.LineBasicMaterial( {
color: 0x00ff00,
opacity: 0.35,
linewidth: 2
} ) );
splines.centripetal = curve;
curve = new THREE.CatmullRomCurve3( positions );
curve.type = 'chordal';
curve.mesh = new THREE.Line( geometry.clone(), new THREE.LineBasicMaterial( {
color: 0x0000ff,
opacity: 0.35,
linewidth: 2
} ) );
splines.chordal = curve;
for ( var k in splines ) {
var spline = splines[ k ];
scene.add( spline.mesh );
}
load( [ new THREE.Vector3( 289.76843686945404, 452.51481137238443, 56.10018915737797 ),
new THREE.Vector3( -53.56300074753207, 171.49711742836848, -14.495472686253045 ),
new THREE.Vector3( -91.40118730204415, 176.4306956436485, -6.958271935582161 ),
new THREE.Vector3( -383.785318791128, 491.1365363371675, 47.869296953772746 ) ] );
}
function addSplineObject( position ) {
var object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( {
color: Math.random() * 0xffffff
} ) );
object.material.ambient = object.material.color;
if ( position ) {
object.position.copy( position );
} else {
object.position.x = Math.random() * 1000 - 500;
object.position.y = Math.random() * 600
object.position.z = Math.random() * 800 - 400;
}
object.castShadow = true;
object.receiveShadow = true;
scene.add( object );
splineHelperObjects.push( object );
return object;
}
function addPoint() {
splinePointsLength ++;
positions.push( addSplineObject()
.position );
updateSplineOutline();
}
function removePoint() {
if ( splinePointsLength <= 4 ) {
return;
}
splinePointsLength --;
positions.pop();
scene.remove( splineHelperObjects.pop() );
updateSplineOutline();
}
function updateSplineOutline() {
var p;
for ( var k in splines ) {
var spline = splines[ k ];
splineMesh = spline.mesh;
for ( var i = 0; i < ARC_SEGMENTS; i ++ ) {
p = splineMesh.geometry.vertices[ i ];
p.copy( spline.getPoint( i / ( ARC_SEGMENTS - 1 ) ) );
}
splineMesh.geometry.verticesNeedUpdate = true;
}
}
function exportSpline() {
var p;
var strplace = [];
for ( i = 0; i < splinePointsLength; i ++ ) {
p = splineHelperObjects[ i ].position;
strplace.push( 'new THREE.Vector3({0}, {1}, {2})'.format( p.x, p.y, p.z ) )
}
console.log( strplace.join( ',\n' ) );
var code = '[' + ( strplace.join( ',\n\t' ) ) + ']';
prompt( 'copy and paste code', code );
}
function load( new_positions ) {
while ( new_positions.length > positions.length ) {
addPoint();
}
while ( new_positions.length < positions.length ) {
removePoint();
}
for ( i = 0; i < positions.length; i ++ ) {
positions[ i ].copy( new_positions[ i ] );
}
updateSplineOutline();
}
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
controls.update();
transformControl.update();
}
function render() {
splines.uniform.mesh.visible = uniform.checked;
splines.centripetal.mesh.visible = centripetal.checked;
splines.chordal.mesh.visible = chordal.checked;
renderer.render( scene, camera );
}
</script>
</body>
</html>
/*
* @author zz85 https://github.com/zz85
*
* Centripetal CatmullRom Curve - which is useful for avoiding
* cusps and self-intersections in non-uniform catmull rom curves.
* http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
*
* curve.type accepts centripetal(default), chordal and catmullrom
* curve.tension is used for catmullrom which defaults to 0.5
*/
THREE.CatmullRomCurve3 = ( function() {
var
tmp = new THREE.Vector3(),
px = new CubicPoly(),
py = new CubicPoly(),
pz = new CubicPoly();
/*
Based on an optimized c++ solution in
- http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
- http://ideone.com/NoEbVM
This CubicPoly class could be used for reusing some variables and calculations,
but for three.js curve use, it could be possible inlined and flatten into a single function call
which can be placed in CurveUtils.
*/
function CubicPoly() {
}
/*
* Compute coefficients for a cubic polynomial
* p(s) = c0 + c1*s + c2*s^2 + c3*s^3
* such that
* p(0) = x0, p(1) = x1
* and
* p'(0) = t0, p'(1) = t1.
*/
CubicPoly.prototype.init = function( x0, x1, t0, t1 ) {
this.c0 = x0;
this.c1 = t0;
this.c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
this.c3 = 2 * x0 - 2 * x1 + t0 + t1;
};
CubicPoly.prototype.initNonuniformCatmullRom = function( x0, x1, x2, x3, dt0, dt1, dt2 ) {
// compute tangents when parameterized in [t1,t2]
var t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;
var t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;
// rescale tangents for parametrization in [0,1]
t1 *= dt1;
t2 *= dt1;
// initCubicPoly
this.init( x1, x2, t1, t2 );
};
// standard Catmull-Rom spline: interpolate between x1 and x2 with previous/following points x1/x4
CubicPoly.prototype.initCatmullRom = function( x0, x1, x2, x3, tension ) {
this.init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
};
CubicPoly.prototype.calc = function( t ) {
var t2 = t * t;
var t3 = t2 * t;
return this.c0 + this.c1 * t + this.c2 * t2 + this.c3 * t3;
};
// Subclass Three.js curve
return THREE.Curve.create(
function ( p /* array of Vector3 */ ) {
this.points = p || [];
},
function ( t ) {
var points = this.points,
point, intPoint, weight, l;
l = points.length;
if ( l < 2 ) console.log( 'duh, you need at least 2 points' );
point = ( l - 1 ) * t;
intPoint = Math.floor( point );
weight = point - intPoint;
if ( weight == 0 && intPoint == l - 1 ) {
intPoint = l - 2;
weight = 1;
}
var p0, p1, p2, p3;
if ( intPoint == 0 ) {
// extrapolate first point
tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
p0 = tmp;
} else {
p0 = points[ intPoint - 1 ];
}
p1 = points[ intPoint ];
p2 = points[ intPoint + 1 ];
if ( intPoint + 2 < l ) {
p3 = points[ intPoint + 2 ]
} else {
// extrapolate last point
tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 2 ] );
p3 = tmp;
}
if ( this.type === undefined || this.type === 'centripetal' || this.type === 'chordal' ) {
// init Centripetal / Chordal Catmull-Rom
var pow = this.type === 'chordal' ? 0.5 : 0.25;
var dt0 = Math.pow( p0.distanceToSquared( p1 ), pow );
var dt1 = Math.pow( p1.distanceToSquared( p2 ), pow );
var dt2 = Math.pow( p2.distanceToSquared( p3 ), pow );
// safety check for repeated points
if ( dt1 < 1e-4 ) dt1 = 1.0;
if ( dt0 < 1e-4 ) dt0 = dt1;
if ( dt2 < 1e-4 ) dt2 = dt1;
px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );
py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );
pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );
} else if ( this.type === 'catmullrom' ) {
var tension = this.tension !== undefined ? this.tension : 0.5;
px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, tension );
py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, tension );
pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, tension );
}
var v = new THREE.Vector3(
px.calc( weight ),
py.calc( weight ),
pz.calc( weight )
);
return v;
}
);
} )();
...@@ -7,6 +7,7 @@ THREE.SplineCurve3 = THREE.Curve.create( ...@@ -7,6 +7,7 @@ THREE.SplineCurve3 = THREE.Curve.create(
function ( points /* array of Vector3 */) { function ( points /* array of Vector3 */) {
THREE.warn( 'THREE.SplineCurve3 will be deprecated. Please use THREE.CatmullRomCurve3' );
this.points = ( points == undefined ) ? [] : points; this.points = ( points == undefined ) ? [] : points;
}, },
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"src/extras/curves/QuadraticBezierCurve3.js", "src/extras/curves/QuadraticBezierCurve3.js",
"src/extras/curves/CubicBezierCurve3.js", "src/extras/curves/CubicBezierCurve3.js",
"src/extras/curves/SplineCurve3.js", "src/extras/curves/SplineCurve3.js",
"src/extras/curves/CatmullRomCurve3.js",
"src/extras/curves/ClosedSplineCurve3.js", "src/extras/curves/ClosedSplineCurve3.js",
"src/extras/animation/AnimationHandler.js", "src/extras/animation/AnimationHandler.js",
"src/extras/animation/Animation.js", "src/extras/animation/Animation.js",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册