Set color for the whole mesh but not every vertex - batching

I want to optimize size of vertex buffer. Currently my layout for VBO is:
x y | r g b a
It's consumed by shader like this:
struct VertexInput {
#location(0) position: vec2<f32>,
#location(1) color: vec4<f32>,
}
And I'm storing mesh in buffer like this: |Mesh1|Mesh2|LargeMesh3|, because my meshes are dynamic. It's being rendered in one drawCall (seems like it's called Draw Call Batching).
I want to reduce sent data to GPU by setting different color for every mesh, not every vertex. And every mesh is different. How can I achive it?
I'm drawing strokes:

I achieved it with #trojanfoe's help with multiple drawCalls.
I created second buffer with stepMode: 'instance' and passed colors to it.
Layout:
vertex: {
module: this.shaderModule,
entryPoint: 'vertex',
buffers: [
{
arrayStride: 2 * VBO_ARRAY.BYTES_PER_ELEMENT,
stepMode: 'vertex',
attributes: [
{
format: 'float32x2',
offset: 0,
shaderLocation: 0,
},
],
},
{
arrayStride: 4 * VBO_ARRAY.BYTES_PER_ELEMENT,
stepMode: 'instance',
attributes: [
{
format: 'float32x4',
offset: 0,
shaderLocation: 1,
},
],
},
],
}
Added to renderPass:
pass.setVertexBuffer(0, this.vbo.buffer)
pass.setVertexBuffer(1, this.clrbo.buffer)
And used in shader as is:
struct VertexInput {
#location(0) position: vec2<f32>,
#location(1) color: vec4<f32>,
}
struct VSOutput {
#builtin(position) position: vec4<f32>,
#location(0) color: vec4<f32>,
}
#vertex
fn vertex(vert: VertexInput) -> VSOutput {
var out: VSOutput;
out.color = vert.color;
....
return out;
}
#fragment
fn fragment(in: VSOutput) -> #location(0) vec4<f32> {
return in.color;
}
However, I'm not sure it will work with multiple meshes merged in one buffer and rendered with one draw call.

Related

Finding the mean point in tiling space?

I have a collection of points in tiling 2D space (I believe its called toroidal geometry/space), and I want to find their mean:
The basic approach would be to just take their mean 'locally' where you would just treat the space as non-tiling. Looking at the example, I'd guess that that would be somewhere in about the middle. However, looking at the extended example, I'd say that the middle is probably one of the worst representations of the data.
I'd say the objective is to find a location where the total variation from the mean is at a minimum
One potential method would be to try all combinations of points in each of the 9 neighbours, and then see which one has the lowest variance, but that becomes extremely inefficient very quickly:
Big O = O(8^n)
I believe it could probably be made more efficient by doing something like treating the x and y independently, but that would only reduce it to O(5^n), so still not manageable.
Perhaps hill-climbing might work? Where I have a random point, and then calculate the lowest possible variance for each point, then make some random adjustments and test again reverting if the variance decreases, I then repeat this until I reach a seemingly optimal value.
Is there a better method? Or maybe some sort of heuristic 'good enough' method?
as of my understanding, you are trying to find the center of mass.
to do so (for each tile) you have to find the sum of each positions multiplied by its weight (assume it is 1 because they are just identical points positioned differently) then divide this by the sum of the weights (which is the number of points in this example as weight = 1). The formula
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML-full"></script> <script type="text/x-mathjax-config"> MathJax.Hub.Config({"HTML-CSS": { preferredFont: "TeX", availableFonts:["STIX","TeX"], linebreaks: { automatic:true }, EqnChunk:(MathJax.Hub.Browser.isMobile ? 10 : 50) }, tex2jax: { inlineMath: [ ["$", "$"], ["\\\\(","\\\\)"] ], displayMath: [ ["$$","$$"], ["\\[", "\\]"] ], processEscapes: true, ignoreClass: "tex2jax_ignore|dno" }, TeX: { noUndefined: { attributes: { mathcolor: "red", mathbackground: "#FFEEEE", mathsize: "90%" } }, Macros: { href: "{}" } }, messageStyle: "none" }); </script>
$$G\left( x,y \right) =\frac{\sum_{i=1}^n{m_i\cdot p_i\left( x_i\,\,,y_i \right)}}{\sum_{i=1}^n{m_i}}$$
in our example:
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS_HTML-full"></script> <script type="text/x-mathjax-config"> MathJax.Hub.Config({"HTML-CSS": { preferredFont: "TeX", availableFonts:["STIX","TeX"], linebreaks: { automatic:true }, EqnChunk:(MathJax.Hub.Browser.isMobile ? 10 : 50) }, tex2jax: { inlineMath: [ ["$", "$"], ["\\\\(","\\\\)"] ], displayMath: [ ["$$","$$"], ["\\[", "\\]"] ], processEscapes: true, ignoreClass: "tex2jax_ignore|dno" }, TeX: { noUndefined: { attributes: { mathcolor: "red", mathbackground: "#FFEEEE", mathsize: "90%" } }, Macros: { href: "{}" } }, messageStyle: "none" }); </script>
$$G\left( x,y \right) =\frac{\sum_{i=1}^n{p_i\left( x_i\,\,,y_i \right)}}{n}$$
here is a python implementation :
#assuming l is a list of 2D tuples as follow: [(x1,y1),(x2,y2),...]
def findCenter(l):
cx,cy = 0,0
for p in l:
cx += p[0]
cy += p[1]
return (cx / len(l), cy / len(l))
# the result is a tuple of non-integer, if you need
# an integer result use: return (cx // len(l),cy // len(l))
# remove the outer parenthesis to take result as 2 separate values
as for multiple tiles you can calculate the center for each tile then the center for the centers, or treat all points from multiple tiles as points from one big tile and calculate the center of all of them.

