Improve node to svg conversion.

This commit is contained in:
jmsgrogan 2023-01-12 17:45:06 +00:00
parent 64f0b3e77a
commit 26ecae46b3
22 changed files with 403 additions and 126 deletions

View file

@ -1,6 +1,7 @@
#include "BlochSphereNode.h"
#include "CircleNode.h"
#include "LineNode.h"
BlochSphereNode::BlochSphereNode(const Point& location)
: AbstractVisualNode(location, "BlochSphereNode")
@ -32,9 +33,17 @@ void BlochSphereNode::update(SceneInfo* sceneInfo)
mChildren.clear();
auto loc = DiscretePoint(mSize / 2.0, mSize / 2.0);
auto loc = Point(mSize / 2.0, mSize / 2.0);
mInnerCircle = std::make_unique<CircleNode>(loc, mSize / 2.0);
mInnerCircle->setMinorRadius(mSize / 4.0);
mOuterCircle = std::make_unique<CircleNode>(loc, mSize/2.0);
addChild(mOuterCircle.get());
mCentreCircle = std::make_unique<CircleNode>(loc, mSize / 50.0);
mCentreCircle->setFillColor(Color(0, 0, 0));
mCentreCircle->setHasStrokeColor(false);
addChild(mInnerCircle.get());
addChild(mOuterCircle.get());
addChild(mCentreCircle.get());
}

View file

@ -1,36 +1,23 @@
#include "TestFramework.h"
#include "TestUtils.h"
#include "TestRenderUtils.h"
#include "BlochSphereNode.h"
#include "BlochSphere.h"
#include "Scene.h"
#include "SvgConverter.h"
#include "SvgWriter.h"
#include "SvgDocument.h"
#include "File.h"
TEST_CASE(TestBlochSphereNode, "quantum_computing")
{
TestRenderer renderer(100, 100);
auto node = std::make_unique<BlochSphereNode>(Point(0.5, 0.5));
Qubit state({ 1.0, 0.0 }, { 0.0, 0.0 });
auto bloch_sphere = std::make_unique<BlochSphere>(state);
node->setSize(100);
node->setContent(bloch_sphere.get());
auto scene = std::make_unique<Scene>();
scene->addNode(node.get());
scene->update();
SvgConverter converter;
auto svg_document = converter.convert(scene.get());
SvgWriter writer;
auto svg_content = writer.toString(svg_document.get());
File svg_file(TestUtils::getTestOutputDir(__FILE__) / "bloch_sphere.svg");
svg_file.writeText(svg_content);
renderer.getScene()->addNode(node.get());
renderer.writeSvg(TestUtils::getTestOutputDir(__FILE__) / "bloch_sphere.svg");
}

View file

@ -17,6 +17,7 @@ public:
TRIANGLE,
POINT,
PATH,
CIRCLE,
UNKNOWN
};

View file

@ -0,0 +1,43 @@
#include "Circle.h"
Circle::Circle(const Point& centre, double radius)
: mCentre(centre),
mRadius(radius)
{
mMinorRadius = mRadius;
}
const Point& Circle::getLocation() const
{
return mCentre;
}
double Circle::getRadius() const
{
return mRadius;
}
double Circle::getMinorRadius() const
{
return mMinorRadius;
}
void Circle::setMinorRadius(double radius)
{
mMinorRadius = radius;
}
void Circle::sample(Grid<unsigned char>* grid) const
{
}
Circle::Bounds Circle::getSize() const
{
return Bounds{ 2.0 * mRadius, 2.0 * mMinorRadius };
}
Circle::Type Circle::getType() const
{
return Type::CIRCLE;
}

View file

@ -0,0 +1,29 @@
#pragma once
#include "AbstractGeometricItem.h"
#include "Point.h"
class Circle : public AbstractGeometricItem
{
public:
Circle(const Point& centre, double radius = 0.5);
const Point& getLocation() const override;
double getRadius() const;
double getMinorRadius() const;
void setMinorRadius(double radius);
void sample(Grid<unsigned char>* grid) const override;
Bounds getSize() const override;
Type getType() const override;
private:
double mMinorRadius{ 0.5 };
double mRadius{ 0.5 };
Point mCentre;
};

