Render thick lines with instanced rendering in directx 11 - directx-11

I want to implement thick lines in directx11.
I thought, I can use the instanced rendering technique to render a "high quality" geometry for each line like this picture shows:
P1 and P2 are representing the not equidistant line verticies wich are given in as a "D3D11_PRIMITIVE_TOPOLOGY_LINELIST". The line thickness is stored into the constantbuffer. each line has the same thickness.
The instance geometry also has an indexbuffer with the information about how to connect the vertices to triangles (in the picture the verticies are I0 - I11).
should I get the P1 and P2 position and the SV_VertexID into one vertexshader thread, I can calculate each position of the I0 - I11 verticies. So that is not a problem.
The question is: Is it possible to use the instanced rendering technique to achieve this?
And if so: Is it a good idea to use it like this? or are there more performance ways to implement thick rounded lines? for example with a geometryshader or just make 1000 drawcalls with that geometry...

I tried a lot to use the instanced rendering idea, but now I changed the idea to geometryshader and it is extremely easy to implement.
As input it becomes a line (2 vertices) and the output are 30 triangles.
Here the pixelshader input struct
struct PS_INPUT
{
float4 Pos : SV_POSITION;
};
Here the geometry shader:
#include <psiPosition.hlsli>
//#pragma warning (disable:3206)
//#pragma warning (disable:3554)
static const float PI = 3.1415926f;
static const float fRatio = 2.0f;
static float fThickness = 0.01f;
void addHalfCircle(inout TriangleStream<PS_INPUT> triangleStream, int nCountTriangles, float4 linePointToConnect, float fPointWComponent, float fAngle)
{
PS_INPUT output = (PS_INPUT)0;
for (int nI = 0; nI < nCountTriangles; ++nI)
{
output.Pos.x = cos(fAngle + (PI / nCountTriangles * nI)) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * nI)) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += linePointToConnect;
output.Pos *= fPointWComponent;
triangleStream.Append(output);
output.Pos = linePointToConnect * fPointWComponent;
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nI + 1))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nI + 1))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += linePointToConnect;
output.Pos *= fPointWComponent;
triangleStream.Append(output);
triangleStream.RestartStrip();
}
}
[maxvertexcount(42)]
void main(line PS_INPUT input[2], inout TriangleStream<PS_INPUT> triangleStream)
{
PS_INPUT output= (PS_INPUT)0;
int nCountTriangles =6;
float4 positionPoint0Transformed = input[0].Pos;
float4 positionPoint1Transformed = input[1].Pos;
float fPoint0w = positionPoint0Transformed.w;
float fPoint1w = positionPoint1Transformed.w;
//calculate out the W parameter, because of usage of perspective rendering
positionPoint0Transformed.xyz = positionPoint0Transformed.xyz / positionPoint0Transformed.w;
positionPoint0Transformed.w = 1.0f;
positionPoint1Transformed.xyz = positionPoint1Transformed.xyz / positionPoint1Transformed.w;
positionPoint1Transformed.w = 1.0f;
//calculate the angle between the 2 points on the screen
float3 positionDifference = positionPoint0Transformed.xyz - positionPoint1Transformed.xyz;
float3 coordinateSysten = float3(1.0f, 0.0f, 0.0f);
positionDifference.z = 0.0f;
coordinateSysten.z = 0.0f;
float fAngle = acos(dot(positionDifference.xy, coordinateSysten.xy) / (length(positionDifference.xy) * length(coordinateSysten.xy)));
if (cross(positionDifference, coordinateSysten).z < 0.0f)
{
fAngle = 2.0f * PI - fAngle;
}
fAngle *= -1.0f;
fAngle -= PI *0.5f;
//first half circle of the line
addHalfCircle(triangleStream, nCountTriangles, positionPoint0Transformed, fPoint0w, fAngle);
addHalfCircle(triangleStream, nCountTriangles, positionPoint1Transformed, fPoint1w, fAngle + PI);
//connection between the two circles
//triangle1
output.Pos.x = cos(fAngle) * fThickness / fRatio;
output.Pos.y = sin(fAngle) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint0Transformed;
output.Pos *= fPoint0w; //undo calculate out the W parameter, because of usage of perspective rendering
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint0Transformed;
output.Pos *= fPoint0w;
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint1Transformed;
output.Pos *= fPoint1w;
triangleStream.Append(output);
//triangle2
output.Pos.x = cos(fAngle) * fThickness / fRatio;
output.Pos.y = sin(fAngle) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint0Transformed;
output.Pos *= fPoint0w;
triangleStream.Append(output);
output.Pos.x = cos(fAngle) * fThickness / fRatio;
output.Pos.y = sin(fAngle) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint1Transformed;
output.Pos *= fPoint1w;
triangleStream.Append(output);
output.Pos.x = cos(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness / fRatio;
output.Pos.y = sin(fAngle + (PI / nCountTriangles * (nCountTriangles))) * fThickness;
output.Pos.z = 0.0f;
output.Pos.w = 0.0f;
output.Pos += positionPoint1Transformed;
output.Pos *= fPoint1w;
triangleStream.Append(output);
}
I know it is extremely hardcoded, but at least it works ;)
Now a picture of a cube with thicker lines (first in perspective projection, second in orthographic projection)
I hope I can help someone with this. If someone has a better idea of implementing thick lines please leave a comment.

Related

pgraphics element won't center on mobile devices | drawingContext.drawImage causing mobile offset

