|
|
223ac6 |
|
|
|
223ac6 |
#include <cassert></cassert>
|
|
|
223ac6 |
#include <cstdlib></cstdlib>
|
|
|
223ac6 |
|
|
|
223ac6 |
#include <algorithm></algorithm>
|
|
|
223ac6 |
#include <map></map>
|
|
|
223ac6 |
|
|
|
223ac6 |
#include <gl gl.h=""></gl>
|
|
|
223ac6 |
#include <gl glext.h=""></gl>
|
|
|
223ac6 |
|
|
|
223ac6 |
#include "mesh.h"
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void Mesh::createbuf() const {
|
|
|
223ac6 |
if (ibuf && vbuf && elements) return;
|
|
|
223ac6 |
dirty();
|
|
|
223ac6 |
if (vertices.empty() || triangles.empty()) return;
|
|
|
223ac6 |
|
|
|
223ac6 |
glGenBuffers(1, &vbuf);
|
|
|
223ac6 |
glBindBuffer(GL_ARRAY_BUFFER, vbuf);
|
|
|
223ac6 |
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*vertices.size(), &vertices.front(), GL_STATIC_DRAW);
|
|
|
223ac6 |
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
223ac6 |
|
|
|
223ac6 |
glGenBuffers(1, &ibuf);
|
|
|
223ac6 |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf);
|
|
|
223ac6 |
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Triangle)*triangles.size(), &triangles.front(), GL_STATIC_DRAW);
|
|
|
223ac6 |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
223ac6 |
|
|
|
223ac6 |
elements = triangles.size()*3u;
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
void Mesh::dirty() const {
|
|
|
223ac6 |
if (ibuf) { glDeleteBuffers(1, &ibuf); ibuf = 0; }
|
|
|
223ac6 |
if (vbuf) { glDeleteBuffers(1, &vbuf); vbuf = 0; }
|
|
|
223ac6 |
elements = 0;
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
void Mesh::clear() {
|
|
|
223ac6 |
dirty();
|
|
|
223ac6 |
triangles.clear();
|
|
|
223ac6 |
vertices.clear();
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
void Mesh::draw() const {
|
|
|
223ac6 |
createbuf();
|
|
|
223ac6 |
if (!elements) return;
|
|
|
223ac6 |
|
|
|
223ac6 |
glBindBuffer(GL_ARRAY_BUFFER, vbuf);
|
|
|
223ac6 |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf);
|
|
|
223ac6 |
glEnableClientState(GL_VERTEX_ARRAY);
|
|
|
223ac6 |
glEnableClientState(GL_NORMAL_ARRAY);
|
|
|
223ac6 |
|
|
|
223ac6 |
glVertexPointer(3, GL_DOUBLE, sizeof(Vertex), 0);
|
|
|
223ac6 |
glNormalPointer(GL_DOUBLE, sizeof(Vertex), (void*)sizeof(Vector3));
|
|
|
223ac6 |
glDrawElements(GL_TRIANGLES, elements, GL_UNSIGNED_INT, 0);
|
|
|
223ac6 |
|
|
|
223ac6 |
glDisableClientState(GL_VERTEX_ARRAY);
|
|
|
223ac6 |
glDisableClientState(GL_NORMAL_ARRAY);
|
|
|
223ac6 |
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
|
223ac6 |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
|
223ac6 |
|
|
|
223ac6 |
//glBegin(GL_TRIANGLES);
|
|
|
223ac6 |
//for(TriangleList::const_iterator i = triangles.begin(); i != triangles.end(); ++i) {
|
|
|
223ac6 |
// for(int j = 0; j < 3; ++j) {
|
|
|
223ac6 |
// glNormal3dv(vertices[ i->v[j] ].normal.c);
|
|
|
223ac6 |
// glVertex3dv(vertices[ i->v[j] ].pos.c);
|
|
|
223ac6 |
// }
|
|
|
223ac6 |
//}
|
|
|
223ac6 |
//glEnd();
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
LinkedMesh::Vertex LinkedMesh::average(const Vertex &a, const Vertex &b)
|
|
|
223ac6 |
{ return Vertex((a.pos + b.pos)*0.5, (a.value + b.value)*0.5); }
|
|
|
223ac6 |
|
|
|
223ac6 |
Vector3 LinkedMesh::sphere_func(const Vertex &v, Real r, Real kr)
|
|
|
223ac6 |
{ return v.pos.norm()*r*pow(1+kr, v.value); }
|
|
|
223ac6 |
|
|
|
223ac6 |
Vector3 LinkedMesh::z_func(const Vertex &v, Real r, Real kr)
|
|
|
223ac6 |
{ return Vector3(v.pos.x, v.pos.y, v.pos.z + v.value*kr + r); }
|
|
|
223ac6 |
|
|
|
223ac6 |
Real LinkedMesh::random()
|
|
|
223ac6 |
{ return real_random2(); }
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::clear() {
|
|
|
223ac6 |
levels.clear();
|
|
|
223ac6 |
triangles.clear();
|
|
|
223ac6 |
edges.clear();
|
|
|
223ac6 |
vertices.clear();
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::next_level(Real k) {
|
|
|
223ac6 |
Level level;
|
|
|
223ac6 |
if (!levels.empty()) {
|
|
|
223ac6 |
const Level &l = levels.back();
|
|
|
223ac6 |
level.k = l.k;
|
|
|
223ac6 |
level.vertex0 = l.vertex1;
|
|
|
223ac6 |
level.edge0 = l.edge1;
|
|
|
223ac6 |
level.triangle0 = l.triangle1;
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
level.k *= k;
|
|
|
223ac6 |
level.vertex1 = (int)vertices.size();
|
|
|
223ac6 |
level.edge1 = (int)edges.size();
|
|
|
223ac6 |
level.triangle1 = (int)triangles.size();
|
|
|
223ac6 |
|
|
|
223ac6 |
assert(level.vertex0 <= level.vertex1);
|
|
|
223ac6 |
assert(level.edge0 <= level.edge1);
|
|
|
223ac6 |
assert(level.triangle0 <= level.triangle1);
|
|
|
223ac6 |
|
|
|
223ac6 |
levels.push_back(level);
|
|
|
223ac6 |
check();
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::check() const {
|
|
|
223ac6 |
if (levels.empty()) {
|
|
|
223ac6 |
assert(vertices.empty());
|
|
|
223ac6 |
assert(edges.empty());
|
|
|
223ac6 |
assert(triangles.empty());
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
for(int li = 0; li < (int)levels.size(); ++li) {
|
|
|
223ac6 |
const bool last_level = (li == (int)levels.size() - 1);
|
|
|
223ac6 |
const Level &l = levels[li];
|
|
|
223ac6 |
const Level pl = li ? levels[li - 1] : Level();
|
|
|
223ac6 |
const Level nl = last_level ? Level() : levels[li + 1];
|
|
|
223ac6 |
|
|
|
223ac6 |
assert(l.k >= 0);
|
|
|
223ac6 |
assert(l.k <= pl.k);
|
|
|
223ac6 |
assert(l.vertex0 == pl.vertex1);
|
|
|
223ac6 |
assert(l.edge0 == pl.edge1);
|
|
|
223ac6 |
assert(l.triangle0 == pl.triangle1);
|
|
|
223ac6 |
|
|
|
223ac6 |
assert(l.vertex0 >= 0);
|
|
|
223ac6 |
assert(l.edge0 >= 0);
|
|
|
223ac6 |
assert(l.triangle0 >= 0);
|
|
|
223ac6 |
|
|
|
223ac6 |
assert(l.vertex1 <= (int)vertices.size());
|
|
|
223ac6 |
assert(l.edge1 <= (int)edges.size());
|
|
|
223ac6 |
assert(l.triangle1 <= (int)triangles.size());
|
|
|
223ac6 |
|
|
|
223ac6 |
assert(l.vertex0 <= l.vertex1);
|
|
|
223ac6 |
assert(l.edge0 <= l.edge1);
|
|
|
223ac6 |
assert(l.triangle0 <= l.triangle1);
|
|
|
223ac6 |
|
|
|
223ac6 |
if (last_level) {
|
|
|
223ac6 |
assert(l.vertex1 == (int)vertices.size());
|
|
|
223ac6 |
assert(l.edge1 == (int)edges.size());
|
|
|
223ac6 |
assert(l.triangle1 == (int)triangles.size());
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
for(int i = l.edge0; i < l.edge1; ++i) {
|
|
|
223ac6 |
const Edge &e = edges[i];
|
|
|
223ac6 |
if (last_level) {
|
|
|
223ac6 |
assert(e.e[0] == 0);
|
|
|
223ac6 |
assert(e.e[1] == 0);
|
|
|
223ac6 |
assert(e.v[1] == -1);
|
|
|
223ac6 |
} else {
|
|
|
223ac6 |
assert(e.e[0] != e.e[1]);
|
|
|
223ac6 |
assert(e.e[0] >= nl.edge0 && e.e[0] < nl.edge1);
|
|
|
223ac6 |
assert(e.e[1] >= nl.edge0 && e.e[1] < nl.edge1);
|
|
|
223ac6 |
assert(e.v[1] >= nl.vertex0 && e.v[1] < nl.vertex1);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
assert(e.v[0] != e.v[1]);
|
|
|
223ac6 |
assert(e.v[0] >= 0 && e.v[0] < l.vertex1);
|
|
|
223ac6 |
assert(e.v[2] >= 0 && e.v[2] < l.vertex1);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
for(int i = l.triangle0; i < l.triangle1; ++i) {
|
|
|
223ac6 |
const Triangle &t = triangles[i];
|
|
|
223ac6 |
for(int j = 0; j < 3; ++j) {
|
|
|
223ac6 |
int jj = (j+1)%3;
|
|
|
223ac6 |
assert(t.e[j] >= l.edge0 && t.e[j] < l.edge1);
|
|
|
223ac6 |
assert(edges[ t.e[j] ].v[ t.d[j] ? 0 : 2 ] == edges[ t.e[jj] ].v[ t.d[jj] ? 2 : 0 ]);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
}
|
|
|
223ac6 |
}
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::generate_level(Real k) {
|
|
|
223ac6 |
const Level level = levels.empty() ? Level() : levels.back();
|
|
|
223ac6 |
const Real kk = level.k * k;
|
|
|
223ac6 |
|
|
|
223ac6 |
for(int i = level.triangle0; i < level.triangle1; ++i) {
|
|
|
223ac6 |
Triangle t = triangles[i];
|
|
|
223ac6 |
|
|
|
223ac6 |
Edge ee[3];
|
|
|
223ac6 |
for(int j = 0; j < 3; ++j) {
|
|
|
223ac6 |
Edge &e = edges[t.e[j]];
|
|
|
223ac6 |
if (e.v[1] < 0) {
|
|
|
223ac6 |
Vertex v = average( vertices[e.v[0]], vertices[e.v[2]] );
|
|
|
223ac6 |
v.value += random()*kk;
|
|
|
223ac6 |
e.v[1] = (int)vertices.size();
|
|
|
223ac6 |
vertices.push_back(v);
|
|
|
223ac6 |
|
|
|
223ac6 |
e.e[0] = (int)edges.size();
|
|
|
223ac6 |
e.e[1] = e.e[0] + 1;
|
|
|
223ac6 |
ee[j] = e;
|
|
|
223ac6 |
edges.push_back(Edge(ee[j].v[0], ee[j].v[1]));
|
|
|
223ac6 |
edges.push_back(Edge(ee[j].v[1], ee[j].v[2]));
|
|
|
223ac6 |
} else {
|
|
|
223ac6 |
ee[j] = e;
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
if (t.d[j]) {
|
|
|
223ac6 |
std::swap(ee[j].e[0], ee[j].e[1]);
|
|
|
223ac6 |
std::swap(ee[j].v[0], ee[j].v[2]);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
int ei = (int)edges.size();
|
|
|
223ac6 |
edges.push_back(Edge(ee[0].v[1], ee[2].v[1]));
|
|
|
223ac6 |
edges.push_back(Edge(ee[1].v[1], ee[0].v[1]));
|
|
|
223ac6 |
edges.push_back(Edge(ee[2].v[1], ee[1].v[1]));
|
|
|
223ac6 |
|
|
|
223ac6 |
triangles.push_back(Triangle( ee[0].e[0], t.d[0], ei + 0, false, ee[2].e[1], t.d[2] ));
|
|
|
223ac6 |
triangles.push_back(Triangle( ee[1].e[0], t.d[1], ei + 1, false, ee[0].e[1], t.d[0] ));
|
|
|
223ac6 |
triangles.push_back(Triangle( ee[2].e[0], t.d[2], ei + 2, false, ee[1].e[1], t.d[1] ));
|
|
|
223ac6 |
|
|
|
223ac6 |
triangles.push_back(Triangle( ei + 0, true, ei + 1, true, ei + 2, true ));
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
next_level(k);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::generate_levels(int count, Real k) {
|
|
|
223ac6 |
for(int i = 0; i < count; ++i)
|
|
|
223ac6 |
generate_level(k);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::smooth() {
|
|
|
223ac6 |
typedef std::pair<real, real=""> Sum;</real,>
|
|
|
223ac6 |
typedef std::vector<sum> SumList;</sum>
|
|
|
223ac6 |
|
|
|
223ac6 |
const Level &level = levels.back();
|
|
|
223ac6 |
SumList sum;
|
|
|
223ac6 |
sum.reserve(level.vertex1);
|
|
|
223ac6 |
for(int i = 0; i < level.vertex1; ++i)
|
|
|
223ac6 |
sum.push_back(Sum(1, vertices[i].value));
|
|
|
223ac6 |
for(int i = level.edge0; i < level.edge1; ++i) {
|
|
|
223ac6 |
const Edge &e = edges[i];
|
|
|
223ac6 |
Sum &s0 = sum[e.v[0]], &s1 = sum[e.v[2]];
|
|
|
223ac6 |
s0.first += 1;
|
|
|
223ac6 |
s1.first += 1;
|
|
|
223ac6 |
s0.second += vertices[e.v[2]].value;
|
|
|
223ac6 |
s1.second += vertices[e.v[1]].value;
|
|
|
223ac6 |
}
|
|
|
223ac6 |
for(int i = 0; i < level.vertex1; ++i) {
|
|
|
223ac6 |
const Sum &s = sum[i];
|
|
|
223ac6 |
vertices[i].value = s.second/s.first;
|
|
|
223ac6 |
}
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::smooth(int count) {
|
|
|
223ac6 |
for(int i = 0; i < count; ++i)
|
|
|
223ac6 |
smooth();
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::to_mesh(Mesh &mesh, VertexFunc func, Real r, Real kr, int level) const {
|
|
|
223ac6 |
if (levels.empty()) return;
|
|
|
223ac6 |
if (level < 0 || level >= (int)levels.size()) level = levels.size() - 1;
|
|
|
223ac6 |
const Level &l = levels[level];
|
|
|
223ac6 |
|
|
|
223ac6 |
int v0 = (int)mesh.vertices.size();
|
|
|
223ac6 |
int v1 = v0 + l.vertex1;
|
|
|
223ac6 |
mesh.vertices.reserve(v1);
|
|
|
223ac6 |
for(int i = 0; i < l.vertex1; ++i)
|
|
|
223ac6 |
mesh.vertices.push_back(Mesh::Vertex( func(vertices[i], r, kr) ));
|
|
|
223ac6 |
|
|
|
223ac6 |
mesh.triangles.reserve(mesh.triangles.size() + (l.triangle1 - l.triangle0));
|
|
|
223ac6 |
for(int i = l.triangle0; i < l.triangle1; ++i) {
|
|
|
223ac6 |
const Triangle &t = triangles[i];
|
|
|
223ac6 |
Mesh::Triangle tt;
|
|
|
223ac6 |
for(int j = 0; j < 3; ++j)
|
|
|
223ac6 |
tt.v[j] = v0 + edges[t.e[j]].v[ t.d[j] ? 2 : 0 ];
|
|
|
223ac6 |
Vector3 normal = (mesh.vertices[tt.v[1]].pos - mesh.vertices[tt.v[0]].pos).cross(
|
|
|
223ac6 |
mesh.vertices[tt.v[2]].pos - mesh.vertices[tt.v[0]].pos ).norm();
|
|
|
223ac6 |
for(int j = 0; j < 3; ++j)
|
|
|
223ac6 |
mesh.vertices[tt.v[j]].normal += normal;
|
|
|
223ac6 |
mesh.triangles.push_back(tt);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
for(int i = v0; i < v1; ++i)
|
|
|
223ac6 |
mesh.vertices[i].normal = mesh.vertices[i].normal.norm();
|
|
|
223ac6 |
|
|
|
223ac6 |
mesh.dirty();
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::triangle(Real k) {
|
|
|
223ac6 |
clear();
|
|
|
223ac6 |
for(int i = 0; i < 3; ++i) {
|
|
|
223ac6 |
Real a = 2*pi/3*i;
|
|
|
223ac6 |
vertices.push_back(Vertex( Vector3(cos(a), sin(a), 0), random()*k ));
|
|
|
223ac6 |
edges.push_back(Edge(i, (i+1)%3));
|
|
|
223ac6 |
}
|
|
|
223ac6 |
triangles.push_back(Triangle(0, false, 1, false, 2, false));
|
|
|
223ac6 |
next_level(k);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::tetrahedron(Real k) {
|
|
|
223ac6 |
clear();
|
|
|
223ac6 |
const Real r = 2*sqrt2/3;
|
|
|
223ac6 |
const Real z = -1/Real(3);
|
|
|
223ac6 |
|
|
|
223ac6 |
vertices.push_back(Vertex( Vector3(0, 0, 1), random()*k ));
|
|
|
223ac6 |
for(int i = 0; i < 3; ++i) {
|
|
|
223ac6 |
Real a = 2*pi/3*i;
|
|
|
223ac6 |
vertices.push_back(Vertex( Vector3(cos(a)*r, sin(a)*r, z), random()*k ));
|
|
|
223ac6 |
edges.push_back(Edge(0, i+1));
|
|
|
223ac6 |
edges.push_back(Edge(i+1, (i+1)%3 + 1));
|
|
|
223ac6 |
triangles.push_back(Triangle(i*2, false, i*2+1, false, (i+1)%3*2, true));
|
|
|
223ac6 |
}
|
|
|
223ac6 |
triangles.push_back(Triangle(5, true, 3, true, 1, true));
|
|
|
223ac6 |
next_level(k);
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
void LinkedMesh::icosahedron(Real k) {
|
|
|
223ac6 |
clear();
|
|
|
223ac6 |
const Real kk = 4*sin(pi/5)*sin(pi/5) - 1;
|
|
|
223ac6 |
const Real r = 2*sqrt(kk)/(kk + 1);
|
|
|
223ac6 |
const Real z = 1 - r*sqrt(kk);
|
|
|
223ac6 |
|
|
|
223ac6 |
// vertices
|
|
|
223ac6 |
vertices.push_back(Vertex( Vector3(0, 0, 1), random()*k ));
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) {
|
|
|
223ac6 |
Real a = 2*pi/5*i;
|
|
|
223ac6 |
vertices.push_back(Vertex( Vector3(cos(a)*r, sin(a)*r, z), random()*k ));
|
|
|
223ac6 |
}
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) {
|
|
|
223ac6 |
Real a = 2*pi/5*i + pi/5;
|
|
|
223ac6 |
vertices.push_back(Vertex( Vector3(cos(a)*r, sin(a)*r, -z), random()*k ));
|
|
|
223ac6 |
}
|
|
|
223ac6 |
vertices.push_back(Vertex( Vector3(0, 0, -1), random()*k ));
|
|
|
223ac6 |
|
|
|
223ac6 |
// edges
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) edges.push_back(Edge(0, i+1));
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) edges.push_back(Edge(i+1, (i+1)%5 + 1));
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) edges.push_back(Edge(i+1, i+6));
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) edges.push_back(Edge(i+6, (i+1)%5 + 1));
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) edges.push_back(Edge(i+6, (i+1)%5 + 6));
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) edges.push_back(Edge(11, i+6));
|
|
|
223ac6 |
|
|
|
223ac6 |
// triangles
|
|
|
223ac6 |
for(int i = 0; i < 5; ++i) {
|
|
|
223ac6 |
int ii = (i+1)%5;
|
|
|
223ac6 |
triangles.push_back(Triangle(i, false, i+5, false, ii, true));
|
|
|
223ac6 |
triangles.push_back(Triangle(i+10, false, i+15, false, i+5, true));
|
|
|
223ac6 |
triangles.push_back(Triangle(i+20, false, ii+10, true, i+15, true));
|
|
|
223ac6 |
triangles.push_back(Triangle(i+25, true, ii+25, false, i+20, true));
|
|
|
223ac6 |
}
|
|
|
223ac6 |
|
|
|
223ac6 |
next_level(k);
|
|
|
223ac6 |
}
|