View file

@ -5,16 +5,22 @@
#include "SceneItem.h"
#include "SceneModel.h"
#include "SceneText.h"
#include "AbstractGeometricItem.h"
#include "AbstractMesh.h"
#include "TriMesh.h"
#include "AbstractFace.h"
#include "SvgShapeElements.h"
#include "Circle.h"
std::unique_ptr<SvgDocument> SvgConverter::convert(Scene* scene)
#include "SvgShapeElements.h"
#include "XmlAttribute.h"
std::unique_ptr<SvgDocument> SvgConverter::convert(Scene* scene, double width, double height) const
{
auto doc = std::make_unique<SvgDocument>();
doc->setViewBox(0.0, 0.0, width, height);
scene->update();
@ -22,44 +28,125 @@ std::unique_ptr<SvgDocument> SvgConverter::convert(Scene* scene)
{
if (item->getType() == SceneItem::Type::MODEL)
{
auto mesh = dynamic_cast<SceneModel*>(item)->getMesh();
auto transform = item->getTransform();
if (mesh->getType() == AbstractMesh::MeshType::TRI)
auto model = dynamic_cast<SceneModel*>(item);
if (model->getGeometry())
{
for(const auto& face : dynamic_cast<TriMesh*>(mesh)->getFaces())
{
auto svg_tri = std::make_unique<SvgPolygon>();
std::vector<DiscretePoint> points(3);
unsigned count = 0;
auto face_locs = face->getNodeLocations();
for(auto& loc : face_locs)
{
const auto x = loc.getX() * transform.getScaleX() + transform.getLocation().getX();
const auto y = loc.getY() * transform.getScaleY() + transform.getLocation().getY();
points[count] = {static_cast<unsigned>(x), static_cast<unsigned>(y)};
count++;
}
svg_tri->setPoints(points);
svg_tri->setFill(item->getFillColor());
doc->getRoot()->addChild(std::move(svg_tri));
if (scene->shouldShowMeshOutline())
{
auto mesh_outline = std::make_unique<SvgPolyline>();
points.push_back(points[0]);
mesh_outline->setPoints(points);
mesh_outline->setStrokeColor({0, 0, 0});
mesh_outline->setStrokeWidth(0.1);
doc->getRoot()->addChild(std::move(mesh_outline));
}
}
convertPrimitive(doc.get(), model);
}
else
{
convertMesh(doc.get(), model, scene->shouldShowMeshOutline());
}
}
else
{
auto text = dynamic_cast<SceneText*>(item);
convertText(doc.get(), text);
}
}
return std::move(doc);
}
void SvgConverter::convertMesh(SvgDocument* document, SceneModel* model, bool showOutline) const
{
auto mesh = model->getMesh();
auto transform = model->getTransform();
if (mesh->getType() == AbstractMesh::MeshType::TRI)
{
for (const auto& face : dynamic_cast<TriMesh*>(mesh)->getFaces())
{
auto svg_tri = std::make_unique<SvgPolygon>();
std::vector<Point> points(3);
unsigned count = 0;
auto face_locs = face->getNodeLocations();
for (const auto& loc : face_locs)
{
const auto x = loc.getX() * transform.getScaleX() + transform.getLocation().getX();
const auto y = loc.getY() * transform.getScaleY() + transform.getLocation().getY();
points[count] = { x, y };
count++;
}
svg_tri->setPoints(points);
svg_tri->setFill(model->getFillColor());
document->getRoot()->addChild(std::move(svg_tri));
if (showOutline)
{
auto mesh_outline = std::make_unique<SvgPolyline>();
points.push_back(points[0]);
mesh_outline->setPoints(points);
mesh_outline->setStrokeColor({ 0, 0, 0 });
mesh_outline->setStrokeWidth(0.1);
document->getRoot()->addChild(std::move(mesh_outline));
}
}
}
}
void SvgConverter::setStyle(SceneModel* model, SvgShapeElement* element) const
{
auto transform = model->getTransform();
if (model->hasFillColor())
{
element->setFill(model->getFillColor());
}
else
{
element->setNoFill();
}
if (model->hasOutlineColor())
{
element->setStrokeColor(model->getOutlineColor());
element->setStrokeWidth(1.0 / transform.getScaleX());
}
else
{
element->setNoStroke();
}
element->addAttribute(std::move(toTransform(transform)));
}
void SvgConverter::convertPrimitive(SvgDocument* document, SceneModel* model) const
{
if (model->getGeometry()->getType() == AbstractGeometricItem::Type::RECTANGLE)
{
auto rect = std::make_unique<SvgRectangle>();
rect->setWidth(1.0);
rect->setHeight(1.0);
setStyle(model, rect.get());
document->getRoot()->addChild(std::move(rect));
}
else if (model->getGeometry()->getType() == AbstractGeometricItem::Type::CIRCLE)
{
auto model_circle = dynamic_cast<Circle*>(model->getGeometry());
auto circle = std::make_unique<SvgCircle>();
circle->setRadius(model_circle->getRadius());
setStyle(model, circle.get());
document->getRoot()->addChild(std::move(circle));
}
}
void SvgConverter::convertText(SvgDocument* document, SceneText* model) const
{
}
std::unique_ptr<XmlAttribute> SvgConverter::toTransform(const Transform& transform) const
{
auto svg_transform = std::make_unique<XmlAttribute>("transform");
std::string ops;
ops += "translate(" + std::to_string(transform.getLocation().getX()) + " " + std::to_string(transform.getLocation().getY()) + ") ";
ops += "scale(" + std::to_string(transform.getScaleX()) + " " + std::to_string(transform.getScaleY()) + ") ";
svg_transform->setValue(ops);
return std::move(svg_transform);
}

