Commit 63e1d174 by Kai Westerkamp

Zwischenstand

parent 10e12c16
......@@ -24,18 +24,24 @@ void main(void)
debugout = Weights;
//debugout = vec4(1);
mat4x4 BoneTransform = Bones[BoneIDs[0]] * Weights[0];
BoneTransform += Bones[BoneIDs[1]] * Weights[1];
BoneTransform += Bones[BoneIDs[2]] * Weights[2];
BoneTransform += Bones[BoneIDs[3]] * Weights[3];
mat4x4 BoneTransform= mat4x4(0.0);
for(int i =0; i <4; i++){
BoneTransform += Bones[BoneIDs[i]] * Weights[i];
}
if(Weights[0] == 0.0){
BoneTransform = mat4x4(1.0);
}
vec4 Pos = BoneTransform*vec4(Position,1.0);
vec4 Nor = BoneTransform*vec4(Normal,0.0);
//Pos = vec4(Position,1.0);
vCamPosition = vec4(MV*Pos).xyz;
vCamNormal = N*Normal;
vCamNormal = N*(Nor.xyz);
vUV = UV;
......
......@@ -44,7 +44,7 @@ void MainWidget::initializeGL(){
// Shader
animationShader = initShader(QLatin1String(":/animate.frag"),QLatin1String(":/animate.vert"));
camDistance = 30.0;
camDistance = 100.0;
m_view = QMatrix4x4();
m_view.lookAt(QVector3D(0.0,camDistance,camDistance),QVector3D(0.0,0.0,0.0),QVector3D(0.0,1.0,0.0));
......@@ -54,7 +54,7 @@ void MainWidget::initializeGL(){
void MainWidget::loadNewMesh(){
QString fn = QFileDialog::getOpenFileName(NULL, tr("Open Mesh..."),
QString("D:\\Projekte\\GraPa\\A5\\Models"),
tr("*.md5mesh *.3ds *.md2 *.obj" ));
tr("*.md5mesh *.3ds *.md2 *.obj *.dae *.dxf" ));
if(fn.isEmpty())
return;
......@@ -98,15 +98,16 @@ void MainWidget::paintGL(){
QMatrix4x4 rot;
int time = QTime::currentTime().second()*1000+QTime::currentTime().msec();
rot.rotate(time/100.0*36/5,QVector3D(0,1,0));
//rot.rotate(time/100.0*36/5,QVector3D(0,1,0));
animationShader->bind();
animationShader->setUniformValue("colorTexture",0);
animationShader->setUniformValue("LightPos",QVector3D(0,100,100));
time = startTime.msecsTo(QTime::currentTime()) /1000.0;
float ftime = startTime.msecsTo(QTime::currentTime()) /1000.0;
mesh->render(animationShader,m_view*rot, m_projection,(float) time);
mesh->render(animationShader,m_view*rot, m_projection,(float) ftime);
animationShader->release();
update();
......
......@@ -12,6 +12,7 @@ void Vertex::AddBoneData(uint BoneID, float Weight){
}
qCritical()<<"More tahn 4 Bones at Vertex"<<pos<<BoneID<<Weight<<"added:"
<<IDs[0]<<weights[0]<<IDs[1]<<weights[1]<<IDs[2]<<weights[2]<<IDs[3]<<weights[3];
}
Mesh::MeshEntry::MeshEntry()
......@@ -61,8 +62,6 @@ Mesh::Node::Node()
children.clear();
}
Mesh::Mesh(QOpenGLFunctions_4_3_Core *f,QString fileName)
{
loaded = false;
......@@ -70,13 +69,13 @@ Mesh::Mesh(QOpenGLFunctions_4_3_Core *f,QString fileName)
numberOfBones = 0;
scene = NULL;
scene= importer.ReadFile(fileName.toStdString(),
//aiProcess_CalcTangentSpace |
aiProcess_GenSmoothNormals |
//aiProcess_JoinIdenticalVertices |
//aiProcess_SortByPType |
// ? aiProcess_FlipUVs |
aiProcess_Triangulate
);
//aiProcess_CalcTangentSpace |
aiProcess_GenSmoothNormals |
//aiProcess_JoinIdenticalVertices |
//aiProcess_SortByPType |
// ? aiProcess_FlipUVs |
aiProcess_Triangulate
);
qDebug()<<"File Read";
if( !scene)
......@@ -138,6 +137,20 @@ Mesh::Mesh(QOpenGLFunctions_4_3_Core *f,QString fileName)
globalInverseTransform = rootNode.transformation.inverted();
//Test Interpolation:
if(false){
const aiAnimation* animation = scene->mAnimations[0];
const aiNodeAnim *nodeAnimation = findAnimOfNode(animation,"upperarm.R");
qDebug()<<"KeyFrames";
for(uint i = 0; i < nodeAnimation->mNumPositionKeys; i++){
qDebug()<<nodeAnimation->mPositionKeys[i].mTime<<convert(nodeAnimation->mPositionKeys[i].mValue);
}
qDebug()<<"Interpolated";
for(float f = 0.0; f < (float)animation->mDuration; f += 0.5f){
qDebug()<<f<<calcInterpolatedTranslation(f,nodeAnimation);
}
}
//Todo read skeleton
......@@ -145,6 +158,12 @@ Mesh::Mesh(QOpenGLFunctions_4_3_Core *f,QString fileName)
}
}
Mesh::~Mesh()
{
entries.clear();
materials.clear();
}
void Mesh::initNode(const aiScene *scene, aiNode *node, Node &newNode,QString debugoffset){
newNode.name = node->mName.length != 0 ? node->mName.C_Str() : "";
newNode.transformation = QMatrix4x4(node->mTransformation[0]);
......@@ -152,7 +171,7 @@ void Mesh::initNode(const aiScene *scene, aiNode *node, Node &newNode,QString de
bool debug = false;
if(debug) qDebug()<<debugoffset.toStdString().c_str() << "NodeName" << newNode.name;
if(boneMap.find(newNode.name) != boneMap.end()){
if(debug) qDebug()<<debugoffset.toStdString().c_str() << "hasBone";
if(debug) qDebug()<<debugoffset.toStdString().c_str() << "hasBone";
}
if(debug) qDebug()<<debugoffset.toStdString().c_str() << " NumMeshes" << newNode.meshes.size();
......@@ -173,7 +192,6 @@ void Mesh::initNode(const aiScene *scene, aiNode *node, Node &newNode,QString de
}
void Mesh::initMaterial(QString dir, unsigned int i, const aiMaterial* material)
{
aiString mname;
......@@ -203,7 +221,9 @@ void Mesh::initMaterial(QString dir, unsigned int i, const aiMaterial* material)
aiString Path;
if (material->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
std::string FullPath = dir.toStdString() + "/" + Path.data;
QString temp = QString(Path.data);
temp = temp.split("\\").last();
std::string FullPath = dir.toStdString() + "/" + temp.toStdString();
materials[i].texture.Load(GL_TEXTURE_2D, QString(FullPath.c_str()));
materials[i].hasTexture = true;
} else{
......@@ -280,8 +300,95 @@ void Mesh::initMeshEntry(int index, aiMesh * entry){
}
void Mesh::render(QOpenGLShaderProgram *shader, QMatrix4x4 V,QMatrix4x4 P, float TimeInSeconds){
if(!loaded)
return;
if(f == NULL){
qDebug()<<"f = null";
return;
}
f->glEnableVertexAttribArray(positionIndex);
f->glEnableVertexAttribArray(normalIndex);
f->glEnableVertexAttribArray(uvIndex);
f->glEnableVertexAttribArray(boneIndex);
f->glEnableVertexAttribArray(boneweightIndex);
updateBoneTransform(TimeInSeconds);
QVector<QMatrix4x4> boneTransforms = QVector<QMatrix4x4>(bones.size());
for(int i = 0; i < bones.size(); i++){
boneTransforms[i] = bones[i].FinalTransformation;
}
if(boneTransforms.size() == 0){
boneTransforms.push_back(QMatrix4x4());
}
shader->setUniformValueArray("Bones",boneTransforms.constData(),boneTransforms.size());
renderNode(shader,rootNode,V,P,QMatrix4x4());
f->glDisableVertexAttribArray(positionIndex);
f->glDisableVertexAttribArray(normalIndex);
f->glDisableVertexAttribArray(uvIndex);
f->glDisableVertexAttribArray(boneIndex);
f->glDisableVertexAttribArray(boneweightIndex);
}
void Mesh::renderNode(QOpenGLShaderProgram *shader, Node &node, QMatrix4x4 V,QMatrix4x4 P,QMatrix4x4 M){
M *= node.transformation;
QMatrix4x4 MV = V*M;
QMatrix4x4 MVP = P*MV;
QMatrix3x3 normalMat = MV.normalMatrix();
shader->setUniformValue("MVP",MVP);
shader->setUniformValue("MV",MV);
shader->setUniformValue("N",normalMat);
for (int i = 0 ; i < node.meshes.size() ; i++) {
int index = node.meshes[i];
//load Material
renderMesh(shader, index);
}
for (int i = 0 ; i < node.children.size() ; i++) {
renderNode(shader,node.children[i],V,P,M);
}
}
void Mesh::renderMesh(QOpenGLShaderProgram *shader, int index)
{
int MaterialIndex = entries[index].materialIndex;
if (MaterialIndex < materials.size()) {
shader->setUniformValue("materialInfo.Diffuse",materials[MaterialIndex].Diffuse);
shader->setUniformValue("materialInfo.Specular",materials[MaterialIndex].Specular);
shader->setUniformValue("materialInfo.Shininess",materials[MaterialIndex].Shininess);
shader->setUniformValue("materialInfo.hasTexture",materials[MaterialIndex].hasTexture);
if(materials[MaterialIndex].hasTexture)
materials[MaterialIndex].texture.bind(f,GL_TEXTURE0);
}
// Draw Vertex Array
f->glBindBuffer(GL_ARRAY_BUFFER, entries[index].VB);
f->glVertexAttribPointer(positionIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
f->glVertexAttribPointer(normalIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12); //12
f->glVertexAttribPointer(uvIndex, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)24); // 12+12
f->glVertexAttribIPointer(boneIndex, 4, GL_INT, sizeof(Vertex), (const GLvoid*)32); // 12+12+8
f->glVertexAttribPointer(boneweightIndex, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)48); //12+12+8+16
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, entries[index].IB);
f->glDrawElements(GL_TRIANGLES, entries[index].numIndex, GL_UNSIGNED_INT, 0);
}
void Mesh::updateBoneTransform(float TimeInSeconds){
if(!scene)
if(!scene || bones.size() == 0)
return;
const aiAnimation* animation = scene->mAnimations[0];
......@@ -297,7 +404,6 @@ void Mesh::updateBoneTransform(float TimeInSeconds){
}
void Mesh::updateBoneHeirachy(float AnimTime,const aiAnimation* animation, Node node, QMatrix4x4 parentTransform){
QMatrix4x4 nodeTransformation = node.transformation;
......@@ -308,6 +414,14 @@ void Mesh::updateBoneHeirachy(float AnimTime,const aiAnimation* animation, Node
//calc Tcurrent Trasnformation;
if (nodeAnimation) {
// if(node.name.endsWith("upperarm.L")){
// qDebug()<<node.name;
// for (uint i = 0 ; i < nodeAnimation->mNumPositionKeys ; i++) {
// qDebug()<<convert(nodeAnimation->mPositionKeys[i].mValue)<<nodeAnimation->mPositionKeys[i].mTime;
// }
// }
QVector3D scale = calcInterpolatedScaling(AnimTime,nodeAnimation);
QQuaternion rot = calcInterpolatedRotation(AnimTime,nodeAnimation);
......@@ -336,7 +450,6 @@ void Mesh::updateBoneHeirachy(float AnimTime,const aiAnimation* animation, Node
}
const aiNodeAnim* Mesh::findAnimOfNode(const aiAnimation* animation, QString NodeName){
for (uint i = 0 ; i < animation->mNumChannels ; i++) {
const aiNodeAnim* nodeanim = animation->mChannels[i];
......@@ -350,29 +463,8 @@ const aiNodeAnim* Mesh::findAnimOfNode(const aiAnimation* animation, QString Nod
}
QVector3D Mesh::calcInterpolatedScaling(float AnimTime,const aiNodeAnim *nodeAnimation){
if(nodeAnimation->mNumScalingKeys == 1){
return convert(nodeAnimation->mScalingKeys[0].mValue);
}
//Index find
int index = 0;
for (uint i = 0 ; i < nodeAnimation->mNumScalingKeys - 1; i++) {
if (AnimTime < (float)nodeAnimation->mScalingKeys[i + 1].mTime) {
index = i;
break;
}
}
float DeltaTime = (float)(nodeAnimation->mScalingKeys[index+1].mTime - nodeAnimation->mScalingKeys[index].mTime);
float Factor = (AnimTime - (float)nodeAnimation->mScalingKeys[index].mTime) / DeltaTime;
QVector3D start = convert(nodeAnimation->mScalingKeys[index].mValue);
QVector3D end = convert(nodeAnimation->mScalingKeys[index+1].mValue);
return linInterpolate(start,end,Factor);
}
//Interpolarisation
QQuaternion Mesh::calcInterpolatedRotation(float AnimTime,const aiNodeAnim *nodeAnimation){
if(nodeAnimation->mNumRotationKeys == 1){
return convert(nodeAnimation->mRotationKeys[0].mValue);
......@@ -389,129 +481,113 @@ QQuaternion Mesh::calcInterpolatedRotation(float AnimTime,const aiNodeAnim *node
float DeltaTime = (float)(nodeAnimation->mRotationKeys[index+1].mTime - nodeAnimation->mRotationKeys[index].mTime);
float Factor = (AnimTime - (float)nodeAnimation->mRotationKeys[index].mTime) / DeltaTime;
// qDebug()<<AnimTime<<DeltaTime<<Factor;
// qDebug()<<AnimTime<<DeltaTime<<Factor;
const aiQuaternion start = nodeAnimation->mRotationKeys[index].mValue;
const aiQuaternion end = nodeAnimation->mRotationKeys[index+1].mValue;
aiQuaternion RotationQ;
aiQuaternion::Interpolate(RotationQ,start,end,Factor);
return convert(RotationQ);
//return convert(start);
return convert(RotationQ);
//return convert(start);
}
QVector3D Mesh::calcInterpolatedScaling(float AnimTime,const aiNodeAnim *nodeAnimation){
return calcInterpolatedVectorKey(AnimTime, nodeAnimation,nodeAnimation->mScalingKeys,nodeAnimation->mNumScalingKeys);
}
QVector3D Mesh::calcInterpolatedTranslation(float AnimTime, const aiNodeAnim *nodeAnimation){
if(nodeAnimation->mNumPositionKeys == 1){
convert(nodeAnimation->mPositionKeys[0].mValue);
return calcInterpolatedVectorKey(AnimTime,nodeAnimation, nodeAnimation->mPositionKeys,nodeAnimation->mNumPositionKeys);
}
QVector3D Mesh::calcInterpolatedVectorKey(float AnimTime,const aiNodeAnim *nodeAnimation, const aiVectorKey* keys, const int numberOfKeys){
if(numberOfKeys == 1){
return convert(keys[0].mValue);
}
//Index find
int index = 0;
for (uint i = 0 ; i < nodeAnimation->mNumPositionKeys - 1 ; i++) {
if (AnimTime < (float)nodeAnimation->mPositionKeys[i + 1].mTime) {
for (int i = 0 ; i < numberOfKeys - 1; i++) {
if (AnimTime < (float)keys[i + 1].mTime) {
index = i;
break;
}
}
float DeltaTime = (float)(keys[index+1].mTime - keys[index].mTime);
float Factor = (AnimTime - (float)keys[index].mTime) / DeltaTime;
float DeltaTime = (float)(nodeAnimation->mPositionKeys[index+1].mTime - nodeAnimation->mPositionKeys[index].mTime);
float Factor = (AnimTime - (float)nodeAnimation->mPositionKeys[index].mTime) / DeltaTime;
QVector3D start = convert(nodeAnimation->mPositionKeys[index].mValue);
QVector3D end = convert(nodeAnimation->mPositionKeys[index+1].mValue);
return linInterpolate(start,end,Factor);
}
QVector3D start = convert(keys[index].mValue);
QVector3D end = convert(keys[index+1].mValue);
return linInterpolate(start,end,Factor);
void Mesh::render(QOpenGLShaderProgram *shader, QMatrix4x4 V,QMatrix4x4 P, float TimeInSeconds){
if(!loaded)
return;
if(f == NULL){
qDebug()<<"f = null";
return;
if(start.distanceToPoint(end) == 0){ // same keyframse not interpolation
return start;
}
f->glEnableVertexAttribArray(positionIndex);
f->glEnableVertexAttribArray(normalIndex);
f->glEnableVertexAttribArray(uvIndex);
f->glEnableVertexAttribArray(boneIndex);
f->glEnableVertexAttribArray(boneweightIndex);
if(index == 0 ){
switch (nodeAnimation->mPreState) {
case aiAnimBehaviour_CONSTANT: // nearest Key
return Factor <0.5f ? start : end;
break;
case aiAnimBehaviour_REPEAT: // use start or end
//TODO
case aiAnimBehaviour_LINEAR: // linear interpolate
return linInterpolate(start,end,Factor);
case aiAnimBehaviour_DEFAULT: // take default node transformation
default:
return linInterpolate(start,end,Factor); //TODO
}
} else if((index+2)== numberOfKeys){
//end
return linInterpolate(start,end,Factor);
updateBoneTransform(TimeInSeconds);
QVector<QMatrix4x4> boneTransforms = QVector<QMatrix4x4>(bones.size());
for(int i = 0; i < bones.size(); i++){
boneTransforms[i] = bones[i].FinalTransformation;
}
shader->setUniformValueArray("Bones",boneTransforms.constData(),bones.size());
} else{
QVector3D before = convert(keys[index-1].mValue);
QVector3D after = convert(keys[index+2].mValue);
renderNode(shader,rootNode,V,P,QMatrix4x4());
f->glDisableVertexAttribArray(positionIndex);
f->glDisableVertexAttribArray(normalIndex);
f->glDisableVertexAttribArray(uvIndex);
f->glDisableVertexAttribArray(boneIndex);
f->glDisableVertexAttribArray(boneweightIndex);
// if(before.distanceToPoint(start) == 0|| end.distanceToPoint(after) == 0){
// qDebug()<<"Zeror before or after"<<AnimTime;
// return linInterpolate(start,end,Factor);
// }
QVector3D interpolated = Catmul(before,start,end,after,Factor);
//qDebug()<<nodeAnimation->mNodeName.C_Str()<<AnimTime<<start<<end<<interpolated;
return interpolated;
}
}
void Mesh::renderNode(QOpenGLShaderProgram *shader, Node &node, QMatrix4x4 V,QMatrix4x4 P,QMatrix4x4 M){
M *= node.transformation;
QVector3D Mesh::Catmul(QVector3D before, QVector3D start, QVector3D end, QVector3D after,float Factor){
float t0 = 0;
float t1 = timeCatmul(before,start,t0);
float t2 = timeCatmul(start,end,t1);
float t3 = timeCatmul(end,after,t2);
float t = Factor;//(1-Factor)*t1+Factor*t2;
//qDebug()<<before<<start<<end<<after;
//qDebug()<<t0<<t1<<t2<<t3<<t;
QMatrix4x4 MV = V*M;
QMatrix4x4 MVP = P*MV;
QMatrix3x3 normalMat = MV.normalMatrix();
QVector3D A1 = (t1-t)/(t1-t0)*before + (t-t0)/(t1-t0)*start;
QVector3D A2 = (t2-t)/(t2-t1)*start + (t-t1)/(t2-t1)*end;
QVector3D A3 = (t3-t)/(t3-t2)*end + (t-t2)/(t3-t2)*after;
shader->setUniformValue("MVP",MVP);
shader->setUniformValue("MV",MV);
shader->setUniformValue("N",normalMat);
QVector3D B1 = (t2-t)/(t2-t0)*A1 + (t-t0)/(t2-t0)*A2;
QVector3D B2 = (t3-t)/(t3-t1)*A2 + (t-t1)/(t3-t1)*A3;
for (int i = 0 ; i < node.meshes.size() ; i++) {
int index = node.meshes[i];
//load Material
renderMesh(shader, index);
}
QVector3D C = (t2-t)/(t2-t1)*B1 + (t-t1)/(t2-t1)*B2;
return C;
for (int i = 0 ; i < node.children.size() ; i++) {
renderNode(shader,node.children[i],V,P,M);
}
}
void Mesh::renderMesh(QOpenGLShaderProgram *shader, int index)
{
int MaterialIndex = entries[index].materialIndex;
if (MaterialIndex < materials.size()) {
shader->setUniformValue("materialInfo.Diffuse",materials[MaterialIndex].Diffuse);
shader->setUniformValue("materialInfo.Specular",materials[MaterialIndex].Specular);
shader->setUniformValue("materialInfo.Shininess",materials[MaterialIndex].Shininess);
shader->setUniformValue("materialInfo.hasTexture",materials[MaterialIndex].hasTexture);
if(materials[MaterialIndex].hasTexture)
materials[MaterialIndex].texture.bind(f,GL_TEXTURE0);
}
// Draw Vertex Array
f->glBindBuffer(GL_ARRAY_BUFFER, entries[index].VB);
f->glVertexAttribPointer(positionIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
f->glVertexAttribPointer(normalIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12); //12
f->glVertexAttribPointer(uvIndex, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)24); // 12+12
f->glVertexAttribIPointer(boneIndex, 4, GL_INT, sizeof(Vertex), (const GLvoid*)32); // 12+12+8
f->glVertexAttribPointer(boneweightIndex, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)48); //12+12+8+16
f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, entries[index].IB);
f->glDrawElements(GL_TRIANGLES, entries[index].numIndex, GL_UNSIGNED_INT, 0);
float Mesh::timeCatmul(QVector3D p1, QVector3D p2, float t_1){
float dist = p1.distanceToPoint(p2);
float t = powf(dist,0.5) +t_1;
// qDebug()<<dist<<t;
return t;
}
Mesh::~Mesh()
{
entries.clear();
materials.clear();
}
......@@ -133,6 +133,7 @@ private:
QVector3D calcInterpolatedScaling(float AnimTime,const aiNodeAnim *nodeAnimation);
QQuaternion calcInterpolatedRotation(float AnimTime,const aiNodeAnim *nodeAnimation);
QVector3D calcInterpolatedTranslation(float AnimTime, const aiNodeAnim *nodeAnimation);
QVector3D calcInterpolatedVectorKey(float AnimTime,const aiNodeAnim *nodeAnimation, const aiVectorKey* keys, const int numberOfKeys);
void renderNode(QOpenGLShaderProgram *shader, Node &node, QMatrix4x4 V,QMatrix4x4 P,QMatrix4x4 M);
......@@ -140,8 +141,10 @@ private:
QVector3D convert(aiVector3D in){ return QVector3D(in.x,in.y,in.z);}
QQuaternion convert(aiQuaternion quat){ return QQuaternion(quat.w,quat.x,quat.y,quat.z);}
QVector3D linInterpolate(QVector3D start, QVector3D end, float Factor){ return (1.0-Factor)*start + Factor*end;}
QVector3D linInterpolate(QVector3D start, QVector3D end, float Factor){ return (1.0-Factor)*start + Factor*end;}
QVector3D Catmul(QVector3D before, QVector3D start, QVector3D end, QVector3D after,float Factor);
float timeCatmul(QVector3D p1, QVector3D p2, float t_1);
};
......
......@@ -17,6 +17,7 @@ void Texture::Load(GLenum textureTarget, QString fileName)
qDebug()<<"No Image to load";
return;
}
qDebug()<<"Loading Texture:"<<fileName;
QString suffix = QFileInfo(fileName).completeSuffix();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment