(Processing) Fuction that draws a ring with the radius I assign - function

I am trying to create a function (ring) that allows me to draw multiple rings around the black circle by only changing the radius, but even when I call it multiple times in draw() it only draws one. I have tried checking if there is something wrong that restricts it to one, but I can't find it. Thank you for your time :)
//VOID
//Set of written numbers
String numbers = "87237462835465598709986654374649";
PFont font;
float r = 40; // Radius of the circle of the written numbers
void setup()
{
size(640, 480);
font = loadFont("AgencyFB-Reg-48.vlw"); //Create futuristic font
textFont(font, 14);
smooth();
}
void draw()
{
background(255);
//Black circle (center of the void)
fill(0);
noStroke();
ellipse(550,80,r*2,r*2);
//FUNCTION for the numbers
ring(40);
}
void ring(float r) {
// Circle for the written numbers
translate(550, 80);
noFill();
noStroke();
ellipse(0, 0, r, r);
// We must keep track of our position along the curve
float arclength = 0;
// For every box
for (int i = 0; i < numbers.length(); i=i+1)
{
// Instead of a constant width, we check the width of each character.
char currentChar = numbers.charAt(i);
float w = textWidth(currentChar);
// Each box is centered so we move half the width
arclength += w/2;
// Angle in radians is the arclength divided by the radius
// Starting on the left side of the circle by adding PI
float theta = PI + arclength / r;
pushMatrix();
// Polar to cartesian coordinate conversion
translate(r*cos(theta), r*sin(theta));
// Rotate the box
rotate(theta+PI/2); // rotation is offset by 90 degrees
// Display the character
fill(0);
text(currentChar,0,0);
popMatrix();
// Move halfway again
arclength += w/2;
}
}

If you call ring() multiple times from draw() your code repeatedly calls translate(). Each one of these calls are additive, so the text just keeps getting further and further away. One way to stop this from happening is to reset it at the bottom of the ring function. This is done by using a reverse sign, e.g., translate(x,y) is changed to translate(-x,-y) at the end of the function to put it back where it was to start with. This change in code will allow you to see multiple rings of text.
float r = 40; // Radius of the circle of the written numbers
String numbers = "87237462835465598709986654374649";
void setup() {
size(400, 400);
smooth();
}
void draw() {
background(255);
fill(0);
noStroke();
ellipse(180, 180, r*2, r*2);
for(int x = 40; x < 100; x+=10){
ring(x);
}
}
void ring(float r) {
translate(180, 180); // Move numbers to location of circle
float arclength = 0;
for (int i = 0; i < numbers.length(); i=i+1) {
char currentChar = numbers.charAt(i);
float w = textWidth(currentChar);
arclength += w/2;
float theta = PI + arclength / r;
pushMatrix();
// Polar to cartesian coordinate conversion
translate(r*cos(theta), r*sin(theta));
// Rotate the box
rotate(theta+PI/2); // rotation is offset by 90 degrees
// Display the character
fill(0);
text(currentChar, 0, 0);
popMatrix();
// Move halfway again
arclength += w/2;
}
translate(-180, -180); // Move it back where it was to start
}

Related

projected cube map shader, white line between edges

I'm trying to write a shader which renders a cube map / cube texture as an equirectangular projection.
The main part of this is done however I get white lines between the faces.
My methodology is:
Starting from UV ([0,1]x[0,1])
Transform to [-1,1]x[-1,1] and than to [-180,180]x[-90,90]
These are now long lat, which can be transformed into 3D (xyz)
Get the face they belong to, as well as their position within this face ([-1,1]x[-1,1])
Transform this face position to a UV within the cube texture
At first I thought the output of step 4 was wrong and that I was sampling from outside the texture, but even after multiplying the face coordinates by 1/2, I still get the white lines.
reference: https://codepen.io/coutteausam/pen/jOKKYYy
float max3(vec3 v) {
return max(max(v.x, v.y), v.z);
}
vec2 sample_cube_map_1(vec3 xyz, out float faceIndex) {
xyz /= length(xyz);
float m = max3(abs(xyz));
if (abs(xyz.x) == m) {
faceIndex = sign(xyz.x);
return xyz.yz / abs(xyz.x);
}
if (abs(xyz.y) == m) {
faceIndex = 2. * sign(xyz.y);
return xyz.xz / abs(xyz.y);
}
if (abs(xyz.z) == m) {
faceIndex = 3. * sign(xyz.z);
return xyz.xy / abs(xyz.z);
}
faceIndex = 1.0;
return vec2(0., 0.);
}
vec2 sample_cube_map(vec3 xyz) {
float face;
vec2 xy = sample_cube_map_1(xyz, face);
xy = (xy + 1.) / 2.; // [-1,1] -> [0,1]
xy.x = clamp(xy.x, 0., 1.);
xy.y = clamp(xy.y, 0., 1.);
if (face == 1.) {
// front
xy += vec2(1., 1.);
}
else if (face == -1.) {
//back
xy.x = 1. - xy.x;
xy += vec2(3., 1.);
}
else if (face == 2.) {
// right
xy.x = 1. - xy.x;
xy += vec2(2., 1.);
}
else if (face == -2.) {
// left
xy += vec2(0., 1.);
}
else if (face == 3.) {
// top
xy = vec2(xy.y, 1. - xy.x);
xy += vec2(1., 2.);
}
else if (face == -3.) {
// bottom
xy = xy.yx;
xy += vec2(1., 0.);
}
else {
xy += vec2(1., 0.);
}
return xy / vec2(4., 3.); // [0,4]x[0,3] -> [0,1]x[0,1]
}
// projects
// uv:([0,1] x [0,1])
// to
// xy:([ -2, 2 ] x [ -1, 1 ])
vec2 uv_2_xy(vec2 uv) {
return vec2(uv.x * 4. - 2., uv.y * 2. - 1.);
}
// projects
// xy:([ -2, 2 ] x [ -1, 1 ])
// to
// longlat: ([ -pi, pi ] x [-pi/2,pi/2])
vec2 xy_2_longlat(vec2 xy) {
float pi = 3.1415926535897932384626433832795;
return xy * pi / 2.;
}
vec3 longlat_2_xyz(vec2 longlat) {
return vec3(cos(longlat.x) * cos(longlat.y), sin(longlat.x) * cos(longlat.y), sin(longlat.y));
}
vec3 uv_2_xyz(vec2 uv) {
return longlat_2_xyz(xy_2_longlat(uv_2_xy(uv)));
}
vec3 roty(vec3 xyz, float alpha) {
return vec3(cos(alpha) * xyz.x + sin(alpha) * xyz.z, xyz.y, cos(alpha) * xyz.z - sin(alpha) * xyz.x);
}
varying vec2 vUv;
uniform sampler2D image;
uniform float time;
void main() {
vec3 xyz = uv_2_xyz(vUv);
xyz = roty(xyz, time);
vec2 uv = sample_cube_map(xyz);
vec4 texturePixel = texture2D(image, vec2(clamp(uv.x, 0., 1.), clamp(uv.y, 0., 1.)));
gl_FragColor = texturePixel;
}
The math behind your shader looks sound. However, you need to consider how texture sampling behaves when dealing with sub-pixels. Take a look at your source texture:
When you cross the boundary between Top to Right, your sampler will try to squeeze all the texture between magenta and teal into a single pixel. Since there's lots of white in that area, that squeezed pixel will be mostly white. Notice this doesn't happen between Top to Front, because there's no white area between those two faces. (Read: mipmapping to see how textures behave when scaled down)
Solutions:
You might be able to sample the nearest full pixel, instead of trying to blend in between them, by turning off mipmapping. To do so, you can change the texture's minification filter to linear filtering.
const imgTexture = new THREE.TextureLoader().load('https://i.imgur.com/tBzfYG5.png');
imgTexture.minFilter = THREE.LinearFilter;
The only downside is that you might get some hard edges along the boundaries.
If your project allows it, you could simply break up your texture into six images, then use the cubemap method offered by Three.js

Processing: How do i create an object every "x" time

What I want to do is to create a new planet in my system for example every 10 seconds and that it starts to move and also prints a "hello" . At the end I want that the 8 planets (ellipses) will be moving together.
I try to use delay(); but I failed .
Can someone help me please?
Planet [] planetCollection = new Planet [8];
float [] wid2 = {100,200,300,400,500,600,700,800};
float [] hig2 = {50,75,100,125,150,175,200,225};
int [] colorR = {100,800,300,400,500,600,700,800};
int [] colorG = {50,225,100,125,150,175,200,225};
int [] colorB = {50,225,100,125,150,175,200,225};
int [] size = {10,12,14,16,18,20,22,24};
int lastTime =0;
int contador =0;
void setup (){
size (1600,1600);
smooth();
//INITIALIZE
for (int i=0 ; i<planetCollection.length; i++){
planetCollection [i] = new Planet(wid2[i], hig2[i], colorR[i],
colorG[i], colorB[i], size[i]);
}
}
void draw (){
background (0);
//CALL FUNCIONALITY
for (int i=0 ; i<planetCollection.length; i++){
planetCollection [i].run();
}
}
class Planet {
//GLOBAL VARIABLES
float val;
float x = 0;
float y = 0;
float wid2;
float hig2;
float speed;
int colorR;
int colorG;
int colorB;
int size;
int centerx = width/2;
int centery = height/4;
//CONTRUCTOR
Planet(float _w, float _h,int _colorR,int _colorG,int _colorB, int _size){
wid2=_w;
hig2=_h;
colorR= _colorR;
colorG= _colorG;
colorB= _colorB;
size = _size;
speed=10/(2*PI * sqrt ((pow(wid2,2)+pow (hig2,2)/2))); ;
}
//FUNCTIONS
void run (){
move();
display();
}
void move (){
x= sin (val);
y= cos(val);
x *=wid2;
y *=hig2;
//SUN/CENTER
noStroke();
fill (255,238,41);
ellipse (centerx,centery,40,40);
if (dist (mouseX,mouseY,centerx,centery)<20){
if(mousePressed){
speed =0;
}
}
//
x+= centerx;
y+= centery;
val += speed;
}
void display (){
//PLanets
fill(colorR, colorG, colorB);
ellipse(x, y, size, size);
///Orbits
noFill();
stroke(255);
ellipse(centerx, centery, wid2*2, hig2*2);
println ("posicionx "+x);
println ("posiciony "+y);
println ("width "+wid2);
println ("high "+hig2);
println ("val "+val);
println ("speed "+speed);
}
}
You can use the modulo % operator along with the frameCount variable inside the draw() function to do something every X frames.
Here is an example program that draws little circles most frames, but draws a big circle every 60 frames:
void setup() {
size(500, 500);
background(0);
}
void draw() {
ellipse(mouseX, mouseY, 10, 10);
if (frameCount % 60 == 0) {
ellipse(mouseX, mouseY, 50, 50);
}
}
You can build a timer for counting seconds using a helper variable and the in-built variable frameRate. (Note that this solution ensures that you truly count seconds independent on your machine's current workload.)
frameRate tells you how many cycles Processing is currently performing per second (one cycle = one execution of draw, also called one frame). This is usually 60 (frames per second) but can also be lower depending on other processes on your machine (e.g. when running video processing, 3D games etc. the frame rate goes down).
Here's a snippet to see what your current frameRate is:
void draw() {
println(frameRate);
}
And here's the timer using a helper variable counter which is reset every second. You should see a new dot appear on the console output every second.
int counter = 0;
void draw() {
if (counter > frameRate) {
print(".");
counter = 0;
} else {
counter++;
}
}
To make it count every 10 seconds you can just change the if condition to "counter > 10 * frameRate".

Java 2D Polygon outside another

I'd like to know if there is a java way to, given a polygon, draw another one at a given distance and with the same center.
I tried AffineTransform but don't really know how it Works.
Thank you.
You need to translate your polygon by half its centroid width and height. I have included the code that comes from http://paulbourke.net/geometry/polygonmesh/PolygonUtilities.java to calculate the centroid of a polygon.
public void drawPolygon(){
Graphics2D g2 = bufferedImage.createGraphics();
Polygon poly=new Polygon();
poly.addPoint(100, 100);
poly.addPoint(200, 100);
poly.addPoint(200, 200);
poly.addPoint(150, 250);
poly.addPoint(100, 200);
poly.addPoint(100, 100);
g2.setColor(Color.blue);
g2.fillPolygon(poly);
g2.setColor(Color.red);
Point2D.Double []pts=new Point2D.Double[poly.npoints];
for (int i=0;i<poly.npoints;i++){
pts[i]=new Point2D.Double(poly.xpoints[i],poly.ypoints[i]);
}
Point2D centroid=centerOfMass(pts);
g2.translate(-centroid.getX(), -centroid.getY());
g2.scale(2, 2);
g2.drawPolygon(poly);
}
public static double area(Point2D[] polyPoints) {
int i, j, n = polyPoints.length;
double area = 0;
for (i = 0; i < n; i++) {
j = (i + 1) % n;
area += polyPoints[i].getX() * polyPoints[j].getY();
area -= polyPoints[j].getX() * polyPoints[i].getY();
}
area /= 2.0;
return (area);
}
/**
* Function to calculate the center of mass for a given polygon, according
* to the algorithm defined at
* http://local.wasp.uwa.edu.au/~pbourke/geometry/polyarea/
*
* #param polyPoints
* array of points in the polygon
* #return point that is the center of mass
*/
public static Point2D centerOfMass(Point2D[] polyPoints) {
double cx = 0, cy = 0;
double area = area(polyPoints);
// could change this to Point2D.Float if you want to use less memory
Point2D res = new Point2D.Double();
int i, j, n = polyPoints.length;
double factor = 0;
for (i = 0; i < n; i++) {
j = (i + 1) % n;
factor = (polyPoints[i].getX() * polyPoints[j].getY()
- polyPoints[j].getX() * polyPoints[i].getY());
cx += (polyPoints[i].getX() + polyPoints[j].getX()) * factor;
cy += (polyPoints[i].getY() + polyPoints[j].getY()) * factor;
}
area *= 6.0f;
factor = 1 / area;
cx *= factor;
cy *= factor;
res.setLocation(cx, cy);
return res;
}
Another way of doing this, common in the GIS world, is to buffer a polygon. There is a library called Java Topology Suite that will provide this functionality, although it might be harder to figure out what the scale factor is.
There are some very interesting discussions about polygon growing in this post: An algorithm for inflating/deflating (offsetting, buffering) polygons

How to draw dashed lines with using rectangles in AS3?

I'm using starling framework for my game project and it hasn't got any draw dashed line method. Because of this they suggest me to draw dashed lines with using small rectangles which is called quads.
My math is not enough for it, could you give a sample method for rectangles with dashed lines occurring.
Thanks..
This class by Andy Woodruff draws dashed lines
/*
DashedLine class
by Andy Woodruff (http://cartogrammar.com/blog || awoodruff#gmail.com)
May 2008
Still in progress; I'll get a more perfected version eventually. For now take it as is.
This is a Sprite with the capability to do basic lineTo drawing with dashed lines.
Example:
var dashy:DashedLine = new DashedLine(2,0x333333,new Array(3,3,10,3,5,8,7,13));
dashy.moveTo(120,120);
dashy.beginFill(0xcccccc);
dashy.lineTo(220,120);
dashy.lineTo(220,220);
dashy.lineTo(120,220);
dashy.lineTo(120,120);
dashy.endFill();
*/
package com.cartogrammar.drawing {
import flash.display.Shape;
import flash.display.Sprite;
import flash.geom.Point;
import flash.display.CapsStyle;
public class DashedLine extends Sprite {
var lengthsArray:Array = new Array(); // array of dash and gap lengths (dash,gap,dash,gap....)
var lineColor:uint; // line color
var lineWeight:Number; // line weight
var lineAlpha:Number = 1; // line alpha
var curX:Number = 0; // stores current x as it changes with lineTo and moveTo calls
var curY:Number = 0; // same as above, but for y
var remainingDist:Number = 0; // stores distance between the end of the last full dash or gap and the end coordinates specified in lineTo
var curIndex = null; // current index in the length array, so we know which dash or gap to draw
var arraySum:Number = 0; // total length of the dashes and gaps... not currently being used for anything, but maybe useful?
var startIndex:int = 0; // array index (the particular dash or gap) to start with in a lineTo--based on the last dash or gap drawn in the previous lineTo (along with remainingDist, this is so our line can properly continue around corners!)
var fill:Shape = new Shape(); // shappe in the background to be used for fill (if any)
var stroke:Shape = new Shape(); // shape in the foreground to be used for the dashed line
public function DashedLine(weight:Number = 0, color:Number = 0, lengthsArray:Array = null){
if (lengthsArray != null){ // if lengths array was specified, use it
this.lengthsArray = lengthsArray;
} else { // if unspecified, use a default 5-5 line
this.lengthsArray = [5,5];
}
if (this.lengthsArray.length % 2 != 0){ // if array has more dashes than gaps (i.e. an odd number of values), add a 5 gap to the end
lengthsArray.push(5);
}
// sum the dash and gap lengths
for (var i:int in lengthsArray){
arraySum += lengthsArray[i];
}
// set line weight and color properties from constructor arguments
lineWeight = weight;
lineColor = color;
// set the lineStyle according to specified properties - beyond weight and color, we use the defaults EXCEPT no line caps, as they interfere with the desired gaps
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
// add fill and stroke shapes
addChild(fill);
addChild(stroke);
}
// basic moveTo method
public function moveTo(x:Number,y:Number):void{
stroke.graphics.moveTo(x,y); // move to specified x and y
fill.graphics.moveTo(x,y);
// keep track of x and y
curX = x;
curY = y;
// reset remainingDist and startIndex - if we are moving away from last line segment, the next one will start at the beginning of the dash-gap sequence
remainingDist = 0;
startIndex = 0;
}
// lineTo method
public function lineTo(x:Number,y:Number):void{
var slope:Number = (y - curY)/(x - curX); // get slope of segment to be drawn
// record beginning x and y
var startX:Number = curX;
var startY:Number = curY;
// positive or negative direction for each x and y?
var xDir:int = (x < startX) ? -1 : 1;
var yDir:int = (y < startY) ? -1 : 1;
// keep drawing dashes and gaps as long as either the current x or y is not beyond the destination x or y
outerLoop : while (Math.abs(startX-curX) < Math.abs(startX-x) || Math.abs(startY-curY) < Math.abs(startY-y)){
// loop through the array to draw the appropriate dash or gap, beginning with startIndex (either 0 or determined by the end of the last lineTo)
for (var i:int = startIndex; i < lengthsArray.length; i++){
var dist:Number = (remainingDist == 0) ? lengthsArray[i] : remainingDist; // distance to draw is either the dash/gap length from the array or remainingDist left over from the last lineTo if there is any
// get increments of x and y based on distance, slope, and direction - see getCoords()
var xInc:Number = getCoords(dist,slope).x * xDir;
var yInc:Number = getCoords(dist,slope).y * yDir;
// if the length of the dash or gap will not go beyond the destination x or y of the lineTo, draw the dash or gap
if (Math.abs(startX-curX) + Math.abs(xInc) < Math.abs(startX-x) || Math.abs(startY-curY) + Math.abs(yInc) < Math.abs(startY-y)){
if (i % 2 == 0){ // if even index in the array, it is a dash, hence lineTo
stroke.graphics.lineTo(curX + xInc,curY + yInc);
} else { // if odd, it's a gap, so moveTo
stroke.graphics.moveTo(curX + xInc,curY + yInc);
}
// keep track of the new x and y
curX += xInc;
curY += yInc;
curIndex = i; // store the current dash or gap (array index)
// reset startIndex and remainingDist, as these will only be non-zero for the first loop (through the array) of the lineTo
startIndex = 0;
remainingDist = 0;
} else { // if the dash or gap can't fit, break out of the loop
remainingDist = getDistance(curX,curY,x,y); // get the distance between the end of the last dash or gap and the destination x/y
curIndex = i; // store the current index
break outerLoop; // break out of the while loop
}
}
}
startIndex = curIndex; // for next time, the start index is the last index used in the loop
if (remainingDist != 0){ // if there is a remaining distance, line or move from current x/y to the destination x/y
if (curIndex % 2 == 0){ // even = dash
stroke.graphics.lineTo(x,y);
} else { // odd = gap
stroke.graphics.moveTo(x,y);
}
remainingDist = lengthsArray[curIndex] - remainingDist; // remaining distance (which will be used at the beginning of the next lineTo) is now however much is left in the current dash or gap after that final lineTo/moveTo above
} else { // if there is no remaining distance (i.e. the final dash or gap fits perfectly), we're done with the current dash or gap, so increment the start index for next time
if (startIndex == lengthsArray.length - 1){ // go to the beginning of the array if we're at the end
startIndex = 0;
} else {
startIndex++;
}
}
// at last, the current x and y are the destination x and y
curX = x;
curY = y;
fill.graphics.lineTo(x,y); // simple lineTo (invisible line) on the fill shape so that the fill (if one was started via beginFill below) follows along with the dashed line
}
// returns a point with the vertical and horizontal components of a diagonal given the distance and slope
private function getCoords(distance:Number,slope:Number):Point {
var angle:Number = Math.atan(slope); // get the angle from the slope
var vertical:Number = Math.abs(Math.sin(angle)*distance); // vertical from sine of angle and length of hypotenuse - using absolute value here and applying negative as needed in lineTo, because this number doesn't always turn out to be negative or positive exactly when I want it to (haven't thought through the math enough yet to figure out why)
var horizontal:Number = Math.abs(Math.cos(angle)*distance); // horizontal from cosine
return new Point(horizontal,vertical); // return the point
}
// basic Euclidean distance
private function getDistance(startX:Number,startY:Number,endX:Number,endY:Number):Number{
var distance:Number = Math.sqrt(Math.pow((endX-startX),2) + Math.pow((endY-startY),2));
return distance;
}
// clear everything and reset the lineStyle
public function clear():void{
stroke.graphics.clear();
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
fill.graphics.clear();
moveTo(0,0);
}
// set lineStyle with specified weight, color, and alpha
public function lineStyle(w:Number=0,c:Number=0,a:Number=1):void{
lineWeight = w;
lineColor = c;
lineAlpha = a;
stroke.graphics.lineStyle(lineWeight,lineColor,lineAlpha,false,"none",CapsStyle.NONE);
}
// basic beginFill
public function beginFill(c:uint,a:Number=1):void{
fill.graphics.beginFill(c,a);
}
// basic endFill
public function endFill():void{
fill.graphics.endFill();
}
}
}
Thanks For Your Replies, I Wrote My Own Class Which Draws Rectangles To Create A Dashed Lines.
Thanks..

Canvas 3d graph

I have done a 3d graph to plot points in it. I have drawn x,y and z axis. Also you can rotate the axis by pressing arrow keys.Now my problem marking the axis as x,y and z .I tried to add text in canvas by using filltext.But the text gets added to canvas but its not rotating.It is because i have not set rotation effect for it i guess.So how can i set the rotation to text so that when axis rotates the text also rotates together.Below is my code.
<!DOCTYPE html>
<html>
<head>
<title>Canvas Surface Rotation</title>
<style>
body {
text-align: center;
}
canvas {
border: 1px solid black;
}
</style>
<script>
var constants = {
canvasWidth: 600, // In pixels.
canvasHeight: 600, // In pixels.
leftArrow: 37,
upArrow: 38,
rightArrow: 39,
downArrow: 40,
xMin: -10, // These four max/min values define a square on the xy-plane that the surface will be plotted over.
xMax: 10,
yMin: -10,
yMax: 10,
xDelta: 0.01, // Make smaller for more surface points.
yDelta: 0.01, // Make smaller for more surface points.
colorMap: ["#060"], // There are eleven possible "vertical" color values for the surface, based on the last row of http://www.cs.siena.edu/~lederman/truck/AdvanceDesignTrucks/html_color_chart.gif
pointWidth: 2, // The size of a rendered surface point (i.e., rectangle width and height) in pixels.
dTheta: 0.05, // The angle delta, in radians, by which to rotate the surface per key press.
surfaceScale: 24 // An empirically derived constant that makes the surface a good size for the given canvas size.
};
// These are constants too but I've removed them from the above constants literal to ease typing and improve clarity.
var X = 0;
var Y = 1;
var Z = 2;
// -----------------------------------------------------------------------------------------------------
var controlKeyPressed = false; // Shared between processKeyDown() and processKeyUp().
var surface = new Surface(); // A set of points (in vector format) representing the surface.
// -----------------------------------------------------------------------------------------------------
function point(x, y, z)
/*
Given a (x, y, z) surface point, returns the 3 x 1 vector form of the point.
*/
{
return [x, y, z]; // Return a 3 x 1 vector representing a traditional (x, y, z) surface point. This vector form eases matrix multiplication.
}
// -----------------------------------------------------------------------------------------------------
function Surface()
/*
A surface is a list of (x, y, z) points, in 3 x 1 vector format. This is a constructor function.
*/
{
this.points = []; // An array of surface points in vector format. That is, each element of this array is a 3 x 1 array, as in [ [x1, y1, z1], [x2, y2, z2], [x3, y3, z3], ... ]
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.equation = function(x, y)
/*
Given the point (x, y), returns the associated z-coordinate based on the provided surface equation, of the form z = f(x, y).
*/
{
var d = Math.sqrt(x*x + y*y); // The distance d of the xy-point from the z-axis.
return 4*(Math.sin(d) / d); // Return the z-coordinate for the point (x, y, z).
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.generate = function()
/*
Creates a list of (x, y, z) points (in 3 x 1 vector format) representing the surface.
*/
{
var i = 0;
for (var x = constants.xMin; x <= constants.xMax; x += constants.xDelta)
{
for (var y = constants.yMin; y <= constants.yMax; y += constants.yDelta)
{
this.points[i] = point(x, y, this.equation(x, y)); // Store a surface point (in vector format) into the list of surface points.
++i;
}
}
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.color = function()
/*
The color of a surface point is a function of its z-coordinate height.
*/
{
var z; // The z-coordinate for a given surface point (x, y, z).
this.zMin = this.zMax = this.points[0][Z]; // A starting value. Note that zMin and zMax are custom properties that could possibly be useful if this code is extended later.
for (var i = 0; i < this.points.length; i++)
{
z = this.points[i][Z];
if (z < this.zMin) { this.zMin = z; }
if (z > this.zMax) { this.zMax = z; }
}
var zDelta = Math.abs(this.zMax - this.zMin) / constants.colorMap.length;
for (var i = 0; i < this.points.length; i++)
{
this.points[i].color = constants.colorMap[ Math.floor( (this.points[i][Z]-this.zMin)/zDelta ) ];
}
/* Note that the prior FOR loop is functionally equivalent to the follow (much less elegant) loop:
for (var i = 0; i < this.points.length; i++)
{
if (this.points[i][Z] <= this.zMin + zDelta) {this.points[i].color = "#060";}
else if (this.points[i][Z] <= this.zMin + 2*zDelta) {this.points[i].color = "#090";}
else if (this.points[i][Z] <= this.zMin + 3*zDelta) {this.points[i].color = "#0C0";}
else if (this.points[i][Z] <= this.zMin + 4*zDelta) {this.points[i].color = "#0F0";}
else if (this.points[i][Z] <= this.zMin + 5*zDelta) {this.points[i].color = "#9F0";}
else if (this.points[i][Z] <= this.zMin + 6*zDelta) {this.points[i].color = "#9C0";}
else if (this.points[i][Z] <= this.zMin + 7*zDelta) {this.points[i].color = "#990";}
else if (this.points[i][Z] <= this.zMin + 8*zDelta) {this.points[i].color = "#960";}
else if (this.points[i][Z] <= this.zMin + 9*zDelta) {this.points[i].color = "#930";}
else if (this.points[i][Z] <= this.zMin + 10*zDelta) {this.points[i].color = "#900";}
else {this.points[i].color = "#C00";}
}
*/
}
// -----------------------------------------------------------------------------------------------------
function appendCanvasElement()
/*
Creates and then appends the "myCanvas" canvas element to the DOM.
*/
{
var canvasElement = document.createElement('canvas');
canvasElement.width = constants.canvasWidth;
canvasElement.height = constants.canvasHeight;
canvasElement.id = "myCanvas";
canvasElement.getContext('2d').translate(constants.canvasWidth/2, constants.canvasHeight/2); // Translate the surface's origin to the center of the canvas.
document.body.appendChild(canvasElement); // Make the canvas element a child of the body element.
}
//------------------------------------------------------------------------------------------------------
Surface.prototype.sortByZIndex = function(A, B)
{
return A[Z] - B[Z]; // Determines if point A is behind, in front of, or at the same level as point B (with respect to the z-axis).
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.draw = function()
{
var myCanvas = document.getElementById("myCanvas"); // Required for Firefox.
var ctx = myCanvas.getContext("2d");
this.points = surface.points.sort(surface.sortByZIndex); // Sort the set of points based on relative z-axis position. If the points are visibly small, you can sort of get away with removing this step.
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.font="20px Arial";
ctx.fillText("X",250,0);
for (var i = 0; i < this.points.length; i++)
{
ctx.fillStyle = this.points[i].color;
ctx.fillRect(this.points[i][X] * constants.surfaceScale, this.points[i][Y] * constants.surfaceScale, constants.pointWidth, constants.pointWidth);
}
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.multi = function(R)
/*
Assumes that R is a 3 x 3 matrix and that this.points (i.e., P) is a 3 x n matrix. This method performs P = R * P.
*/
{
var Px = 0, Py = 0, Pz = 0; // Variables to hold temporary results.
var P = this.points; // P is a pointer to the set of surface points (i.e., the set of 3 x 1 vectors).
var sum; // The sum for each row/column matrix product.
for (var V = 0; V < P.length; V++) // For all 3 x 1 vectors in the point list.
{
Px = P[V][X], Py = P[V][Y], Pz = P[V][Z];
for (var Rrow = 0; Rrow < 3; Rrow++) // For each row in the R matrix.
{
sum = (R[Rrow][X] * Px) + (R[Rrow][Y] * Py) + (R[Rrow][Z] * Pz);
P[V][Rrow] = sum;
}
}
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.erase = function()
{
var myCanvas = document.getElementById("myCanvas"); // Required for Firefox.
var ctx = myCanvas.getContext("2d");
ctx.clearRect(-constants.canvasWidth/2, -constants.canvasHeight/2, myCanvas.width, myCanvas.height);
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.xRotate = function(sign)
/*
Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".
*/
{
var Rx = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.
Rx[0][0] = 1;
Rx[0][1] = 0; // Redundant but helps with clarity.
Rx[0][2] = 0;
Rx[1][0] = 0;
Rx[1][1] = Math.cos( sign*constants.dTheta );
Rx[1][2] = -Math.sin( sign*constants.dTheta );
Rx[2][0] = 0;
Rx[2][1] = Math.sin( sign*constants.dTheta );
Rx[2][2] = Math.cos( sign*constants.dTheta );
this.multi(Rx); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P
this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.
this.draw();
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.yRotate = function(sign)
/*
Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".
*/
{
var Ry = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.
Ry[0][0] = Math.cos( sign*constants.dTheta );
Ry[0][1] = 0; // Redundant but helps with clarity.
Ry[0][2] = Math.sin( sign*constants.dTheta );
Ry[1][0] = 0;
Ry[1][1] = 1;
Ry[1][2] = 0;
Ry[2][0] = -Math.sin( sign*constants.dTheta );
Ry[2][1] = 0;
Ry[2][2] = Math.cos( sign*constants.dTheta );
this.multi(Ry); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P
this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.
this.draw();
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.zRotate = function(sign)
/*
Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".
*/
{
var Rz = [ [0, 0, 0],
[0, 0, 0],
[0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.
Rz[0][0] = Math.cos( sign*constants.dTheta );
Rz[0][1] = -Math.sin( sign*constants.dTheta );
Rz[0][2] = 0; // Redundant but helps with clarity.
Rz[1][0] = Math.sin( sign*constants.dTheta );
Rz[1][1] = Math.cos( sign*constants.dTheta );
Rz[1][2] = 0;
Rz[2][0] = 0
Rz[2][1] = 0;
Rz[2][2] = 1;
this.multi(Rz); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P
this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.
this.draw();
}
// -----------------------------------------------------------------------------------------------------
function processKeyDown(evt)
{
if (evt.ctrlKey)
{
switch (evt.keyCode)
{
case constants.upArrow:
// No operation other than preventing the default behavior of the arrow key.
evt.preventDefault(); // This prevents the default behavior of the arrow keys, which is to scroll the browser window when scroll bars are present. The user can still scroll the window with the mouse.
break;
case constants.downArrow:
// No operation other than preventing the default behavior of the arrow key.
evt.preventDefault();
break;
case constants.leftArrow:
// console.log("ctrl+leftArrow");
surface.zRotate(-1); // The sign determines if the surface rotates "clockwise" or "counterclockwise".
evt.preventDefault();
break;
case constants.rightArrow:
// console.log("ctrl+rightArrow");
surface.zRotate(1);
evt.preventDefault();
break;
}
return; // When the control key is pressed, only the left and right arrows have meaning, no need to process any other key strokes (i.e., bail now).
}
// Assert: The control key is not pressed.
switch (evt.keyCode)
{
case constants.upArrow:
// console.log("upArrow");
surface.xRotate(1);
evt.preventDefault();
break;
case constants.downArrow:
// console.log("downArrow");
surface.xRotate(-1);
evt.preventDefault();
break;
case constants.leftArrow:
// console.log("leftArrow");
surface.yRotate(-1);
evt.preventDefault();
break;
case constants.rightArrow:
// console.log("rightArrow");
surface.yRotate(1);
evt.preventDefault();
break;
}
}
// -----------------------------------------------------------------------------------------------------
Surface.prototype.plot = function(x, y, z)
/*
add the point (x, y, z) (in 3 x 1 vector format) to the surface.
*/
{
this.points.push(point(x, y, z)); // Store a surface point
var x=0;
for (var x = constants.xMin; x <= constants.xMax; x += constants.xDelta)
{
this.points.push(point(x, 0, 0));
}
/*for (var x = constants.xMax+1; x <= constants.xMax+2; x += constants.xDelta)
{
this.points.push(point(11, 0, 0))
}*/
for (var x = constants.xMin; x <= constants.xMax; x += constants.yDelta)
{
this.points.push(point(0, x, 0));
}
for (var x = constants.xMin; x <= constants.xMax; x += constants.yDelta)
{
this.points.push(point(0,0,x));
}
}
function onloadInit()
{
appendCanvasElement(); // Create and append the canvas element to the DOM.
surface.draw(); // Draw the surface on the canvas.
document.addEventListener('keydown', processKeyDown, false); // Used to detect if the control key has been pressed.
}
// -----------------------------------------------------------------------------------------------------
//surface.generate(); // Creates the set of points reprsenting the surface. Must be called before color().
surface.plot(1,1,1);
surface.color(); // Based on the min and max z-coordinate values, chooses colors for each point based on the point's z-ccordinate value (i.e., height).
window.addEventListener('load', onloadInit, false); // Perform processing that must occur after the page has fully loaded.
</script>
</head>
<body>
<p>The z-axis extends out from the center of the screen.<br>
To rotate about the x-axis, press the up/down arrow keys.
To rotate about the y-axis, press the left/right arrow keys.
To rotate about the z-axis, press the ctrl+left/ctrl+down arrow keys.
Note that pressing an arrow key down continuously will not rotate the surface. The surface is rotated once per key press.</p>
<!-- The canvas element is append to the DOM here. -->
</body>
</html>
Text is drawn on a rectangular plane. Let the co-ordinates of top left be (xtl,ytl,ztl)
to right corner be (xtr,ytr,ztr) and bottom left be (xbl,ybl,zbl) then any transformations have to be applied to these coordinates and then the coordoinates for the 2D projection onto the canvas have to be calculated. This will produce a parallelogram into which the text can be drawn but would also need to be transformed.
The simplest would be to calculate the top left corner transformation and draw standard text at that point, perhaps reducing text size depending on z.