Related
I'm trying to animate a spiral using a line, but can only seem to get it to work using ellipses.
Does anyone know how to replace the ellipse() with line()?
here is the code:
var angle = 0.0;
var offset = 60;
var scalar = 10;
var speed = 0.05;
function setup() {
createCanvas(600, 120);
fill(0);
}
function draw() {
var x = offset + cos(angle) * scalar;
var y = offset + sin(angle) * scalar;
ellipse( x, y, 2, 2);
angle += speed;
scalar += speed;
}
Assuming you would like to draw the entire spiral instantaneously using line segments, the you simply need a for loop that calculates the x and y coordinates for the current and next point in the spiral for some increment of change, and then draw lines between each pair of points. There are certainly numerous ways to write such a for loop, depending on what the constrains are (do you want a specific number of rings in your spiral? a specific number of degrees of rotation?), but importantly the bigger your increment of change the less smooth your spiral will look. Here is an example that uses the mouse position to determine the number of rings and the size of the change increments:
function setup() {
createCanvas(windowWidth, windowHeight);
stroke(0);
strokeWeight(4);
textAlign(LEFT, TOP);
}
function draw() {
background(255);
// let the horizontal mouse position indicate the
// size of the steps
let speed = map(mouseX, 0, width, 0.01, 1, true);
// let the vertical mouse position indicate the
// total amount of rotation
let maxRotation = map(mouseY, 0, height, TWO_PI, TWO_PI * 50, true);
push();
noStroke();
fill('red');
text(`Rings: ${(maxRotation / TWO_PI).toFixed(1)}, Speed: ${speed.toFixed(2)}`, 10, 10);
pop();
translate(width / 2, height / 2);
let scalar = 10;
if (speed <= 0) {
console.error('cannot have <= 0 speed');
return;
}
for (let angle = 0; angle < maxRotation; angle += speed, scalar += speed) {
const x = cos(angle) * scalar;
const y = sin(angle) * scalar;
const x2 = cos(angle + speed) * (scalar + speed);
const y2 = sin(angle + speed) * (scalar + speed);
line(x, y, x2, y2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
A couple of days ago I asked a question about translations and rotations in Processing.
I wanted to:
translate, invert and rotate a single quadrilateral (PShape object) multiple times
then change the height of one of its 2 top vertices
so as the whole thing act as an articulated arm that can be bent either to the right or the left.
Thanks to the help of #Rabbid76 I was able to achieve this effect but I am now facing another issue when translating the last 5 top horizontally inverted quads.
When bending the object, the first 3 quads get separated from the last 5 and. And the more the bending leg is curved, the farther they get apart.
I would really appreciate if someone could help me fix the translation part (from line 65 to 68) so as the quads stay attached to each other to matter how strong the bending is.
Any suggestion regarding that matter would be also greatly appreciated.
SCRIPT
int W = 40;
int H = 40;
int nQuads = 8;
int xOffset = 27;
float[] p0 = {-W/2 + xOffset, -H/2};
float[] p1 = {-W/2, H/2};
float[] p2 = {W/2, H/2};
float[] p3 = {W/2, -H/2};
PShape object;
void setup(){
size(600, 600, P2D);
smooth(8);
}
void draw(){
background(255);
// Bending to the left
float bending = sin(frameCount*.05) * .1;
p0[1] -= bending;
pushMatrix();
translate(width/2, height/2);
float minX = min( min(p0[0], p3[0]), min(p2[0], p1[0]) );
float maxX = max( max(p0[0], p3[0]), max(p2[0], p1[0]) );
float cptX = (minX+maxX)/2;
//Rotation Angle
float angle = atan2(p3[1]-p0[1], p3[0]-p0[0]);
//Pivot Height
float PH = p0[1] + (p3[1]-p0[1]) * (cptX-p0[0])/(p3[0]-p0[0]);
for (int i = 0; i < nQuads; i++){
float PivotHeight = (i % 2 == 1) ? PH : H/2;
//Height translation
if (i > 0){
translate(0, PivotHeight);
}
//Rotate once every 2 quads
if (i%2 == 1){
rotate(angle*2);
}
//Height translation
//Flip all quads except 1st one
if (i > 0){
translate(0, PivotHeight);
scale(1, -1);
}
//NOT working --> Flipping horizontally the last 5 top QUADS
if (i == 3){
scale(-1, 1);
translate(- xOffset, 0); //trying to align the quads on the X axis. Y translation is missing
rotate(-angle*2);
}
object();
}
popMatrix();
}
void object() {
beginShape(QUADS);
vertex(p0[0], p0[1]);
vertex(p1[0], p1[1]);
vertex(p2[0], p2[1]);
vertex(p3[0], p3[1]);
endShape();
}
Just providing a workaround to my own question but won't accept it as a valid answer as I don't really understand what I'm doing and it's probably not the most efficient solution.
int W = 40;
int H = 40;
int nQuads = 8;
int xOffset = 27;
float[] p0 = {-W/2 + xOffset, -H/2};
float[] p1 = {-W/2, H/2};
float[] p2 = {W/2, H/2};
float[] p3 = {W/2, -H/2};
PShape object;
void setup(){
size(600, 600, P2D);
smooth(8);
}
void draw(){
background(255);
// Bending to the left
float bending = sin(frameCount*.05) * .3;
p0[1] -= bending;
pushMatrix();
translate(width/2, height/2);
float minX = min( min(p0[0], p3[0]), min(p2[0], p1[0]) );
float maxX = max( max(p0[0], p3[0]), max(p2[0], p1[0]) );
float cptX = (minX+maxX)/2;
//Rotation Angle
float angle = atan2(p3[1]-p0[1], p3[0]-p0[0]);
//Pivot Height
float PH = p0[1] + (p3[1]-p0[1]) * (cptX-p0[0])/(p3[0]-p0[0]);
for (int i = 0; i < nQuads; i++){
float PivotHeight = (i % 2 == 1) ? PH : H/2;
//Height translation
if (i > 0){
translate(0, PivotHeight);
}
//Rotate once every 2 quads
if (i%2 == 1){
rotate(angle*2);
}
//Height translation
//Flip all quads except 1st one
if (i > 0){
translate(0, PivotHeight);
scale(1, -1);
}
//Flipping horizontally the last 5 top QUADS
if (i == 3){
scale(-1, 1);
translate(0, PivotHeight);
rotate(-angle*2);
translate(0, PivotHeight);
translate(-xOffset , H/2 - p0[1]);
}
object();
}
popMatrix();
}
void object() {
beginShape(QUADS);
vertex(p0[0], p0[1]);
vertex(p1[0], p1[1]);
vertex(p2[0], p2[1]);
vertex(p3[0], p3[1]);
endShape();
}
make a 24 hour clock. What makes it 24 hour is the clock face will count up from zero hour to 23. But the clock face has only either the AM numbering of the hours or the PM numbering of the hours shown at any one time. The AM numbering is from 0 to 11. The PM is from 12 to 23
I need use the drawNum code and i can't get it to work,
this is the code i have so far...
float x[], y[];
float diam;
color c[];
int n; //number of balls
void setup()
{
size (500, 500);
colorMode(HSB);
background(0);
frameRate(1);
x = new float [n];
y = new float [n];
}
void draw()
{
float h, m, s;
float radius;
float cx, cy;
float clockface;
float hoursRadius, minutesRadius, hoursTick, secondsRadius, minutesTick;
radius = min(height/2.0, width/2.0);
cx = width/2.0;
cy = height/2.0;
clockface = radius * 0.9;
hoursRadius = radius * 0.5;
minutesRadius = radius * 0.65;
secondsRadius = radius * 0.72;
hoursTick = radius * 0.04;
minutesTick = hoursTick * 0.5;
// get time
s = second();
m = minute();
h = hour()%12 + m/60.0;
//draw clock face
fill(40);
noStroke();
ellipseMode(RADIUS);
ellipse(cx, cy, clockface, clockface);
//
drawHand(cx, cy, s*6.0, secondsRadius, 2);
drawHand(cx, cy, m*6.0, minutesRadius, 3);
drawHand(cx, cy, h*30.0, hoursRadius, 5);
for ( int i=0; i<60; i++) {
if (i%5==0) {
drawNum(cx, cy, i*6, secondsRadius, 10, 0);
} else {
drawNum(cx, cy, i*6, secondsRadius, 5, 23);
}
}
// draw the ticks
for ( int i=0; i<60; i++) {
if (i%5==0) {
drawTick(cx, cy, i*6, secondsRadius, 10);
} else {
drawTick(cx, cy, i*6, secondsRadius, 5);
}
}
}
void drawTick( float x, float y,
float angle, float len,
float weight) {
fill(angleToColor(angle));
noStroke();
rectMode(CENTER);
rect(x + cos(radians(angle-90))*len,
y + sin(radians(angle-90))*len,
weight, weight) ;
}
void drawHand( float x, float y,
float angle, float len,
float weight) {
strokeWeight(weight);
stroke(angleToColor(angle));
line(x, y,
x + cos(radians(angle-90))*len,
y + sin(radians(angle-90))*len);
}
color angleToColor(float angle) {
return color(map(angle, 0, 360, 0, 255), 255, 255);
}
void drawNum(float x,float y,
float angle, float len,
float tsize, int num) {
float i;
textSize(36);
fill(0);
noStroke();
for (num = 0; num < 12; num++) {
int hoursNum = (x + cos(radians(angle-90))*len,
y + sin(radians(angle-90))*len,
num, num)) ;;
}
}
You need to convert the number into a string (text) to display it. This is done with function str(arg) which returns a value of type String. The argument arg can be an int, float, char, byte or boolean type.
The function for drawing text is text( s, x, y ) where s is the string to display and x and y are the location.
EDIT:
The hour() function returns hours on a 24-hour clock, measured from 0-23 instead of 1-24. Right-click on the function in the Processing window and select "Find in Reference" to see the details. The code hour()%12 is taking the hour value modulo 12, which means when the value reached 12 it cycles back to zero, and values 12-23 are reported as 0-11.
As others have discussed, GLSL lacks any kind of printf debugging.
But sometimes I really want to examine numeric values while debugging my shaders.
I've been trying to create a visual debugging tool.
I found that it's possible to render an arbitrary series of digits fairly easily in a shader, if you work with a sampler2D in which the digits 0123456789 have been rendered in monospace. Basically, you just juggle your x coordinate.
Now, to use this to examine a floating-point number, I need an algorithm for converting a float to a sequence of decimal digits, such as you might find in any printf implementation.
Unfortunately, as far as I understand the topic, these algorithms seem to need to represent the
floating-point number in a higher-precision format, and I don't see how this is going to be
possible in GLSL where I seem to have only 32-bit floats available.
For this reason, I think this question is not a duplicate of any general "how does printf work" question, but rather specifically about how such algorithms can be made to work under the constraints of GLSL. I've seen this question and answer, but have no idea what's going on there.
The algorithms I've tried aren't very good.
My first try, marked Version A (commented out) seemed pretty bad:
to take three random examples, RenderDecimal(1.0) rendered as 1.099999702, RenderDecimal(2.5) gave me
2.599999246 and RenderDecimal(2.6) came out as 2.699999280.
My second try, marked Version B, seemed
slightly better: 1.0 and 2.6 both come out fine, but RenderDecimal(2.5) still mismatches an apparent
rounding-up of the 5 with the fact that the residual is 0.099.... The result appears as 2.599000022.
My minimal/complete/verifiable example, below, starts with some shortish GLSL 1.20 code, and then
I happen to have chosen Python 2.x for the rest, just to get the shaders compiled and the textures loaded and rendered. It requires the pygame, NumPy, PyOpenGL and PIL third-party packages. Note that the Python is really just boilerplate and could be trivially (though tediously) re-written in C or anything else. Only the GLSL code at the top is critical for this question, and for this reason I don't think the python or python 2.x tags would be helpful.
It requires the following image to be saved as digits.png:
vertexShaderSource = """\
varying vec2 vFragCoordinate;
void main(void)
{
vFragCoordinate = gl_Vertex.xy;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""
fragmentShaderSource = """\
varying vec2 vFragCoordinate;
uniform vec2 uTextureSize;
uniform sampler2D uTextureSlotNumber;
float OrderOfMagnitude( float x )
{
return x == 0.0 ? 0.0 : floor( log( abs( x ) ) / log( 10.0 ) );
}
void RenderDecimal( float value )
{
// Assume that the texture to which uTextureSlotNumber refers contains
// a rendering of the digits '0123456789' packed together, such that
const vec2 startOfDigitsInTexture = vec2( 0, 0 ); // the lower-left corner of the first digit starts here and
const vec2 sizeOfDigit = vec2( 100, 125 ); // each digit spans this many pixels
const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in
value = abs( value );
vec2 pos = vFragCoordinate - startOfDigitsInTexture;
float dpstart = max( 0.0, OrderOfMagnitude( value ) );
float decimal_position = dpstart - floor( pos.x / sizeOfDigit.x );
float remainder = mod( pos.x, sizeOfDigit.x );
if( pos.x >= 0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0 && pos.y < sizeOfDigit.y )
{
float digit_value;
// Version B
float dp, running_value = value;
for( dp = dpstart; dp >= decimal_position; dp -= 1.0 )
{
float base = pow( 10.0, dp );
digit_value = mod( floor( running_value / base ), 10.0 );
running_value -= digit_value * base;
}
// Version A
//digit_value = mod( floor( value * pow( 10.0, -decimal_position ) ), 10.0 );
vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + remainder + digit_value * sizeOfDigit.x, startOfDigitsInTexture.y + pos.y );
gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
}
// Render the decimal point
if( ( decimal_position == -1.0 && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
( decimal_position == 0.0 && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
{
gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
}
}
void main(void)
{
gl_FragColor = texture2D( uTextureSlotNumber, vFragCoordinate / uTextureSize );
RenderDecimal( 2.5 ); // for current demonstration purposes, just a constant
}
"""
# Python (PyOpenGL) code to demonstrate the above
# (Note: the same OpenGL calls could be made from any language)
import os, sys, time
import OpenGL
from OpenGL.GL import *
from OpenGL.GLU import *
import pygame, pygame.locals # just for getting a canvas to draw on
try: from PIL import Image # PIL.Image module for loading image from disk
except ImportError: import Image # old PIL didn't package its submodules on the path
import numpy # for manipulating pixel values on the Python side
def CompileShader( type, source ):
shader = glCreateShader( type )
glShaderSource( shader, source )
glCompileShader( shader )
result = glGetShaderiv( shader, GL_COMPILE_STATUS )
if result != 1:
raise Exception( "Shader compilation failed:\n" + glGetShaderInfoLog( shader ) )
return shader
class World:
def __init__( self, width, height ):
self.window = pygame.display.set_mode( ( width, height ), pygame.OPENGL | pygame.DOUBLEBUF )
# compile shaders
vertexShader = CompileShader( GL_VERTEX_SHADER, vertexShaderSource )
fragmentShader = CompileShader( GL_FRAGMENT_SHADER, fragmentShaderSource )
# build shader program
self.program = glCreateProgram()
glAttachShader( self.program, vertexShader )
glAttachShader( self.program, fragmentShader )
glLinkProgram( self.program )
# try to activate/enable shader program, handling errors wisely
try:
glUseProgram( self.program )
except OpenGL.error.GLError:
print( glGetProgramInfoLog( self.program ) )
raise
# enable alpha blending
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE )
glEnable( GL_DEPTH_TEST )
glEnable( GL_BLEND )
glBlendEquation( GL_FUNC_ADD )
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )
# set projection and background color
gluOrtho2D( 0, width, 0, height )
glClearColor( 0.0, 0.0, 0.0, 1.0 )
self.uTextureSlotNumber_addr = glGetUniformLocation( self.program, 'uTextureSlotNumber' )
self.uTextureSize_addr = glGetUniformLocation( self.program, 'uTextureSize' )
def RenderFrame( self, *textures ):
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
for t in textures: t.Draw( world=self )
pygame.display.flip()
def Close( self ):
pygame.display.quit()
def Capture( self ):
w, h = self.window.get_size()
rawRGB = glReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE )
return Image.frombuffer( 'RGB', ( w, h ), rawRGB, 'raw', 'RGB', 0, 1 ).transpose( Image.FLIP_TOP_BOTTOM )
class Texture:
def __init__( self, source, slot=0, position=(0,0,0) ):
# wrangle array
source = numpy.array( source )
if source.dtype.type not in [ numpy.float32, numpy.float64 ]: source = source.astype( float ) / 255.0
while source.ndim < 3: source = numpy.expand_dims( source, -1 )
if source.shape[ 2 ] == 1: source = source[ :, :, [ 0, 0, 0 ] ] # LUMINANCE -> RGB
if source.shape[ 2 ] == 2: source = source[ :, :, [ 0, 0, 0, 1 ] ] # LUMINANCE_ALPHA -> RGBA
if source.shape[ 2 ] == 3: source = source[ :, :, [ 0, 1, 2, 2 ] ]; source[ :, :, 3 ] = 1.0 # RGB -> RGBA
# now it can be transferred as GL_RGBA and GL_FLOAT
# housekeeping
self.textureSize = [ source.shape[ 1 ], source.shape[ 0 ] ]
self.textureSlotNumber = slot
self.textureSlotCode = getattr( OpenGL.GL, 'GL_TEXTURE%d' % slot )
self.listNumber = slot + 1
self.position = list( position )
# transfer texture content
glActiveTexture( self.textureSlotCode )
self.textureID = glGenTextures( 1 )
glBindTexture( GL_TEXTURE_2D, self.textureID )
glEnable( GL_TEXTURE_2D )
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA32F, self.textureSize[ 0 ], self.textureSize[ 1 ], 0, GL_RGBA, GL_FLOAT, source[ ::-1 ] )
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST )
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST )
# define surface
w, h = self.textureSize
glNewList( self.listNumber, GL_COMPILE )
glBegin( GL_QUADS )
glColor3f( 1, 1, 1 )
glNormal3f( 0, 0, 1 )
glVertex3f( 0, h, 0 )
glVertex3f( w, h, 0 )
glVertex3f( w, 0, 0 )
glVertex3f( 0, 0, 0 )
glEnd()
glEndList()
def Draw( self, world ):
glPushMatrix()
glTranslate( *self.position )
glUniform1i( world.uTextureSlotNumber_addr, self.textureSlotNumber )
glUniform2f( world.uTextureSize_addr, *self.textureSize )
glCallList( self.listNumber )
glPopMatrix()
world = World( 1000, 800 )
digits = Texture( Image.open( 'digits.png' ) )
done = False
while not done:
world.RenderFrame( digits )
for event in pygame.event.get():
# Press 'q' to quit or 's' to save a timestamped snapshot
if event.type == pygame.locals.QUIT: done = True
elif event.type == pygame.locals.KEYUP and event.key in [ ord( 'q' ), 27 ]: done = True
elif event.type == pygame.locals.KEYUP and event.key in [ ord( 's' ) ]:
world.Capture().save( time.strftime( 'snapshot-%Y%m%d-%H%M%S.png' ) )
world.Close()
+1 for interesting problem. Was curious so I tried to code this. I need the use of arrays so I chose #version 420 core. My app is rendering single quad covering screen with coordinates <-1,+1>. I am using whole ASCII 8x8 pixel 32x8 characters font texture I created some years ago:
The vertex is simple:
//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
layout(location=0) in vec4 vertex;
out vec2 pos; // screen position <-1,+1>
void main()
{
pos=vertex.xy;
gl_Position=vertex;
}
//---------------------------------------------------------------------------
Fragment is a bit more complicated:
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
in vec2 pos; // screen position <-1,+1>
out vec4 gl_FragColor; // fragment output color
uniform sampler2D txr_font; // ASCII 32x8 characters font texture unit
uniform float fxs,fys; // font/screen resolution ratio
//---------------------------------------------------------------------------
const int _txtsiz=32; // text buffer size
int txt[_txtsiz],txtsiz; // text buffer and its actual size
vec4 col; // color interface for txt_print()
//---------------------------------------------------------------------------
void txt_decimal(float x) // print float x into txt
{
int i,j,c; // l is size of string
float y,a;
const float base=10;
// handle sign
if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
else { txt[txtsiz]='+'; txtsiz++; }
// divide to int(x).fract(y) parts of number
y=x; x=floor(x); y-=x;
// handle integer part
i=txtsiz; // start of integer part
for (;txtsiz<_txtsiz;)
{
a=x;
x=floor(x/base);
a-=base*x;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (x<=0.0) break;
}
j=txtsiz-1; // end of integer part
for (;i<j;i++,j--) // reverse integer digits
{
c=txt[i]; txt[i]=txt[j]; txt[j]=c;
}
// handle fractional part
for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;)
{
y*=base;
a=floor(y);
y-=a;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (y<=0.0) break;
}
txt[txtsiz]=0; // string terminator
}
//---------------------------------------------------------------------------
void txt_print(float x0,float y0) // print txt at x0,y0 [chars]
{
int i;
float x,y;
// fragment position [chars] relative to x0,y0
x=0.5*(1.0+pos.x)/fxs; x-=x0;
y=0.5*(1.0-pos.y)/fys; y-=y0;
// inside bbox?
if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return;
// get font texture position for target ASCII
i=int(x); // char index in txt
x-=float(i);
i=txt[i];
x+=float(int(i&31));
y+=float(int(i>>5));
x/=32.0; y/=8.0; // offset in char texture
col=texture2D(txr_font,vec2(x,y));
}
//---------------------------------------------------------------------------
void main()
{
col=vec4(0.0,1.0,0.0,1.0); // background color
txtsiz=0;
txt[txtsiz]='F'; txtsiz++;
txt[txtsiz]='l'; txtsiz++;
txt[txtsiz]='o'; txtsiz++;
txt[txtsiz]='a'; txtsiz++;
txt[txtsiz]='t'; txtsiz++;
txt[txtsiz]=':'; txtsiz++;
txt[txtsiz]=' '; txtsiz++;
txt_decimal(12.345);
txt_print(1.0,1.0);
gl_FragColor=col;
}
//---------------------------------------------------------------------------
Here my CPU side uniforms:
glUniform1i(glGetUniformLocation(prog_id,"txr_font"),0);
glUniform1f(glGetUniformLocation(prog_id,"fxs"),(8.0)/float(xs));
glUniform1f(glGetUniformLocation(prog_id,"fys"),(8.0)/float(ys));
where xs,ys is my screen resolution. Font is 8x8 in unit 0
Here output for the test fragment code:
If your floating point accuracy is decreased due to HW implementation then you should consider printing in hex where no accuracy loss is present (using binary access). That could be converted to decadic base on integers later ...
see:
string hex2dec conversion on integer math
[Edit2] old style GLSL shaders
I tried to port to old style GLSL and suddenly it works (before it would not compile with arrays present but when I think of it I was trying char[] which was the real reason).
//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
varying vec2 pos; // screen position <-1,+1>
void main()
{
pos=gl_Vertex.xy;
gl_Position=gl_Vertex;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
varying vec2 pos; // screen position <-1,+1>
uniform sampler2D txr_font; // ASCII 32x8 characters font texture unit
uniform float fxs,fys; // font/screen resolution ratio
//---------------------------------------------------------------------------
const int _txtsiz=32; // text buffer size
int txt[_txtsiz],txtsiz; // text buffer and its actual size
vec4 col; // color interface for txt_print()
//---------------------------------------------------------------------------
void txt_decimal(float x) // print float x into txt
{
int i,j,c; // l is size of string
float y,a;
const float base=10.0;
// handle sign
if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
else { txt[txtsiz]='+'; txtsiz++; }
// divide to int(x).fract(y) parts of number
y=x; x=floor(x); y-=x;
// handle integer part
i=txtsiz; // start of integer part
for (;txtsiz<_txtsiz;)
{
a=x;
x=floor(x/base);
a-=base*x;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (x<=0.0) break;
}
j=txtsiz-1; // end of integer part
for (;i<j;i++,j--) // reverse integer digits
{
c=txt[i]; txt[i]=txt[j]; txt[j]=c;
}
// handle fractional part
for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;)
{
y*=base;
a=floor(y);
y-=a;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (y<=0.0) break;
}
txt[txtsiz]=0; // string terminator
}
//---------------------------------------------------------------------------
void txt_print(float x0,float y0) // print txt at x0,y0 [chars]
{
int i;
float x,y;
// fragment position [chars] relative to x0,y0
x=0.5*(1.0+pos.x)/fxs; x-=x0;
y=0.5*(1.0-pos.y)/fys; y-=y0;
// inside bbox?
if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return;
// get font texture position for target ASCII
i=int(x); // char index in txt
x-=float(i);
i=txt[i];
x+=float(int(i-((i/32)*32)));
y+=float(int(i/32));
x/=32.0; y/=8.0; // offset in char texture
col=texture2D(txr_font,vec2(x,y));
}
//---------------------------------------------------------------------------
void main()
{
col=vec4(0.0,1.0,0.0,1.0); // background color
txtsiz=0;
txt[txtsiz]='F'; txtsiz++;
txt[txtsiz]='l'; txtsiz++;
txt[txtsiz]='o'; txtsiz++;
txt[txtsiz]='a'; txtsiz++;
txt[txtsiz]='t'; txtsiz++;
txt[txtsiz]=':'; txtsiz++;
txt[txtsiz]=' '; txtsiz++;
txt_decimal(12.345);
txt_print(1.0,1.0);
gl_FragColor=col;
}
//---------------------------------------------------------------------------
First of all I want to mention that the amazing solution of Spektre is almost perfect and even more a general solution for text output. I gave his answer an upvote.
As an alternative, I present a minimally invasive solution, and improve the code of the question.
I do not want to conceal the fact that I have studied the solution of Spektre and integrated into my solution.
// Assume that the texture to which uTextureSlotNumber refers contains
// a rendering of the digits '0123456789' packed together, such that
const vec2 startOfDigitsInTexture = vec2( 100, 125 ); // the lower-left corner of the first digit starts here and
const vec2 sizeOfDigit = vec2( 0.1, 0.2 ); // each digit spans this many pixels
const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in
void RenderDigit( int strPos, int digit, vec2 pos )
{
float testStrPos = pos.x / sizeOfDigit.x;
if ( testStrPos >= float(strPos) && testStrPos < float(strPos+1) )
{
float start = sizeOfDigit.x * float(digit);
vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + start + mod( pos.x, sizeOfDigit.x ), startOfDigitsInTexture.y + pos.y );
gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
}
}
The function ValueToDigits interprets a floating point number an fills up an array with the digits.
Each number in the array is in (0, 9).
const int MAX_DIGITS = 32;
int digits[MAX_DIGITS];
int noOfDigits = 0;
int posOfComma = 0;
void Reverse( int start, int end )
{
for ( ; start < end; ++ start, -- end )
{
int digit = digits[start];
digits[start] = digits[end];
digits[end] = digit;
}
}
void ValueToDigits( float value )
{
const float base = 10.0;
int start = noOfDigits;
value = abs( value );
float frac = value; value = floor(value); frac -= value;
// integral digits
for ( ; value > 0.0 && noOfDigits < MAX_DIGITS; ++ noOfDigits )
{
float newValue = floor( value / base );
digits[noOfDigits] = int( value - base * newValue );
value = newValue;
}
Reverse( start, noOfDigits-1 );
posOfComma = noOfDigits;
// fractional digits
for ( ; frac > 0.0 && noOfDigits < MAX_DIGITS; ++ noOfDigits )
{
frac *= base;
float digit = floor( frac );
frac -= digit;
digits[noOfDigits] = int( digit );
}
}
Call ValueToDigits in your original function and find the digit and textur coordinates for the current fragment.
void RenderDecimal( float value )
{
// fill the array of digits with the floating point value
ValueToDigits( value );
// Render the digits
vec2 pos = vFragCoordinate.xy - startOfDigitsInTexture;
if( pos.x >= 0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0 && pos.y < sizeOfDigit.y )
{
// render the digits
for ( int strPos = 0; strPos < noOfDigits; ++ strPos )
RenderDigit( strPos, digits[strPos], pos );
}
// Render the decimal point
float testStrPos = pos.x / sizeOfDigit.x;
float remainder = mod( pos.x, sizeOfDigit.x );
if( ( testStrPos >= float(posOfComma) && testStrPos < float(posOfComma+1) && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
( testStrPos >= float(posOfComma-1) && testStrPos < float(posOfComma) && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
{
gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
}
}
Here's my updated fragment shader, which can be dropped into the listing in my original question. It implements the decimal-digit-finding algorithm Spektre proposed, in a way that is even compatible with the legacy GLSL 1.20 dialect I'm using. Without that constraint, Spektre's solution is, of course, much more elegant and powerful.
varying vec2 vFragCoordinate;
uniform vec2 uTextureSize;
uniform sampler2D uTextureSlotNumber;
float Digit( float x, int position, float base )
{
int i;
float digit;
if( position < 0 )
{
x = fract( x );
for( i = -1; i >= position; i-- )
{
if( x <= 0.0 ) { digit = 0.0; break; }
x *= base;
digit = floor( x );
x -= digit;
}
}
else
{
x = floor( x );
float prevx;
for( i = 0; i <= position; i++ )
{
if( x <= 0.0 ) { digit = 0.0; break; }
prevx = x;
x = floor( x / base );
digit = prevx - base * x;
}
}
return digit;
}
float OrderOfMagnitude( float x )
{
return x == 0.0 ? 0.0 : floor( log( abs( x ) ) / log( 10.0 ) );
}
void RenderDecimal( float value )
{
// Assume that the texture to which uTextureSlotNumber refers contains
// a rendering of the digits '0123456789' packed together, such that
const vec2 startOfDigitsInTexture = vec2( 0, 0 ); // the lower-left corner of the first digit starts here and
const vec2 sizeOfDigit = vec2( 100, 125 ); // each digit spans this many pixels
const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in
value = abs( value );
vec2 pos = vFragCoordinate - startOfDigitsInTexture;
float dpstart = max( 0.0, OrderOfMagnitude( value ) );
int decimal_position = int( dpstart - floor( pos.x / sizeOfDigit.x ) );
float remainder = mod( pos.x, sizeOfDigit.x );
if( pos.x >= 0.0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0.0 && pos.y < sizeOfDigit.y )
{
float digit_value = Digit( value, decimal_position, 10.0 );
vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + remainder + digit_value * sizeOfDigit.x, startOfDigitsInTexture.y + pos.y );
gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
}
// Render the decimal point
if( ( decimal_position == -1 && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
( decimal_position == 0 && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
{
gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
}
}
void main(void)
{
gl_FragColor = texture2D( uTextureSlotNumber, vFragCoordinate / uTextureSize );
RenderDecimal( 2.5 ); // for current demonstration purposes, just a constant
}
I’m looking for a proper way of finding points along a PShape contour.
My goal is to generate the same number of points along the two distances from a given point to another (right distance and left distance), then mark a point in the exact center between the the two points that are the same step number on each side. (I’m not sure if I’m being easily understandable, and I cannot attach img already, so I attach processing code).
I imagine that the first step for getting it done is to calculate the exact distance between the start and end points, following the path. Maybe I’m wrong.
Any help on this matter would be very very welcome.
PGraphics g ;
PVector[] values = new PVector[7];
void setup(){
size(1024,768,P3D);
fillVal();
smooth();
}
void draw(){
background(0);
drawSiluette(g);
}
void fillVal(){
values[0]=new PVector ( 336.0, 272.0, 0.0 );
values[1]=new PVector ( 305.0, 428.0, 0.0 );
values[2]=new PVector ( 489.0, 516.0, 0.0 );
values[3]=new PVector ( 639.0, 400.0, 0.0);
values[4]=new PVector ( 565.0, 283.0, 0.0 );
values[5]=new PVector ( 469.0, 227.0, 0.0 );
values[6]=new PVector ( 403.0, 216.0, 0.0 );
}
void drawSiluette(PGraphics _s){
_s = createGraphics(width,height);
pushMatrix();
_s.beginDraw();
_s.noFill();
_s.strokeWeight(3);
_s.stroke(255);
_s.beginShape();
for(int i = 0; i <values.length;i++){
if(i==0 || i==values.length-1){
for(int it = 0; it<2;it++)
_s.curveVertex(values[0].x,values[0].y);
}else
_s.curveVertex(values[i].x,values[i].y);
}
_s.endShape(CLOSE);
popMatrix();
_s.endDraw();
image(_s,0,0);
//start and end points
pushMatrix();
noStroke();
fill(255,0,0);
ellipseMode(CENTER);
ellipse(values[0].x,values[0].y,10,10);
ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
popMatrix();
}
The question is a little unclear. To
generate the same number of points along the two distances from a
given point to another
you can simply linearly interpolate between two points (lerp for short).
This functionality is built into the PVector's lerp() function.
The function takes three parameters:
the start point
the end point
a normalised value, which is a value between 0.0 and 1.0
You can think of the normalised value as a percentage:
0.0 = 0%
0.25 = 25%
1.0 = 100%
etc.
Here's a basic example demonstration interpolation between two points with a given number of points in between:
PVector from = new PVector(100,100);
PVector to = new PVector(300,300);
int numPoints = 10;
void setup(){
size(400,400);
fill(0);
}
void draw(){
background(255);
for(int i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
float interpolationAmount = (float)i / numPoints;
//float interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
text("numPoints: " + numPoints,10,15);
}
void mouseDragged(){
if(keyPressed) {
to.set(mouseX,mouseY);
}else{
from.set(mouseX,mouseY);
}
}
void keyPressed(){
if(keyCode == UP) numPoints++;
if(keyCode == DOWN && numPoints > 0) numPoints--;
}
You can run this as a demo bellow:
var from,to;
var numPoints = 10;
function setup(){
createCanvas(400,400);
fill(0);
from = createVector(100,100);
to = createVector(300,300);
}
function draw(){
background(255);
for(var i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
//var interpolationAmount = (float)i / numPoints;
var interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
var interpolatedPoint = p5.Vector.lerp(from,to,interpolationAmount);//PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
text("usage:\nclick & drag to move start point\nhold a key pressed while clicking and drag to move end point\nuse LEFT/RIGHT arrow to change number of points: " + numPoints,10,15);
}
function mouseDragged(){
if(keyIsPressed) {
to.set(mouseX,mouseY);
}else{
from.set(mouseX,mouseY);
}
}
function keyPressed(){
if(keyCode == LEFT_ARROW) numPoints++;
if(keyCode == RIGHT_ARROW && numPoints > 0) numPoints--;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.4/p5.min.js"></script>
The functionality could be encapsulated into a reusable function:
void drawPointsInbetween(PVector from,PVector to,int numPoints){
for(int i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
float interpolationAmount = (float)i / numPoints;
//float interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
}
Back to your code, one thing that sticks out, although it's not related your main question is the fact that you're creating a new PGraphics instance multiple times per second. You probably don't want to do that. Currently, you should be able to draw straight into Processing with no need for PGraphics.
PVector[] values = new PVector[7];
void setup(){
size(1024,768,P3D);
fillVal();
smooth();
}
void draw(){
background(0);
drawSiluette(g);
}
void fillVal(){
values[0]=new PVector ( 336.0, 272.0, 0.0 );
values[1]=new PVector ( 305.0, 428.0, 0.0 );
values[2]=new PVector ( 489.0, 516.0, 0.0 );
values[3]=new PVector ( 639.0, 400.0, 0.0);
values[4]=new PVector ( 565.0, 283.0, 0.0 );
values[5]=new PVector ( 469.0, 227.0, 0.0 );
values[6]=new PVector ( 403.0, 216.0, 0.0 );
}
void drawSiluette(PGraphics _s){
//_s = createGraphics(width,height);
pushMatrix();
//_s.beginDraw();
noFill();
strokeWeight(3);
stroke(255);
beginShape();
for(int i = 0; i <values.length;i++){
if(i==0 || i==values.length-1){
for(int it = 0; it<2;it++)
curveVertex(values[0].x,values[0].y);
}else
curveVertex(values[i].x,values[i].y);
}
endShape(CLOSE);
popMatrix();
//start and end points
pushMatrix();
noStroke();
fill(255,0,0);
ellipseMode(CENTER);
ellipse(values[0].x,values[0].y,10,10);
ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
popMatrix();
}
Adding the points in between function would be as simple as this:
PVector[] values = new PVector[7];
int numPoints = 10;
void setup(){
size(1024,768,P3D);
fillVal();
smooth();
}
void draw(){
background(0);
drawSiluette(g);
}
void fillVal(){
values[0]=new PVector ( 336.0, 272.0, 0.0 );
values[1]=new PVector ( 305.0, 428.0, 0.0 );
values[2]=new PVector ( 489.0, 516.0, 0.0 );
values[3]=new PVector ( 639.0, 400.0, 0.0);
values[4]=new PVector ( 565.0, 283.0, 0.0 );
values[5]=new PVector ( 469.0, 227.0, 0.0 );
values[6]=new PVector ( 403.0, 216.0, 0.0 );
}
void drawSiluette(PGraphics _s){
//_s = createGraphics(width,height);
pushMatrix();
//_s.beginDraw();
noFill();
strokeWeight(3);
stroke(255);
beginShape();
for(int i = 0; i <values.length;i++){
if(i==0 || i==values.length-1){
for(int it = 0; it<2;it++)
curveVertex(values[0].x,values[0].y);
}else
curveVertex(values[i].x,values[i].y);
}
endShape(CLOSE);
popMatrix();
//start and end points
pushMatrix();
noStroke();
fill(255,0,0);
ellipseMode(CENTER);
ellipse(values[0].x,values[0].y,10,10);
ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
popMatrix();
//draw inbetween points
for(int i = 1 ; i < values.length; i++){
drawPointsInbetween(values[i-1],values[i],numPoints);
}
//draw last to first
drawPointsInbetween(values[values.length-1],values[0],numPoints);
}
void drawPointsInbetween(PVector from,PVector to,int numPoints){
for(int i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
float interpolationAmount = (float)i / numPoints;
//float interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
}
Here's a preview:
Notice that the interpolation is linear. For curves you might want to look at
higher order interpolation functions such as quadratic or cubic.
Hermite curves are an example of cubic curve.
Here's a basic the formula:
and here's a basic Processing demo interpolating points on a Hermite curve:
float percent = 0;
PVector P0 = new PVector(10,90);//1st control pt
PVector T0 = new PVector(300,200);//1st anchor pt - NOTE! The anchors are relative to the controls
PVector P1 = new PVector(400,90);//2nd control pt
PVector T1 = new PVector(-100,400);//2nd anchor pt
PVector[] points = {P0,T0,P1,T1};
PVector pointAtPercent;
void setup(){
size(500,500);
reset();
}
void reset(){
P1.x = 200 + random(200);//randomize a wee bit
T1.x = random(-100,100);
percent = 0;
background(255);
loop();
}
void draw() {
pointAtPercent = hermite(percent, points);//compute point
//render on screen
ellipse(pointAtPercent.x,pointAtPercent.y,10,10);
//update percentage of traversal along curve
percent += .015;
//if the curve has been drawn, stop drawing
if(percent >= 1) noLoop();
}
void mousePressed(){
reset();
}
PVector hermite(float t,PVector[] points){
PVector result = new PVector();
result.x = (2 * pow(t,3) - 3 * t * t + 1) * points[0].x+
(pow(t,3) - 2 * t * t + t) * points[1].x +
(- 2 * pow(t,3) + 3*t*t) * points[2].x +
( pow(t,3) - t*t) * points[3].x;
result.y = (2 * pow(t,3) - 3 * t * t + 1) * points[0].y+
(pow(t,3) - 2 * t * t + t) * points[1].y +
(- 2 * pow(t,3) + 3*t*t) * points[2].y +
( pow(t,3) - t*t) * points[3].y;
return result;
}
It's unclear how exactly you're aiming to interpolate between your points, but hopefully the above concepts should help you achieve your goal.