I’m working on some of Tim Rodenbrökers code involving a copy() function (https://timrodenbroeker.de/processing-tutorial-kinetic-typography-1/), expanding it and making it ready for web.
This involves replacing the copy() function with drawingContext.drawImage() for performance increase (found here: https://discourse.processing.org/t/p5-js-copy-function-slow-performance-since-version-0-10-0/30007).
Doing this works great for desktop; on mobile, however, the pgraphics element (centered on the canvas, usually), moves position.
Using the regular copy() function centeres it correctly.
The positioning varies according to mobile screen size, I can’t seem to figure out the exact behavior to fix. It's not the font size, I've tried adapting the position to screen.size and document.documentElement.clientWidth, no luck.
let font;
let pg;
function setup() {
font = loadFont("./assets/FGrotesk-Regular.otf");
createCanvas(innerWidth, innerHeight);
pg = createGraphics(innerWidth, innerHeight, P2D);
frameRate(60);
pixelDensity(1);
}
function draw() {
background(0);
pg.background(0);
pg.fill(255);
pg.textFont(font);
pg.textSize(380);
pg.push();
pg.translate(innerWidth / 2, innerHeight / 2);
pg.textAlign(CENTER, CENTER);
pg.text("Enrico", 0, -50);
pg.text("Gisana", 0, 50);
pg.pop();
let tilesX = 400;
let tilesY = 20;
let tileW = int(width / tilesX);
let tileH = int(height / tilesY);
for (y = 0; y < tilesY; y++) {
for (x = 0; x < tilesX; x++) {
// WARP
let wave_x = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
let wave_y = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
if (mouseX - (width / 2) >= 0) {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
} else {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
}
if (mouseY - (height / 2) >= 0) {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
} else {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
}
// SOURCE
let sx = x * tileW + wave_x;
// + wave should be added here
let sy = y * tileH - wave_y;
let sw = tileW;
let sh = tileH;
// DESTINATION
let dx = x * tileW;
let dy = y * tileH;
let dw = tileW;
let dh = tileH;
drawingContext.drawImage(pg.elt, sx, sy, sw, sh, dx, dy, dw, dh);
}
}
}

How can I calculate the inertia tensor of a hollow object defined by a triangle mesh?

I want to calculate the mass, center of mass, and inertia tensor of player created objects. Some of the objects will be hollow instead of solid. I am creating a closed triangle mesh for their object. Note that this is for a physics building game so I want the values to be as accurate as possible (within reason).
Based on the references listed below, I can get the properties for a solid object by creating a tetrahedron from each triangle of the mesh and calculating the signed volume which gives the mass (volume * density) and the center of mass. I even got the inertia tensor calculation working.
How do I do the same for a hollow object?
For the mass and center of mass, I am iterating through the triangles of the mesh and totaling their area and calculating the area-weighted average of their positions, then multiplying the surface area by a "density" value to get the mass.
public static (float, Vector3) CalculateSurfaceArea(Mesh mesh)
{
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
float totalArea = 0f;
Vector3 temp = Vector3.zero;
for (int i = 0; i <= triangles.Length - 3; i += 3)
{
Vector3 v1 = vertices[triangles[i]];
Vector3 v2 = vertices[triangles[i + 1]];
Vector3 v3 = vertices[triangles[i + 2]];
Vector3 edge21 = v2 - v1;
Vector3 edge31 = v3 - v1;
float area = Vector3.Cross(edge21, edge31).magnitude / 2f; // area of the triangle
totalArea += area;
Vector3 triCenter = (v1 + v2 + v3) / 3f; // center of the triangle
temp += triCenter * area;
}
Vector3 areaCenter = temp / totalArea;
return (totalArea, areaCenter);
}
For the inertia tensor, I am trying a similar approach where I iterate through all the triangles and total up their moments of inertia and using the parallel-axis theorem to account for their positions but (a) I am not sure this is correct and (b) how do I calculate the products of inertia (Ixy, Ixz, Iyz)?
public static (Vector3, Quaternion) CalculateHollowInertiaTensor(Mesh mesh)
{
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
double Ixx = 0f;
double Iyy = 0f;
double Izz = 0f;
double Ixy = 0f;
double Ixz = 0f;
double Iyz = 0f;
for (int i = 0; i <= triangles.Length - 3; i += 3)
{
Vector3 v1 = vertices[triangles[i]];
Vector3 v2 = vertices[triangles[i + 1]];
Vector3 v3 = vertices[triangles[i + 2]];
Vector3 edge21 = v2 - v1;
Vector3 edge31 = v3 - v1;
Vector3 center = (v1 + v2 + v3) / 3f; // center of the triangle
Vector3 offset = center - v1;
float area = Vector3.Cross(edge21, edge31).magnitude / 2f; // area of the triangle
// Moment of inertia of triangle rotating around the first vertex
// https://en.wikipedia.org/wiki/List_of_moments_of_inertia
// I = (1/6)m(P.P + P.Q + Q.Q)
float triIxx = (edge21.y * edge21.y + edge21.z * edge21.z + edge21.y * edge31.y + edge21.z * edge31.z + edge31.y * edge31.y + edge31.z * edge31.z) / 6f;
float triIyy = (edge21.x * edge21.x + edge21.z * edge21.z + edge21.x * edge31.x + edge21.z * edge31.z + edge31.x * edge31.x + edge31.z * edge31.z) / 6f;
float triIzz = (edge21.x * edge21.x + edge21.y * edge21.y + edge21.x * edge31.x + edge21.y * edge31.y + edge31.x * edge31.x + edge31.y * edge31.y) / 6f;
// Shift to the center of the triangle
triIxx -= offset.y * offset.y + offset.z * offset.z;
triIyy -= offset.x * offset.x + offset.z * offset.z;
triIzz -= offset.x * offset.x + offset.y * offset.y;
// Shift to the origin (using parallel-axis theorem)
triIxx += center.y * center.y + center.z * center.z;
triIyy += center.x * center.x + center.z * center.z;
triIzz += center.x * center.x + center.y * center.y;
Ixx += triIxx * area;
Iyy += triIyy * area;
Izz += triIzz * area;
//Ixy += area * center.x * center.y;
//Ixz += area * center.x * center.z;
//Iyz += area * center.y * center.z;
}
Matrix<double> inertiaTensor = Matrix<double>.Build.Dense(3, 3);
inertiaTensor[0, 0] = Ixx;
inertiaTensor[1, 1] = Iyy;
inertiaTensor[2, 2] = Izz;
inertiaTensor[0, 1] = inertiaTensor[1, 0] = -Ixy;
inertiaTensor[0, 2] = inertiaTensor[2, 0] = -Ixz;
inertiaTensor[1, 2] = inertiaTensor[2, 1] = -Iyz;
Debug.Log(inertiaTensor);
//Matrix<double> inertiaTensorInverse = inertiaTensor.Inverse();
// Find the principal axes and simplified inertia tensor
Evd<double> evd = inertiaTensor.Evd();
Vector3 inertiaTensorDiagonal = MakeVector3(evd.EigenValues.Real());
Quaternion inertiaTensorRotation = Quaternion.LookRotation(
MakeVector3(evd.EigenVectors.Column(2)),
MakeVector3(evd.EigenVectors.Column(1))
);
return (inertiaTensorDiagonal, inertiaTensorRotation);
}
References I have found so far:
How to calculate the volume of a 3D mesh object the surface of which is made up triangles
How to calculate the volume of a 3D mesh object the surface of which is made up triangles
List of moments of inertia
https://en.wikipedia.org/wiki/List_of_moments_of_inertia
Polyhedral Mass Properties, David Eberly
https://www.geometrictools.com/Documentation/PolyhedralMassProperties.pdf
Explicit Exact Formulas for the 3-D Tetrahedron Inertia Tensor in Terms of its Vertex Coordinates, F. Tonon
http://docsdrive.com/pdfs/sciencepublications/jmssp/2005/8-11.pdf
Efficient Feature Extraction for 2D/3D Objects in Mesh Representation, Cha Zhang and Tsuhan Chen
http://chenlab.ece.cornell.edu/Publication/Cha/icip01_Cha.pdf
===== EDIT =====
Based on John Alexiou's answer, I reimplemented the algorithm for hollow objects:
public static (float, Vector3, Vector3, Quaternion) CalculateHollowMassCenterInertiaTensor(Mesh mesh)
{
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
double Ixx = 0f;
double Iyy = 0f;
double Izz = 0f;
double Ixy = 0f;
double Ixz = 0f;
double Iyz = 0f;
float totalMass = 0f;
Vector3 temp = Vector3.zero;
for (int i = 0; i <= triangles.Length - 3; i += 3)
{
Vector3 v1 = vertices[triangles[i]];
Vector3 v2 = vertices[triangles[i + 1]];
Vector3 v3 = vertices[triangles[i + 2]];
Vector3 center = (v1 + v2 + v3) / 3f; // center of the triangle
float area = (Vector3.Cross(v1, v2) + Vector3.Cross(v2, v3) + Vector3.Cross(v3, v1)).magnitude / 2f; // area of the triangle
totalMass += area;
temp += center * area;
Ixx += area * (v1.y * v1.y + v1.z * v1.z + v2.y * v2.y + v2.z * v2.z + v3.y * v3.y + v3.z * v3.z) / 3f;
Iyy += area * (v1.x * v1.x + v1.z * v1.z + v2.x * v2.x + v2.z * v2.z + v3.x * v3.x + v3.z * v3.z) / 3f;
Izz += area * (v1.x * v1.x + v1.y * v1.y + v2.x * v2.x + v2.y * v2.y + v3.x * v3.x + v3.y * v3.y) / 3f;
Ixy += area * (v1.x * v1.y + v2.x * v2.y + v3.x * v3.y) / 3f;
Ixz += area * (v1.x * v1.z + v2.x * v2.z + v3.x * v3.z) / 3f;
Iyz += area * (v1.y * v1.z + v2.y * v2.z + v3.y * v3.z) / 3f;
}
Vector3 centerOfMass = temp / totalMass;
Matrix<double> inertiaTensor = Matrix<double>.Build.Dense(3, 3);
inertiaTensor[0, 0] = Ixx;
inertiaTensor[1, 1] = Iyy;
inertiaTensor[2, 2] = Izz;
inertiaTensor[0, 1] = inertiaTensor[1, 0] = -Ixy;
inertiaTensor[0, 2] = inertiaTensor[2, 0] = -Ixz;
inertiaTensor[1, 2] = inertiaTensor[2, 1] = -Iyz;
Debug.Log(inertiaTensor);
// Find the principal axes and simplified inertia tensor
Evd<double> evd = inertiaTensor.Evd();
Vector3 inertiaTensorDiagonal = MakeVector3(evd.EigenValues.Real());
Quaternion inertiaTensorRotation = Quaternion.LookRotation(
MakeVector3(evd.EigenVectors.Column(2)),
MakeVector3(evd.EigenVectors.Column(1))
);
return (totalMass, centerOfMass, inertiaTensorDiagonal, inertiaTensorRotation);
}
It is giving me the wrong values for the inertia tensor though.
mass
Expected Inertial Tensor
Actual Inertial Tensor
Cube
6.00
1.6667 1.6667 1.6667
3.0000 3.0000 3.0000
Sphere
3.11
0.5236 0.5236 0.5236
0.5151 0.5162 0.5163
Cylinder
7.80
4.5488 1.7671 4.5488
8.7134 1.8217 8.7134
Cube has 1m sides, Sphere has 0.5m radius, Cylinder has 0.5m radius and 2m height. All are centered around the origin.
Note that I am using the total surface area as the mass. Assume that the thickness * density = 1 so mass = area * thickness * density becomes mass = area * 1. I will be picking better values for those later because I assume that will not have that big of an effect on the algorithm.
Also note that some rounding is to be expected because the shapes are being approximated by a triangular mesh.
For a shell body to have mass and mass moment of inertia, the sides must have some thickness ε>0.
This defines the mass of a triangle defined by the vectors A, B and C and thickness ε and density ρ as
area = 1/2*Math.Abs( Vector3.Cross(A,B) + Vector3.Cross(B,C) + Vector3.Cross(C,A) )
mass = ρ*area*ε
or the above can be used to find the density from the total mass of an object, once the total surface area is calculated.
To find the mass moment of inertia you need to define a function returning the MMOI matrix of a unit mass particle from the location vector r=(x,y,z).
Matrix3 Mmoi(Vector3 r)
{
// | y^2+z^2 -x y -x z |
// I = | -x y x^2+z^2 -y z |
// | -x z -y z x^2+y^2 |
return new Matrix3(
r.y*r.y + r.z*r.z, -r.x*r.y, -r.x*r.y,
-r.x*r.y, r.x*r.x + r.z*r.y, -r.y*r.z,
-r.x*r.z, -r.y*r.z, r.x*r.x + r.y*r.y);
}
and then calculate the MMOI of a triangle from the vertices and the mass
Matrix3 Mmoi(double m, Vector3 A, Vector3 B, Vector3 C)
{
return (m/3)*(Mmoi(A)+Mmoi(B)+Mmoi(C));
}
The above is derived from the surface integral over the triangle, and since [SO] does not support math formatting I am omitting the details here.
Yes the above is true, the MMOI of a surface triangle is that of the average MMOI of the three vertices.
Update
In correlating the above with CAD I realized you have to integrate over both front and back surfaces of the triangle. The result that matches CAD mass properties is
Matrix3 Mmoi(double m, Vector3 A, Vector3 B, Vector3 C)
{
return 2*(m/3)*(Mmoi(A)+Mmoi(B)+Mmoi(C));
}

What is this called and how to achieve! Visuals in processing

Hey does anyone know how to achieve this effect using processing or what this is called?
I have been trying to use the wave gradient example in the processing library and implementing Perlin noise but I can not get close to the gif quality.
I know the artist used processing but can not figure out how!
Link to gif:
https://giphy.com/gifs/processing-jodeus-QInYLzY33wMwM
The effect is reminescent of Op Art (optical illusion art): I recommend reading/learning more about this fascinating genre and artists like:
Bridget Riley
(Bridget Riley, Intake, 1964)
(Bridget Riley, Hesistate, 1964,
Copyright: (c) Bridget Riley 2018. All rights reserved. / Photo (c) Tate)
Victor Vasarely
(Victor Vasarely, Zebra Couple)
(Victor Vasarely, VegaII)
Frank Stella
(Frank Stella, Untitled 1965, Image curtesy of Art Gallery NSW)
and more
You notice this waves are reminiscent/heavily inspired by Bridget Riley's work.
I also recommend checking out San Charoenchai;s album visualiser for Beach House - 7
As mentioned in my comment: you should post your attempt.
Waves and perlin noise could work for sure.
There are many ways to achieve a similar look.
Here's tweaked version of Daniel Shiffman's Noise Wave example:
int numWaves = 24;
float[] yoff = new float[numWaves]; // 2nd dimension of perlin noise
float[] yoffIncrements = new float[numWaves];
void setup() {
size(640, 360);
noStroke();
for(int i = 0 ; i < numWaves; i++){
yoffIncrements[i] = map(i, 0, numWaves - 1, 0.01, 0.03);
}
}
void draw() {
background(0);
float waveHeight = height / numWaves;
for(int i = 0 ; i < numWaves; i++){
float waveY = i * waveHeight;
fill(i % 2 == 0 ? color(255) : color(0));
// We are going to draw a polygon out of the wave points
beginShape();
float xoff = 0; // Option #1: 2D Noise
// float xoff = yoff; // Option #2: 1D Noise
// Iterate over horizontal pixels
for (float x = 0; x <= width + 30; x += 20) {
// Calculate a y value according to noise, map to
float y = map(noise(xoff, yoff[i]), 0, 1, waveY , waveY + (waveHeight * 3)); // Option #1: 2D Noise
// float y = map(noise(xoff), 0, 1, 200,300); // Option #2: 1D Noise
// Set the vertex
vertex(x, y);
// Increment x dimension for noise
xoff += 0.05;
}
// increment y dimension for noise
yoff[i] += yoffIncrements[i];
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
}
}
Notice the quality of the noise wave in comparison to the image you're trying to emulate: there is a constant rhythm to it. To me that is a hint that it's using cycling sine waves changing phase and amplitude (potentially even adding waves together).
I've written an extensive answer on animating sine waves here
(Reuben Margolin's kinectic sculpture system demo)
From your question it sounds like you would be comfortable implementing a sine wave animation. It it helps, here's an example of adding two waves together:
void setup(){
size(600,600);
noStroke();
}
void draw(){
background(0);
// how many waves per sketch height
int heightDivisions = 30;
// split the sketch height into equal height sections
float heightDivisionSize = (float)height / heightDivisions;
// for each height division
for(int j = 0 ; j < heightDivisions; j++){
// use % 2 to alternate between black and white
// see https://processing.org/reference/modulo.html and
// https://processing.org/reference/conditional.html for more
fill(j % 2 == 0 ? color(255) : color(0));
// offset drawing on Y axis
translate(0,(j * heightDivisionSize));
// start a wave shape
beginShape();
// first vertex is at the top left corner
vertex(0,height);
// how many horizontal (per wave) divisions ?
int widthDivisions = 12;
// equally space the points on the wave horizontally
float widthDivsionSize = (float)width / widthDivisions;
// for each point on the wave
for(int i = 0; i <= widthDivisions; i++){
// calculate different phases
// play with arithmetic operators to make interesting wave additions
float phase1 = (frameCount * 0.01) + ((i * j) * 0.025);
float phase2 = (frameCount * 0.05) + ((i + j) * 0.25);
// calculate vertex x position
float x = widthDivsionSize * i;
// multiple sine waves
// (can use cos() and use other ratios too
// 150 in this case is the wave amplitude (e.g. from -150 to + 150)
float y = ((sin(phase1) * sin(phase2) * 150));
// draw calculated vertex
vertex(x,y);
}
// last vertex is at bottom right corner
vertex(width,height);
// finish the shape
endShape();
}
}
The result:
Minor note on performance: this could be implemented more efficiently using PShape, however I recommend playing with the maths/geometry to find the form you're after, then as a last step think of optimizing it.
My intention is not to show you how to create an exact replica, but to show there's more to Op Art than an effect and hopefully inspire you to explore other methods of achieving something similar in the hope that you will discover your own methods and outcomes: something new and of your own through fun happy accidents.
In terms of other techniques/avenues to explore:
displacement maps:
Using an alternating black/white straight bars texture on wavy 3D geometry
using shaders:
Shaders are a huge topic on their own, but it's worth noting:
There's a very good Processing Shader Tutorial
You might be able to explore frament shaders on shadertoy, tweak the code in browser then make slight changes so you can run them in Processing.
Here are a few quick examples:
https://www.shadertoy.com/view/Wts3DB
tweaked for black/white waves in Processing as shader-Wts3DB.frag
// https://www.shadertoy.com/view/Wts3DB
uniform vec2 iResolution;
uniform float iTime;
#define COUNT 6.
#define COL_BLACK vec3(23,32,38) / 255.0
#define SF 1./min(iResolution.x,iResolution.y)
#define SS(l,s) smoothstep(SF,-SF,l-s)
#define hue(h) clamp( abs( fract(h + vec4(3,2,1,0)/3.) * 6. - 3.) -1. , 0., 1.)
// Original noise code from https://www.shadertoy.com/view/4sc3z2
#define MOD3 vec3(.1031,.11369,.13787)
vec3 hash33(vec3 p3)
{
p3 = fract(p3 * MOD3);
p3 += dot(p3, p3.yxz+19.19);
return -1.0 + 2.0 * fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
}
float simplex_noise(vec3 p)
{
const float K1 = 0.333333333;
const float K2 = 0.166666667;
vec3 i = floor(p + (p.x + p.y + p.z) * K1);
vec3 d0 = p - (i - (i.x + i.y + i.z) * K2);
vec3 e = step(vec3(0.0), d0 - d0.yzx);
vec3 i1 = e * (1.0 - e.zxy);
vec3 i2 = 1.0 - e.zxy * (1.0 - e);
vec3 d1 = d0 - (i1 - 1.0 * K2);
vec3 d2 = d0 - (i2 - 2.0 * K2);
vec3 d3 = d0 - (1.0 - 3.0 * K2);
vec4 h = max(0.6 - vec4(dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3)), 0.0);
vec4 n = h * h * h * h * vec4(dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.0)));
return dot(vec4(31.316), n);
}
void mainImage( vec4 fragColor, vec2 fragCoord )
{
}
void main(void) {
//vec2 uv = vec2(gl_FragColor.x / iResolution.y, gl_FragColor.y / iResolution.y);
vec2 uv = gl_FragCoord.xy / iResolution.y;
float m = 0.;
float t = iTime *.5;
vec3 col;
for(float i=COUNT; i>=0.; i-=1.){
float edge = simplex_noise(vec3(uv * vec2(2., 0.) + vec2(0, t + i*.15), 3.))*.2 + (.95/COUNT)*i;
float mi = SS(edge, uv.y) - SS(edge + .095, uv.y);
m += mi;
if(mi > 0.){
col = vec3(1.0);
}
}
col = mix(COL_BLACK, col, m);
gl_FragColor = vec4(col,1.0);
// mainImage(gl_FragColor,gl_FragCoord);
}
loaded in Processing as:
PShader shader;
void setup(){
size(300,300,P2D);
noStroke();
shader = loadShader("shader-Wts3DB.frag");
shader.set("iResolution",(float)width, float(height));
}
void draw(){
background(0);
shader.set("iTime",frameCount * 0.05);
shader(shader);
rect(0,0,width,height);
}
https://www.shadertoy.com/view/MtsXzl
tweaked as shader-MtsXzl.frag
//https://www.shadertoy.com/view/MtsXzl
#define SHOW_GRID 1
const float c_scale = 0.5;
const float c_rate = 2.0;
#define FLT_MAX 3.402823466e+38
uniform vec3 iMouse;
uniform vec2 iResolution;
uniform float iTime;
//=======================================================================================
float CubicHermite (float A, float B, float C, float D, float t)
{
float t2 = t*t;
float t3 = t*t*t;
float a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0;
float b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0;
float c = -A/2.0 + C/2.0;
float d = B;
return a*t3 + b*t2 + c*t + d;
}
//=======================================================================================
float hash(float n) {
return fract(sin(n) * 43758.5453123);
}
//=======================================================================================
float GetHeightAtTile(vec2 T)
{
float rate = hash(hash(T.x) * hash(T.y))*0.5+0.5;
return (sin(iTime*rate*c_rate) * 0.5 + 0.5) * c_scale;
}
//=======================================================================================
float HeightAtPos(vec2 P)
{
vec2 tile = floor(P);
P = fract(P);
float CP0X = CubicHermite(
GetHeightAtTile(tile + vec2(-1.0,-1.0)),
GetHeightAtTile(tile + vec2(-1.0, 0.0)),
GetHeightAtTile(tile + vec2(-1.0, 1.0)),
GetHeightAtTile(tile + vec2(-1.0, 2.0)),
P.y
);
float CP1X = CubicHermite(
GetHeightAtTile(tile + vec2( 0.0,-1.0)),
GetHeightAtTile(tile + vec2( 0.0, 0.0)),
GetHeightAtTile(tile + vec2( 0.0, 1.0)),
GetHeightAtTile(tile + vec2( 0.0, 2.0)),
P.y
);
float CP2X = CubicHermite(
GetHeightAtTile(tile + vec2( 1.0,-1.0)),
GetHeightAtTile(tile + vec2( 1.0, 0.0)),
GetHeightAtTile(tile + vec2( 1.0, 1.0)),
GetHeightAtTile(tile + vec2( 1.0, 2.0)),
P.y
);
float CP3X = CubicHermite(
GetHeightAtTile(tile + vec2( 2.0,-1.0)),
GetHeightAtTile(tile + vec2( 2.0, 0.0)),
GetHeightAtTile(tile + vec2( 2.0, 1.0)),
GetHeightAtTile(tile + vec2( 2.0, 2.0)),
P.y
);
return CubicHermite(CP0X, CP1X, CP2X, CP3X, P.x);
}
//=======================================================================================
vec3 NormalAtPos( vec2 p )
{
float eps = 0.01;
vec3 n = vec3( HeightAtPos(vec2(p.x-eps,p.y)) - HeightAtPos(vec2(p.x+eps,p.y)),
2.0*eps,
HeightAtPos(vec2(p.x,p.y-eps)) - HeightAtPos(vec2(p.x,p.y+eps)));
return normalize( n );
}
//=======================================================================================
float RayIntersectSphere (vec4 sphere, in vec3 rayPos, in vec3 rayDir)
{
//get the vector from the center of this circle to where the ray begins.
vec3 m = rayPos - sphere.xyz;
//get the dot product of the above vector and the ray's vector
float b = dot(m, rayDir);
float c = dot(m, m) - sphere.w * sphere.w;
//exit if r's origin outside s (c > 0) and r pointing away from s (b > 0)
if(c > 0.0 && b > 0.0)
return -1.0;
//calculate discriminant
float discr = b * b - c;
//a negative discriminant corresponds to ray missing sphere
if(discr < 0.0)
return -1.0;
//ray now found to intersect sphere, compute smallest t value of intersection
float collisionTime = -b - sqrt(discr);
//if t is negative, ray started inside sphere so clamp t to zero and remember that we hit from the inside
if(collisionTime < 0.0)
collisionTime = -b + sqrt(discr);
return collisionTime;
}
//=======================================================================================
vec3 DiffuseColor (in vec3 pos)
{
#if SHOW_GRID
pos = mod(floor(pos),2.0);
return vec3(mod(pos.x, 2.0) < 1.0 ? 1.0 : 0.0);
#else
return vec3(0.1, 0.8, 0.9);
#endif
}
//=======================================================================================
vec3 ShadePoint (in vec3 pos, in vec3 rayDir, float time, bool fromUnderneath)
{
vec3 diffuseColor = DiffuseColor(pos);
vec3 reverseLightDir = normalize(vec3(1.0,1.0,-1.0));
vec3 lightColor = vec3(1.0);
vec3 ambientColor = vec3(0.05);
vec3 normal = NormalAtPos(pos.xz);
normal *= fromUnderneath ? -1.0 : 1.0;
// diffuse
vec3 color = diffuseColor;
float dp = dot(normal, reverseLightDir);
if(dp > 0.0)
color += (diffuseColor * lightColor);
return color;
}
//=======================================================================================
vec3 HandleRay (in vec3 rayPos, in vec3 rayDir, in vec3 pixelColor, out float hitTime)
{
float time = 0.0;
float lastHeight = 0.0;
float lastY = 0.0;
float height;
bool hitFound = false;
hitTime = FLT_MAX;
bool fromUnderneath = false;
vec2 timeMinMax = vec2(0.0, 20.0);
time = timeMinMax.x;
const int c_numIters = 100;
float deltaT = (timeMinMax.y - timeMinMax.x) / float(c_numIters);
vec3 pos = rayPos + rayDir * time;
float firstSign = sign(pos.y - HeightAtPos(pos.xz));
for (int index = 0; index < c_numIters; ++index)
{
pos = rayPos + rayDir * time;
height = HeightAtPos(pos.xz);
if (sign(pos.y - height) * firstSign < 0.0)
{
fromUnderneath = firstSign < 0.0;
hitFound = true;
break;
}
time += deltaT;
lastHeight = height;
lastY = pos.y;
}
if (hitFound) {
time = time - deltaT + deltaT*(lastHeight-lastY)/(pos.y-lastY-height+lastHeight);
pos = rayPos + rayDir * time;
pixelColor = ShadePoint(pos, rayDir, time, fromUnderneath);
hitTime = time;
}
return pixelColor;
}
//=======================================================================================
void main()
{
// scrolling camera
vec3 cameraOffset = vec3(iTime, 0.5, iTime);
//----- camera
vec2 mouse = iMouse.xy / iResolution.xy;
vec3 cameraAt = vec3(0.5,0.5,0.5) + cameraOffset;
float angleX = iMouse.z > 0.0 ? 6.28 * mouse.x : 3.14 + iTime * 0.25;
float angleY = iMouse.z > 0.0 ? (mouse.y * 6.28) - 0.4 : 0.5;
vec3 cameraPos = (vec3(sin(angleX)*cos(angleY), sin(angleY), cos(angleX)*cos(angleY))) * 5.0;
// float angleX = 0.8;
// float angleY = 0.8;
// vec3 cameraPos = vec3(0.0,0.0,0.0);
cameraPos += vec3(0.5,0.5,0.5) + cameraOffset;
vec3 cameraFwd = normalize(cameraAt - cameraPos);
vec3 cameraLeft = normalize(cross(normalize(cameraAt - cameraPos), vec3(0.0,sign(cos(angleY)),0.0)));
vec3 cameraUp = normalize(cross(cameraLeft, cameraFwd));
float cameraViewWidth = 6.0;
float cameraViewHeight = cameraViewWidth * iResolution.y / iResolution.x;
float cameraDistance = 6.0; // intuitively backwards!
// Objects
vec2 rawPercent = (gl_FragCoord.xy / iResolution.xy);
vec2 percent = rawPercent - vec2(0.5,0.5);
vec3 rayTarget = (cameraFwd * vec3(cameraDistance,cameraDistance,cameraDistance))
- (cameraLeft * percent.x * cameraViewWidth)
+ (cameraUp * percent.y * cameraViewHeight);
vec3 rayDir = normalize(rayTarget);
float hitTime = FLT_MAX;
vec3 pixelColor = vec3(1.0, 1.0, 1.0);
pixelColor = HandleRay(cameraPos, rayDir, pixelColor, hitTime);
gl_FragColor = vec4(clamp(pixelColor,0.0,1.0), 1.0);
}
and the mouse interactive Processing sketch:
PShader shader;
void setup(){
size(300,300,P2D);
noStroke();
shader = loadShader("shader-MtsXzl.frag");
shader.set("iResolution",(float)width, float(height));
}
void draw(){
background(0);
shader.set("iTime",frameCount * 0.05);
shader.set("iMouse",(float)mouseX , (float)mouseY, mousePressed ? 1.0 : 0.0);
shader(shader);
rect(0,0,width,height);
}
Shadertoy is great way to play/learn: have fun !
Update
Here's a quick test tweaking Daniel Shiffman's 3D Terrain Generation example to add a stripped texture and basic sine waves instead of perlin noise:
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/IKB1hWWedMk
int cols, rows;
int scl = 20;
int w = 2000;
int h = 1600;
float flying = 0;
float[][] terrain;
PImage texture;
void setup() {
size(600, 600, P3D);
textureMode(NORMAL);
noStroke();
cols = w / scl;
rows = h/ scl;
terrain = new float[cols][rows];
texture = getBarsTexture(512,512,96);
}
void draw() {
flying -= 0.1;
float yoff = flying;
for (int y = 0; y < rows; y++) {
float xoff = 0;
for (int x = 0; x < cols; x++) {
//terrain[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100);
terrain[x][y] = map(sin(xoff) * sin(yoff), 0, 1, -60, 60);
xoff += 0.2;
}
yoff += 0.2;
}
background(0);
translate(width/2, height/2+50);
rotateX(PI/9);
translate(-w/2, -h/2);
for (int y = 0; y < rows-1; y++) {
beginShape(TRIANGLE_STRIP);
texture(texture);
for (int x = 0; x < cols; x++) {
float u0 = map(x,0,cols-1,0.0,1.0);
float u1 = map(x+1,0,cols-1,0.0,1.0);
float v0 = map(y,0,rows-1,0.0,1.0);
float v1 = map(y+1,0,rows-1,0.0,1.0);
vertex(x*scl, y*scl, terrain[x][y], u0, v0);
vertex(x*scl, (y+1)*scl, terrain[x][y+1], u1, v1);
}
endShape();
}
}
PGraphics getBarsTexture(int textureWidth, int textureHeight, int numBars){
PGraphics texture = createGraphics(textureWidth, textureHeight);
int moduleSide = textureWidth / numBars;
texture.beginDraw();
texture.background(0);
texture.noStroke();
for(int i = 0; i < numBars; i+= 2){
texture.rect(0, i * moduleSide, textureWidth, moduleSide);
}
texture.endDraw();
return texture;
}

