Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
车家大少爷
three.js
提交
ed4534ef
T
three.js
项目概览
车家大少爷
/
three.js
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
T
three.js
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
ed4534ef
编写于
10月 27, 2015
作者:
M
Mr.doob
浏览文件
操作
浏览文件
下载
差异文件
Merge branches 'dev' and 'dev' of
https://github.com/mrdoob/three.js
into dev
上级
b489348d
f90e45b2
变更
6
展开全部
隐藏空白更改
内联
并排
Showing
6 changed file
with
724 addition
and
65 deletion
+724
-65
examples/js/AnimationClipCreator.js
examples/js/AnimationClipCreator.js
+33
-57
examples/js/TimelinerController.js
examples/js/TimelinerController.js
+243
-0
examples/js/libs/timeliner_gui.min.js
examples/js/libs/timeliner_gui.min.js
+182
-0
examples/webgl_animation_authoring.html
examples/webgl_animation_authoring.html
+182
-0
src/animation/AnimationClip.js
src/animation/AnimationClip.js
+19
-6
src/animation/PropertyBinding.js
src/animation/PropertyBinding.js
+65
-2
未找到文件。
examples/js/AnimationClipCreator.js
浏览文件 @
ed4534ef
/**
*
* Creator of typical test AnimationClips / KeyframeTracks
*
*
* @author Ben Houston / http://clara.io/
* @author David Sarno / http://lighthaus.us/
*/
...
...
@@ -11,73 +11,64 @@ THREE.AnimationClipCreator = function() {
THREE
.
AnimationClipCreator
.
CreateRotationAnimation
=
function
(
period
,
axis
)
{
var
keys
=
[];
keys
.
push
(
{
time
:
0
,
value
:
0
}
);
keys
.
push
(
{
time
:
period
,
value
:
360
}
);
var
times
=
[
0
,
period
],
values
=
[
0
,
360
];
axis
=
axis
||
'
x
'
;
var
trackName
=
'
.rotation[
'
+
axis
+
'
]
'
;
var
track
=
new
THREE
.
NumberKeyframeTrack
(
trackName
,
key
s
);
var
track
=
new
THREE
.
NumberKeyframeTrack
(
trackName
,
times
,
value
s
);
var
clip
=
new
THREE
.
AnimationClip
(
'
rotate.x
'
,
10
,
[
track
]
);
//console.log( 'rotateClip', clip );
return
new
THREE
.
AnimationClip
(
null
,
period
,
[
track
]
);
return
clip
;
};
THREE
.
AnimationClipCreator
.
CreateScaleAxisAnimation
=
function
(
period
,
axis
)
{
var
keys
=
[];
keys
.
push
(
{
time
:
0
,
value
:
0
}
);
keys
.
push
(
{
time
:
period
,
value
:
360
}
);
var
times
=
[
0
,
period
],
values
=
[
0
,
1
];
axis
=
axis
||
'
x
'
;
var
trackName
=
'
.scale[
'
+
axis
+
'
]
'
;
var
track
=
new
THREE
.
NumberKeyframeTrack
(
trackName
,
key
s
);
var
track
=
new
THREE
.
NumberKeyframeTrack
(
trackName
,
times
,
value
s
);
var
clip
=
new
THREE
.
AnimationClip
(
'
scale.x
'
,
10
,
[
track
]
);
//console.log( 'scaleClip', clip );
return
new
THREE
.
AnimationClip
(
null
,
period
,
[
track
]
);
return
clip
;
};
THREE
.
AnimationClipCreator
.
CreateShakeAnimation
=
function
(
duration
,
shakeScale
)
{
var
keys
=
[]
;
var
times
=
[],
values
=
[],
tmp
=
new
THREE
.
Vector3
()
;
for
(
var
i
=
0
;
i
<
duration
*
10
;
i
++
)
{
keys
.
push
(
{
time
:
(
i
/
10.0
),
value
:
new
THREE
.
Vector3
(
Math
.
random
()
*
2.0
-
1.0
,
Math
.
random
()
*
2.0
-
1.0
,
Math
.
random
()
*
2.0
-
1.0
).
multiply
(
shakeScale
)
}
);
times
.
push
(
i
/
10
);
tmp
.
set
(
Math
.
random
()
*
2.0
-
1.0
,
Math
.
random
()
*
2.0
-
1.0
,
Math
.
random
()
*
2.0
-
1.0
).
multiply
(
shakeScale
).
toArray
(
values
,
values
.
length
);
}
var
trackName
=
'
.position
'
;
var
track
=
new
THREE
.
VectorKeyframeTrack
(
trackName
,
key
s
);
var
track
=
new
THREE
.
VectorKeyframeTrack
(
trackName
,
times
,
value
s
);
var
clip
=
new
THREE
.
AnimationClip
(
'
shake
'
+
duration
,
duration
,
[
track
]
);
//console.log( 'shakeClip', clip );
return
new
THREE
.
AnimationClip
(
null
,
duration
,
[
track
]
);
return
clip
;
};
THREE
.
AnimationClipCreator
.
CreatePulsationAnimation
=
function
(
duration
,
pulseScale
)
{
var
keys
=
[]
;
var
times
=
[],
values
=
[],
tmp
=
new
THREE
.
Vector3
()
;
for
(
var
i
=
0
;
i
<
duration
*
10
;
i
++
)
{
times
.
push
(
i
/
10
);
var
scaleFactor
=
Math
.
random
()
*
pulseScale
;
keys
.
push
(
{
time
:
(
i
/
10.0
),
value
:
new
THREE
.
Vector3
(
scaleFactor
,
scaleFactor
,
scaleFactor
)
}
);
tmp
.
set
(
scaleFactor
,
scaleFactor
,
scaleFactor
).
toArray
(
values
,
values
.
length
);
}
...
...
@@ -85,55 +76,40 @@ THREE.AnimationClipCreator.CreatePulsationAnimation = function( duration, pulseS
var
track
=
new
THREE
.
VectorKeyframeTrack
(
trackName
,
keys
);
var
clip
=
new
THREE
.
AnimationClip
(
'
scale
'
+
duration
,
duration
,
[
track
]
);
//console.log( 'scaleClip', clip );
return
new
THREE
.
AnimationClip
(
null
,
duration
,
[
track
]
);
return
clip
;
};
THREE
.
AnimationClipCreator
.
CreateVisibilityAnimation
=
function
(
duration
)
{
var
keys
=
[];
keys
.
push
(
{
time
:
0
,
value
:
true
}
);
keys
.
push
(
{
time
:
duration
-
1
,
value
:
false
}
);
keys
.
push
(
{
time
:
duration
,
value
:
true
}
);
var
times
=
[
0
,
duration
/
2
,
duration
],
values
=
[
true
,
false
,
true
];
var
trackName
=
'
.visible
'
;
var
track
=
new
THREE
.
BooleanKeyframeTrack
(
trackName
,
key
s
);
var
track
=
new
THREE
.
BooleanKeyframeTrack
(
trackName
,
times
,
value
s
);
var
clip
=
new
THREE
.
AnimationClip
(
'
visible
'
+
duration
,
duration
,
[
track
]
);
//console.log( 'scaleClip', clip );
return
new
THREE
.
AnimationClip
(
null
,
duration
,
[
track
]
);
return
clip
;
};
THREE
.
AnimationClipCreator
.
CreateMaterialColorAnimation
=
function
(
duration
,
colors
,
loop
)
{
var
timeStep
=
duration
/
colors
.
length
;
var
keys
=
[];
var
times
=
[],
values
=
[],
timeStep
=
duration
/
colors
.
length
;
for
(
var
i
=
0
;
i
<=
colors
.
length
;
i
++
)
{
keys
.
push
(
{
time
:
i
*
timeStep
,
value
:
colors
[
i
%
colors
.
length
]
}
);
timees
.
push
(
i
*
timeStep
);
values
.
push
(
colors
[
i
%
colors
.
length
]
);
}
var
trackName
=
'
.material[0].color
'
;
var
track
=
new
THREE
.
ColorKeyframeTrack
(
trackName
,
key
s
);
var
track
=
new
THREE
.
ColorKeyframeTrack
(
trackName
,
times
,
value
s
);
var
clip
=
new
THREE
.
AnimationClip
(
'
colorDiffuse
'
,
10
,
[
track
]
);
//console.log( 'diffuseClip', clip );
return
new
THREE
.
AnimationClip
(
null
,
duration
,
[
track
]
);
return
clip
;
};
examples/js/TimelinerController.js
0 → 100644
浏览文件 @
ed4534ef
/**
* Controller class for the Timeliner GUI.
*
* Timeliner GUI library (required to use this class):
*
* ./libs/timeliner_gui.min.js
*
* Source code:
*
* https://github.com/tschw/timeliner
* https://github.com/zz85/timeliner (fork's origin)
*
* @author tschw
*
*/
THREE
.
TimelinerController
=
function
TimelinerController
(
scene
,
trackInfo
,
onUpdate
)
{
this
.
_scene
=
scene
;
this
.
_trackInfo
=
trackInfo
;
this
.
_onUpdate
=
onUpdate
;
this
.
_mixer
=
new
THREE
.
AnimationMixer
(
scene
);
this
.
_clip
=
null
;
this
.
_action
=
null
;
this
.
_tracks
=
{};
this
.
_propRefs
=
{};
this
.
_channelNames
=
[];
};
THREE
.
TimelinerController
.
prototype
=
{
constructor
:
THREE
.
TimelinerController
,
init
:
function
(
timeliner
)
{
var
tracks
=
[],
trackInfo
=
this
.
_trackInfo
;
for
(
var
i
=
0
,
n
=
trackInfo
.
length
;
i
!==
n
;
++
i
)
{
var
spec
=
trackInfo
[
i
];
tracks
.
push
(
this
.
_addTrack
(
spec
.
type
,
spec
.
propertyPath
,
spec
.
initialValue
,
spec
.
interpolation
)
);
}
this
.
_clip
=
new
THREE
.
AnimationClip
(
'
editclip
'
,
0
,
tracks
);
this
.
_action
=
this
.
_mixer
.
clipAction
(
this
.
_clip
).
play
();
},
setDisplayTime
:
function
(
time
)
{
this
.
_action
.
time
=
time
;
this
.
_mixer
.
update
(
0
);
this
.
_onUpdate
();
},
getChannelNames
:
function
()
{
return
this
.
_channelNames
;
},
getChannelKeyTimes
:
function
(
channelName
)
{
return
this
.
_tracks
[
channelName
].
times
;
},
setKeyframe
:
function
(
channelName
,
time
)
{
var
track
=
this
.
_tracks
[
channelName
],
times
=
track
.
times
,
index
=
times
.
indexOf
(
time
);
if
(
index
===
-
1
)
index
=
times
.
length
;
var
values
=
track
.
values
,
offset
=
index
*
track
.
getValueSize
();
// note: not calling track.getValueSize with
// inconsistent array sizes
times
[
index
]
=
time
;
this
.
_propRefs
[
channelName
].
getValue
(
values
,
offset
);
this
.
_sort
(
track
);
this
.
_clip
.
resetDuration
();
},
delKeyframe
:
function
(
channelName
,
time
)
{
var
track
=
this
.
_tracks
[
channelName
],
times
=
track
.
times
,
index
=
times
.
indexOf
(
time
);
if
(
index
===
0
)
{
// we disallow to remove the first keyframe
// since the animation system is designed to
// always produce a defined state - we allow
// the initial state to be changed however
this
.
setKeyframe
(
channelName
,
time
);
}
else
if
(
index
!==
-
1
)
{
var
values
=
track
.
values
,
stride
=
track
.
getValueSize
(),
offset
=
index
*
stride
,
nValues
=
values
.
length
;
// note: not calling track.getValueSize with
// inconsistent array sizes
times
[
index
]
=
times
[
times
.
length
-
1
];
times
.
pop
();
for
(
var
i
=
nValues
-
stride
;
i
!==
nValues
;
++
i
)
{
values
[
offset
++
]
=
values
[
i
];
}
values
.
length
=
nValues
-
stride
;
this
.
_sort
(
track
);
this
.
_clip
.
resetDuration
();
}
},
hasKeyframe
:
function
(
channelName
,
time
)
{
var
track
=
this
.
_tracks
[
channelName
],
times
=
track
.
times
,
index
=
times
.
indexOf
(
time
);
return
index
!==
-
1
;
},
serialize
:
function
()
{
var
result
=
{},
names
=
this
.
_channelNames
,
tracks
=
this
.
_tracks
;
for
(
var
i
=
0
,
n
=
names
.
length
;
i
!==
n
;
++
i
)
{
var
name
=
names
[
i
],
track
=
tracks
[
name
];
result
[
name
]
=
{
times
:
track
.
times
,
values
:
track
.
values
};
}
return
result
;
},
deserialize
:
function
(
structs
)
{
var
names
=
this
.
_channelNames
,
tracks
=
this
.
_tracks
;
for
(
var
i
=
0
,
n
=
names
.
length
;
i
!==
n
;
++
i
)
{
var
name
=
names
[
i
],
track
=
tracks
[
name
];
data
=
structs
[
name
];
this
.
_setArray
(
track
.
times
,
data
.
times
);
this
.
_setArray
(
track
.
values
,
data
.
values
);
}
// update display
this
.
setDisplayTime
(
this
.
_mixer
.
time
);
},
_sort
:
function
(
track
)
{
var
times
=
track
.
times
,
order
=
THREE
.
AnimationUtils
.
getKeyframeOrder
(
times
);
this
.
_setArray
(
times
,
THREE
.
AnimationUtils
.
sortedArray
(
times
,
1
,
order
)
);
var
values
=
track
.
values
,
stride
=
track
.
getValueSize
();
this
.
_setArray
(
values
,
THREE
.
AnimationUtils
.
sortedArray
(
values
,
stride
,
order
)
);
},
_setArray
:
function
(
dst
,
src
)
{
dst
.
length
=
0
;
dst
.
push
.
apply
(
dst
,
src
);
},
_addTrack
:
function
(
type
,
prop
,
initialValue
,
interpolation
)
{
var
track
=
new
type
(
prop
,
[
0
],
initialValue
,
interpolation
);
// data must be in JS arrays so it can be resized
track
.
times
=
Array
.
prototype
.
slice
.
call
(
track
.
times
);
track
.
values
=
Array
.
prototype
.
slice
.
call
(
track
.
values
);
this
.
_channelNames
.
push
(
prop
);
this
.
_tracks
[
prop
]
=
track
;
// for recording the state:
this
.
_propRefs
[
prop
]
=
new
THREE
.
PropertyBinding
(
this
.
_scene
,
prop
);
return
track
;
}
};
examples/js/libs/timeliner_gui.min.js
0 → 100644
浏览文件 @
ed4534ef
此差异已折叠。
点击以展开。
examples/webgl_animation_authoring.html
0 → 100644
浏览文件 @
ed4534ef
<!DOCTYPE html>
<html
lang=
"en"
>
<head>
<title>
three.js webgl - animation authoring
</title>
<meta
charset=
"utf-8"
>
<style>
body
{
margin
:
0px
;
background-color
:
#000000
;
color
:
#fff
;
font-family
:
Monospace
;
font-size
:
15px
;
line-height
:
30px
;
overflow
:
hidden
;
}
#info
{
text-align
:
center
;
position
:
absolute
;
top
:
0px
;
width
:
100%
;
padding
:
15px
;
z-index
:
100
;
}
</style>
</head>
<body>
<div
id=
"info"
>
"W" translate | "E" rotate | "R" scale | "+" increase size | "-" decrease size
<br
/>
Press "Q" to toggle world/local space, hold down "Ctrl" to snap to grid
</div>
<script
src=
"../build/three.js"
></script>
<script
src=
"js/controls/TransformControls.js"
></script>
<script
src=
"js/libs/timeliner_gui.min.js"
></script>
<script
src=
"js/TimelinerController.js"
></script>
<script>
var
camera
,
scene
,
renderer
,
control
;
init
();
render
();
function
init
()
{
renderer
=
new
THREE
.
WebGLRenderer
();
renderer
.
setPixelRatio
(
window
.
devicePixelRatio
);
renderer
.
setSize
(
window
.
innerWidth
,
window
.
innerHeight
);
renderer
.
sortObjects
=
false
;
document
.
body
.
appendChild
(
renderer
.
domElement
);
//
camera
=
new
THREE
.
PerspectiveCamera
(
70
,
window
.
innerWidth
/
window
.
innerHeight
,
1
,
3000
);
camera
.
position
.
set
(
1000
,
500
,
1000
);
camera
.
lookAt
(
new
THREE
.
Vector3
(
0
,
200
,
0
)
);
scene
=
new
THREE
.
Scene
();
scene
.
add
(
new
THREE
.
GridHelper
(
500
,
100
)
);
var
light
=
new
THREE
.
DirectionalLight
(
0xffffff
,
2
);
light
.
position
.
set
(
1
,
1
,
1
);
scene
.
add
(
light
);
var
texture
=
THREE
.
ImageUtils
.
loadTexture
(
'
textures/crate.gif
'
,
THREE
.
UVMapping
,
render
);
texture
.
anisotropy
=
renderer
.
getMaxAnisotropy
();
var
geometry
=
new
THREE
.
BoxGeometry
(
200
,
200
,
200
);
var
material
=
new
THREE
.
MeshLambertMaterial
(
{
map
:
texture
}
);
control
=
new
THREE
.
TransformControls
(
camera
,
renderer
.
domElement
);
control
.
addEventListener
(
'
change
'
,
render
);
var
mesh
=
new
THREE
.
Mesh
(
geometry
,
material
);
mesh
.
name
=
"
MyBox
"
;
scene
.
add
(
mesh
);
control
.
attach
(
mesh
);
scene
.
add
(
control
);
window
.
addEventListener
(
'
resize
'
,
onWindowResize
,
false
);
window
.
addEventListener
(
'
keydown
'
,
function
(
event
)
{
switch
(
event
.
keyCode
)
{
case
81
:
// Q
control
.
setSpace
(
control
.
space
===
"
local
"
?
"
world
"
:
"
local
"
);
break
;
case
17
:
// Ctrl
control
.
setTranslationSnap
(
100
);
control
.
setRotationSnap
(
THREE
.
Math
.
degToRad
(
15
)
);
break
;
case
87
:
// W
control
.
setMode
(
"
translate
"
);
break
;
case
69
:
// E
control
.
setMode
(
"
rotate
"
);
break
;
case
82
:
// R
control
.
setMode
(
"
scale
"
);
break
;
case
187
:
case
107
:
// +, =, num+
control
.
setSize
(
control
.
size
+
0.1
);
break
;
case
189
:
case
109
:
// -, _, num-
control
.
setSize
(
Math
.
max
(
control
.
size
-
0.1
,
0.1
)
);
break
;
}
});
window
.
addEventListener
(
'
keyup
'
,
function
(
event
)
{
switch
(
event
.
keyCode
)
{
case
17
:
// Ctrl
control
.
setTranslationSnap
(
null
);
control
.
setRotationSnap
(
null
);
break
;
}
});
var
trackInfo
=
[
{
type
:
THREE
.
VectorKeyframeTrack
,
propertyPath
:
'
MyBox.position
'
,
initialValue
:
[
0
,
0
,
0
],
interpolation
:
THREE
.
InterpolateSmooth
},
{
type
:
THREE
.
QuaternionKeyframeTrack
,
propertyPath
:
'
MyBox.quaternion
'
,
initialValue
:
[
0
,
0
,
0
,
1
],
interpolation
:
THREE
.
InterpolateLinear
}
];
new
Timeliner
(
new
THREE
.
TimelinerController
(
scene
,
trackInfo
,
render
)
);
}
function
onWindowResize
()
{
camera
.
aspect
=
window
.
innerWidth
/
window
.
innerHeight
;
camera
.
updateProjectionMatrix
();
renderer
.
setSize
(
window
.
innerWidth
,
window
.
innerHeight
);
render
();
}
function
render
()
{
control
.
update
();
renderer
.
render
(
scene
,
camera
);
}
</script>
</body>
</html>
src/animation/AnimationClip.js
浏览文件 @
ed4534ef
...
...
@@ -15,12 +15,7 @@ THREE.AnimationClip = function ( name, duration, tracks ) {
// this means it should figure out its duration by scanning the tracks
if
(
this
.
duration
<
0
)
{
for
(
var
i
=
0
;
i
<
this
.
tracks
.
length
;
i
++
)
{
var
track
=
this
.
tracks
[
i
];
this
.
duration
=
Math
.
max
(
track
.
times
[
track
.
times
.
length
-
1
]
);
}
this
.
resetDuration
();
}
...
...
@@ -35,6 +30,24 @@ THREE.AnimationClip.prototype = {
constructor
:
THREE
.
AnimationClip
,
resetDuration
:
function
()
{
var
tracks
=
this
.
tracks
,
duration
=
0
;
for
(
var
i
=
0
,
n
=
tracks
.
length
;
i
!==
n
;
++
i
)
{
var
track
=
this
.
tracks
[
i
];
duration
=
Math
.
max
(
duration
,
track
.
times
[
track
.
times
.
length
-
1
]
);
}
this
.
duration
=
duration
;
},
trim
:
function
()
{
for
(
var
i
=
0
;
i
<
this
.
tracks
.
length
;
i
++
)
{
...
...
src/animation/PropertyBinding.js
浏览文件 @
ed4534ef
...
...
@@ -237,6 +237,12 @@ THREE.PropertyBinding.prototype = {
this
.
resolvedProperty
=
nodeProperty
;
}
else
if
(
Array
.
isArray
(
nodeProperty
)
)
{
bindingType
=
this
.
BindingType
.
EntireArray
;
this
.
resolvedProperty
=
nodeProperty
;
}
else
{
this
.
propertyName
=
propertyName
;
...
...
@@ -274,8 +280,9 @@ Object.assign( THREE.PropertyBinding.prototype, { // prototype, continued
BindingType
:
{
Direct
:
0
,
ArrayElement
:
1
,
HasFromToArray
:
2
EntireArray
:
1
,
ArrayElement
:
2
,
HasFromToArray
:
3
},
Versioning
:
{
...
...
@@ -292,6 +299,18 @@ Object.assign( THREE.PropertyBinding.prototype, { // prototype, continued
},
function
getValue_array
(
buffer
,
offset
)
{
var
source
=
this
.
node
[
this
.
propertyName
];
for
(
var
i
=
0
,
n
=
source
.
length
;
i
!==
n
;
++
i
)
{
buffer
[
offset
++
]
=
source
[
i
];
}
},
function
getValue_arrayElement
(
buffer
,
offset
)
{
buffer
[
offset
]
=
this
.
resolvedProperty
[
this
.
propertyIndex
];
...
...
@@ -331,6 +350,50 @@ Object.assign( THREE.PropertyBinding.prototype, { // prototype, continued
}
],
[
// EntireArray
function
setValue_array
(
buffer
,
offset
)
{
var
dest
=
this
.
resolvedProperty
;
for
(
var
i
=
0
,
n
=
dest
.
length
;
i
!==
n
;
++
i
)
{
dest
[
i
]
=
buffer
[
offset
++
];
}
},
function
setValue_array_setNeedsUpdate
(
buffer
,
offset
)
{
var
dest
=
this
.
resolvedProperty
;
for
(
var
i
=
0
,
n
=
dest
.
length
;
i
!==
n
;
++
i
)
{
dest
[
i
]
=
buffer
[
offset
++
];
}
this
.
targetObject
.
needsUpdate
=
true
;
},
function
setValue_array_setMatrixWorldNeedsUpdate
(
buffer
,
offset
)
{
var
dest
=
this
.
resolvedProperty
;
for
(
var
i
=
0
,
n
=
dest
.
length
;
i
!==
n
;
++
i
)
{
dest
[
i
]
=
buffer
[
offset
++
];
}
this
.
targetObject
.
matrixWorldNeedsUpdate
=
true
;
}
],
[
// ArrayElement
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录