View file

@ -3,10 +3,28 @@
#include <memory>
class SvgDocument;
class SvgShapeElement;
class XmlAttribute;
class Scene;
class SceneModel;
class SceneText;
class Transform;
class SvgConverter
{
public:
std::unique_ptr<SvgDocument> convert(Scene* scene);
std::unique_ptr<SvgDocument> convert(Scene* scene, double width = 800.0, double height = 800.0) const;
private:
void convertMesh(SvgDocument* document, SceneModel* model, bool showOutline = false) const;
void convertPrimitive(SvgDocument* document, SceneModel* model) const;
void convertText(SvgDocument* document, SceneText* model) const;
void setStyle(SceneModel* model, SvgShapeElement* element) const;
std::unique_ptr<XmlAttribute> toTransform(const Transform& transform) const;
};

View file

@ -5,8 +5,7 @@
#include "AbstractMesh.h"
#include "MeshPrimitives.h"
#include "SceneInfo.h"
#include <iostream>
#include "Circle.h"
CircleNode::CircleNode(const Point& location, double radius)
: GeometryNode(location),
@ -51,22 +50,53 @@ void CircleNode::setRadius(double radius)
}
}
void CircleNode::setMinorRadius(double radius)
{
if (mMinorRadius != radius)
{
mMinorRadius = radius;
mTransformIsDirty = true;
}
}
void CircleNode::createOrUpdateGeometry(SceneInfo* sceneInfo)
{
if (sceneInfo->mSupportsGeometryPrimitives)
{
auto circle = std::make_unique<Circle>(Point{ 0, 0 }, 0.5);
circle->setMinorRadius(mMinorRadius);
mBackgroundItem = std::make_unique<SceneModel>(std::move(circle));
}
else
{
auto mesh = MeshPrimitives::buildCircleAsTriMesh();
mBackgroundItem = std::make_unique<SceneModel>(std::move(mesh));
}
mBackgroundItem->setName(mName + "_Model");
}
void CircleNode::updateMaterial()
{
if (!mBackgroundItem)
{
return;
}
if (mHasFillColor)
{
mBackgroundItem->setFillColor(mFillColor);
}
if (mHasStrokeColor)
{
mBackgroundItem->setOutlineColor(mStrokeColor);
}
}
void CircleNode::update(SceneInfo* sceneInfo)
{
if (!mBackgroundItem || mGeometryIsDirty)
{
auto mesh = MeshPrimitives::buildCircleAsTriMesh();
if (!mBackgroundItem)
{
mBackgroundItem = std::make_unique<SceneModel>(std::move(mesh));
mBackgroundItem->setName(mName + "_Model");
mBackgroundItem->setFillColor(mFillColor);
}
else
{
mBackgroundItem->updateMesh(std::move(mesh));
}
createOrUpdateGeometry(sceneInfo);
mGeometryIsDirty = false;
}
@ -78,7 +108,7 @@ void CircleNode::update(SceneInfo* sceneInfo)
if (mMaterialIsDirty)
{
mBackgroundItem->setFillColor(mFillColor);
updateMaterial();
mMaterialIsDirty = false;
}
}