Texture coordinates for triangle fans

I am trying to "fill" a surface of some geometry that I drew. I am using GL_TRIANGLE_FAN primitive. (for example : 1 hub (center) point and 12 other points). I have calculated texture coordinates for each vertex in the interval 0-1. But as a result I get this , its a little bit confused. I desire to get result like that image . Please help, what is wrong here?
How can I calculate correct texture coordinates in such Triangulation ( GL_TRIANGLE_FAN )
in the image red dots is my points
*Code - Snippet :
assert(("CROSS type intersection needs only 5 vertices : center point and "
"rest points in anticlockwise order", (lp->size() > 5) && (lp->size() < 5)));
osg::Vec3 vAlong_1,vAlong_2;
vAlong_1 = (*lp)[1] - (*lp)[4];
vAlong_2 = (*lp)[1] - (*lp)[2];
eps = ((*lp)[2] - (*lp)[4]).length() * 0.2 / 2;
vAlong_1.normalize();
vAlong_2.normalize();
_edgeCoords->push_back((*lp)[0]);
_edgeCoords->push_back((*lp)[1]);
if (CMF::euclidDistance((*lp)[0],(*lp)[1]) <= CMF::euclidDistance((*lp)[0],(*lp)[2])) {
float cosAlpha = -(vAlong_1 * vAlong_2);
float extraLength = ((*lp)[2] - (*lp)[1]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[1] + vAlong_1 * (eps + extraLength));
_edgeCoords->push_back((*lp)[2] + vAlong_1 * eps);
} else {
float cosAlpha = (vAlong_1 * vAlong_2);
float extraLength = ((*lp)[2] - (*lp)[1]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[1] + vAlong_1 * eps);
_edgeCoords->push_back((*lp)[2] + vAlong_1 * (eps + extraLength));
}
_edgeCoords->push_back((*lp)[2]);
if (CMF::euclidDistance((*lp)[0],(*lp)[2]) <= CMF::euclidDistance((*lp)[0],(*lp)[3])) {
float cosAlpha = -(vAlong_1 * vAlong_2);
float extraLength = ((*lp)[3] - (*lp)[2]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[2] - vAlong_2 * (eps + extraLength));
_edgeCoords->push_back((*lp)[3] - vAlong_2 * eps);
} else {
float cosAlpha = (vAlong_1 * vAlong_2);
float extraLength = ((*lp)[3] - (*lp)[2]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[2] - vAlong_2 * eps);
_edgeCoords->push_back((*lp)[3] - vAlong_2 * (eps + extraLength));
}
_edgeCoords->push_back((*lp)[3]);
if (CMF::euclidDistance((*lp)[0],(*lp)[3]) <= CMF::euclidDistance((*lp)[0],(*lp)[4])) {
float cosAlpha = -(vAlong_1 * vAlong_2);
float extraLength = ((*lp)[4] - (*lp)[3]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[3] - vAlong_1 * (eps + extraLength));
_edgeCoords->push_back((*lp)[4] - vAlong_1 * eps);
} else {
float cosAlpha = (vAlong_1 * vAlong_2);
float extraLength = ((*lp)[4] - (*lp)[3]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[3] - vAlong_1 * eps);
_edgeCoords->push_back((*lp)[4] - vAlong_1 * (eps + extraLength));
}
_edgeCoords->push_back((*lp)[4]);
if (CMF::euclidDistance((*lp)[0],(*lp)[1]) <= CMF::euclidDistance((*lp)[0],(*lp)[4])) {
float cosAlpha = -(vAlong_1 * vAlong_2);
float extraLength = ((*lp)[4] - (*lp)[1]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[4] + vAlong_2 * eps);
_edgeCoords->push_back((*lp)[1] + vAlong_2 * (eps + extraLength));
} else {
float cosAlpha = (vAlong_1 * vAlong_2);
float extraLength = ((*lp)[4] - (*lp)[1]).length() * cosAlpha;
_edgeCoords->push_back((*lp)[4] + vAlong_2 * (eps + extraLength));
_edgeCoords->push_back((*lp)[1] + vAlong_2 * eps);
}
_edgeCoords->push_back((*lp)[1]);
_tCoords->push_back(osg::Vec2(0.5,0.5));
_tCoords->push_back(osg::Vec2(0.666,0.666));
_tCoords->push_back(osg::Vec2(0.666,1.0));
_tCoords->push_back(osg::Vec2(0.333,1.0));
_tCoords->push_back(osg::Vec2(0.333,0.666));
_tCoords->push_back(osg::Vec2(0.0,0.666));
_tCoords->push_back(osg::Vec2(0.0,0.333));
_tCoords->push_back(osg::Vec2(0.333,0.333));
_tCoords->push_back(osg::Vec2(0.333,0.0));
_tCoords->push_back(osg::Vec2(0.666,0.0));
_tCoords->push_back(osg::Vec2(0.666,0.333));
_tCoords->push_back(osg::Vec2(1.0,0.333));
_tCoords->push_back(osg::Vec2(1.0,0.666));
_tCoords->push_back(osg::Vec2(0.666,0.666));
Try keeping the 2d positions always equal to the texture coordinates for each vertex. That will ensure your geometry appears as an undistorted cutout of your texture. You can then rescale and center the mesh as you like without distorting the texture by applying transforms to the vertex positions.
One way to do this would be to create a function that pushes a single vertex, accepting the 2d coordinates of the vertex and any transforms you want to apply. The function would then push the 2d coordinates as texcoords, then transform them and push the result as positions.

