/* QGL ObjectBase & Light Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "globject.h" #include "glcamera.h" #include "glmesh.h" #include "glscene.h" #include // static int _count = 0; ObjectBase::ObjectBase(Mesh * geom, Material * mat) { blend_src = GL_SRC_ALPHA; blend_dest = GL_ONE_MINUS_SRC_ALPHA; mat_.setToIdentity(); mesh_ = geom; setPreset(0); currentPreset().material = mat; // qDebug() << "ObjectBase, now" << ++_count; } ObjectBase::~ObjectBase() { // qDebug() << "~ObjectBase, now" << --_count; if (parent_) parent_->children_.removeAll(this); if (scene_) { scene_->__objectDeleted(this); scene_->setTreeChanged(); scene_->setTreeStructChanged(); } foreach(ObjectBase * c, children_) { c->parent_ = nullptr; delete c; } } ObjectBase * ObjectBase::clone(bool withChildren) { ObjectBase * o = new ObjectBase(); o->prev_pass = prev_pass; o->is_init = false; o->presets = presets; o->cur_preset = cur_preset; o->color_ = color_; o->type_ = type_; o->raw_matrix = raw_matrix; o->mat_ = mat_; o->trans = trans; o->trans_texture = trans_texture; o->itransform_ = itransform_; o->bound = bound; o->name_ = name_; // + "_copy"; o->blend_src = blend_src; o->blend_dest = blend_dest; o->pos_h = pos_h; o->mesh_ = mesh_; o->meta = meta; o->scene_ = nullptr; if (withChildren) { for (int i = 0; i < children_.size(); ++i) o->addChild(children_[i]->clone(withChildren)); } return o; } void ObjectBase::destroy() { if (mesh_) delete mesh_; } void ObjectBase::init() { calculateBoundingBox(); // material_.reflection.create(); // qDebug() << "init" << vbo.buffer_; is_init = true; } RenderPass ObjectBase::pass() const { RenderPass ret = rpSolid; if (currentPreset().material) if (currentPreset().material->hasTransparency()) ret = rpTransparent; return ret; } void ObjectBase::setScene(Scene * v) { scene_ = v; foreach(ObjectBase * c, children_) c->setScene(v); } void ObjectBase::addChild(ObjectBase * o) { if (o == this) return; if (o->parent_) o->parent_->children_.removeAll(o); children_ << o; o->parent_ = this; o->setScene(scene_); o->buildTransform(); /*if (scene_) { ObjectBaseList cl = o->children(true); cl << o; //foreach (ObjectBase * i, cl) { // emit view_->objectAdded(i); //} }*/ setSceneTreeChanged(); } void ObjectBase::removeChild(ObjectBase * o) { if (o == this) return; children_.removeAll(o); o->parent_ = nullptr; o->buildTransform(); setSceneTreeChanged(); } void ObjectBase::removeChild(int index) { children_[index]->parent_ = nullptr; children_[index]->buildTransform(); children_.removeAt(index); setSceneTreeChanged(); } void ObjectBase::clearChildren(bool deleteAll) { foreach(ObjectBase * i, children_) { i->scene_ = nullptr; i->parent_ = nullptr; i->clearChildren(deleteAll); if (deleteAll) { delete i; } else { i->buildTransform(); } } children_.clear(); setSceneTreeChanged(); } ObjectBase * ObjectBase::child(int index) { if (index < 0 || index >= children_.size()) return nullptr; return children_[index]; } ObjectBase * ObjectBase::child(const QString & name) { foreach(ObjectBase * i, children_) if (i->name_ == name) return i; return nullptr; } const ObjectBase * ObjectBase::child(int index) const { if (index < 0 || index >= children_.size()) return nullptr; return children_[index]; } const ObjectBase * ObjectBase::child(const QString & name) const { foreach(ObjectBase * i, children_) if (i->name_ == name) return i; return nullptr; } ObjectBaseList ObjectBase::children(bool all_) { if (!all_) return children_; ObjectBaseList cl; addChildren(cl, this); return cl; } bool ObjectBase::isVisible(bool check_parents) const { if (!check_parents) return currentPreset().visible; if (!currentPreset().visible) return false; ObjectBase * p = parent_; while (p) { if (!p->currentPreset().visible) return false; p = p->parent_; } return true; } void ObjectBase::setVisible(bool v) { currentPreset().visible = v; setSceneTreeChanged(); } void ObjectBase::setVisibleForAllPresets(bool v) { for (auto & p: presets) p.visible = v; } void ObjectBase::setReceiveShadows(bool on) { currentPreset().receive_shadow = on; setObjectsChanged(); } void ObjectBase::setCastShadows(bool on) { currentPreset().cast_shadow = on; if (type_ == glLight) ((Light *)this)->apply(); else setObjectsChanged(); } void ObjectBase::move(const QVector3D & dv) { trans.setTranslation(pos() + dv); buildTransform(); } void ObjectBase::moveTo(const QVector3D & dv) { trans.setTranslation(dv); buildTransform(); } void ObjectBase::move(GLfloat dx, GLfloat dy, GLfloat dz) { move(QVector3D(dx, dy, dz)); buildTransform(); } void ObjectBase::moveTo(GLfloat dx, GLfloat dy, GLfloat dz) { moveTo(QVector3D(dx, dy, dz)); buildTransform(); } void ObjectBase::moveX(GLfloat o) { trans.setTranslationX(posX() + o); buildTransform(); } void ObjectBase::moveY(GLfloat o) { trans.setTranslationY(posY() + o); buildTransform(); } void ObjectBase::moveZ(GLfloat o) { trans.setTranslationZ(posZ() + o); buildTransform(); } void ObjectBase::setPosX(GLfloat o) { trans.setTranslationX(o); buildTransform(); } void ObjectBase::setPosY(GLfloat o) { trans.setTranslationY(o); buildTransform(); } void ObjectBase::setPosZ(GLfloat o) { trans.setTranslationZ(o); buildTransform(); } void ObjectBase::setPos(GLfloat x, GLfloat y, GLfloat z) { trans.setTranslation(QVector3D(x, y, z)); buildTransform(); } void ObjectBase::setPos(const QVector3D & p) { trans.setTranslation(p); buildTransform(); } void ObjectBase::resetPos() { trans.setTranslation(QVector3D()); buildTransform(); } void ObjectBase::rotateX(GLfloat a) { raw_matrix = false; trans.setRotationX(trans.rotationX() + a); buildTransform(); } void ObjectBase::rotateY(GLfloat a) { raw_matrix = false; trans.setRotationY(trans.rotationY() + a); buildTransform(); } void ObjectBase::rotateZ(GLfloat a) { raw_matrix = false; trans.setRotationZ(trans.rotationZ() + a); // angles_.setZ(angles_.z() + a); // while (angles_.z() < -360.f) angles_.setZ(angles_.z() + 360.f); // while (angles_.z() > 360.f) angles_.setZ(angles_.z() - 360.f); buildTransform(); } void ObjectBase::setRotationX(GLfloat a) { raw_matrix = false; trans.setRotationX(a); buildTransform(); } void ObjectBase::setRotationY(GLfloat a) { raw_matrix = false; trans.setRotationY(a); buildTransform(); } void ObjectBase::setRotationZ(GLfloat a) { raw_matrix = false; trans.setRotationZ(a); // angles_.setZ(a); // while (angles_.z() < -360.f) angles_.setZ(angles_.z() + 360.f); // while (angles_.z() > 360.f) angles_.setZ(angles_.z() - 360.f); buildTransform(); } void ObjectBase::setRotation(const QVector3D & a) { raw_matrix = false; trans.setRotation(a); buildTransform(); } void ObjectBase::resetRotation() { raw_matrix = false; trans.setRotation(QVector3D()); buildTransform(); } void ObjectBase::scale(const QVector3D & sv) { raw_matrix = false; trans.setScale(trans.scale3D() * sv); buildTransform(); } void ObjectBase::scale(GLfloat sx, GLfloat sy, GLfloat sz) { raw_matrix = false; scale(QVector3D(sx, sy, sz)); buildTransform(); } void ObjectBase::scale(GLfloat sx, GLfloat sy) { raw_matrix = false; scale(QVector3D(sx, sy, sy)); buildTransform(); } void ObjectBase::scale(GLfloat sx) { raw_matrix = false; scale(QVector3D(sx, sx, sx)); buildTransform(); } void ObjectBase::scaleX(GLfloat a) { raw_matrix = false; trans.setScaleX(trans.scale3D().x() + a); buildTransform(); } void ObjectBase::scaleY(GLfloat a) { raw_matrix = false; trans.setScaleY(trans.scale3D().y() + a); buildTransform(); } void ObjectBase::scaleZ(GLfloat a) { raw_matrix = false; trans.setScaleZ(trans.scale3D().z() + a); buildTransform(); } void ObjectBase::setScale(const QVector3D & a) { raw_matrix = false; trans.setScale(a); buildTransform(); } void ObjectBase::setScale(GLfloat a) { raw_matrix = false; trans.setScale(a); buildTransform(); } void ObjectBase::setScaleX(GLfloat a) { raw_matrix = false; trans.setScaleX(a); buildTransform(); } void ObjectBase::setScaleY(GLfloat a) { raw_matrix = false; trans.setScaleY(a); buildTransform(); } void ObjectBase::setScaleZ(GLfloat a) { raw_matrix = false; trans.setScaleZ(a); buildTransform(); } void ObjectBase::resetScale() { raw_matrix = false; trans.setScale(1.f); buildTransform(); } void ObjectBase::setTransform(const Transform & t) { trans = t; buildTransform(); } void ObjectBase::addChildren(ObjectBaseList & list, ObjectBase * where) { foreach(ObjectBase * i, where->children_) { list << i; addChildren(list, i); } } void ObjectBase::calculateBoundingBox() { bound = Box3D(); if (mesh_) { bound = mesh_->boundingBox(); QVector c = bound.corners(), tc; foreach(QVector3D p, c) tc << (itransform_ * QVector4D(p, 1)).toVector3D(); bound = Box3D(tc); } foreach(ObjectBase * i, children_) { i->calculateBoundingBox(); bound |= i->boundingBox(); } } void ObjectBase::updateTransform() { buildTransform(true); } void ObjectBase::setProperty(const QString & pn, const QVariant & v) { meta[pn] = v; } QVariant ObjectBase::property(const QString & pn, bool * exists) const { if (exists) *exists = meta.contains(pn); return meta.value(pn); } bool ObjectBase::hasProperty(const QString & pn) const { return meta.contains(pn); } void ObjectBase::removeProperty(const QString & pn) { meta.remove(pn); } void ObjectBase::setPreset(int preset) { if (preset < 0) preset = 0; if (presets.size() <= preset) presets.resize(preset + 1); cur_preset = preset; for (auto * c: children_) c->setPreset(preset); } QVector ObjectBase::availablePresets() const { QVector ret; for (int i = 0; i < presets.size(); ++i) ret << i; return ret; } void ObjectBase::setMatrix(const QMatrix4x4 & t) { // raw_matrix = true; // mat_ = t; // pos_ = mat_.column(3).toVector3D(); // mat_.setColumn(3, QVector4D(0., 0., 0., 1.)); raw_matrix = false; trans.setMatrix(t); buildTransform(); } QMatrix4x4 ObjectBase::matrix() const { return trans.matrix(); } QVector3D ObjectBase::inParentSpace(const QVector3D & v) const { if (!parent_) return v; return (parent_->matrix() * QVector4D(v, 1)).toVector3D(); } void ObjectBase::transferTransformToChildren(bool only_scale) { QMatrix4x4 m = trans.matrix(); if (only_scale) m = trans.matrixScale(); foreach(ObjectBase * i, children_) i->trans.setMatrix(m * i->trans.matrix()); if (only_scale) resetScale(); else setMatrix(QMatrix4x4()); } void ObjectBase::cleanTree() { for (int i = 0; i < children_.size(); ++i) { ObjectBase * o = children_[i]; if (!o->hasChildren() && !o->mesh() && (o->type() == glMesh)) { delete o; --i; } o->cleanTree(); } } void ObjectBase::setTextureTransform(const Transform & t) { trans_texture = t; setObjectsChanged(); } void ObjectBase::setTextureMatrix(const QMatrix4x4 & t) { trans_texture.setMatrix(t); setObjectsChanged(); } QMatrix4x4 ObjectBase::textureMatrix() const { return trans_texture.matrix(); } QGenericMatrix<3, 2, float> ObjectBase::textureGLMatrix() const { QGenericMatrix<3, 2, float> ret = textureMatrix().toGenericMatrix<3, 2>(); ret(0, 2) = -trans_texture.translation().x(); ret(1, 2) = -trans_texture.translation().y(); return ret; } void ObjectBase::setAcceptLight(bool yes) { currentPreset().accept_light = yes; setObjectsChanged(); } void ObjectBase::setAcceptFog(bool yes) { currentPreset().accept_fog = yes; setObjectsChanged(); } bool ObjectBase::isSelected(bool check_parents) const { if (!check_parents) return selected_; if (selected_) return true; ObjectBase * p = parent_; while (p) { if (p->selected_) return true; p = p->parent_; } return false; } void ObjectBase::setSelected(bool yes) { // qDebug() << "select" << name() << view_; if (select_) selected_ = yes; if (!selected_) selected_aim = false; } ObjectBase * ObjectBase::selectedParent() const { ObjectBase * p = parent_; while (p) { if (p->selected_) return p; p = p->parent_; } return 0; } void ObjectBase::setMaterial(Material * m, bool with_children) { currentPreset().material = m; if (with_children) for (auto * i: children_) i->setMaterial(m, true); setObjectsChanged(); if (scene_) scene_->mat_changed = scene_->tree_changed = true; } void ObjectBase::setMaterialForPreset(Material * m, int p, bool with_children) { if (p < 0 || p >= presets.size()) return; presets[p].material = m; if (with_children) for (auto * i: children_) i->setMaterialForPreset(m, p, true); setObjectsChanged(); if (scene_) scene_->mat_changed = scene_->tree_changed = true; } Material * ObjectBase::materialForPreset(int p) { if (p < 0 || p >= presets.size()) return nullptr; return presets[p].material; } void ObjectBase::setColor(QColor c, bool with_children) { color_ = c; if (with_children) foreach(ObjectBase * i, children_) i->setColor(c, true); setObjectsChanged(); } void ObjectBase::setMesh(Mesh * v) { if (scene_) v = scene_->attachMesh(v); mesh_ = v; setSceneTreeChanged(); setObjectsChanged(); } void ObjectBase::buildTransform(bool force) { if (force) trans.setDirty(); itransform_.setToIdentity(); ObjectBase * p = parent_; if (p) itransform_ = p->itransform_; // if (raw_matrix) { // itransform_.translate(pos_); // itransform_ *= mat_; // //qDebug() << "raw_matrix" << itransform_; // } else localTransform(itransform_); // qDebug() << name_ << itransform_; foreach(ObjectBase * i, children_) i->buildTransform(force); setObjectsChanged(); } void ObjectBase::initInternal() { init(); foreach(ObjectBase * i, children_) i->initInternal(); } void ObjectBase::localTransform(QMatrix4x4 & m) { m *= trans.matrix(); } void ObjectBase::setSceneTreeChanged() { if (scene_) { scene_->setTreeChanged(); scene_->setTreeStructChanged(); } setObjectsChanged(); } void ObjectBase::setObjectsChanged() { int p = pass(); if (mesh_) { mesh_->setObjectsChanged(p, true); mesh_->setSelectionChanged(p, true); if (prev_pass != p) { mesh_->setObjectsChanged(prev_pass, true); mesh_->setSelectionChanged(prev_pass, true); } } prev_pass = p; } QMatrix4x4 ObjectBase::worldMatrix(QMatrix4x4 parent) const { QMatrix4x4 mat; // mat.translate(pos_); // if (raw_matrix) { // mat *= mat_; // } else { // if (angles_.z() != 0.f) mat.rotate(angles_.z(), 0., 0., 1.); // if (angles_.y() != 0.f) mat.rotate(angles_.y(), 0., 1., 0.); // if (angles_.x() != 0.f) mat.rotate(angles_.x(), 1., 0., 0.); // mat.scale(scale_); // } mat = trans.matrix(); return parent * mat; } AimedObject::AimedObject() { aim_dist = 1.; } QVector3D AimedObject::worldAim() const { QVector3D ret = worldPos() + worldDirection() * aim_dist; return ret; } void AimedObject::setAim(const QVector3D & p) { QVector3D dir = p - pos(); trans.setRotation(Transform::fromDirection(dir, trans.rotationY())); aim_dist = dir.length(); buildTransform(); // if (!p.isNull()) // qDebug() << "setAim" << p << aim() << worldAim(); } QVector3D AimedObject::direction() const { return trans.direction(); } void AimedObject::setDirection(const QVector3D & d) { // double len = qMax(aim_.length(), 0.001f); // aim_ = d.normalized() * len; buildTransform(); } void AimedObject::flyCloser(double s) { double tl = 1. / (1. + s); move(direction() * aim_dist * (1. - tl)); aim_dist *= tl; } void AimedObject::flyFarer(double s) { double tl = 1. * (1. + s); move(direction() * aim_dist * (1. - tl)); aim_dist *= tl; } void AimedObject::flyToDistance(double d) { move(direction() * (aim_dist - d)); aim_dist = d; // qDebug() << d << (aim() - pos()).length() << aim(); } void AimedObject::moveForward(const float & x, bool withZ) { QVector3D dv = itransform_.mapVector(QVector3D(0, 0, -x)); if (!withZ) dv[2] = 0.; move(dv); } void AimedObject::moveLeft(const float & x, bool withZ) { QVector3D dv = itransform_.mapVector(QVector3D(-x, 0, 0)); if (!withZ) dv[2] = 0.; move(dv); } void AimedObject::moveUp(const float & x, bool onlyZ) { QVector3D dv = itransform_.mapVector(QVector3D(0, x, 0)); if (onlyZ) dv[0] = dv[1] = 0.; move(dv); } void AimedObject::orbitZ(const float & a) { QVector3D pa = aim(); rotateZ(-a); move(pa - aim()); } void AimedObject::orbitXY(const float & a) { QVector3D pa = aim(); rotateX(-a); move(pa - aim()); } void AimedObject::transformChanged() {} Light::Light(const QVector3D & p, const QColor & c, float i): AimedObject(), shadow_map(nullptr, 0, false) { type_ = glLight; light_type = Omni; intensity = i; color_ = c; angle_start = angle_end = 90.; decay_linear = decay_quadratic = decay_start = 0.; decay_const = decay_end = 1.; size = 0.1; setPos(p); setDirection(0, 0, -1.); } ObjectBase * Light::clone(bool withChildren) { Light * o = new Light(*this); // GLObjectBase::clone(withChildren); o->is_init = false; o->name_ = name_; // + "_copy"; o->scene_ = nullptr; o->children_.clear(); if (withChildren) { for (int i = 0; i < children_.size(); ++i) o->addChild(children_[i]->clone(withChildren)); } o->color_ = color_; o->light_type = light_type; o->trans = trans; o->trans_texture = trans_texture; o->aim_dist = aim_dist; o->angle_start = angle_start; o->angle_end = angle_end; o->intensity = intensity; o->decay_const = decay_const; o->decay_linear = decay_linear; o->decay_quadratic = decay_quadratic; o->meta = meta; o->shadow_map.reinit(); return o; } void Light::apply() { if (scene_) scene_->setLightsChanged(); } QDataStream & operator<<(QDataStream & s, const ObjectBase::Preset & p) { ChunkStream cs; cs.add(1, p.visible).add(2, p.accept_light).add(3, p.accept_fog).add(4, p.cast_shadow).add(5, p.receive_shadow); s << cs.data(); return s; } QDataStream & operator>>(QDataStream & s, ObjectBase::Preset & p) { ChunkStream cs(s); cs.readAll(); cs.get(1, p.visible).get(2, p.accept_light).get(3, p.accept_fog).get(4, p.cast_shadow).get(5, p.receive_shadow); return s; } QDataStream & operator<<(QDataStream & s, const ObjectBase * p) { ChunkStream cs; // qDebug() << "place" << p->name() << "..."; cs.add(1, int(p->type_)) .add(7, p->raw_matrix) .add(8, p->line_width) .add(14, p->mat_) .add(16, p->children_.size()) .add(17, p->name_) .add(18, p->meta) .add(19, p->color_) .add(20, p->trans) .add(21, p->trans_texture) .add(22, p->presets); // qDebug() << "place self done"; if (p->type_ == ObjectBase::glLight) { // qDebug() << "place light ..."; const Light * l = (const Light *)p; cs.add(101, l->angle_start) .add(102, l->angle_end) .add(103, l->intensity) .add(104, l->decay_const) .add(105, l->decay_linear) .add(106, l->decay_quadratic) .add(107, l->decay_start) .add(108, l->decay_end) .add(109, int(l->light_type)) .add(111, l->distance()) .add(112, l->size) .add(113, l->light_map); } if (p->type_ == ObjectBase::glCamera) { // qDebug() << "place camera ..."; const Camera * c = (const Camera *)p; cs.add(200, c->aim()) .add(201, c->fov_) .add(202, c->depth_start) .add(206, c->mirror_x) .add(207, c->mirror_y) .add(208, c->distance()) .add(209, c->roll_); } // qDebug() << "place" << p->name() << cs.data().size() << s.device()->size(); s << cs.data(); foreach(const ObjectBase * c, p->children_) s << c; return s; } QDataStream & operator>>(QDataStream & s, ObjectBase *& p) { ChunkStream cs(s); p = nullptr; int ccnt = 0; Light * l = nullptr; Camera * c = nullptr; // qDebug() << "read obj ..."; while (!cs.atEnd()) { switch (cs.read()) { case 1: { ObjectBase::Type type = (ObjectBase::Type)cs.getData(); switch (type) { case ObjectBase::glMesh: p = new ObjectBase(); break; case ObjectBase::glLight: p = new Light(); l = (Light *)p; break; case ObjectBase::glCamera: p = new Camera(); c = (Camera *)p; break; default: break; } if (p) p->type_ = type; } break; case 2: if (p) p->currentPreset().accept_light = cs.getData(); break; case 3: if (p) p->currentPreset().accept_fog = cs.getData(); break; case 4: if (p) p->currentPreset().visible = cs.getData(); break; case 5: if (p) p->currentPreset().cast_shadow = cs.getData(); break; case 6: if (p) p->currentPreset().receive_shadow = cs.getData(); break; case 7: if (p) p->raw_matrix = cs.getData(); break; case 8: if (p) p->line_width = cs.getData(); break; case 14: if (p) p->mat_ = cs.getData(); break; case 16: if (p) ccnt = cs.getData(); break; case 17: if (p) p->name_ = cs.getData(); break; case 18: if (p) p->meta = cs.getData(); break; case 19: if (p) p->color_ = cs.getData(); break; case 20: if (p) p->trans = cs.getData(); break; case 21: if (p) p->trans_texture = cs.getData(); break; case 22: if (p) { p->presets = cs.getData>(); if (p->presets.isEmpty()) p->presets.resize(1); } break; case 100: if (l) l->setAim(cs.getData()); break; case 101: if (l) l->angle_start = cs.getData(); break; case 102: if (l) l->angle_end = cs.getData(); break; case 103: if (l) l->intensity = cs.getData(); break; case 104: if (l) l->decay_const = cs.getData(); break; case 105: if (l) l->decay_linear = cs.getData(); break; case 106: if (l) l->decay_quadratic = cs.getData(); break; case 107: if (l) l->decay_start = cs.getData(); break; case 108: if (l) l->decay_end = cs.getData(); break; case 109: if (l) l->light_type = (Light::Type)cs.getData(); break; case 111: if (l) l->setDistance(cs.getData()); break; case 112: if (l) l->size = cs.getData(); break; case 113: if (l) l->light_map = cs.getData(); break; case 200: if (c) c->setAim(cs.getData()); break; case 201: if (c) c->setFOV(cs.getData()); break; case 202: if (c) c->setDepthStart(cs.getData()); break; case 206: if (c) c->mirror_x = cs.getData(); break; case 207: if (c) c->mirror_y = cs.getData(); break; case 208: if (c) c->setDistance(cs.getData()); break; case 209: if (c) c->roll_ = cs.getData(); break; } } // qDebug() << p->name() << ccnt; for (int i = 0; i < ccnt; ++i) { ObjectBase * c = nullptr; s >> c; if (!c) continue; c->parent_ = p; p->children_ << c; } p->buildTransform(); return s; }