View file

@ -7,18 +7,22 @@ class CircleNode : public GeometryNode
public:
CircleNode(const Point& location, double radius);
Type getType();
Type getType() override;
double getRadius() const;
SceneItem* getSceneItem(std::size_t idx) const override;
std::size_t getNumSceneItems() const override;
void setMinorRadius(double radius);
void setRadius(double radius);
void update(SceneInfo* sceneInfo) override;
private:
double mRadius{1};
void createOrUpdateGeometry(SceneInfo* sceneInfo);
void updateMaterial();
double mRadius{1.0};
double mMinorRadius{ 1.0 };
std::unique_ptr<SceneModel> mBackgroundItem;
std::unique_ptr<SceneModel> mOutlineItem;

View file

@ -0,0 +1,18 @@
#pragma once
#include "GeometryNode.h"
class LineNode : public GeometryNode
{
public:
LineNode(const Point& location)
: GeometryNode(location)
{
}
Type getType()
{
return Type::Line;
}
};

View file

@ -86,6 +86,23 @@ void RectangleNode::createOrUpdateGeometry(SceneInfo* sceneInfo)
mBackgroundItem->setName(mName + "_Model");
}
void RectangleNode::updateMaterial()
{
if (!mBackgroundItem)
{
return;
}
if (mHasFillColor)
{
mBackgroundItem->setFillColor(mFillColor);
}
if (mHasStrokeColor)
{
mBackgroundItem->setOutlineColor(mStrokeColor);
}
}
void RectangleNode::update(SceneInfo* sceneInfo)
{
if (!mBackgroundItem || mGeometryIsDirty)
@ -96,20 +113,13 @@ void RectangleNode::update(SceneInfo* sceneInfo)
if (mTransformIsDirty)
{
mBackgroundItem->updateTransform({mLocation, static_cast<double>(mWidth), static_cast<double>(mHeight)});
mBackgroundItem->updateTransform({mLocation, mWidth, mHeight});
mTransformIsDirty = false;
}
if (mMaterialIsDirty)
{
if (mHasFillColor)
{
mBackgroundItem->setFillColor(mFillColor);
}
if (mHasStrokeColor)
{
mBackgroundItem->setOutlineColor(mStrokeColor);
}
updateMaterial();
mMaterialIsDirty = false;
}
}

View file

@ -24,6 +24,7 @@ public:
private:
void createOrUpdateGeometry(SceneInfo* sceneInfo);
void updateMaterial();
double mWidth{1};
double mHeight{1};

View file

@ -27,23 +27,7 @@ public:
protected:
unsigned mStrokeThickness{0};
Type mType;
bool mGeometryIsDirty{true};
};
class LineNode : public GeometryNode
{
public:
LineNode(const Point& location)
: GeometryNode(location)
{
}
Type getType()
{
return Type::Line;
}
};
using GeometryNodePtr = std::unique_ptr<GeometryNode>;

View file