Help with drawing a sphere in OpenGL ES

I have the following code, but I can't find the bug. It seems to be a memory-related issue. The original code is taken from the 3d library which comes with the OpenGL superbible, and i'm trying to adapt it for openGL es. Any ideas as to why it's segfaulting all the time?
- (void)drawSphereOfRadius:(GLfloat)fRadius nbSlices:(GLint)iSlices nbStacks:(GLint)iStacks
{
GLfloat *vertexPointer = malloc(sizeof(GLfloat) * iStacks * iSlices * 3 * 2);
GLfloat drho = (GLfloat)(3.141592653589) / (GLfloat) iStacks;
GLfloat dtheta = 2.0f * (GLfloat)(3.141592653589) / (GLfloat) iSlices;
GLfloat ds = 1.0f / (GLfloat) iSlices;
GLfloat dt = 1.0f / (GLfloat) iStacks;
GLfloat t = 1.0f;
GLfloat s = 0.0f;
GLint i, j; // Looping variables
int idx = 0;
for (i = 0; i < iStacks; i++)
{
GLfloat rho = (GLfloat)i * drho;
GLfloat srho = (GLfloat)(sin(rho));
GLfloat crho = (GLfloat)(cos(rho));
GLfloat srhodrho = (GLfloat)(sin(rho + drho));
GLfloat crhodrho = (GLfloat)(cos(rho + drho));
s = 0.0f;
for ( j = 0; j <= iSlices; j++)
{
GLfloat theta = (j == iSlices) ? 0.0f : j * dtheta;
GLfloat stheta = (GLfloat)(-sin(theta));
GLfloat ctheta = (GLfloat)(cos(theta));
GLfloat x = stheta * srho;
GLfloat y = ctheta * srho;
GLfloat z = crho;
glNormal3f(x, y, z);
vertexPointer[idx] = x * fRadius;
vertexPointer[idx + 1] = y * fRadius;
vertexPointer[idx + 2] = z * fRadius;
x = stheta * srhodrho;
y = ctheta * srhodrho;
z = crhodrho;
s += ds;
glNormal3f(x, y, z);
vertexPointer[idx + 3] = x * fRadius;
vertexPointer[idx + 4] = y * fRadius;
vertexPointer[idx + 5] = z * fRadius;
idx += 6;
}
t -= dt;
}
glVertexPointer(3, GL_FLOAT, 0, vertexPointer);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_TRIANGLE_STRIP, 0, iStacks * iSlices * 2 );
free(vertexPointer);
//glPopMatrix();
}
Your j loop is doing iSlices + 1 iterations so you need to allocate
sizeof(GLfloat) * iStacks * ( iSlices + 1 ) * 3 * 2
bytes for vertexPointer.

Resources