mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +00:00
wg_engine: geometry generating optimization
Streaming model for massive vertex and index creations: minimize memory allocations, range checks and other conditions Reduce number of segments length calculations (sqrt) and bbox (min and max). Update distances and bboxes on a whole buffer and only if necessary. For shapes without strokes compute distances not necessary at all. bbox can be updated only on the final stage of geometry workflow, but not on the each stage. Using stack memory instead of heap. its more cache friendly and did not fragment memory, faster memory allocations (weak place of realization) Using cache for points distances and whole path length. Updates only if necessary Validation of geometry consistency executes only on the final stage of path life cicle. It more friendly for data streaming: no any conditions and branches. Using binary search for strokes trimming Pre-cached circles geometry for caps and joints Refactored strokes elements generation functions. Code is more readable and modifiable in general. Can be easily fixed if some geometry issues will be finded
This commit is contained in:
parent
06441437c0
commit
ee6a7214d4
7 changed files with 545 additions and 701 deletions
|
@ -12,7 +12,6 @@ source_file = [
|
||||||
'tvgWgBindGroups.cpp',
|
'tvgWgBindGroups.cpp',
|
||||||
'tvgWgCommon.cpp',
|
'tvgWgCommon.cpp',
|
||||||
'tvgWgCompositor.cpp',
|
'tvgWgCompositor.cpp',
|
||||||
'tvgWgGeometry.cpp',
|
|
||||||
'tvgWgPipelines.cpp',
|
'tvgWgPipelines.cpp',
|
||||||
'tvgWgRenderData.cpp',
|
'tvgWgRenderData.cpp',
|
||||||
'tvgWgRenderer.cpp',
|
'tvgWgRenderer.cpp',
|
||||||
|
|
|
@ -48,9 +48,9 @@ void WgCompositor::initialize(WgContext& context, uint32_t width, uint32_t heigh
|
||||||
storageInterm.initialize(context, width, height);
|
storageInterm.initialize(context, width, height);
|
||||||
storageDstCopy.initialize(context, width, height);
|
storageDstCopy.initialize(context, width, height);
|
||||||
// composition and blend geometries
|
// composition and blend geometries
|
||||||
WgGeometryData geometryData;
|
WgVertexBufferInd vertexBuffer;
|
||||||
geometryData.appendBlitBox();
|
vertexBuffer.appendBlitBox();
|
||||||
meshData.update(context, &geometryData);
|
meshData.update(context, vertexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,436 +0,0 @@
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
closed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void WgPolyline::appendPoint(WgPoint pt)
|
|
||||||
{
|
|
||||||
if (pts.count > 0) {
|
|
||||||
float distance = pts.last().dist(pt);
|
|
||||||
// adjust precision because of real user data points
|
|
||||||
// can be further than the accepted accuracy,
|
|
||||||
// but still be considered tha same
|
|
||||||
if (!tvg::zero(distance*1e-1)) {
|
|
||||||
// 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;
|
|
||||||
for (auto t = 0.0f; t <= 1.0f; 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);
|
|
||||||
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]);
|
|
||||||
closed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
closed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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, float scale)
|
|
||||||
{
|
|
||||||
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)(scale * 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::appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke, float scale)
|
|
||||||
{
|
|
||||||
assert(stroke);
|
|
||||||
assert(polyline);
|
|
||||||
static WgPolyline dashed;
|
|
||||||
dashed.clear();
|
|
||||||
// ignore single points polyline
|
|
||||||
// append multiple 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 segment
|
|
||||||
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, scale);
|
|
||||||
dashed.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// update current subline length
|
|
||||||
currentLength -= dist[i+1];
|
|
||||||
}
|
|
||||||
// draw last subline
|
|
||||||
if (dashIndex % 2 == 0) {
|
|
||||||
dashed.appendPoint(pts.last());
|
|
||||||
appendStroke(&dashed, stroke, scale);
|
|
||||||
dashed.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void WgGeometryData::appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, const WgPoint& v2, StrokeJoin join, float halfWidth, float miterLimit, float scale)
|
|
||||||
{
|
|
||||||
WgPoint dir0 = (v1 - v0).normal();
|
|
||||||
WgPoint dir1 = (v2 - v1).normal();
|
|
||||||
WgPoint nrm0 { +dir0.y, -dir0.x };
|
|
||||||
WgPoint nrm1 { +dir1.y, -dir1.x };
|
|
||||||
WgPoint offset0 = nrm0 * halfWidth;
|
|
||||||
WgPoint offset1 = nrm1 * halfWidth;
|
|
||||||
if (join == StrokeJoin::Round) {
|
|
||||||
appendCircle(v1, halfWidth, scale);
|
|
||||||
} else if (join == StrokeJoin::Bevel) {
|
|
||||||
appendRect(v1 - offset0, v1 + offset1, v1 - offset1, v1 + offset0);
|
|
||||||
} else if (join == StrokeJoin::Miter) {
|
|
||||||
WgPoint nrm = (nrm0 + nrm1);
|
|
||||||
// adjust precision because dot product could return above 1 that results acos returns Nan
|
|
||||||
if (!tvg::zero((dir0.x * dir1.y - dir0.y * dir1.x)*1e-1)) {
|
|
||||||
nrm.normalize();
|
|
||||||
float cosine = nrm.dot(nrm0);
|
|
||||||
float angle = std::acos(dir0.dot(dir1.negative()));
|
|
||||||
float miterRatio = 1.0f / (std::sin(angle) * 0.5);
|
|
||||||
if (miterRatio <= miterLimit) {
|
|
||||||
appendRect(v1 + nrm * (halfWidth / cosine), v1 + offset0, v1 + offset1, v1);
|
|
||||||
appendRect(v1 - nrm * (halfWidth / cosine), v1 - offset0, v1 - offset1, v1);
|
|
||||||
} else {
|
|
||||||
appendRect(v1 - offset0, v1 + offset1, v1 - offset1, v1 + offset0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void WgGeometryData::appendStroke(const WgPolyline* polyline, const RenderStroke *stroke, float scale)
|
|
||||||
{
|
|
||||||
assert(stroke);
|
|
||||||
assert(polyline);
|
|
||||||
float wdt = stroke->width * 0.5f;
|
|
||||||
|
|
||||||
// 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, scale);
|
|
||||||
appendCircle(polyline->pts[1], wdt, scale);
|
|
||||||
} 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 if (polyline->pts.count > 2) { // multi-lined sub-path
|
|
||||||
if (polyline->closed) {
|
|
||||||
WgPoint v0 = polyline->pts[polyline->pts.count - 2];
|
|
||||||
WgPoint v1 = polyline->pts[0];
|
|
||||||
WgPoint v2 = polyline->pts[1];
|
|
||||||
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit, scale);
|
|
||||||
} 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, scale);
|
|
||||||
} 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, scale);
|
|
||||||
} 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) {
|
|
||||||
WgPoint v0 = polyline->pts[i - 1];
|
|
||||||
appendStrokeJoin(v0, v1, v2, stroke->join, wdt, stroke->miterlimit, scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,102 +24,434 @@
|
||||||
#define _TVG_WG_GEOMETRY_H_
|
#define _TVG_WG_GEOMETRY_H_
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <functional>
|
||||||
#include "tvgMath.h"
|
#include "tvgMath.h"
|
||||||
#include "tvgRender.h"
|
#include "tvgRender.h"
|
||||||
#include "tvgArray.h"
|
|
||||||
|
|
||||||
class WgPoint
|
// base vector operations
|
||||||
{
|
static Point operator-(const Point& a) { return {-a.x, -a.y}; }
|
||||||
public:
|
static inline float length2(const Point& a) { return a.x*a.x+a.y*a.y; };
|
||||||
float x;
|
static inline float distance2(const Point& a, const Point& b) { return length2(a - b); };
|
||||||
float y;
|
static inline float distance(const Point& a, const Point& b) { return length(a - b); };
|
||||||
|
static inline float dot(const Point& a, const Point& b) { return a.x*b.x + a.y*b.y; };
|
||||||
|
static inline Point min(const Point& a, const Point& b) { return { std::min(a.x, b.x), std::min(a.y, b.y) }; };
|
||||||
|
static inline Point max(const Point& a, const Point& b) { return { std::max(a.x, b.x), std::max(a.y, b.y) }; };
|
||||||
|
static inline Point lerp(const Point& a, const Point& b, float t) { return a * (1.0f - t) + b * t; };
|
||||||
|
static inline Point normalize(const Point& a) { float rlen = 1.0f / length(a); return { a.x * rlen, a.y * rlen }; }
|
||||||
|
|
||||||
WgPoint() {}
|
// default size of vertex and index buffers
|
||||||
WgPoint(float x, float y): x(x), y(y) {}
|
#define WG_POINTS_COUNT 16384
|
||||||
WgPoint(const Point& p): x(p.x), y(p.y) {}
|
|
||||||
|
|
||||||
WgPoint operator + (const WgPoint& p) const { return { x + p.x, y + p.y }; }
|
// simple vertex buffer
|
||||||
WgPoint operator - (const WgPoint& p) const { return { x - p.x, y - p.y }; }
|
struct WgVertexBuffer {
|
||||||
WgPoint operator * (const WgPoint& p) const { return { x * p.x, y * p.y }; }
|
Point vbuff[WG_POINTS_COUNT]; // vertex buffer
|
||||||
WgPoint operator / (const WgPoint& p) const { return { x / p.x, y / p.y }; }
|
float vdist[WG_POINTS_COUNT]; // distance to previous point
|
||||||
|
float vleng[WG_POINTS_COUNT]; // distance to the first point through all previous points
|
||||||
WgPoint operator + (const float c) const { return { x + c, y + c }; }
|
size_t vcount{};
|
||||||
WgPoint operator - (const float c) const { return { x - c, y - c }; }
|
|
||||||
WgPoint operator * (const float c) const { return { x * c, y * c }; }
|
|
||||||
WgPoint operator / (const float c) const { return { x / c, y / c }; }
|
|
||||||
|
|
||||||
WgPoint negative() const { return {-x, -y}; }
|
|
||||||
inline void negate() { x = -x; y = -y; }
|
|
||||||
inline float length() const { return sqrt(x*x + y*y); }
|
|
||||||
inline float length2() const { return x*x + y*y; }
|
|
||||||
inline float dot(const WgPoint& p) const { return x * p.x + y * p.y; }
|
|
||||||
inline float dist(const WgPoint& p) const { return sqrt(dist2(p)); }
|
|
||||||
inline float dist2(const WgPoint& p) const { return ((p.x - x)*(p.x - x) + (p.y - y)*(p.y - y)); }
|
|
||||||
inline bool equal(const WgPoint& p) const { return tvg::equal(x, p.x) && tvg::equal(y, p.y); }
|
|
||||||
inline void normalize() { float rlen = 1.0f / length(); x *= rlen; y *= rlen; }
|
|
||||||
inline WgPoint normal() const { float rlen = 1.0f / length(); return { x * rlen, y * rlen }; }
|
|
||||||
inline WgPoint lerp(const WgPoint& p, float t) const { return { x + (p.x - x) * t, y + (p.y - y) * t }; };
|
|
||||||
inline WgPoint trans(const Matrix& m) const { return { x * m.e11 + y * m.e12 + m.e13, x * m.e21 + y * m.e22 + m.e23 }; };
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WgMath
|
|
||||||
{
|
|
||||||
Array<float> sinus;
|
|
||||||
Array<float> cosin;
|
|
||||||
bool initialized{};
|
|
||||||
|
|
||||||
void initialize();
|
|
||||||
void release();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct WgPolyline
|
|
||||||
{
|
|
||||||
Array<WgPoint> pts;
|
|
||||||
Array<float> dist;
|
|
||||||
// polyline bbox points indexes
|
|
||||||
uint32_t iminx{};
|
|
||||||
uint32_t iminy{};
|
|
||||||
uint32_t imaxx{};
|
|
||||||
uint32_t imaxy{};
|
|
||||||
// total polyline length
|
|
||||||
float len{};
|
|
||||||
bool closed{};
|
bool closed{};
|
||||||
|
|
||||||
WgPolyline();
|
// callback for external process of polyline
|
||||||
|
using onPolylineFn = std::function<void(const WgVertexBuffer& buff)>;
|
||||||
|
|
||||||
void appendPoint(WgPoint pt);
|
// reset buffer
|
||||||
void appendCubic(WgPoint p1, WgPoint p2, WgPoint p3, size_t nsegs = 16);
|
void reset() {
|
||||||
|
vcount = 0;
|
||||||
|
closed = false;
|
||||||
|
}
|
||||||
|
|
||||||
void trim(WgPolyline* polyline, float trimBegin, float trimEnd) const;
|
// get the last point with optional index offset from the end
|
||||||
|
Point last(size_t offset = 0) const {
|
||||||
|
return vbuff[vcount - offset - 1];
|
||||||
|
}
|
||||||
|
|
||||||
void close();
|
// get the last distance with optional index offset from the end
|
||||||
void clear();
|
float lastDist(size_t offset = 0) const {
|
||||||
|
return vdist[vcount - offset - 1];
|
||||||
|
}
|
||||||
|
|
||||||
void getBBox(WgPoint& pmin, WgPoint& pmax) const;
|
// get total length
|
||||||
|
float total() const {
|
||||||
|
return (vcount == 0) ? 0.0f : vleng[vcount-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get next vertex index by length using binary search
|
||||||
|
size_t getIndexByLength(float len) const {
|
||||||
|
if (vcount <= 1) return 0;
|
||||||
|
size_t left = 0;
|
||||||
|
size_t right = vcount - 1;
|
||||||
|
while (left <= right) {
|
||||||
|
size_t mid = left + (right - left) / 2;
|
||||||
|
if (vleng[mid] == len) return mid;
|
||||||
|
else if (vleng[mid] < len) left = mid + 1;
|
||||||
|
else right = mid - 1;
|
||||||
|
}
|
||||||
|
return right + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get min and max values of the buffer
|
||||||
|
void getMinMax(Point& pmin, Point& pmax) const {
|
||||||
|
if (vcount == 0) return;
|
||||||
|
pmax = pmin = vbuff[0];
|
||||||
|
for (size_t i = 1; i < vcount; i++) {
|
||||||
|
pmin = min(pmin, vbuff[i]);
|
||||||
|
pmax = max(pmax, vbuff[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update points distancess to the prev point and total length
|
||||||
|
void updateDistances() {
|
||||||
|
if (vcount == 0) return;
|
||||||
|
vdist[0] = 0.0f;
|
||||||
|
vleng[0] = 0.0f;
|
||||||
|
for (size_t i = 1; i < vcount; i++) {
|
||||||
|
vdist[i] = distance(vbuff[i-1], vbuff[i]);
|
||||||
|
vleng[i] = vleng[i-1] + vdist[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close vertex buffer
|
||||||
|
void close() {
|
||||||
|
// check if last point is not to close to the first point
|
||||||
|
if (!tvg::zero(distance2(vbuff[0], last())))
|
||||||
|
append(vbuff[0]);
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append point
|
||||||
|
void append(const Point& p) {
|
||||||
|
vbuff[vcount] = p;
|
||||||
|
vcount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append source vertex buffer in index range from start to end (end not included)
|
||||||
|
void appendRange(const WgVertexBuffer& buff, size_t start_index, size_t end_index) {
|
||||||
|
if (start_index <= end_index)
|
||||||
|
for (size_t i = start_index; i < end_index; i++)
|
||||||
|
append(buff.vbuff[i]);
|
||||||
|
if (start_index > end_index) {
|
||||||
|
for (size_t i = start_index; i < buff.vcount; i++)
|
||||||
|
append(buff.vbuff[i]);
|
||||||
|
for (size_t i = 0; i < end_index; i++)
|
||||||
|
append(buff.vbuff[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append circle (list of triangles)
|
||||||
|
void appendCircle(float radius) {
|
||||||
|
// get approx circle length
|
||||||
|
float clen = 2.0f * radius * MATH_PI;
|
||||||
|
size_t nsegs = std::max((uint32_t)(clen / 8), 16U);
|
||||||
|
// append circle
|
||||||
|
Point prev { std::sin(0.0f) * radius, std::cos(0.0f) * radius };
|
||||||
|
for (size_t i = 1; i <= nsegs; i++) {
|
||||||
|
float t = (2.0f * MATH_PI * i) / nsegs;
|
||||||
|
Point curr { std::sin(t) * radius, std::cos(t) * radius };
|
||||||
|
append(Point{0.0f, 0.0f});
|
||||||
|
append(prev);
|
||||||
|
append(curr);
|
||||||
|
prev = curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append cubic spline
|
||||||
|
void appendCubic(const Point& v0, const Point& v1, const Point& v2, const Point& v3) {
|
||||||
|
// get approx cubic length
|
||||||
|
float clen = distance(v0, v1) + distance(v1, v2) + distance(v2, v3);
|
||||||
|
size_t nsegs = std::max((uint32_t)(clen / 8), 16U);
|
||||||
|
// append cubic
|
||||||
|
Bezier bezier{v0, v1, v2, v3};
|
||||||
|
for (size_t i = 1; i <= nsegs; i++)
|
||||||
|
append(bezier.at((float)i / nsegs));
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim source buffer
|
||||||
|
void trim(const WgVertexBuffer& buff, float beg, float end) {
|
||||||
|
// empty buffer guard
|
||||||
|
if (buff.vcount == 0) return;
|
||||||
|
// initialize
|
||||||
|
float len_beg = buff.total() * beg;
|
||||||
|
float len_end = buff.total() * end;
|
||||||
|
// find points
|
||||||
|
size_t index_beg = buff.getIndexByLength(len_beg);
|
||||||
|
size_t index_end = buff.getIndexByLength(len_end);
|
||||||
|
float len_total_beg = buff.vleng[index_beg];
|
||||||
|
float len_total_end = buff.vleng[index_end];
|
||||||
|
float len_seg_beg = buff.vdist[index_beg];
|
||||||
|
float len_seg_end = buff.vdist[index_end];
|
||||||
|
// append points
|
||||||
|
float t_beg = len_seg_beg > 0.0f ? 1.0f - (len_total_beg - len_beg) / len_seg_beg : 0.0f;
|
||||||
|
float t_end = len_seg_end > 0.0f ? 1.0f - (len_total_end - len_end) / len_seg_end : 0.0f;
|
||||||
|
if (index_beg > 0) append(lerp(buff.vbuff[index_beg-1], buff.vbuff[index_beg], t_beg));
|
||||||
|
appendRange(buff, index_beg, index_end);
|
||||||
|
if (index_end > 0) append(lerp(buff.vbuff[index_end-1], buff.vbuff[index_end], t_end));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// decode path with callback for external prcesses
|
||||||
|
void decodePath(const RenderShape& rshape, bool update_dist, onPolylineFn onPolyline) {
|
||||||
|
// decode path
|
||||||
|
reset();
|
||||||
|
size_t pntIndex = 0;
|
||||||
|
for (uint32_t cmdIndex = 0; cmdIndex < rshape.path.cmds.count; cmdIndex++) {
|
||||||
|
PathCommand cmd = rshape.path.cmds[cmdIndex];
|
||||||
|
if (cmd == PathCommand::MoveTo) {
|
||||||
|
// after path decoding we need to update distances and total length
|
||||||
|
if (update_dist) updateDistances();
|
||||||
|
if ((onPolyline) && (vcount != 0))
|
||||||
|
onPolyline(*this);
|
||||||
|
reset();
|
||||||
|
append(rshape.path.pts[pntIndex]);
|
||||||
|
pntIndex++;
|
||||||
|
} else if (cmd == PathCommand::LineTo) {
|
||||||
|
append(rshape.path.pts[pntIndex]);
|
||||||
|
pntIndex++;
|
||||||
|
} else if (cmd == PathCommand::Close) {
|
||||||
|
close();
|
||||||
|
} else if (cmd == PathCommand::CubicTo) {
|
||||||
|
appendCubic(vbuff[vcount - 1], rshape.path.pts[pntIndex + 0], rshape.path.pts[pntIndex + 1], rshape.path.pts[pntIndex + 2]);
|
||||||
|
pntIndex += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// after path decoding we need to update distances and total length
|
||||||
|
if (update_dist) updateDistances();
|
||||||
|
if ((onPolyline) && (vcount != 0))
|
||||||
|
onPolyline(*this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// simple indexed vertex buffer
|
||||||
|
struct WgVertexBufferInd {
|
||||||
|
Point vbuff[WG_POINTS_COUNT*2];
|
||||||
|
Point tbuff[WG_POINTS_COUNT*2];
|
||||||
|
uint32_t ibuff[WG_POINTS_COUNT*4];
|
||||||
|
size_t vcount = 0;
|
||||||
|
size_t icount = 0;
|
||||||
|
|
||||||
struct WgGeometryData
|
// reset buffer
|
||||||
{
|
void reset() {
|
||||||
static WgMath* gMath;
|
icount = vcount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
WgPolyline positions{};
|
// get min and max values of the buffer
|
||||||
Array<WgPoint> texCoords{};
|
void getMinMax(Point& pmin, Point& pmax) const {
|
||||||
Array<uint32_t> indexes{};
|
if (vcount == 0) return;
|
||||||
|
pmax = pmin = vbuff[0];
|
||||||
|
for (size_t i = 1; i < vcount; i++) {
|
||||||
|
pmin = min(pmin, vbuff[i]);
|
||||||
|
pmax = max(pmax, vbuff[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WgGeometryData();
|
// append image box with tex coords
|
||||||
void clear();
|
void appendImageBox(float w, float h) {
|
||||||
|
Point points[4] { { 0.0f, 0.0f }, { w, 0.0f }, { w, h }, { 0.0f, h } };
|
||||||
|
appendImageBox(points);
|
||||||
|
}
|
||||||
|
|
||||||
void appendBox(WgPoint pmin, WgPoint pmax);
|
// append blit box with tex coords
|
||||||
void appendRect(WgPoint p0, WgPoint p1, WgPoint p2, WgPoint p3);
|
void appendBlitBox() {
|
||||||
void appendCircle(WgPoint center, float radius, float scale = 1.0f);
|
Point points[4] { { -1.0f, +1.0f }, { +1.0f, +1.0f }, { +1.0f, -1.0f }, { -1.0f, -1.0f } };
|
||||||
void appendImageBox(float w, float h);
|
appendImageBox(points);
|
||||||
void appendBlitBox();
|
}
|
||||||
void appendStrokeDashed(const WgPolyline* polyline, const RenderStroke *stroke, float scale);
|
|
||||||
void appendStrokeJoin(const WgPoint& v0, const WgPoint& v1, const WgPoint& v2,
|
// append image box with tex coords
|
||||||
StrokeJoin join, float halfWidth, float miterLimit, float scale);
|
void appendImageBox(Point points[4]) {
|
||||||
void appendStroke(const WgPolyline* polyline, const RenderStroke *stroke, float scale);
|
// append vertexes
|
||||||
|
vbuff[vcount+0] = points[0];
|
||||||
|
vbuff[vcount+1] = points[1];
|
||||||
|
vbuff[vcount+2] = points[2];
|
||||||
|
vbuff[vcount+3] = points[3];
|
||||||
|
// append tex coords
|
||||||
|
tbuff[vcount+0] = { 0.0f, 0.0f };
|
||||||
|
tbuff[vcount+1] = { 1.0f, 0.0f };
|
||||||
|
tbuff[vcount+2] = { 1.0f, 1.0f };
|
||||||
|
tbuff[vcount+3] = { 0.0f, 1.0f };
|
||||||
|
// append indexes
|
||||||
|
ibuff[icount+0] = vcount + 0;
|
||||||
|
ibuff[icount+1] = vcount + 1;
|
||||||
|
ibuff[icount+2] = vcount + 2;
|
||||||
|
ibuff[icount+3] = vcount + 0;
|
||||||
|
ibuff[icount+4] = vcount + 2;
|
||||||
|
ibuff[icount+5] = vcount + 3;
|
||||||
|
// update buffer
|
||||||
|
vcount += 4;
|
||||||
|
icount += 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append quad - two triangles formed from four points
|
||||||
|
void appendQuad(const Point& p0, const Point& p1, const Point& p2, const Point& p3) {
|
||||||
|
// append vertexes
|
||||||
|
vbuff[vcount+0] = p0;
|
||||||
|
vbuff[vcount+1] = p1;
|
||||||
|
vbuff[vcount+2] = p2;
|
||||||
|
vbuff[vcount+3] = p3;
|
||||||
|
// append indexes
|
||||||
|
ibuff[icount+0] = vcount + 0;
|
||||||
|
ibuff[icount+1] = vcount + 1;
|
||||||
|
ibuff[icount+2] = vcount + 2;
|
||||||
|
ibuff[icount+3] = vcount + 1;
|
||||||
|
ibuff[icount+4] = vcount + 3;
|
||||||
|
ibuff[icount+5] = vcount + 2;
|
||||||
|
// update buffer
|
||||||
|
vcount += 4;
|
||||||
|
icount += 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dash buffer by pattern
|
||||||
|
void appendStrokesDashed(const WgVertexBuffer& buff, const RenderStroke* rstroke) {
|
||||||
|
// dashed buffer
|
||||||
|
WgVertexBuffer dashed;
|
||||||
|
dashed.reset();
|
||||||
|
// ignore single points polyline
|
||||||
|
if (buff.vcount < 2) return;
|
||||||
|
const float* dashPattern = rstroke->dashPattern;
|
||||||
|
size_t dashCnt = rstroke->dashCnt;
|
||||||
|
// starting state
|
||||||
|
uint32_t index_dash = 0;
|
||||||
|
float len_total = dashPattern[index_dash];
|
||||||
|
// iterate by polyline points
|
||||||
|
for (uint32_t i = 0; i < buff.vcount - 1; i++) {
|
||||||
|
// append current polyline point
|
||||||
|
if (index_dash % 2 == 0)
|
||||||
|
dashed.append(buff.vbuff[i]);
|
||||||
|
// move inside polyline segment
|
||||||
|
while(len_total < buff.vdist[i+1]) {
|
||||||
|
// get current point
|
||||||
|
float t = len_total / buff.vdist[i+1];
|
||||||
|
dashed.append(buff.vbuff[i] + (buff.vbuff[i+1] - buff.vbuff[i]) * t);
|
||||||
|
// update current state
|
||||||
|
index_dash = (index_dash + 1) % dashCnt;
|
||||||
|
len_total += dashPattern[index_dash];
|
||||||
|
// preceed stroke if dash
|
||||||
|
if (index_dash % 2 != 0) {
|
||||||
|
dashed.updateDistances();
|
||||||
|
appendStrokes(dashed, rstroke);
|
||||||
|
dashed.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// update current subline length
|
||||||
|
len_total -= buff.vdist[i+1];
|
||||||
|
}
|
||||||
|
// draw last subline
|
||||||
|
if (index_dash % 2 == 0) {
|
||||||
|
dashed.append(buff.last());
|
||||||
|
dashed.updateDistances();
|
||||||
|
appendStrokes(dashed, rstroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append buffer with optional offset
|
||||||
|
void appendBuffer(const WgVertexBuffer& buff, Point offset = Point{0.0f, 0.0f}) {
|
||||||
|
for (uint32_t i = 0; i < buff.vcount; i++ ) {
|
||||||
|
vbuff[vcount + i] = buff.vbuff[i] + offset;
|
||||||
|
ibuff[icount + i] = vcount + i;
|
||||||
|
}
|
||||||
|
vcount += buff.vcount;
|
||||||
|
icount += buff.vcount;
|
||||||
|
};
|
||||||
|
|
||||||
|
// append line
|
||||||
|
void appendLine(const Point& v0, const Point& v1, float dist, float halfWidth) {
|
||||||
|
Point sub = v1 - v0;
|
||||||
|
Point nrm = { +sub.y / dist * halfWidth, -sub.x / dist * halfWidth };
|
||||||
|
appendQuad(v0 - nrm, v0 + nrm, v1 - nrm, v1 + nrm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append bevel joint
|
||||||
|
void appendBevel(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth) {
|
||||||
|
Point sub1 = v1 - v0;
|
||||||
|
Point sub2 = v2 - v1;
|
||||||
|
Point nrm1 { +sub1.y / dist1 * halfWidth, -sub1.x / dist1 * halfWidth };
|
||||||
|
Point nrm2 { +sub2.y / dist2 * halfWidth, -sub2.x / dist2 * halfWidth };
|
||||||
|
appendQuad(v1 - nrm1, v1 + nrm1, v1 - nrm2, v1 + nrm2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append miter joint
|
||||||
|
void appendMitter(const Point& v0, const Point& v1, const Point& v2, float dist1, float dist2, float halfWidth, float miterLimit) {
|
||||||
|
Point sub1 = v1 - v0;
|
||||||
|
Point sub2 = v2 - v1;
|
||||||
|
Point nrm1 { +sub1.y / dist1, -sub1.x / dist1 };
|
||||||
|
Point nrm2 { +sub2.y / dist2, -sub2.x / dist2 };
|
||||||
|
Point offset1 = nrm1 * halfWidth;
|
||||||
|
Point offset2 = nrm2 * halfWidth;
|
||||||
|
Point nrm = normalize(nrm1 + nrm2);
|
||||||
|
float cosine = dot(nrm, nrm1);
|
||||||
|
float angle = std::acos(dot(nrm1, -nrm2));
|
||||||
|
float miterRatio = 1.0f / (std::sin(angle) * 0.5f);
|
||||||
|
if (miterRatio <= miterLimit) {
|
||||||
|
appendQuad(v1 + nrm * (halfWidth / cosine), v1 + offset2, v1 + offset1, v1);
|
||||||
|
appendQuad(v1 - nrm * (halfWidth / cosine), v1 - offset2, v1 - offset1, v1);
|
||||||
|
} else {
|
||||||
|
appendQuad(v1 - offset1, v1 + offset2, v1 - offset2, v1 + offset1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append square cap
|
||||||
|
void appendSquare(Point v0, Point v1, float dist, float halfWidth) {
|
||||||
|
Point sub = v1 - v0;
|
||||||
|
Point offset = sub / dist * halfWidth;
|
||||||
|
Point nrm = { +offset.y, -offset.x };
|
||||||
|
appendQuad(v1 - nrm, v1 + nrm, v1 + offset - nrm, v1 + offset + nrm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append strokes
|
||||||
|
void appendStrokes(const WgVertexBuffer& buff, const RenderStroke* rstroke) {
|
||||||
|
assert(rstroke);
|
||||||
|
// empty buffer gueard
|
||||||
|
if (buff.vcount < 2) return;
|
||||||
|
float halfWidth = rstroke->width * 0.5f;
|
||||||
|
|
||||||
|
// append core lines
|
||||||
|
for (size_t i = 1; i < buff.vcount; i++)
|
||||||
|
appendLine(buff.vbuff[i-1], buff.vbuff[i], buff.vdist[i], halfWidth);
|
||||||
|
|
||||||
|
// append caps (square)
|
||||||
|
if ((rstroke->cap == StrokeCap::Square) && !buff.closed) {
|
||||||
|
appendSquare(buff.vbuff[1], buff.vbuff[0], buff.vdist[1], halfWidth);
|
||||||
|
appendSquare(buff.last(1), buff.last(0), buff.lastDist(0), halfWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append round joints and caps
|
||||||
|
if ((rstroke->join == StrokeJoin::Round) || (rstroke->cap == StrokeCap::Round)) {
|
||||||
|
// create mesh for circle
|
||||||
|
WgVertexBuffer circle;
|
||||||
|
circle.appendCircle(halfWidth);
|
||||||
|
// append caps (round)
|
||||||
|
if (rstroke->cap == StrokeCap::Round) {
|
||||||
|
appendBuffer(circle, buff.vbuff[0]);
|
||||||
|
// append ending cap if polyline is not closed
|
||||||
|
if (!buff.closed)
|
||||||
|
appendBuffer(circle, buff.last());
|
||||||
|
}
|
||||||
|
// append joints (round)
|
||||||
|
if (rstroke->join == StrokeJoin::Round) {
|
||||||
|
for (size_t i = 1; i < buff.vcount - 1; i++)
|
||||||
|
appendBuffer(circle, buff.vbuff[i]);
|
||||||
|
if (buff.closed) appendBuffer(circle, buff.last());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append closed endings
|
||||||
|
if (buff.closed) {
|
||||||
|
// close by bevel
|
||||||
|
if (rstroke->join == StrokeJoin::Bevel)
|
||||||
|
appendBevel(buff.last(1), buff.vbuff[0], buff.vbuff[1], buff.lastDist(0), buff.vdist[1], halfWidth);
|
||||||
|
// close by mitter
|
||||||
|
else if (rstroke->join == StrokeJoin::Miter) {
|
||||||
|
appendMitter(buff.last(1), buff.vbuff[0], buff.vbuff[1], buff.lastDist(0), buff.vdist[1], halfWidth, rstroke->miterlimit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append joints (bevel)
|
||||||
|
if (rstroke->join == StrokeJoin::Bevel) {
|
||||||
|
for (size_t i = 1; i < buff.vcount - 1; i++)
|
||||||
|
appendBevel(buff.vbuff[i-1], buff.vbuff[i], buff.vbuff[i+1], buff.vdist[i], buff.vdist[i+1], halfWidth);
|
||||||
|
// append joints (mitter)
|
||||||
|
} else if (rstroke->join == StrokeJoin::Miter) {
|
||||||
|
for (size_t i = 1; i < buff.vcount - 1; i++)
|
||||||
|
appendMitter(buff.vbuff[i-1], buff.vbuff[i], buff.vbuff[i+1], buff.vdist[i], buff.vdist[i+1], halfWidth, rstroke->miterlimit);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _TVG_WG_GEOMETRY_H_
|
#endif // _TVG_WG_GEOMETRY_H_
|
|
@ -55,35 +55,36 @@ void WgMeshData::drawImage(WgContext& context, WGPURenderPassEncoder renderPassE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void WgMeshData::update(WgContext& context, const WgPolyline* polyline)
|
void WgMeshData::update(WgContext& context, const WgVertexBuffer& vertexBuffer)
|
||||||
{
|
{
|
||||||
assert(polyline);
|
assert(vertexBuffer.vcount > 2);
|
||||||
assert(polyline->pts.count > 2);
|
vertexCount = vertexBuffer.vcount;
|
||||||
vertexCount = polyline->pts.count;
|
indexCount = (vertexBuffer.vcount - 2) * 3;
|
||||||
indexCount = (polyline->pts.count - 2) * 3;
|
// buffer position data create and write
|
||||||
context.allocateBufferVertex(bufferPosition, (float *)&polyline->pts[0], vertexCount * sizeof(float) * 2);
|
context.allocateBufferVertex(bufferPosition, (float *)&vertexBuffer.vbuff, vertexCount * sizeof(float) * 2);
|
||||||
|
// buffer index data create and write
|
||||||
context.allocateBufferIndexFan(vertexCount);
|
context.allocateBufferIndexFan(vertexCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void WgMeshData::update(WgContext& context, const WgGeometryData* geometryData)
|
void WgMeshData::update(WgContext& context, const WgVertexBufferInd& vertexBufferInd)
|
||||||
{
|
{
|
||||||
assert(geometryData);
|
assert(vertexBufferInd.vcount > 2);
|
||||||
vertexCount = geometryData->positions.pts.count;
|
vertexCount = vertexBufferInd.vcount;
|
||||||
indexCount = geometryData->indexes.count;
|
indexCount = vertexBufferInd.icount;
|
||||||
// buffer position data create and write
|
// buffer position data create and write
|
||||||
if (geometryData->positions.pts.count > 0)
|
if (vertexCount > 0)
|
||||||
context.allocateBufferVertex(bufferPosition, (float *)&geometryData->positions.pts[0], vertexCount * sizeof(float) * 2);
|
context.allocateBufferVertex(bufferPosition, (float *)&vertexBufferInd.vbuff, vertexCount * sizeof(float) * 2);
|
||||||
// buffer tex coords data create and write
|
// buffer tex coords data create and write
|
||||||
if (geometryData->texCoords.count > 0)
|
if (vertexCount > 0)
|
||||||
context.allocateBufferVertex(bufferTexCoord, (float *)&geometryData->texCoords[0], vertexCount * sizeof(float) * 2);
|
context.allocateBufferVertex(bufferTexCoord, (float *)&vertexBufferInd.tbuff, vertexCount * sizeof(float) * 2);
|
||||||
// buffer index data create and write
|
// buffer index data create and write
|
||||||
if (geometryData->indexes.count > 0)
|
if (indexCount > 0)
|
||||||
context.allocateBufferIndex(bufferIndex, &geometryData->indexes[0], indexCount * sizeof(uint32_t));
|
context.allocateBufferIndex(bufferIndex, vertexBufferInd.ibuff, indexCount * sizeof(uint32_t));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void WgMeshData::update(WgContext& context, const WgPoint pmin, const WgPoint pmax)
|
void WgMeshData::update(WgContext& context, const Point pmin, const Point pmax)
|
||||||
{
|
{
|
||||||
vertexCount = 4;
|
vertexCount = 4;
|
||||||
indexCount = 6;
|
indexCount = 6;
|
||||||
|
@ -144,25 +145,23 @@ WgMeshDataPool* WgMeshDataGroup::gMeshDataPool = nullptr;
|
||||||
// WgMeshDataGroup
|
// WgMeshDataGroup
|
||||||
//***********************************************************************
|
//***********************************************************************
|
||||||
|
|
||||||
void WgMeshDataGroup::append(WgContext& context, const WgPolyline* polyline)
|
void WgMeshDataGroup::append(WgContext& context, const WgVertexBuffer& vertexBuffer)
|
||||||
{
|
{
|
||||||
assert(polyline);
|
assert(vertexBuffer.vcount >= 3);
|
||||||
assert(polyline->pts.count >= 3);
|
|
||||||
meshes.push(gMeshDataPool->allocate(context));
|
meshes.push(gMeshDataPool->allocate(context));
|
||||||
meshes.last()->update(context, polyline);
|
meshes.last()->update(context, vertexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void WgMeshDataGroup::append(WgContext& context, const WgGeometryData* geometryData)
|
void WgMeshDataGroup::append(WgContext& context, const WgVertexBufferInd& vertexBufferInd)
|
||||||
{
|
{
|
||||||
assert(geometryData);
|
assert(vertexBufferInd.vcount >= 3);
|
||||||
assert(geometryData->positions.pts.count >= 3);
|
|
||||||
meshes.push(gMeshDataPool->allocate(context));
|
meshes.push(gMeshDataPool->allocate(context));
|
||||||
meshes.last()->update(context, geometryData);
|
meshes.last()->update(context, vertexBufferInd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void WgMeshDataGroup::append(WgContext& context, const WgPoint pmin, const WgPoint pmax)
|
void WgMeshDataGroup::append(WgContext& context, const Point pmin, const Point pmax)
|
||||||
{
|
{
|
||||||
meshes.push(gMeshDataPool->allocate(context));
|
meshes.push(gMeshDataPool->allocate(context));
|
||||||
meshes.last()->update(context, pmin, pmax);
|
meshes.last()->update(context, pmin, pmax);
|
||||||
|
@ -307,7 +306,29 @@ void WgRenderDataPaint::updateClips(tvg::Array<tvg::RenderData> &clips) {
|
||||||
// WgRenderDataShape
|
// WgRenderDataShape
|
||||||
//***********************************************************************
|
//***********************************************************************
|
||||||
|
|
||||||
void WgRenderDataShape::updateBBox(WgPoint pmin, WgPoint pmax)
|
void WgRenderDataShape::appendShape(WgContext context, const WgVertexBuffer& vertexBuffer)
|
||||||
|
{
|
||||||
|
if (vertexBuffer.vcount < 3) return;
|
||||||
|
Point pmin{}, pmax{};
|
||||||
|
vertexBuffer.getMinMax(pmin, pmax);
|
||||||
|
meshGroupShapes.append(context, vertexBuffer);
|
||||||
|
meshGroupShapesBBox.append(context, pmin, pmax);
|
||||||
|
updateBBox(pmin, pmax);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void WgRenderDataShape::appendStroke(WgContext context, const WgVertexBufferInd& vertexBufferInd)
|
||||||
|
{
|
||||||
|
if (vertexBufferInd.vcount < 3) return;
|
||||||
|
Point pmin{}, pmax{};
|
||||||
|
vertexBufferInd.getMinMax(pmin, pmax);
|
||||||
|
meshGroupStrokes.append(context, vertexBufferInd);
|
||||||
|
meshGroupStrokesBBox.append(context, pmin, pmax);
|
||||||
|
updateBBox(pmin, pmax);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void WgRenderDataShape::updateBBox(Point pmin, Point pmax)
|
||||||
{
|
{
|
||||||
pMin.x = std::min(pMin.x, pmin.x);
|
pMin.x = std::min(pMin.x, pmin.x);
|
||||||
pMin.y = std::min(pMin.y, pmin.y);
|
pMin.y = std::min(pMin.y, pmin.y);
|
||||||
|
@ -316,11 +337,11 @@ void WgRenderDataShape::updateBBox(WgPoint pmin, WgPoint pmax)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void WgRenderDataShape::updateAABB(const Matrix& rt) {
|
void WgRenderDataShape::updateAABB(const Matrix& tr) {
|
||||||
WgPoint p0 = WgPoint(pMin.x, pMin.y).trans(rt);
|
Point p0 = Point{pMin.x, pMin.y} * tr;
|
||||||
WgPoint p1 = WgPoint(pMax.x, pMin.y).trans(rt);
|
Point p1 = Point{pMax.x, pMin.y} * tr;
|
||||||
WgPoint p2 = WgPoint(pMin.x, pMax.y).trans(rt);
|
Point p2 = Point{pMin.x, pMax.y} * tr;
|
||||||
WgPoint p3 = WgPoint(pMax.x, pMax.y).trans(rt);
|
Point p3 = Point{pMax.x, pMax.y} * tr;
|
||||||
aabb.x = std::min({p0.x, p1.x, p2.x, p3.x});
|
aabb.x = std::min({p0.x, p1.x, p2.x, p3.x});
|
||||||
aabb.y = std::min({p0.y, p1.y, p2.y, p3.y});
|
aabb.y = std::min({p0.y, p1.y, p2.y, p3.y});
|
||||||
aabb.w = std::max({p0.x, p1.x, p2.x, p3.x}) - aabb.x;
|
aabb.w = std::max({p0.x, p1.x, p2.x, p3.x}) - aabb.x;
|
||||||
|
@ -333,143 +354,75 @@ void WgRenderDataShape::updateMeshes(WgContext &context, const RenderShape &rsha
|
||||||
releaseMeshes(context);
|
releaseMeshes(context);
|
||||||
strokeFirst = rshape.stroke ? rshape.stroke->strokeFirst : false;
|
strokeFirst = rshape.stroke ? rshape.stroke->strokeFirst : false;
|
||||||
|
|
||||||
float scale = std::max(sqrt(tr.e11*tr.e11 + tr.e21*tr.e21), 1.0f);
|
// path decoded vertex buffer
|
||||||
Array<WgPolyline*> polylines{};
|
WgVertexBuffer pbuff;
|
||||||
// decode path
|
// append shape without strokes
|
||||||
size_t pntIndex = 0;
|
if (!rshape.stroke) {
|
||||||
for (uint32_t cmdIndex = 0; cmdIndex < rshape.path.cmds.count; cmdIndex++) {
|
pbuff.decodePath(rshape, false, [&](const WgVertexBuffer& path_buff) {
|
||||||
PathCommand cmd = rshape.path.cmds[cmdIndex];
|
appendShape(context, path_buff);
|
||||||
if (cmd == PathCommand::MoveTo) {
|
});
|
||||||
// proceed current polyline
|
// append shape with strokes
|
||||||
polylines.push(new WgPolyline);
|
} else if (rshape.stroke->trim.simultaneous) {
|
||||||
polylines.last()->appendPoint(rshape.path.pts[pntIndex]);
|
float tbeg{}, tend{};
|
||||||
pntIndex++;
|
if (!rshape.stroke->strokeTrim(tbeg, tend)) { tbeg = 0.0f; tend = 1.0f; }
|
||||||
} else if (cmd == PathCommand::LineTo) {
|
if (tbeg == tend) return;
|
||||||
polylines.last()->appendPoint(rshape.path.pts[pntIndex]);
|
pbuff.decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
|
||||||
pntIndex++;
|
appendShape(context, path_buff);
|
||||||
} else if (cmd == PathCommand::Close) {
|
proceedStrokes(context, rshape.stroke, tbeg, tend, path_buff);
|
||||||
polylines.last()->close();
|
});
|
||||||
} else if (cmd == PathCommand::CubicTo) {
|
// append shape with strokes with simultaneous flag
|
||||||
assert(polylines.last()->pts.count > 0);
|
} else {
|
||||||
WgPoint pt0 = polylines.last()->pts.last().trans(tr);
|
float totalLen = 0.0f;
|
||||||
WgPoint pt1 = WgPoint(rshape.path.pts[pntIndex + 0]).trans(tr);
|
// append shapes
|
||||||
WgPoint pt2 = WgPoint(rshape.path.pts[pntIndex + 1]).trans(tr);
|
pbuff.decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
|
||||||
WgPoint pt3 = WgPoint(rshape.path.pts[pntIndex + 2]).trans(tr);
|
appendShape(context, path_buff);
|
||||||
uint32_t nsegs = std::max((uint32_t)(pt0.dist(pt1) + pt1.dist(pt2) + pt2.dist(pt3)), 32U);
|
totalLen += path_buff.total();
|
||||||
polylines.last()->appendCubic(
|
});
|
||||||
rshape.path.pts[pntIndex + 0],
|
// append strokes
|
||||||
rshape.path.pts[pntIndex + 1],
|
float tbeg{}, tend{};
|
||||||
rshape.path.pts[pntIndex + 2],
|
if (!rshape.stroke->strokeTrim(tbeg, tend)) { tbeg = 0.0f; tend = 1.0f; }
|
||||||
nsegs / 4);
|
if (tbeg == tend) return;
|
||||||
pntIndex += 3;
|
float len_beg = totalLen * tbeg; // trim length begin
|
||||||
}
|
float len_end = totalLen * tend; // trim length end
|
||||||
}
|
float len_acc = 0.0; // accumulated length
|
||||||
// proceed shapes
|
// append strokes
|
||||||
float totalLen{};
|
pbuff.decodePath(rshape, true, [&](const WgVertexBuffer& path_buff) {
|
||||||
for (uint32_t i = 0; i < polylines.count; i++) {
|
float len_path = path_buff.total(); // current path length
|
||||||
totalLen += polylines[i]->len;
|
float tbeg = ((len_acc <= len_beg) && (len_acc + len_path > len_beg)) ? (len_beg - len_acc) / len_path : 0.0f;
|
||||||
updateShapes(context, polylines[i]);
|
float tend = ((len_acc <= len_end) && (len_acc + len_path > len_end)) ? (len_end - len_acc) / len_path : 1.0f;
|
||||||
}
|
if ((len_acc + len_path >= len_beg) && (len_acc <= len_end))
|
||||||
// proceed strokes
|
proceedStrokes(context, rshape.stroke, tbeg, tend, path_buff);
|
||||||
if (rshape.stroke) {
|
len_acc += len_path;
|
||||||
float trimBegin{};
|
});
|
||||||
float trimEnd{};
|
}
|
||||||
if (!rshape.stroke->strokeTrim(trimBegin, trimEnd)) { trimBegin = 0.0f; trimEnd = 1.0f; }
|
|
||||||
if (rshape.stroke->trim.simultaneous) {
|
|
||||||
for (uint32_t i = 0; i < polylines.count; i++)
|
|
||||||
updateStrokes(context, polylines[i], rshape.stroke, scale, trimBegin, trimEnd);
|
|
||||||
} else {
|
|
||||||
if (trimBegin <= trimEnd) {
|
|
||||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, trimBegin, trimEnd);
|
|
||||||
} else {
|
|
||||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, 0.0f, trimEnd);
|
|
||||||
updateStrokesList(context, polylines, rshape.stroke, totalLen, scale, trimBegin, 1.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// delete polylines
|
|
||||||
for (uint32_t i = 0; i < polylines.count; i++)
|
|
||||||
delete polylines[i];
|
|
||||||
// update shapes bbox
|
// update shapes bbox
|
||||||
updateAABB(tr);
|
updateAABB(tr);
|
||||||
meshDataBBox.update(context, pMin, pMax);
|
meshDataBBox.update(context, pMin, pMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void WgRenderDataShape::updateShapes(WgContext& context, const WgPolyline* polyline)
|
void WgRenderDataShape::proceedStrokes(WgContext context, const RenderStroke* rstroke, float tbeg, float tend, const WgVertexBuffer& buff)
|
||||||
{
|
{
|
||||||
assert(polyline);
|
assert(rstroke);
|
||||||
// generate fill geometry
|
WgVertexBufferInd stroke_buff;
|
||||||
if (polyline->pts.count >= 3) {
|
// trim -> dash -> stroke
|
||||||
WgPoint pmin{}, pmax{};
|
if ((tbeg != 0.0f) || (tend != 1.0f)) {
|
||||||
polyline->getBBox(pmin, pmax);
|
if (tbeg == tend) return;
|
||||||
meshGroupShapes.append(context, polyline);
|
WgVertexBuffer trimed_buff;
|
||||||
meshGroupShapesBBox.append(context, pmin, pmax);
|
trimed_buff.trim(buff, tbeg, tend);
|
||||||
updateBBox(pmin, pmax);
|
trimed_buff.updateDistances();
|
||||||
}
|
// trim ->dash -> stroke
|
||||||
}
|
if (rstroke->dashPattern) stroke_buff.appendStrokesDashed(trimed_buff, rstroke);
|
||||||
|
// trim -> stroke
|
||||||
void WgRenderDataShape::updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float scale, float totalLen, float trimBegin, float trimEnd)
|
else stroke_buff.appendStrokes(trimed_buff, rstroke);
|
||||||
{
|
} else
|
||||||
float tp1 = totalLen * trimBegin; // trim point begin
|
// dash -> stroke
|
||||||
float tp2 = totalLen * trimEnd; // trim point end
|
if (rstroke->dashPattern) {
|
||||||
float pc = 0; // point current
|
stroke_buff.appendStrokesDashed(buff, rstroke);
|
||||||
for (uint32_t i = 0; i < polylines.count; i++) {
|
// stroke
|
||||||
float pl = polylines[i]->len; // current polyline length
|
} else
|
||||||
float trimBegin = ((pc <= tp1) && (pc + pl > tp1)) ? (tp1 - pc) / pl : 0.0f;
|
stroke_buff.appendStrokes(buff, rstroke);
|
||||||
float trimEnd = ((pc <= tp2) && (pc + pl > tp2)) ? (tp2 - pc) / pl : 1.0f;
|
appendStroke(context, stroke_buff);
|
||||||
if ((pc + pl >= tp1) && (pc <= tp2))
|
|
||||||
updateStrokes(context, polylines[i], rstroke, scale, trimBegin, trimEnd);
|
|
||||||
pc += pl;
|
|
||||||
// break if reached the tail
|
|
||||||
if (pc > tp2) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void WgRenderDataShape::updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float scale, float trimBegin, float trimEnd)
|
|
||||||
{
|
|
||||||
assert(polyline);
|
|
||||||
// generate strokes geometry
|
|
||||||
if ((polyline->pts.count >= 1) && rstroke && (rstroke->width > 0.0f)) {
|
|
||||||
static WgGeometryData geometryData; geometryData.clear();
|
|
||||||
static WgPolyline trimmed;
|
|
||||||
// trim -> split -> stroke
|
|
||||||
if (trimBegin == trimEnd) return;
|
|
||||||
if ((rstroke->dashPattern) && ((trimBegin != 0.0f) || (trimEnd != 1.0f))) {
|
|
||||||
trimmed.clear();
|
|
||||||
if (trimBegin < trimEnd)
|
|
||||||
polyline->trim(&trimmed, trimBegin, trimEnd);
|
|
||||||
else {
|
|
||||||
polyline->trim(&trimmed, trimBegin, 1.0f);
|
|
||||||
polyline->trim(&trimmed, 0.0f, trimEnd);
|
|
||||||
}
|
|
||||||
geometryData.appendStrokeDashed(&trimmed, rstroke, scale);
|
|
||||||
} else // trim -> stroke
|
|
||||||
if ((trimBegin != 0.0f) || (trimEnd != 1.0f)) {
|
|
||||||
trimmed.clear();
|
|
||||||
if (trimBegin < trimEnd)
|
|
||||||
polyline->trim(&trimmed, trimBegin, trimEnd);
|
|
||||||
else {
|
|
||||||
polyline->trim(&trimmed, trimBegin, 1.0f);
|
|
||||||
polyline->trim(&trimmed, 0.0f, trimEnd);
|
|
||||||
}
|
|
||||||
geometryData.appendStroke(&trimmed, rstroke, scale);
|
|
||||||
} else // split -> stroke
|
|
||||||
if (rstroke->dashPattern) {
|
|
||||||
geometryData.appendStrokeDashed(polyline, rstroke, scale);
|
|
||||||
} else { // stroke
|
|
||||||
geometryData.appendStroke(polyline, rstroke, scale);
|
|
||||||
}
|
|
||||||
// append render meshes and bboxes
|
|
||||||
if(geometryData.positions.pts.count >= 3) {
|
|
||||||
WgPoint pmin{}, pmax{};
|
|
||||||
geometryData.positions.getBBox(pmin, pmax);
|
|
||||||
meshGroupStrokes.append(context, &geometryData);
|
|
||||||
meshGroupStrokesBBox.append(context, pmin, pmax);
|
|
||||||
updateBBox(pmin, pmax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,9 +37,9 @@ struct WgMeshData {
|
||||||
void drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
|
void drawFan(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
|
||||||
void drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
|
void drawImage(WgContext& context, WGPURenderPassEncoder renderPassEncoder);
|
||||||
|
|
||||||
void update(WgContext& context, const WgPolyline* polyline);
|
void update(WgContext& context, const WgVertexBuffer& vertexBuffer);
|
||||||
void update(WgContext& context, const WgGeometryData* geometryData);
|
void update(WgContext& context, const WgVertexBufferInd& vertexBufferInd);
|
||||||
void update(WgContext& context, const WgPoint pmin, const WgPoint pmax);
|
void update(WgContext& context, const Point pmin, const Point pmax);
|
||||||
void release(WgContext& context);
|
void release(WgContext& context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,9 +58,9 @@ struct WgMeshDataGroup {
|
||||||
|
|
||||||
Array<WgMeshData*> meshes{};
|
Array<WgMeshData*> meshes{};
|
||||||
|
|
||||||
void append(WgContext& context, const WgPolyline* polyline);
|
void append(WgContext& context, const WgVertexBuffer& vertexBuffer);
|
||||||
void append(WgContext& context, const WgGeometryData* geometryData);
|
void append(WgContext& context, const WgVertexBufferInd& vertexBufferInd);
|
||||||
void append(WgContext& context, const WgPoint pmin, const WgPoint pmax);
|
void append(WgContext& context, const Point pmin, const Point pmax);
|
||||||
void release(WgContext& context);
|
void release(WgContext& context);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,17 +119,17 @@ struct WgRenderDataShape: public WgRenderDataPaint
|
||||||
WgMeshData meshDataBBox{};
|
WgMeshData meshDataBBox{};
|
||||||
WgMeshDataGroup meshGroupStrokes{};
|
WgMeshDataGroup meshGroupStrokes{};
|
||||||
WgMeshDataGroup meshGroupStrokesBBox{};
|
WgMeshDataGroup meshGroupStrokesBBox{};
|
||||||
WgPoint pMin{};
|
Point pMin{};
|
||||||
WgPoint pMax{};
|
Point pMax{};
|
||||||
bool strokeFirst{};
|
bool strokeFirst{};
|
||||||
FillRule fillRule{};
|
FillRule fillRule{};
|
||||||
|
|
||||||
void updateBBox(WgPoint pmin, WgPoint pmax);
|
void appendShape(WgContext context, const WgVertexBuffer& vertexBuffer);
|
||||||
void updateAABB(const Matrix& rt);
|
void appendStroke(WgContext context, const WgVertexBufferInd& vertexBufferInd);
|
||||||
|
void updateBBox(Point pmin, Point pmax);
|
||||||
|
void updateAABB(const Matrix& tr);
|
||||||
void updateMeshes(WgContext& context, const RenderShape& rshape, const Matrix& tr);
|
void updateMeshes(WgContext& context, const RenderShape& rshape, const Matrix& tr);
|
||||||
void updateShapes(WgContext& context, const WgPolyline* polyline);
|
void proceedStrokes(WgContext context, const RenderStroke* rstroke, float tbeg, float tend, const WgVertexBuffer& buff);
|
||||||
void updateStrokesList(WgContext& context, Array<WgPolyline*> polylines, const RenderStroke* rstroke, float scale, float totalLen, float trimBegin, float trimEnd);
|
|
||||||
void updateStrokes(WgContext& context, const WgPolyline* polyline, const RenderStroke* rstroke, float scale, float trimBegin, float trimEnd);
|
|
||||||
void releaseMeshes(WgContext& context);
|
void releaseMeshes(WgContext& context);
|
||||||
void release(WgContext& context) override;
|
void release(WgContext& context) override;
|
||||||
Type type() override { return Type::Shape; };
|
Type type() override { return Type::Shape; };
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
|
|
||||||
WgRenderer::WgRenderer()
|
WgRenderer::WgRenderer()
|
||||||
{
|
{
|
||||||
WgGeometryData::gMath = new WgMath();
|
|
||||||
WgGeometryData::gMath->initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,8 +31,6 @@ WgRenderer::~WgRenderer()
|
||||||
{
|
{
|
||||||
release();
|
release();
|
||||||
mContext.release();
|
mContext.release();
|
||||||
WgGeometryData::gMath->release();
|
|
||||||
delete WgGeometryData::gMath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,11 +126,11 @@ RenderData WgRenderer::prepare(RenderSurface* surface, RenderData data, const Ma
|
||||||
|
|
||||||
// update image data
|
// update image data
|
||||||
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Image)) {
|
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Image)) {
|
||||||
WgGeometryData geometryData;
|
WgVertexBufferInd vertexBufferInd;
|
||||||
geometryData.appendImageBox(surface->w, surface->h);
|
vertexBufferInd.appendImageBox(surface->w, surface->h);
|
||||||
mContext.pipelines->layouts.releaseBindGroup(renderDataPicture->bindGroupPicture);
|
mContext.pipelines->layouts.releaseBindGroup(renderDataPicture->bindGroupPicture);
|
||||||
renderDataPicture->meshData.release(mContext);
|
renderDataPicture->meshData.release(mContext);
|
||||||
renderDataPicture->meshData.update(mContext, &geometryData);
|
renderDataPicture->meshData.update(mContext, vertexBufferInd);
|
||||||
renderDataPicture->imageData.update(mContext, surface);
|
renderDataPicture->imageData.update(mContext, surface);
|
||||||
renderDataPicture->bindGroupPicture = mContext.pipelines->layouts.createBindGroupTexSampled(
|
renderDataPicture->bindGroupPicture = mContext.pipelines->layouts.createBindGroupTexSampled(
|
||||||
mContext.samplerLinearRepeat, renderDataPicture->imageData.textureView
|
mContext.samplerLinearRepeat, renderDataPicture->imageData.textureView
|
||||||
|
|
Loading…
Add table
Reference in a new issue