@ -20,20 +20,33 @@ const Color& MaterialNode::getStrokeColor() const
return mStrokeColor;
}
void MaterialNode::setHasStrokeColor(bool hasStroke)
{
if (mHasStrokeColor != hasStroke)
{
mHasStrokeColor = hasStroke;
mMaterialIsDirty = true;
}
}
void MaterialNode::setFillColor(const Color& color)
{
mHasFillColor = true;
if (mFillColor != color)
{
mMaterialIsDirty = true;
mFillColor = color;
mFillColor = color;
}
}
void MaterialNode::setStrokeColor(const Color& color)
{
mHasStrokeColor = true;
if (mStrokeColor != color)
{
mMaterialIsDirty = true;
mStrokeColor = color;
mStrokeColor = color;
}
}

View file

@ -12,6 +12,8 @@ public:
const Color& getFillColor() const;
const Color& getStrokeColor() const;
void setHasStrokeColor(bool hasStroke);
void setFillColor(const Color& color);
void setStrokeColor(const Color& color);

View file

@ -47,6 +47,8 @@ void Scene::setBackgroundColor(const Color& color)
void Scene::updateNode(AbstractVisualNode* node)
{
node->update(mSceneInfo.get());
for (auto child : node->getChildren())
{
if (child->getIsVisible())
@ -55,8 +57,6 @@ void Scene::updateNode(AbstractVisualNode* node)
}
}
node->update(mSceneInfo.get());
for (std::size_t idx=0; idx< node->getNumSceneItems(); idx++)
{
if (auto item = node->getSceneItem(idx))

View file

@ -27,6 +27,11 @@ list(APPEND web_LIB_INCLUDES
html/elements/HtmlHeadElement.cpp
html/elements/HtmlBodyElement.cpp
html/elements/HtmlParagraphElement.cpp
svg/SvgDocument.h
svg/SvgWriter.h
svg/SvgShapeElement.h
svg/SvgElement.h
svg/elements/SvgShapeElements.h
svg/SvgDocument.cpp
svg/SvgWriter.cpp
svg/SvgShapeElement.cpp

View file

@ -10,6 +10,20 @@ SvgShapeElement::SvgShapeElement(const std::string& tagName)
}
void SvgShapeElement::setNoFill()
{
auto attr = std::make_unique<XmlAttribute>("fill");
attr->setValue("none");
addAttribute(std::move(attr));
}
void SvgShapeElement::setNoStroke()
{
auto attr = std::make_unique<XmlAttribute>("stroke");
attr->setValue("none");
addAttribute(std::move(attr));
}
void SvgShapeElement::setFill(const Color& fill)
{
auto attr = std::make_unique<XmlAttribute>("fill");

View file

@ -10,8 +10,11 @@ public:
void setFill(const Color& fill);
void setNoFill();
void setStrokeWidth(double width);
void setStrokeColor(const Color& stroke);
void setNoStroke();
};

View file

@ -8,19 +8,19 @@ SvgCircle::SvgCircle()
}
void SvgCircle::setLocation(const DiscretePoint& loc)
void SvgCircle::setLocation(const Point& loc)
{
auto cx = std::make_unique<XmlAttribute>("cx");
auto cy = std::make_unique<XmlAttribute>("cy");
cx->setValue(std::to_string(loc.GetX()));
cy->setValue(std::to_string(loc.GetY()));
cx->setValue(std::to_string(loc.getX()));
cy->setValue(std::to_string(loc.getY()));
addAttribute(std::move(cx));
addAttribute(std::move(cy));
}
void SvgCircle::setRadius(unsigned rad)
void SvgCircle::setRadius(double rad)
{
auto r = std::make_unique<XmlAttribute>("r");
r->setValue(std::to_string(rad));
@ -33,19 +33,19 @@ SvgRectangle::SvgRectangle()
}
void SvgRectangle::setLocation(const DiscretePoint& loc)
void SvgRectangle::setLocation(const Point& loc)
{
auto x = std::make_unique<XmlAttribute>("x");
auto y = std::make_unique<XmlAttribute>("y");
x->setValue(std::to_string(loc.GetX()));
y->setValue(std::to_string(loc.GetY()));
x->setValue(std::to_string(loc.getX()));
y->setValue(std::to_string(loc.getY()));
addAttribute(std::move(x));
addAttribute(std::move(y));
}
void SvgRectangle::setWidth(unsigned w)
void SvgRectangle::setWidth(double w)
{
auto width = std::make_unique<XmlAttribute>("width");
@ -54,7 +54,7 @@ void SvgRectangle::setWidth(unsigned w)
addAttribute(std::move(width));
}
void SvgRectangle::setHeight(unsigned h)
void SvgRectangle::setHeight(double h)
{
auto height = std::make_unique<XmlAttribute>("height");
@ -70,14 +70,14 @@ SvgPolygon::SvgPolygon()
}
void SvgPolygon::setPoints(const std::vector<DiscretePoint>& locs)
void SvgPolygon::setPoints(const std::vector<Point>& locs)
{
auto points = std::make_unique<XmlAttribute>("points");
std::stringstream sstr;
for (const auto& loc : locs)
{
sstr << loc.GetX() << "," << loc.GetY() << " ";
sstr << loc.getX() << "," << loc.getY() << " ";
}
points->setValue(sstr.str());
addAttribute(std::move(points));
@ -91,14 +91,14 @@ SvgPolyline::SvgPolyline()
addAttribute(std::move(fill));
}
void SvgPolyline::setPoints(const std::vector<DiscretePoint>& locs)
void SvgPolyline::setPoints(const std::vector<Point>& locs)
{
auto points = std::make_unique<XmlAttribute>("points");
std::stringstream sstr;
for (const auto& loc : locs)
{
sstr << loc.GetX() << "," << loc.GetY() << " ";
sstr << loc.getX() << "," << loc.getY() << " ";
}
points->setValue(sstr.str());
addAttribute(std::move(points));

View file

@ -1,7 +1,7 @@
#pragma once
#include "SvgShapeElement.h"
#include "DiscretePoint.h"
#include "Point.h"
#include "XmlAttribute.h"
@ -10,9 +10,9 @@ class SvgCircle : public SvgShapeElement
public:
SvgCircle();
void setLocation(const DiscretePoint& loc);
void setLocation(const Point& loc);
void setRadius(unsigned rad);
void setRadius(double rad);
};
class SvgRectangle : public SvgShapeElement
@ -20,11 +20,11 @@ class SvgRectangle : public SvgShapeElement
public:
SvgRectangle();
void setLocation(const DiscretePoint& loc);
void setLocation(const Point& loc);
void setWidth(unsigned width);
void setWidth(double width);
void setHeight(unsigned height);
void setHeight(double height);
};
class SvgPolygon : public SvgShapeElement
@ -32,7 +32,7 @@ class SvgPolygon : public SvgShapeElement
public:
SvgPolygon();
void setPoints(const std::vector<DiscretePoint>& loc);
void setPoints(const std::vector<Point>& loc);
};
class SvgPolyline : public SvgShapeElement
@ -40,7 +40,7 @@ class SvgPolyline : public SvgShapeElement
public:
SvgPolyline();
void setPoints(const std::vector<DiscretePoint>& loc);
void setPoints(const std::vector<Point>& loc);
};
class SvgPath : public SvgShapeElement

View file

@ -3,9 +3,16 @@
#include "DrawingSurface.h"
#include "DrawingContext.h"
#include "AbstractPainter.h"
#include "Scene.h"
#include "SvgConverter.h"
#include "SvgWriter.h"
#include "SvgDocument.h"
#include "Image.h"
#include "PngWriter.h"
#include "Scene.h"
#include "File.h"
class TestRenderer
{
@ -34,6 +41,18 @@ public:
writer.write(image);
}
void writeSvg(const Path& path)
{
SvgConverter converter;
auto svg_document = converter.convert(mSurface->getScene());
SvgWriter writer;
auto svg_content = writer.toString(svg_document.get());
File svg_file(path);
svg_file.writeText(svg_content);
}
private:
std::unique_ptr<DrawingSurface> mSurface;
std::unique_ptr<DrawingContext> mDrawingContext;