Merging topojson using topomerge messes up winding order

I'm trying to create a custom world map where countries are merged into regions instead of having individual countries. Unfortunately for some reason something seems to get messed up with the winding order along the process.
As base data I'm using the natural earth 10m_admin_0_countries shape files available here. As criteria for merging countries I have a lookup map that looks like this:
const countryGroups = {
"EUR": ["ALA", "AUT", "BEL"...],
"AFR": ["AGO", "BDI", "BEN"...],
...
}
To merge the shapes I'm using topojson-client. Since I want to have a higher level of control than the CLI commands offer, I wrote a script. It goes through the lookup map and picks out all the topojson features that belong to a group and merges them into one shape and places the resulting merged features into a geojson frame:
const topojsonClient = require("topojson-client");
const topojsonServer = require("topojson-server");
const worldTopo = topojsonServer.topology({
countries: JSON.parse(fs.readFileSync("./world.geojson", "utf-8")),
});
const geoJson = {
type: "FeatureCollection",
features: Object.entries(countryGroups).map(([region, ids]) => {
const relevantCountries = worldTopo.objects.countries.geometries.filter(
(country, i) =>
ids.indexOf(country.properties.ISO_A3) >= 0
);
return {
type: "Feature",
properties: { region, countries: ids },
geometry: topojsonClient.merge(worldTopo, relevantCountries),
};
}),
};
So far everything works well (allegedly). When I try to visualise the map using github gist (or any other visualisation tool like vega lite) the shapes seem to be all messed up. I'm suspecting that I'm doing something wrong during the merging of the features but I can't figure out what it is.
When I try to do the same using the CLI it seems to work fine. But since I need more control over the merging, using just the CLI is not really an option.
The last feature, called "World", should contain all remaining countries, but instead, it contains all countries, period. You can see this in the following showcase.
var w = 900,
h = 300;
var projection = d3.geoMercator().translate([w / 2, h / 2]).scale(100);
var path = d3.geoPath().projection(projection);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select('svg')
.attr('width', w)
.attr('height', h);
var url = "https://gist.githubusercontent.com/Flave/832ebba5726aeca3518b1356d9d726cb/raw/5957dca433cbf50fe4dea0c3fa94bb4f91c754b7/world-regions-wrong.topojson";
d3.json(url)
.then(data => {
var geojson = topojson.feature(data, data.objects.regions);
geojson.features.forEach(f => {
console.log(f.properties.region, f.properties.countries);
});
svg.selectAll('path')
// Reverse because it's the last feature that is the problem
.data(geojson.features.reverse())
.enter()
.append('path')
.attr('d', path)
.attr('fill', d => color(d.properties.region))
.attr('stroke', d => color(d.properties.region))
.on('mouseenter', function() {
d3.select(this).style('fill-opacity', 1);
})
.on('mouseleave', function() {
d3.select(this).style('fill-opacity', null);
});
});
path {
fill-opacity: 0.3;
stroke-width: 2px;
stroke-opacity: 0.4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.js"></script>
<script src="https://d3js.org/topojson.v3.js"></script>
<svg></svg>
To fix this, I'd make sure to always remove all assigned countries from the list. From your data, I can't see where "World" is defined, and if it contains all countries on earth, or if it's a wildcard assignment.
In any case, you should be able to fix it by removing all matches from worldTopo:
const topojsonClient = require("topojson-client");
const topojsonServer = require("topojson-server");
const worldTopo = topojsonServer.topology({
countries: JSON.parse(fs.readFileSync("./world.geojson", "utf-8")),
});
const geoJson = {
type: "FeatureCollection",
features: Object.entries(countryGroups).map(([region, ids]) => {
const relevantCountries = worldTopo.objects.countries.geometries.filter(
(country, i) =>
ids.indexOf(country.properties.ISO_A3) >= 0
);
relevantCountries.forEach(c => {
const index = worldTopo.indexOf(c);
if (index === -1) throw Error(`Expected to find country ${c.properties.ISO_A3} in worldTopo`);
worldTopo.splice(index, 1);
});
return {
type: "Feature",
properties: { region, countries: ids },
geometry: topojsonClient.merge(worldTopo, relevantCountries),
};
}),
};

Highcharts Plugin Recently Broke - "this.init is not a function"

I have a js file that create a Highcharts chart object. I added a plugin to change label contrast. The plugin is below:
/**
* Override getContrast function; make threshold for showing white text very high
*/
(function(H) {
H.Renderer.prototype.getContrast = function(rgba) {
rgba = H.Color(rgba).rgba;
return rgba[0] + rgba[1] + rgba[2] > 210 ? '#000000' : '#FFFFFF';
};
}(Highcharts));
It was working just fine for several months. I recently noticed that the chart is no longer rendering, and my error console is showing this: Uncaught TypeError: this.init is not a function
at Object.e [as Color] (highcharts.src.js:2839)
I was able to trace the source back to this part of the Highcharts.src.js file:
/**
* Handle color operations. Some object methods are chainable.
*
* #param {Highcharts.ColorType} input
* The input color in either rbga or hex format
*/
function Color(input) {
// Collection of parsers. This can be extended from the outside by pushing
// parsers to Highcharts.Color.prototype.parsers.
this.parsers = [{
// RGBA color
// eslint-disable-next-line max-len
regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,
parse: function (result) {
return [
pInt(result[1]),
pInt(result[2]),
pInt(result[3]),
parseFloat(result[4], 10)
];
}
}, {
// RGB color
regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
parse: function (result) {
return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
}
}];
this.rgba = [];
this.init(input); //<!--ERROR RIGHT HERE
}
It looks like something change internally on Highcharts' end, specifically the init() method for the Color subclass. Has anyone else had a similar error?
Since v8.0.1, Highcharts.Color must be instanciated with the new keyword.
Before
stops: [
[0, Highcharts.getOptions().colors[0]],
[1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
]
Since v8.0.1
stops: [
[0, Highcharts.getOptions().colors[0]],
[1, new Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
]
https://github.com/highcharts/highcharts/issues/13053

Text Gradient Highcharts Wordcloud

Wordcloud-highcharts is now available in its latest release (>=6.0) and now I am baffling with laying out the gradient (linear/radial) over texts. For example in this link, if I change the colors array in the series object to
fillColor: {
linearGradient: [0, 0, 0, 200],
stops: [
[0, 'white'],
[1, 'black']
]
},
It does nothing.
Wordcloud has limited functionality and however I am unable to do accomplish this task. Even tried appending multiple defs for different text gradients with (k is such that, it lies between 0 to n)
<defs>
<radialGradient id="myRadialGradient_k"
fx="5%" fy="5%" r="65%"
spreadMethod="pad">
<stop offset="0%" stop-color="#00ee00" stop-opacity="1"/>
<stop offset="100%" stop-color="#006600" stop-opacity="1" />
</radialGradient>
</defs>
and searching for the class and apply css to fill with this myLinearGradient_k by fill:url(#myLinearGradient_k);. But it is very clumsy and heavy. Also search by id is not possible in this case and appending to class highcharts-point is the only possibility, which limits the options.
You may find this live demo helpful: http://jsfiddle.net/kkulig/mnj07vam/
In chart.events.load I placed the code responsible for finding maximum weight, creating gradients (every point has a separate one) and applying them:
chart: {
events: {
load: function() {
var points = this.series[0].points,
renderer = this.renderer,
maxWeight = 0;
// find maximum weight
points.forEach(function(p) {
if (p.weight > maxWeight) {
maxWeight = p.weight;
}
});
points.forEach(function(p, i) {
var id = 'grad' + i;
// create gradient
var gradient = renderer.createElement('linearGradient').add(renderer.defs).attr({
id: id,
x1: "0%",
y1: "0%",
x2: "100%",
y2: "0%"
});
var stop1 = renderer.createElement('stop').add(gradient).attr({
offset: "0%",
style: "stop-color:rgb(255,255,0);stop-opacity:1"
});
var stop2 = renderer.createElement('stop').add(gradient).attr({
offset: Math.round(p.weight / maxWeight * 100) + "%",
style: "stop-color:rgb(255,0,0);stop-opacity:1"
});
// apply gradient
p.update({
color: 'url(#' + id + ')'
}, false);
});
this.redraw();
}
}
}
API reference: https://api.highcharts.com/class-reference/Highcharts.SVGRenderer#createElement

Highcharts - apply gradient on custom colors

This is my data model:
data = [{y: 123, color: "#FF7600"}, {y: 321, color: "#00FFE3"}, {y: 213,color: "#444444"}]
Then the series is added to a pie chart:
$http({ method: 'GET', url: /pie-chart, params: {})
.success(function (data) {
chart.addSeries({
type: 'pie',
data: data
})
});
Here's the official highcharts demo: http://www.highcharts.com/demo/pie-gradient
It loops through data, read colors, creates color array and uses this array when drawing chart.
But i'm thinking about solution which avoids extracting colors from JSON.
Any idea? Thanks a lot.
Edited, solved
Gave it up :).
I ended up creating color arrays as described in highcharts demo.
It works well.
// Get colors from received data, create color array,
var colors = [];
for (var i = 0; i < data[0].series.length; i++) {
colors.push(data[0].series[i].color);
// Delete original colors, so that new radialized are used
delete(data[i].color);
}
// Use color array and radialize each color
Highcharts.getOptions().colors = Highcharts.map(colors, function(color) {
return {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 0 },
stops: [
[0, color],
[1, Highcharts.Color(color).brighten(-0.3).get('rgb')] // darken
]
};
});
The solution mentioned above sets the colors in the global defaults. This is okay if you have only one chart, but if you have multiple it can be problematic, as the colors will apply for all charts.
You can colorize this on the individual chart level by remapping the colors just in the local data array. Here is what I do for my pie charts.
chartData is an array of data like:
[
{
"color": "#01080f",
"name": "No Status",
"y": 8570
},
{
"color": "#1A942C",
"name": "Deployed",
"y": 27952
},
...
{
"color": "#f36e20",
"name": "Out of sync",
"y": 241
}
]
In my javascript code it is retrieved from the server and applied to the Highcharts object's series.data element.
Just manipulate that data element before you add it to the highcharts object.
// Retrieve your chart data
$.getJSON('/api/endpoint/policystatus', function (chartData) {
// Function replaces flat colors with gradients
function colorizeData(data) {
data.color = {
radialGradient: {cx: 0.5, cy: 0.3, r: 0.7},
stops: [
[0, data.color],
[1, Highcharts.Color(data.color).brighten(-0.3).get('rgb')] // darken
]
};
}
// Call the function for each element in the retrieved data
chartData.forEach(colorizeData);
// Continue on to build your chart
$('#pie-general-status').highcharts({
// ....
The above 'colorizeData' takes the chart data input, looks for the 'color' element, then replaces it with the Highcharts gradient based on the same color.
Note that you must use hex or RGB values; it will not work with colors defined as the words 'green' or 'blue'.

Resources