thorvg/src/renderer/wg_engine/tvgWgGeometry.cpp
Sergii Liebodkin 53de5f2ff7 wg_engine: fix incorrect geomatry
Fix computation of segments count for cubic curves.
Using screen coordinates of base points, instead of world coordinates
2024-06-20 19:03:42 +09:00

472 lines
16 KiB
C++

/*
* Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "tvgWgGeometry.h"
//***********************************************************************
// WgPolyline
//***********************************************************************
WgMath* WgGeometryData::gMath = nullptr;
void WgMath::initialize()
{
if (initialized) return;
initialized = true;
constexpr uint32_t nPoints = 360;
sinus.reserve(nPoints);
cosin.reserve(nPoints);
for (uint32_t i = 0; i < nPoints; i++) {
float angle = i * (2 * MATH_PI) / nPoints;
sinus.push(sin(angle));
cosin.push(cos(angle));
}
};
void WgMath::release() {
sinus.clear();
cosin.clear();
};
//***********************************************************************
// WgPolyline
//***********************************************************************
WgPolyline::WgPolyline()
{
constexpr uint32_t nPoints = 360;
pts.reserve(nPoints);
dist.reserve(nPoints);
}
void WgPolyline::appendPoint(WgPoint pt)
{
if (pts.count > 0) {
float distance = pts.last().dist(pt);
if (distance > 0) {
// update min and max indexes
iminx = pts[iminx].x >= pt.x ? pts.count : iminx;
imaxx = pts[imaxx].x <= pt.x ? pts.count : imaxx;
iminy = pts[iminy].y >= pt.y ? pts.count : iminy;
imaxy = pts[imaxy].y <= pt.y ? pts.count : imaxy;
// update total length
len += distance;
// update points and distances
pts.push(pt);
dist.push(distance);
};
} else {
// reset min and max indexes and total length
iminx = imaxx = iminy = imaxy = 0;
len = 0.0f;
// update points and distances
pts.push(pt);
dist.push(0.0f);
}
}
void WgPolyline::appendCubic(WgPoint p1, WgPoint p2, WgPoint p3, size_t nsegs)
{
WgPoint p0 = pts.count > 0 ? pts.last() : WgPoint(0.0f, 0.0f);
nsegs = nsegs == 0 ? 1 : nsegs;
float dt = 1.0f / (float)nsegs;
float t = 0.0f;
for (size_t i = 1; i <= nsegs; i++) {
t += dt;
// get cubic spline interpolation coefficients
float t0 = 1.0f * (1.0f - t) * (1.0f - t) * (1.0f - t);
float t1 = 3.0f * (1.0f - t) * (1.0f - t) * t;
float t2 = 3.0f * (1.0f - t) * t * t;
float t3 = 1.0f * t * t * t;
appendPoint(p0 * t0 + p1 * t1 + p2 * t2 + p3 * t3);
}
}
void WgPolyline::trim(WgPolyline* polyline, float trimBegin, float trimEnd) const
{
assert(polyline);
polyline->clear();
float begLen = len * trimBegin;
float endLen = len * trimEnd;
float currentLength = 0.0f;
// find start point
uint32_t indexStart = 0;
WgPoint pointStart{};
currentLength = 0.0f;
for (indexStart = 1; indexStart < pts.count; indexStart++) {
currentLength += dist[indexStart];
if(currentLength >= begLen) {
float t = 1.0f - (currentLength - begLen) / dist[indexStart];
pointStart = pts[indexStart-1] * (1.0f - t) + pts[indexStart] * t;
break;
}
}
if (indexStart >= pts.count) return;
// find end point
uint32_t indexEnd = 0;
WgPoint pointEnd{};
currentLength = 0.0f;
for (indexEnd = 1; indexEnd < pts.count; indexEnd++) {
currentLength += dist[indexEnd];
if(currentLength >= endLen) {
float t = 1.0f - (currentLength - endLen) / dist[indexEnd];
pointEnd = pts[indexEnd-1] * (1.0f - t) + pts[indexEnd] * t;
break;
}
}
if (indexEnd >= pts.count) return;
// fill polyline
polyline->appendPoint(pointStart);
for (uint32_t i = indexStart; i <= indexEnd - 1; i++)
polyline->appendPoint(pts[i]);
polyline->appendPoint(pointEnd);
}
void WgPolyline::close()
{
if (pts.count > 0) appendPoint(pts[0]);
}
bool WgPolyline::isClosed() const
{
return (pts.count >= 2) && (pts[0].dist2(pts.last()) == 0.0f);
}
void WgPolyline::clear()
{
// reset min and max indexes and total length
iminx = imaxx = iminy = imaxy = 0;
len = 0.0f;
// clear points and distances
pts.clear();
dist.clear();
}
void WgPolyline::getBBox(WgPoint& pmin, WgPoint& pmax) const
{
pmin.x = pts[iminx].x;
pmin.y = pts[iminy].y;
pmax.x = pts[imaxx].x;
pmax.y = pts[imaxy].y;
}
//***********************************************************************
// WgGeometryData
//***********************************************************************
WgGeometryData::WgGeometryData() {
constexpr uint32_t nPoints = 10240;
positions.pts.reserve(nPoints);
texCoords.reserve(nPoints);
indexes.reserve(nPoints);
}
void WgGeometryData::clear()
{
indexes.clear();
positions.clear();
texCoords.clear();
}
void WgGeometryData::appendBox(WgPoint pmin, WgPoint pmax)
{
appendRect(
{ pmin.x, pmin.y },
{ pmax.x, pmin.y },
{ pmin.x, pmax.y },
{ pmax.x, pmax.y });
}
void WgGeometryData::appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3)
{
uint32_t index = positions.pts.count;
positions.appendPoint(p0); // +0
positions.appendPoint(p1); // +1
positions.appendPoint(p2); // +2
positions.appendPoint(p3); // +3
indexes.push(index + 0);
indexes.push(index + 1);
indexes.push(index + 2);
indexes.push(index + 1);
indexes.push(index + 3);
indexes.push(index + 2);
}
void WgGeometryData::appendCircle(WgPoint center, float radius)
{
uint32_t indexCenter = positions.pts.count;
positions.appendPoint(center);
uint32_t index = positions.pts.count;
positions.appendPoint({ center.x + gMath->sinus[0] * radius, center.y + gMath->cosin[0] * radius });
uint32_t nPoints = (uint32_t)(radius * 2.0f);
nPoints = nPoints < 8 ? 8 : nPoints;
const uint32_t step = gMath->sinus.count / nPoints;
for (uint32_t i = step; i < gMath->sinus.count; i += step) {
positions.appendPoint({ center.x + gMath->sinus[i] * radius, center.y + gMath->cosin[i] * radius });
indexes.push(index); // prev point
indexes.push(indexCenter); // center point
indexes.push(index + 1); // curr point
index++;
}
positions.appendPoint({ center.x + gMath->sinus[0] * radius, center.y + gMath->cosin[0] * radius });
indexes.push(index); // prev point
indexes.push(indexCenter); // center point
indexes.push(index + 1); // curr point
positions.appendPoint(center);
}
void WgGeometryData::appendImageBox(float w, float h)
{
positions.appendPoint({ 0.0f, 0.0f });
positions.appendPoint({ w , 0.0f });
positions.appendPoint({ w , h });
positions.appendPoint({ 0.0f, h });
texCoords.push({ 0.0f, 0.0f });
texCoords.push({ 1.0f, 0.0f });
texCoords.push({ 1.0f, 1.0f });
texCoords.push({ 0.0f, 1.0f });
indexes.push(0);
indexes.push(1);
indexes.push(2);
indexes.push(0);
indexes.push(2);
indexes.push(3);
}
void WgGeometryData::appendBlitBox()
{
positions.appendPoint({ -1.0f, +1.0f });
positions.appendPoint({ +1.0f, +1.0f });
positions.appendPoint({ +1.0f, -1.0f });
positions.appendPoint({ -1.0f, -1.0f });
texCoords.push({ 0.0f, 0.0f });
texCoords.push({ 1.0f, 0.0f });
texCoords.push({ 1.0f, 1.0f });
texCoords.push({ 0.0f, 1.0f });
indexes.push(0);
indexes.push(1);
indexes.push(2);
indexes.push(0);
indexes.push(2);
indexes.push(3);
}
void WgGeometryData::appendMesh(const RenderMesh* rmesh)
{
assert(rmesh);
positions.pts.reserve(rmesh->triangleCnt * 3);
texCoords.reserve(rmesh->triangleCnt * 3);
indexes.reserve(rmesh->triangleCnt * 3);
for (uint32_t i = 0; i < rmesh->triangleCnt; i++) {
positions.appendPoint(rmesh->triangles[i].vertex[0].pt);
positions.appendPoint(rmesh->triangles[i].vertex[1].pt);
positions.appendPoint(rmesh->triangles[i].vertex[2].pt);
texCoords.push(rmesh->triangles[i].vertex[0].uv);
texCoords.push(rmesh->triangles[i].vertex[1].uv);
texCoords.push(rmesh->triangles[i].vertex[2].uv);
indexes.push(i*3 + 0);
indexes.push(i*3 + 1);
indexes.push(i*3 + 2);
}
}
void WgGeometryData::appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke)
{
assert(stroke);
assert(polyline);
static WgPolyline dashed;
dashed.clear();
// ignore singpe points polyline
// append multy points dashed polyline
if (polyline->pts.count >= 2) {
auto& pts = polyline->pts;
auto& dist = polyline->dist;
// starting state
uint32_t dashIndex = 0;
float currentLength = stroke->dashPattern[dashIndex];
// iterate by polyline points
for (uint32_t i = 0; i < pts.count - 1; i++) {
// append current polyline point
if (dashIndex % 2 == 0)
dashed.appendPoint(pts[i]);
// move inside polyline sergemnt
while(currentLength < dist[i+1]) {
// get current point
float t = currentLength / dist[i+1];
dashed.appendPoint(pts[i] + (pts[i+1] - pts[i]) * t);
// update current state
dashIndex = (dashIndex + 1) % stroke->dashCnt;
currentLength += stroke->dashPattern[dashIndex];
// append stroke if dash
if (dashIndex % 2 != 0) {
appendStroke(&dashed, stroke);
dashed.clear();
}
}
// update current subline length
currentLength -= dist[i+1];
}
// draw last subline
if (dashIndex % 2 == 0) {
dashed.appendPoint(pts.last());
appendStroke(&dashed, stroke);
dashed.clear();
}
}
}
void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke *stroke)
{
assert(stroke);
assert(polyline);
float wdt = stroke->width / 2;
// single line sub-path
if (polyline->pts.count == 2) {
WgPoint v0 = polyline->pts[0];
WgPoint v1 = polyline->pts[1];
WgPoint dir0 = (v1 - v0) / polyline->dist[1];
WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x };
if (stroke->cap == StrokeCap::Round) {
appendRect(v0 - nrm0 * wdt, v0 + nrm0 * wdt, v1 - nrm0 * wdt, v1 + nrm0 * wdt);
appendCircle(polyline->pts[0], wdt);
appendCircle(polyline->pts[1], wdt);
} else if (stroke->cap == StrokeCap::Butt) {
appendRect(v0 - nrm0 * wdt, v0 + nrm0 * wdt, v1 - nrm0 * wdt, v1 + nrm0 * wdt);
} else if (stroke->cap == StrokeCap::Square) {
appendRect(
v0 - nrm0 * wdt - dir0 * wdt, v0 + nrm0 * wdt - dir0 * wdt,
v1 - nrm0 * wdt + dir0 * wdt, v1 + nrm0 * wdt + dir0 * wdt
);
}
} else
// multi-lined sub-path
if (polyline->pts.count > 2) {
if (polyline->isClosed()) {
if (stroke->join == StrokeJoin::Round) {
appendCircle(polyline->pts[0], wdt);
} else {
float dist0 = polyline->dist.last();
float dist1 = polyline->dist[1];
WgPoint v0 = polyline->pts[polyline->pts.count - 2];
WgPoint v1 = polyline->pts[0];
WgPoint v2 = polyline->pts[1];
WgPoint dir0 = (v1 - v0) / dist0;
WgPoint dir1 = (v2 - v1) / dist1;
WgPoint nrm0 { +dir0.y, -dir0.x };
WgPoint nrm1 { +dir1.y, -dir1.x };
WgPoint offset0 = nrm0 * wdt;
WgPoint offset1 = nrm1 * wdt;
if (stroke->join == StrokeJoin::Bevel) {
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
} else if (stroke->join == StrokeJoin::Miter) {
WgPoint nrm = (nrm0 + nrm1).normal();
float cosine = nrm.dot(nrm0);
if ((cosine != 0.0f) && (abs(cosine) != 1.0f) && (abs(wdt / cosine) <= stroke->miterlimit * 2)) {
appendRect(v1 + nrm * (wdt / cosine), v1 + offset0, v1 + offset1, v1 - nrm * (wdt / cosine));
} else {
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
}
}
}
} else {
// append first cap
WgPoint v0 = polyline->pts[0];
WgPoint v1 = polyline->pts[1];
WgPoint dir0 = (v1 - v0) / polyline->dist[1];
WgPoint nrm0 = WgPoint{ -dir0.y, +dir0.x };
if (stroke->cap == StrokeCap::Round) {
appendCircle(v0, wdt);
} else if (stroke->cap == StrokeCap::Butt) {
// no cap needed
} else if (stroke->cap == StrokeCap::Square) {
appendRect(v0 - nrm0 * wdt - dir0 * wdt, v0 + nrm0 * wdt - dir0 * wdt, v0 - nrm0 * wdt, v0 + nrm0 * wdt);
}
// append last cap
v0 = polyline->pts[polyline->pts.count - 2];
v1 = polyline->pts[polyline->pts.count - 1];
dir0 = (v1 - v0) / polyline->dist[polyline->pts.count - 1];
nrm0 = WgPoint{ -dir0.y, +dir0.x };
if (stroke->cap == StrokeCap::Round) {
appendCircle(v1, wdt);
} else if (stroke->cap == StrokeCap::Butt) {
// no cap needed
} else if (stroke->cap == StrokeCap::Square) {
appendRect(v1 - nrm0 * wdt, v1 + nrm0 * wdt, v1 - nrm0 * wdt + dir0 * wdt, v1 + nrm0 * wdt + dir0 * wdt);
}
}
// append sub-lines
for (uint32_t i = 0; i < polyline->pts.count - 1; i++) {
float dist1 = polyline->dist[i + 1];
WgPoint v1 = polyline->pts[i + 0];
WgPoint v2 = polyline->pts[i + 1];
WgPoint dir1 = (v2 - v1) / dist1;
WgPoint nrm1 { +dir1.y, -dir1.x };
WgPoint offset1 = nrm1 * wdt;
appendRect(v1 - offset1, v1 + offset1, v2 - offset1, v2 + offset1);
if (i > 0) {
if (stroke->join == StrokeJoin::Round) {
appendCircle(v1, wdt);
} else {
float dist0 = polyline->dist[i + 0];
WgPoint v0 = polyline->pts[i - 1];
WgPoint dir0 = (v1 - v0) / dist0;
WgPoint nrm0 { +dir0.y, -dir0.x };
WgPoint offset0 = nrm0 * wdt;
if (stroke->join == StrokeJoin::Bevel) {
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
} else if (stroke->join == StrokeJoin::Miter) {
WgPoint nrm = (nrm0 + nrm1).normal();
float cosine = nrm.dot(nrm0);
if ((cosine != 0.0f) && (abs(cosine) < 1.0f) && (abs(wdt / cosine) <= stroke->miterlimit * 2)) {
appendRect(v1 + nrm * (wdt / cosine), v1 + offset0, v1 + offset1, v1);
appendRect(v1 - nrm * (wdt / cosine), v1 - offset0, v1 - offset1, v1);
} else {
appendRect(v1 - offset0, v1 + offset0, v1 - offset1, v1 + offset1);
}
}
}
}
}
}
}