wwwiki
::
3D
+
| 1 |
help
|
pages
|
skin
|
login
|
code
www!k! v.20121031
! CE SITE NECESSITE JAVASCRIPT !
wwwiki :: éditeur
_h1 λ3D {span {@ font-size:0.3em;} (am 07/12/2012)} {div {@ position:absolute; top:10px; left:785px;} {show {@ src: http://amartyfree.free.fr/alain.marty/data/coque_double.jpg; height:100px; width:500px;} POVRAY's version of the ship code with a pontoon bridge and some more equipments. Looks like a dutch sabot, isnt'it ? Much more rounder then the Steve Jobs' Venus Yacht built in Nederlands ...}} {center {canvas {@ id:mycanvas; width:600px; height:600px; background:#ffe; border:1px dashed black; box-shadow:4px 4px 18px black; margin:30px 10px 10px; } } {br} Yes, you may click on "draw" to see something and then play with view parameters. Enjoy ! {br} {b Note} : Firefox doesn't work with the 5 sliders added under Rot/X, Rot/Y, Rot/Z, focale and scale. {br} You may use Chrome or Safari to play with them ! } _h6 1) a window into 3D _p With the JS code - in less then 600 lines and no external library - embedded in this wiki page, it is possible to define and display some curves and wireframed surfaces and volumes. The code displaying the main XYZ-axis, the cube scene and some figures is written in a unique function {b display()} which looks like this : {pre °° function display ( can ) { // the ID of the canvas var ctx = W3D.init2D( can ); W3D.init3D( -60, 0, 60, 500, 1 ); // the camera's parameters of the wiki3D world ... my_cubic( 180, 4 ); // see definition further ... } // where my_cubic() is function defined like this : function my_cubic ( d, r0 ) { // r0 is the level of recursion // a cubic is built on four poles (control points) var poles = [ {x:-d,y:-d,z:-d,w:1}, // points are defined in 4D space {x: d,y:-d,z:-d,w:1}, // see further ... {x: d,y:-d,z: d,w:1}, {x: d,y: d,z: d,w:1} ]; W3D.draw( poles ); // drawing the polygon control var cubic = PF.line_build( poles, r0 ); // blossoming the cubic curve W3D.draw( cubic ); // drawing the curve } °°} _p {b Note :} _ul {b 1)} points are defined as 4 terms hash arrays : {b °°{x:x,y:y,z:z,w:w}°°}, more to see in Section {b Working in 4D}. _ul {b 2)} curves, surfaces and volumes are defined as mono, bi and tri-dimensionnal arrays of 4D points : {b Ln = [p0, p1, ..., pn]}, {b Sn = [L0, L1, ..., Ln]}, {b Vn = [S0, S1, ..., Sn]} ; see also Section {b pForms}. _ul {b 3)} curves, surfaces and volumes are displayed as wireframes (no hidden faces rendering). _ul {b 4)} the 3D engine does clipping of the objects in the scene between a near and a far plane on OZ axis. Partial drawings may occur when the objects are too close to the near plane ; play with the focal and scale viewing parameters to display complete objects. _p Some figure tests are defined at the end of the code and can be displayed in the canvas. Feel free to copy the page's code (GPL copyleft) and play with it in the [[sandbox]] page opened to edition. Tell me what you think about in the [[forum]] page or mail me something. See you soon. _p Have a look to the 3D code below to analyze the API and to go further in the comprehension of the 3D engine. More explanations will be given soon ... _h6 2) Pascalian Forms _p The code of the PF object is coming from this work [ [[pForms|http://marty.alain.free.fr/pFwiki/?view=abstract]] & [[straightaway|http://marty.alain.free.fr/recherche/pformes/straightaway.pdf ]] ] initially designed for POVRAY then for Sketchup in the Ruby language. Simple Curves, Surfaces and Volumes can be enhanced as "Pascalian Forms" built using a recursive algorithm computing the middle of two points, without any algebra. It's an application of the {i Paul de Casteljau} algorithm, to be compared with the {i Pierre Bézier} analytical approach. More infos [[here|http://marty.alain.free.fr/01design.8/?view=poles]]. For instance, a quadratic and a cubic curves are defined by 3 and 4 control points [p0, p1, p2] and [p0, p1, p2, p3] : _p In the Bézier approach a point of these curves is defined this way : {pre p(t) = (1-t){sup 2}.p0 + 2.(1-t)t.p1 + t{sup 2}.p2 p(t) = (1-t){sup 3}.p0 + 3.(1-t){sup 2}t.p1 + 3.(1-t)t{sup 2}.p2 + t{sup 3}.p3 for t in [0,1]. } _p In the de Casteljau approach a point of these curves is built using two and three linear interpolations on t values in the range [0,1]: {center {img {@ src:http://marty.alain.free.fr/01design.8/data/poles/pformes/Bezier_2.gif;}} {img {@ src:http://marty.alain.free.fr/01design.8/data/poles/pformes/Bezier_3.gif;}} } _p In the pForm approach, interpolations are reduced to a recursive search of the points with t=1/2 ; the final pCurve will link the middle points. The actual "calculus" is reduced to a {b divide by 2 process}, something that could be hardcoded in a simple and fast {b binary shiftRight} in the processor unit. _p {b Notes :} _ul {b 1)} The Bezier expressions of the quadratic and cubic curves are built on the Pascal's triangle coefficients : [1,2,1] and [1,3,3,1] ? The "Pascalian Forms" extend these expressions to multilinear combinations of points and pForms in 4D space, generating pCurves, pSurfaces and pVolumes. _ul {b 2)} today, the PF library written in this page is limited to basic (and fundamental) functions : {b build()}, {b stretch()}, {b get_subform()}, {b get_point()}, {b diag()}, {b embed()} and a few others. _ul30 1) The {b build()} function takes a form (curve, surface, volume) and returns a pForm containing the blossomed array to be displayed by the function {b W3D.draw()}. _ul30 2) The {b stretch()} function returns a pForm stretched between two points of the embedding pForm. _ul30 3) The {b get_subformt()} function returns a sub pForm, a point from a pCurve, a curve from a pSurface, a pSurface from a pVolume. _ul30 4) The {b get_point()} function returns the global 3D coords of a point defined in a pForm, _ul30 5) The {b diag()} function returns a diagonal of a pForm ; the diagonal of a pCurve is its mid point, the diagonal of a pSurface is a pCurve, the diagonal of a pVolume is a pSurface. _ul30 6) The {b embed()} function combines the stretch() and the diag() functions to define pCurves immersed in a pSurface or a pVolume. And further to do any geometry in these pForms, defining parabolas, cubics, circles, etc. _ul {b 3)} pCurves don't interpolate control points, excepted the first and last ones. It's possible to link pCurves to build {b splines}, interpolating or not nodal points, ensuring the continuity of tangents at nodal points. Idem (soon) for pSurfaces and pVolumes. _ul {b 4)} basically, a pForm is a "fractal blossoming" of whatever be a form defined by a one, two or three dimensionnal array, a square can be blossomed to produce a rounded closed curve, a cube becomes a rounded closed pSurface, or a full pVolume. _ul {b 5)} More functionalities can be seen in [ [[pForms|http://marty.alain.free.fr/pFwiki/?view=abstract]] ] and in linked works : degree elevation, curves' Serret-Frenet axis and surface's normals, interpolations, splines, Coons patches, revolution surfaces, cross surfaces, pipes, .... _ul {b 6)} Don't forget : freehand sketches can be helped by a basic knowledge of Bézier curves, without any computer : {center {show {@ src:http://amartyfree.free.fr/alain.marty/data/pformes/pFplane.jpg; height:230px; width:800px;}Knowing the de Casteljau algorithm for drawing a cubic curve and the 30cm (1') modulus for mastering proportions, you can sketch a little plane ; with a paper and a pencil ; no computer !} {show {@ src:http://epsilonwiki.free.fr/data/pF_exemple/farin_chinois.jpg; height:230px; width:500px;}A poly-cubic defined chinese character on a flag in the wind (from Farin).} } _p Actually, computers don't compute complex mathematical expressions, they only {u {b recursively divide by 2}} ! _h6 3) working in 4D, viewing in 3D _p Curves, surfaces and volumes are defined in 4D space and displayed in 3D space through a 2D window. _p We are working with simple or multilinear arrays of 4D points {b °°{x:x;y:y,z:z,w:w}°°}. When the 4th coordinate "w" is equal to "1", the pForms are classical Bezier curves, surfaces and volumes whose algebrical expressions are polynomial forms with pascalian coefficients : a parabola is a quadratic polynom, a cubic is a cubic polynom, etc... But a circle can't be expressed as a polynom ! For instance, a circle of radius "1" and centered in the plane OXY uses to be defined in the trigonometrical expression : {pre °°p(angle) = {x:cos(angle),y:sin(angle),z:0}°° with angle €[0,2π]} _p But it can be expressed as a rational expression, a quotient of two polygons : {pre p(t) = °°{°°x:(1-t{sup 2})/(1+t{sup 2}),y:2t/(1+t{sup 2}),z:0°°}°° with t = tan(angle/2)} _p Do you see the way ? The following quadratic expression : {pre p(t) = °°{°°x:1-t{sup 2},y:r*2t,z:1+t{sup 2}°°}°° with t €[-infini,infini] } _p corresponds to a quadratic curve, a parabola ; dividing the x and y coordinates by the z coordinate gives the rational expression of the circle. This process is known as a "{b projective transformation}". The 2D circle can be seen as the {b shadow} in 2D plane of the 3D parabola. Now, you have to imagine the same process for the projective transformation {b 4D to 3D space}. So, beyond the circle, giving to w some value different from 1 will lead to so-called "rational curves" (ratio for quotient), such as conic sections (circles, ellipsis, parabolas, hyperbolas), and a lot of other curves used in CAD, like cones, cylinders, spheres, torus, and so on. _p {b Notes :} _ul {b 1)} don't forget, {u points must be defined in the 4D space}, at least with {b w equal to 1} ! See below the code defining a circle arc (90° and 180°). _ul {b 2)} actually, pForms are {b NRBs} (Non Rational Beziers) ; the so-called {b NURBS} (Non Uniform Rational B-Splines) used in CAD tools, are nothing but concatenations of {b NRBs} in a more convenient "packaging" (See figure n°7). _ul {b 3)} In Plato's fictional dialogue, Socrates begins by describing a scenario in which what people take to be real would in fact be an illusion. {i He asks Glaucon to imagine a cave inhabited by prisoners who have been chained and held immobile since childhood: not only are their legs (but not arms) held in place, but their necks are also fixed, so they are compelled to gaze at a wall in front of them. Behind the prisoners is an enormous fire, and between the fire and the prisoners is a raised walkway, along which people walk carrying things on their heads "including figures of men and animals made of wood, stone and other materials". The prisoners cannot see the raised walkway or the people walking, but they watch the shadows cast by the men, not knowing they are shadows. There are also echoes off the wall from the noise produced from the walkway.} More to see [[here|http://en.wikipedia.org/wiki/Allegory_of_the_Cave]]. _h6 4) the JS code _p The JS code ( (about 1900 lines)) begins by the call of five main functions (acting as closed moduli) returning selected public functions : _ul 1) MAT MODULUS (170 lines) for useful math functions : matrices, vectors, ... _ul 2) W3D MODULUS (300 lines) for the wiki3d world, _ul 3) PF MODULUS (350 lines) for pForms (to be extended soon according to the Ruby code in [[pForms|http://marty.alain.free.fr/pFwiki/?view=abstract]]), _ul 4) PF_GEO MODULUS (150 lines) for special geometry (splines, pipes, coons patches, ...) _ul 5) PF-TEST MODULUS (870 lines) with a list of call examples _p The code ends with a display function drawing in the canvas. Note that the five moduli could be externalized in another wiki page and shared in the wiki, or, when stabilized, they could be hard-coded in an external JS file as the wiki's main JS code. But meanwhile, the code integrated in the wiki page can be edited and tested directly, quickly and inline. With the help of the development tools integrated in the browser (for instance in Firefox and in Chrome), the wiki gives a rather good development tool. The 1900 lines of JS code have been written directly in the wiki. °°° A COMPOSITION OF PFORMS °°° {center {show {@ src:http://marty.alain.free.fr/pFwiki/data/tears_20111211.jpg; height:300px; width:600px;}A shell made of 8 pS44 patches twisted in a deformation box, a pVolume pV223.}} {pre {@ id:code; border:1px dashed;}°° ////////////////////// // START OF MODULI : MAIN FUNCTIONS MAT, W3D, PF, PF_GEO, PF_TEST // See the end of each modulus to see the list of its public functions ////////////////////// ////////////////////// // 1) MAT MODULUS ////////////////////// var MAT = (function() { function get_dim ( f ) { var dim = 0; while (Array.isArray(f)) { f = f[0]; dim++; } return dim; } function transform ( m, p0 ) { // 4x4 matrix * 4 vector -> 4 vector var p1 = []; for (var i=0; i< 4; i++) { var temp = 0; for (var j=0; j< 4; j++) temp += m[i][j]*p0[j]; p1[i] = temp; } return p1; } function product ( m1, m2 ) { // 4x4 matrix * 4x4 matrix -> 4x4 matrix var m = []; for (var i=0; i< 4; i++) { var mm = []; for (var j=0; j< 4; j++) { var s = 0; for (var k=0; k< 4; k++) { s += m1[i][k]*m2[k][j]; } mm[j] = s; } m[i] = mm; } return m; } function ij_ji ( tab ) { // transpose ij matrix -> ji matrix var mu = tab.length, mv = tab[0].length; var t = []; for (var i=0; i< mv; i++) { var tt = []; for (var j=0; j< mu; j++) { tt[j] = tab[j][i]; } t[i] = tt; } return t; } function ijk_jki ( tab ) { // transpose ijk matrix -> jki matrix var mu = tab.length, mv = tab[0].length, mw = tab[0][0].length; var t = []; for (var i=0; i< mw; i++) { var tt = []; for (var j=0; j< mu; j++) { var ttt = []; for (var k=0; k< mv; k++) ttt[k] = tab[j][k][i]; tt[j] = ttt; } t[i] = tt; } return t; } function ijk_kij ( tab ) { // transpose ijk matrix -> kij matrix var mu = tab.length, mv = tab[0].length, mw = tab[0][0].length; var t = []; for (var i=0; i< mv; i++) { var tt = []; for (var j=0; j< mw; j++) { var ttt = []; for (var k=0; k< mu; k++) ttt[k] = tab[k][i][j]; tt[j] = ttt; } t[i] = tt; } return t; } function invert ( tab ) { var _tab = []; var n = tab.length; for (var i=0; i< n; i++) _tab[i] = tab[n-1-i]; return _tab; } function deep_copy( f ) { var ff = []; if (!Array.isArray( f[0] )) { for (var i=0; i< f.length; i++) ff[i] = f[i]; } else if (!Array.isArray( f[0][0] )) { for (var i=0; i< f.length; i++) { var fff = []; for (var j=0; j< f[0].length; j++) { fff[j] = f[i][j]; } ff[i] = fff; } } else { for (var i=0; i< f.length; i++) { var fff = []; for (var j=0; j< f[0].length; j++) { var ffff = []; for (var k=0; k< f[0][0].length; k++) { ffff[k] = f[i][j][k]; } fff[j] = ffff; } ff[i] = fff; } } return ff; } function build_vect( v1, v2 ) { // input : two 4D points, output : one 3D vect return { x:v1.x/v1.w-v2.x/v2.w, y:v1.y/v1.w-v2.y/v2.w, z:v1.z/v1.w-v2.z/v2.w }; } function cross( v1, v2 ) { // input : two 3D vect, output : one 3D vect return { x:v1.y*v2.z - v1.z*v2.y, y:v1.z*v2.x - v1.x*v2.z, z:v1.x*v2.y - v1.y*v2.x }; } function dot( v1, v2 ) { // input : two 3D vect, output : scalar product return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; } function normalize( v ) { // input : one 3D vect, output : the normalized 3D vect var norm = Math.sqrt( dot( v, v ) ); return { x:v.x/norm, y:v.y/norm, z:v.z/norm } ; // zero test to be done ! } function addvect2point ( p, v ) { // input : a 4D point, a 3D vect, output : a 4D point return {x:p.x+v.x*p.w, y:p.y+v.y*p.w, z:p.z+v.z*p.w, w:p.w}; } function subvect2point ( p, v ) { // input : a 4D point, a 3D vect, output : a 4D point return {x:p.x-v.x*p.w, y:p.y-v.y*p.w, z:p.z-v.z*p.w, w:p.w}; } function interpol ( p0, p1, t ) { // interpolation : (1-t)*p0 + t*p1 return { x: (1-t)*p0.x + t*p1.x, y: (1-t)*p0.y + t*p1.y, z: (1-t)*p0.z + t*p1.z, w: (1-t)*p0.w + t*p1.w }; } function mid ( p0, p1 ) { // compute the middle of 2 points, may be faster then interpol with t = 1/2 return { x: (p0.x + p1.x)/2, y: (p0.y + p1.y)/2, z: (p0.z + p1.z)/2, w: (p0.w + p1.w)/2 }; } function symetric( p0, p1 ) { // p = 2p1 - p0 , symetric of p0 related to p1 return {x:2*p1.x - p0.x, y:2*p1.y - p0.y, z: 2*p1.z - p0.z, w:2*p1.w - p0.w}; } // PUBLIC FUNCTIONS return { get_dim:get_dim, transform:transform, product:product, ij_ji:ij_ji, ijk_jki:ijk_jki, ijk_kij:ijk_kij, invert:invert, deep_copy:deep_copy, build_vect:build_vect, addvect2point:addvect2point, subvect2point:subvect2point, cross:cross, dot:dot, normalize:normalize, interpol:interpol, mid:mid, symetric:symetric }; })(); // end MAT ////////////////////// // 2) W3D MODULUS ////////////////////// var W3D = (function() { var CTX = null; // W3D global : 2D context var CAM = []; // W3D global : 3D camera function init2D( can ) { var canvas = $(can); CTX = canvas.getContext( '2d' ); CTX.setTransform(1, 0, 0, 1, 0, 0); // reset all transforms CTX.clearRect(0, 0, canvas.width, canvas.height); // clear the canvas CTX.beginPath(); // clear all objects CTX.translate( canvas.width/2, canvas.height/2 ); return CTX; } function init3D( a,b,c,d,s ) { CAM = [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] ]; CAM = MAT.product( CAM, perspective( d ) ); CAM = MAT.product( CAM, rotate( 'OX', a ) ); CAM = MAT.product( CAM, rotate( 'OY', b ) ); CAM = MAT.product( CAM, rotate( 'OZ', c ) ); CAM = MAT.product( CAM, scale( s, -s, -s ) ); draw_frame( 150 ); draw_cube( 150 ); draw_title( 'λ3D' ); } // TRANSFORMATIONS function rotate( axe, a ) { // R4 -> R4 var a = 2*Math.PI/360*a, cosa = Math.cos(a), sina = Math.sin(a), m = ''; if (axe == 'OX') m = [ [1,0,0,0], [0,cosa,-sina,0], [0,sina,cosa,0], [0,0,0,1] ]; if (axe == 'OY') m = [ [cosa,0,sina,0], [0,1,0,0], [-sina,0,cosa,0], [0,0,0,1] ]; if (axe == 'OZ') m = [ [cosa,-sina,0,0], [sina,cosa,0,0], [0,0,1,0], [0,0,0,1] ]; return m; } function scale( sx, sy, sz ) { // R4 -> R4 return [ [sx,0,0,0], [0,sy,0,0], [0,0,sz,0], [0,0,0,1] ]; } function translate( tx, ty, tz ) { // R4 -> R4 return [ [1,0,0,tx], [0,1,0,ty], [0,0,1,tz], [0,0,0,1] ]; } function perspective ( f ) { // R4 -> R4 ( x=X, y=Y, z=Z, w=Z/f+1 ) return [ [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,1/f,1] ]; } // APPLY TRANSFORMATIONS function f_transform ( m, f ) { // apply m on a form if (!Array.isArray(f)) // it's a point f = p_transform( m, f ); else if (!Array.isArray(f[0])) for (var i=0; i< f.length; i++) // it's a curve f[i] = p_transform( m, f[i] ); else if (!Array.isArray(f[0][0])) // it's a surface for (var i=0; i< f.length; i++) for (var j=0; j< f[i].length; j++) f[i][j] = p_transform( m, f[i][j] ); else for (var i=0; i< f.length; i++) // it's a volume for (var j=0; j< f[i].length; j++) for (var k=0; k< f[i][j].length; k++) f[i][j][k] = p_transform( m, f[i][j][k] ); } function p_transform ( m, p0 ) { // apply m on a 4D point var p0 = [ p0.x, p0.y, p0.z, p0.w ]; // format point -> array var p1 = MAT.transform( m, p0 ); // transform p1 in R4 return {x:p1[0], y:p1[1], z:p1[2], w:p1[3] }; // return a R4 point } function f_scale ( f, sx, sy, sz ) { // apply scale on a form f_transform ( scale( sx, sy, sz ), f ); } function f_rotate( f, axe, angle ) { // apply rotate on a form f_transform ( rotate( axe, angle ), f ); } function f_translate( f, tx, ty, tz ) { // apply translate on a form f_transform ( translate( tx, ty, tz ), f ); } // DRAW var NEAR = -400, FAR = 500; // clipping planes on OZ, to be tuned up ! function draw_segment ( seg ) { CTX.moveTo( seg[0].x, seg[0].y ); CTX.lineTo( seg[1].x, seg[1].y ); } function draw_triangle ( tri ) { CTX.moveTo( tri[0].x, tri[0].y ); CTX.lineTo( tri[1].x, tri[1].y ); CTX.lineTo( tri[2].x, tri[2].y ); CTX.lineTo( tri[0].x, tri[0].y ); } function draw_line ( f ) { // build and draw segments var tab = []; for (var i=0; i< f.length; i++) { var p = p_transform( CAM, f[i] ); tab[i] = {x:p.x/p.w, y:p.y/p.w, z:p.z/p.w}; } // draw only segments entirely in clipping planes, no intersection computing for (var i=0; i< tab.length-1; i++) { if ( tab[i].z > NEAR && tab[i].z < FAR && tab[i+1].z > NEAR && tab[i+1].z < FAR ) draw_segment ( [ tab[i], tab[i+1] ] ); } } function draw_surf ( f, uv ) { if (uv == undefined) uv = 'u'; if (uv == 'u' || uv == 'v') { // draw u and v lines var tab = []; if (uv == 'u') { tab = f; } if (uv == 'v') { tab = MAT.ij_ji( f ); } for (var i=0; i< tab.length; i++) draw_line( tab[i] ); } else { // build and draw triangles var tab = []; for (var i=0; i< f.length; i++) { var temp = []; for (var j=0; j< f[0].length; j++) { var p = p_transform( CAM, f[i][j] ); temp[j] = {x:p.x/p.w, y:p.y/p.w, z:p.z/p.w}; } tab[i] = temp; } // draw only triangles entirely in clipping planes, no intersection computing for (var i=0; i< tab.length-1; i++) { for (var j=0; j< tab[0].length-1; j++) { if ( tab[i][j].z > NEAR && tab[i][j].z < FAR && tab[i+1][j].z > NEAR && tab[i+1][j].z < FAR && tab[i+1][j+1].z > NEAR && tab[i+1][j+1].z < FAR ) draw_triangle ( [ tab[i][j], tab[i+1][j], tab[i+1][j+1] ] ); } } } } function draw_vol ( f, uvw ) { // draws w, v or u lines if (uvw == undefined) uvw = 'w'; var tab = []; if (uvw == 'w') { tab = f; } if (uvw == 'v') { tab = MAT.ijk_kij( f ); } if (uvw == 'u') { tab = MAT.ijk_jki( f ); } for (var i=0; i< tab.length; i++) for (var j=0; j< tab[0].length; j++) draw_line( tab[i][j] ); } function draw( f, sens ) { // sens is nothing, uv or uvw CTX.beginPath(); if ( !Array.isArray(f) ) // a point draw_point( f ); else if ( !Array.isArray(f[0]) ) // a curve draw_line( f ); else if( !Array.isArray(f[0][0]) ) // a surface draw_surf( f, sens ); else // a volume draw_vol( f, sens ); CTX.stroke(); } // SOME SPECIAL DRAWINGS function draw_point( p0, width, d ) { if (width == undefined) width = 2; if (d == undefined) d = 10; CTX.save(); CTX.beginPath(); CTX.lineWidth = width; CTX.strokeStyle = '#f00'; draw_line( [ {x:p0.x-d, y:p0.y, z:p0.z,w:1}, {x:p0.x+d, y:p0.y, z:p0.z,w:1} ] ); CTX.stroke(); CTX.beginPath(); CTX.lineWidth = width; CTX.strokeStyle = '#0f0'; draw_line( [ {x:p0.x, y:p0.y-d, z:p0.z,w:1}, {x:p0.x, y:p0.y+d, z:p0.z,w:1} ] ); CTX.stroke(); CTX.beginPath(); CTX.lineWidth = width; CTX.strokeStyle = '#00f'; draw_line( [ {x:p0.x, y:p0.y, z:p0.z-d,w:1}, {x:p0.x, y:p0.y, z:p0.z+d,w:1} ] ); CTX.stroke(); CTX.restore(); } function draw_frame( d ) { CTX.save(); CTX.beginPath(); CTX.strokeStyle = '#f00'; draw_line( [ {x:-d, y:0, z:0,w:1}, {x:d, y:0, z:0,w:1} ] ); CTX.stroke(); CTX.beginPath(); CTX.strokeStyle = '#0f0'; draw_line( [ {x:0, y:-d, z:0,w:1}, {x:0, y:d, z:0,w:1} ] ); CTX.stroke(); CTX.beginPath(); CTX.strokeStyle = '#00f'; draw_line( [ {x:0, y:0, z:-d,w:1}, {x:0, y:0, z:d,w:1} ] ); CTX.stroke(); CTX.restore(); } function draw_cube( d ) { var p000 = {x:-d, y:-d, z:-d,w:1}, p001 = {x:d, y:-d, z:-d,w:1}; var p010 = {x:-d, y:d, z:-d,w:1}, p011 = {x:d, y:d, z:-d,w:1}; var p100 = {x:-d, y:-d, z: d,w:1}, p101 = {x:d, y:-d, z: d,w:1}; var p110 = {x:-d, y:d, z: d,w:1}, p111 = {x:d, y:d, z: d,w:1}; CTX.save(); CTX.beginPath(); CTX.strokeStyle = '#888'; draw_line( [ p000, p001, p011, p010, p000, p100, p101, p111, p110, p100 ] ); CTX.stroke(); CTX.restore(); } function draw_title ( str ) { CTX.save() CTX.beginPath(); CTX.lineWidth = 1; CTX.font = 'bold 48px courier new'; CTX.fillStyle = '#888'; CTX.strokeStyle = '#f00'; CTX.fillText( str, 190, -250 ); CTX.closePath(); CTX.restore(); } function draw_PTNB ( ptnb, length ) { CTX.save(); CTX.lineWidth = 3; CTX.strokeStyle = '#f00'; W3D.draw( [ ptnb.p, MAT.interpol( ptnb.p, ptnb.t, length ) ] ); CTX.strokeStyle = '#0f0'; W3D.draw( [ ptnb.p, MAT.interpol( ptnb.p, ptnb.n, length ) ] ); CTX.strokeStyle = '#00f'; W3D.draw( [ ptnb.p, MAT.interpol( ptnb.p, ptnb.b, length ) ] ); CTX.restore(); } function draw_PTUTVN ( ptutvn, length ) { CTX.save(); CTX.lineWidth = 3; CTX.strokeStyle = '#f00'; W3D.draw( [ ptutvn.p, MAT.interpol( ptutvn.p, ptutvn.tu, length ) ] ); CTX.strokeStyle = '#0f0'; W3D.draw( [ ptutvn.p, MAT.interpol( ptutvn.p, ptutvn.tv, length ) ] ); CTX.strokeStyle = '#00f'; W3D.draw( [ ptutvn.p, MAT.interpol( ptutvn.p, ptutvn.n, length ) ] ); CTX.restore(); } function draw_PN ( pn, length ) { CTX.save(); CTX.lineWidth = 3; CTX.strokeStyle = '#00f'; W3D.draw( [ pn.p, MAT.interpol( pn.p, pn.n, length ) ] ); CTX.restore(); } function draw_vol_faces( faces, uv ) { CTX.save(); CTX.strokeStyle = '#888'; for (var i=0; i< 6; i++) draw( faces[i], uv ); CTX.restore(); } function text_display ( str ) { CTX.save() CTX.beginPath(); CTX.lineWidth = 1; CTX.font = 'normal 12px courier new'; CTX.strokeStyle = '#f00'; CTX.strokeText( str, -280, 280 ); CTX.closePath(); CTX.restore(); } // PUBLIC FUNCTIONS return { init2D:init2D, init3D:init3D, rotate:f_rotate, translate:f_translate, scale:f_scale, draw:draw, draw_point:draw_point, draw_PTNB:draw_PTNB, draw_PTUTVN:draw_PTUTVN, draw_PN:draw_PN, draw_vol_faces:draw_vol_faces, text_display:text_display }; })(); // end W3D ////////////////////// // 3) PFORMS MODULUS ////////////////////// var PF = (function() { function split ( P, u ) { // split a curve on u var temp = []; for (var i=0; i< P.length; i++) temp[i] = P[i]; var tri = []; tri[0] = temp; for (var j=0; j< P.length-1; j++) { var temp = []; for (var i=0; i< tri[j].length-1; i++) temp[i] = (u == undefined)? MAT.mid( tri[j][i], tri[j][i+1] ) : MAT.interpol( tri[j][i], tri[j][i+1], u ); tri[j+1] = temp; } var PL = [], PR = []; for (var i=0; i< P.length; i++) { PL[i] = tri[i][0]; PR[i] = tri[P.length-1-i][i]; } return {left:PL, right:PR}; } // BUILD function line_build ( f, r ) { // f is a curve r = (r == undefined)? 0 : r; var Q = []; function _build (P, n) { if (n == 0) { var start = Q.length; for (var i=0; i< P.length-1; i++) Q[start+i] = P[i]; } else { var sss = split( P ); _build( sss.left, n-1 ); _build( sss.right, n-1 ); } } _build( f, r ); Q[Q.length] = f[f.length-1]; return Q; // returns the built curve } function surf_build ( f, r0, r1 ) { r0 = (r0 == undefined)? 0 : r0; r1 = (r1 == undefined)? 0 : r1; var tab = []; for (var i=0; i< f.length; i++) tab[i] = line_build( f[i], r0 ); tab = MAT.ij_ji( tab ); for (var i=0; i< tab.length; i++) tab[i] = line_build( tab[i], r1 ); return tab; } function vol_build( f, r0, r1, r2 ) { r0 = (r0 == undefined)? 0 : r0; r1 = (r1 == undefined)? 0 : r1; r2 = (r2 == undefined)? 0 : r2; var tab = []; for (var i=0; i< f.length; i++) tab[i] = surf_build ( f[i], r0, r1 ); tab = MAT.ijk_kij( tab ); for (var i=0; i< tab.length; i++) for (var j=0; j< tab[0].length; j++) tab[i][j] = line_build( tab[i][j], r2 ); return tab; } function build ( f, r ) { // r should be tested !! if (!Array.isArray(f) ) // it's a point return f; // returns the point else if (!Array.isArray(f[0]) ) // it's a curve return line_build ( f, r ); // returns the built curve else if (!Array.isArray(f[0][0]) ) // it's a surface return surf_build ( f, r[0], r[1] ); // returns the built surface else // it's a volume return vol_build ( f, r[0], r[1], r[2] ); // returns the built volume } // STRETCH function line_stretch ( f, u0, u1 ) { // [0,1] -> [u0,u1] var ff = split( f, u1 ); var fff = split( ff.left, u0 ); return fff.right; } function line_start ( f, u ) { // [0,1] -> [u,1] simplified from line_stretch return split( f, u ).right; } function surf_stretch ( f, p0, p1 ) { // stretch f between p0 and p1 var tab = []; for (var i=0; i< f.length; i++) tab[i] = line_stretch( f[i], p0.x/p1.x, p1.x ); // stretch in the u dimension tab = MAT.ij_ji( tab ); for (var i=0; i< tab.length; i++) tab[i] = line_stretch( tab[i], p0.y/p1.y, p1.y ); // stretch in the u dimension return tab; } function surf_start ( f, p ) { // [{x:0,y:0}, {x:1,y:1}] -> [{x:p.x,y:p.y}, {x:1,y:1}] var tab = []; for (var i=0; i< f.length; i++) tab[i] = line_start( f[i], p.x ); tab = MAT.ij_ji( tab ); for (var i=0; i< tab.length; i++) tab[i] = line_start( tab[i], p.y ); return tab; } function vol_stretch ( f, p0, p1 ) { // stretch f between p0 and p1 var tab = []; for (var i=0; i< f.length; i++) tab[i] = surf_stretch ( f[i], p0, p1 ); tab = MAT.ijk_kij( tab ); for (var i=0; i< tab.length; i++) for (var j=0; j< tab[0].length; j++) tab[i][j] = line_stretch( tab[i][j], p0.z/p1.z, p1.z ); return tab; } function stretch ( f, p0, p1 ) { if (!Array.isArray(f) ) // it's a point return f; // returns the point else if (!Array.isArray(f[0]) ) // it's a curve return line_stretch ( f, p0, p1 ); // returns the stretched curve else if (!Array.isArray(f[0][0]) ) // it's a surface return surf_stretch ( f, p0, p1 ); // returns a surface else // it's the stretched volume return vol_stretch ( f, p0, p1 ); // returns the stretched volume } // UP degree elevation function line_up ( curve, d ) { for (var i=0; i< d; i++) { var n = curve.length; var temp = []; temp[0] = curve[0]; temp[n] = curve[n-1]; for (var j=1; j< n; j++) temp[j] = MAT.interpol( curve[j], curve[j-1], j/n ); curve = temp; } return curve; } function surf_up ( surf, d ) { var s = []; for (var i=0; i< surf.length; i++) s[i] = line_up( surf[i], d[0] ); s = MAT.ij_ji( s ); for (var i=0; i< s.length; i++) s[i] = line_up( s[i], d[1] ); s = MAT.ij_ji( s ); return s; } function up ( f, d ) { if (!Array.isArray(f) ) // a point return f; else if (!Array.isArray(f[0]) ) // a curve return line_up ( f, d ); else if (!Array.isArray(f[0][0]) ) // a surf return surf_up ( f, d ); else return f; } // GET_SUBFORM function get_curve_subform ( f, u ) { // curve -> point var lr = split( f, u ); // returns left curve + right curve return lr.right[0]; // returns the first point of right curve } function get_surf_subform ( f, u, sens) { // surface -> curve var curv = []; for (var i=0; i< f.length; i++) { var lr = split( f[i], u ); // returns left curve + right curve curv[i] = lr.right[0]; // returns the first point of right curve } return curv; // returns a curve } function get_vol_subform ( f, u, sens ) { // volume -> surface var surf = []; for (var i=0; i< f.length; i++) surf[i] = get_surf_subform ( f[i], u ); return surf; } function get_subform ( f, u ) { if ( !Array.isArray( f ) ) // it's a point return f; // returns the point else if ( !Array.isArray( f[0] ) ) // it's a curve return get_curve_subform ( f, u ); // returns a point else if ( !Array.isArray( f[0][0] ) ) // it's a surface return get_surf_subform ( f, u ); // returns a curve else // it's a volume return get_vol_subform ( f, u ); // returns a surface } function build_vol_faces( vol, r ) { // the six faces of a volume var tab = []; tab[0] = build( get_subform( vol, 0 ), r ); tab[1] = build( get_subform( vol, 1 ), r ); tab[2] = build( get_subform( MAT.ijk_jki( vol ), 0 ), r ); tab[3] = build( get_subform( MAT.ijk_jki( vol ), 1 ), r ); tab[4] = build( get_subform( MAT.ijk_kij( vol ), 0 ), r ); tab[5] = build( get_subform( MAT.ijk_kij( vol ), 1 ), r ); return tab; } // GET_POINT, GET_PTNB, ... (http://en.wikipedia.org/wiki/Moving_frame) function get_point ( f, p ) { if ( !Array.isArray( f ) ) // it's a point return f; // returns the point else if ( !Array.isArray( f[0] ) ) // it's a curve return get_subform ( f, p ); // returns a point else if ( !Array.isArray( f[0][0] ) ) // it's a surface return get_subform ( get_subform ( f, p.x ), p.y ); else // it's a volume return get_subform( get_subform( get_subform( f, p.x ), p.y ), p.z ); } function addvect2point ( p, v ) { // input : a 4D point, a 3D vect, output : a 4D point return {x:p.x+v.x*p.w, y:p.y+v.y*p.w, z:p.z+v.z*p.w, w:p.w}; } function get_PTNB ( curve, u ) { // local Serret-Frenet frame at u var u = (u >0.9999)? 0.9999 :u; var c = line_start( curve, u ); var p0 = c[0], p1 = c[1], p2 = c[2]; var v1 = MAT.build_vect( p1, p0 ); var v2 = MAT.build_vect( p2, p0 ); var bb = MAT.cross( v1, v2 ); var nn = MAT.cross( bb, v1 ); var tt = v1; tt = MAT.addvect2point( p0, MAT.normalize( tt ) ); nn = MAT.addvect2point( p0, MAT.normalize( nn ) ); bb = MAT.addvect2point( p0, MAT.normalize( bb ) ); return {p:p0, t:tt, n:nn, b:bb}; // four points defining the unitary PTNB frame } function get_PTUTVN ( surf, p ) { // local frame to the surface at p var u = (p.x >0.9999)? 0.9999 : p.x; var v = (p.y >0.9999)? 0.9999 : p.y; var s = surf_start( surf, {x:u, y:v} ); var p00 = s[0][0], p01 = s[0][1], p10 = s[1][0]; var tu = MAT.build_vect( p01, p00 ); var tv = MAT.build_vect( p10, p00 ); var nn = MAT.cross( tu, tv ); tu = MAT.addvect2point( p00, MAT.normalize( tu ) ); tv = MAT.addvect2point( p00, MAT.normalize( tv ) ); nn = MAT.addvect2point( p00, MAT.normalize( nn ) ); return {p:p00, tu:tu, tv:tv, n:nn}; // four points defining the unitary PTUTVN frame } function get_PN ( surf, p ) { // local normal vector to the surface at p var u = (p.x >0.9999)? 0.9999 : p.x; var v = (p.y >0.9999)? 0.9999 : p.y; var s = surf_start( surf, {x:u, y:v} ); var p00 = s[0][0], p01 = s[0][1], p10 = s[1][0]; var tu = MAT.build_vect( p01, p00 ); var tv = MAT.build_vect( p10, p00 ); var nn = MAT.cross( tu, tv ); nn = MAT.addvect2point( p00, MAT.normalize( nn ) ); return {p:p00, n:nn}; // two points defining the normal vector } // DIAG function diag_m2 ( f ) { // returns the diagonal of a pSm2 var m = f.length; var pp = []; pp[0] = f[0][0]; for (var i=1; i< m; i++) { pp[i] = MAT.interpol( f[i][0], f[i-1][1], 1.0*i/m ); } pp[m] = f[m-1][1]; return pp; } function surf_diag ( f ) { // exemple 5x3 var m = f.length; // nombre de lignes 5 4 3 2 var n = f[0].length; // nombre de colonnes 3 4 5 6 if (m>2) { var ss = []; for (var i=0; i< m-1; i++) { ss[i] = diag_m2( MAT.ij_ji( [ f[i], f[i+1] ] ) ); } return surf_diag( ss ); } else return diag_m2( MAT.ij_ji( f ) ); } function vol_diag ( f ) { var tab = []; for (var i=0; i< f.length; i++) tab[i] = surf_diag( f[i] ); return tab; } function diag ( f ) { if (!Array.isArray(f) ) return f; else if (!Array.isArray(f[0]) ) return get_subform( f, 0.5 ); else if (!Array.isArray(f[0][0]) ) return surf_diag ( f ); else return vol_diag ( f ); } // EMBED A CURVE function embed ( f, curve, r0, r1 ) { // blossoming must be done here curve = build( curve, r0 ); var tab = [] for (var i=0; i< curve.length-1; i++) { var segment = diag( stretch( f, curve[i], curve[i+1] ) ); // the first diag if (Array.isArray(f[0][0])) // it's a volume segment = diag( segment ); segment = build( segment, r1 ); var start = tab.length; for (var j=0; j< segment.length; j++) tab[start + j] = segment[j]; } return tab; // the blossomed embedded curve } // PUBLIC FUNCTIONS return { build:build, stretch:stretch, up:up, get_subform:get_subform, get_point:get_point, get_PTNB:get_PTNB, get_PTUTVN:get_PTUTVN, get_PN:get_PN, diag:diag, build_vol_faces:build_vol_faces, embed:embed }; })(); // end PF ////////////////////// // 4) GEOMETRY ON PF MODULUS ////////////////////// var PF_GEO = (function() { // PRIMITIVES AND COMBINATIONS function hermite ( ns0, ns1 ) { // from hermite to Bezier return [ ns0.nod, MAT.addvect2point( ns0.nod, ns0.sat ), MAT.addvect2point( ns1.nod, ns1.sat ), ns1.nod ]; } function __circle_arc ( r, u0, u1 ) { // a 180° circle arc when u0 = 0 and u1 = 1 // W3D.draw( PF.build( circle_arc( 100, -0.35, 1.5 ), 4 ) ); var k = Math.sqrt(2)/2; // a quarter of a circle with a parabola : // var arc = [ {x:0, y:0, z:0, w:1}, // {x:r*k, y:0, z:0, w:k}, // {x:r, y:r, z:0, w:1} ]; // half of a circle with a pL5 : var arc = [ {x:r, y:0, z:0, w:1}, {x:r*k, y:r*k, z:0, w:k}, {x:0, y:r, z:0, w:2/3}, {x:-r*k, y:r*k, z:0, w:k}, {x:-r, y:0, z:0, w:1} ]; if (u0 != undefined && u1 != undefined) arc = PF.stretch( arc, u0, u1 ); return arc; } function circle_arc ( r, u0, u1, type ) { // a 180° circle arc when u0 = 0 and u1 = 1 // W3D.draw( PF.build( circle_arc( 100, -0.35, 1.5 ), 4 ) ); var k = Math.sqrt(2)/2; type = (type == undefined)? 3 : type; switch (type) { case 2 : // half of a circle with a pL4 : var arc = [ {x:r, y:0, z:0, w:1}, {x:r/3, y:2/3*r, z:0, w:1/3}, {x:-r/3, y:2/3*r, z:0, w:1/3}, {x:-r, y:0, z:0, w:1} ]; break; case 3 : // half of a circle with a pL5 : var arc = [ {x:r, y:0, z:0, w:1}, {x:r*k, y:r*k, z:0, w:k}, {x:0, y:r, z:0, w:2/3}, {x:-r*k, y:r*k, z:0, w:k}, {x:-r, y:0, z:0, w:1} ]; break; default : // a quarter of a circle with a parabola : var arc = [ {x:0, y:0, z:0, w:1}, {x:r*k, y:0, z:0, w:k}, {x:r, y:r, z:0, w:1} ]; } if (u0 != undefined && u1 != undefined) arc = PF.stretch( arc, u0, u1 ); return arc; } function cylinder ( r, h, u0, u1 ) { // a quarter of a cylinder // W3D.draw( PF.build( cylinder( 100, -0.35, 1.5 ), 4 ) ); var k = Math.sqrt(2)/2; var cyl = [ [ {x:r, y:0, z:0, w:1 }, {x:r*k, y:r*k, z:0, w:k }, {x:0, y:r, z:0, w:1 } ], [ {x:r, y:0, z:h, w:1 }, {x:r*k, y:r*k, z:h*k, w:k }, {x:0, y:r, z:h, w:1 } ] ]; if (u0 != undefined && u1 != undefined) cyl = PF.stretch( cyl, u0, u1 ); return cyl; } function sphere ( r ) { // a 1/8 sphere var k = Math.sqrt(2)/2; var sphere = [ [ {x:r, y:0, z:0, w:1}, {x:r*k, y:r*k, z:0, w:k}, {x:0, y:r, z:0, w:1} ], [ {x:r*k, y:0, z:-r*k, w:k}, {x:r*k*k, y:r*k*k, z:-r*k*k, w:k*k}, {x:0, y:r*k, z:0, w:k} ], [ {x:0, y:0, z:-r, w:1}, {x:0, y:r*k, z:-r*k, w:k}, {x:0, y:r, z:0, w:1} ] ]; return sphere; } function torus ( r1, r2 ) { // a 1/16 of a torus var k = Math.sqrt(2)/2; var r12 = r1 + r2; r2 = Math.abs( r2 ); var torus = [ [ {x:0, y:0, z:-r12, w:1}, {x:0, y:r2*k, z:-r12*k, w:k}, {x:0, y:r2, z:-r1, w:1} ], [ {x:r12*k, y:0, z:-r12*k, w:k}, {x:r12*k*k, y:r2*k*k, z:-r12*k*k, w:k*k}, {x:r1*k, y:r2*k, z:-r1*k, w:k} ], [ {x:r12, y:0, z:0, w:1}, {x:r12*k, y:r2*k, z:0, w:k}, {x:r1, y:r2, z:0, w:1} ] ]; return torus; } function cross ( c1, c2 ) { // two pCurves defined in plane OXY -> lathe var surf = []; for (var i=0; i< c1.length; i++) { var pp = []; for (var j=0; j< c2.length; j++) { pp[j] = { x:c1[i].x * c2[j].x, y:c1[i].w * c2[j].y, z:c1[i].y * c2[j].x, w:c1[i].w * c2[j].w }; } surf[i] = pp; } return surf; } function tube ( path, sec ) { var m = path.length, n = sec.length; var tab = []; for (var i=0; i< m; i++) { var u = i/(m-1); var ptnb = PF.get_PTNB( path, u ); var p0 = ptnb.p, pt = ptnb.t, pn = ptnb.n, pb = ptnb.b; var nn = { x: pn.x*p0.w/pn.w - p0.x, y: pn.y*p0.w/pn.w - p0.y, z: pn.z*p0.w/pn.w - p0.z, w: 0 }; var bb = { x: pb.x*p0.w/pb.w - p0.x, y: pb.y*p0.w/pb.w - p0.y, z: pb.z*p0.w/pb.w - p0.z, w: 0 }; var tt = { x: pt.x*p0.w/pt.w - p0.x, y: pt.y*p0.w/pt.w - p0.y, z: pt.z*p0.w/pt.w - p0.z, w: 0 }; var ppp = []; for (var j=0; j< n; j++) { var p = sec[j]; ppp[j] = { x: p0.x*p.w + nn.x*p.x + bb.x*p.y + tt.x*p.z, y: p0.y*p.w + nn.y*p.x + bb.y*p.y + tt.y*p.z, z: p0.z*p.w + nn.z*p.x + bb.z*p.y + tt.z*p.z, w: p0.w*p.w + nn.w*p.x + bb.w*p.y + tt.w*p.z }; } tab[i] = ppp; } return tab; } function parallel ( surf, rec, d ) { var surf1 = PF.build( surf, [rec,rec] ); // blossoming here var m = surf1.length; var n = surf1[0].length; var s0 = [], s1 = []; for (var i=0; i< m; i++) { var ss0 = [], ss1 = []; for (var j=0; j< n; j++) { var pn = PF.get_PN( surf, {x:i/(m-1), y:j/(n-1)} ); var spn = PF.stretch( [pn.p, pn.n], d ); ss0[j] = spn[0]; ss1[j] = spn[1]; } s0[i] = ss0; s1[i] = ss1; } return [s0, s1]; // it's a volume } function coons ( c1, c2, c3 , c4 ) { // 4 concurrent cubics c1//c2 and c3//c4 -> bicubic (pS44) var s1 = PF.up( [ c1, c2 ], [0,2] ); var s2 = PF.up( [ c3, c4 ], [0,2] ); s2 = MAT.ij_ji( s2 ); var s3 = PF.up( [ [ c1[0], c1[3] ], [ c2[0], c2[3] ] ], [2,2] ); var coons = []; for (var i=0; i< 4; i++) { var temp = []; for (var j=0; j< 4; j++) temp[j] = { x:s1[i][j].x + s2[i][j].x - s3[i][j].x, y:s1[i][j].y + s2[i][j].y - s3[i][j].y, z:s1[i][j].z + s2[i][j].z - s3[i][j].z, w:s1[i][j].w + s2[i][j].w - s3[i][j].w // 1+1-1 = 1, OK but to be tested }; coons[i] = temp; } return coons; } function cubic_from_nodes ( p0, p1 ) { return [ p0.nod, MAT.addvect2point( p0.nod, p0.tu ), MAT.subvect2point( p1.nod, p1.tu ), p1.nod ]; } function spline_from_nodes( tab, closed ) { // from an array of nodes to an array of cubics var spline = []; for (var i=0; i< tab.length-1; i++) spline[i] = cubic_from_nodes ( tab[i], tab[i+1] ); if (closed !=undefined && closed == true) spline[tab.length-1] = cubic_from_nodes ( tab[tab.length-1], tab[0] ); return spline; } // PUBLIC FUNCTIONS return { hermite:hermite, circle_arc:circle_arc, cylinder:cylinder, sphere:sphere, torus:torus, cross:cross, tube:tube, parallel:parallel, coons:coons, cubic_from_nodes:cubic_from_nodes, spline_from_nodes:spline_from_nodes }; })(); // end PF_GEO ////////////////////// // 5) TEST FIGURES MODULUS ////////////////////// var PF_TEST = (function() { // CURVES function regular_polygon ( p0, r, n ) { // an n sides regular polygon var rad = Math.PI / 180; var delta = rad * 360 / n; var poly = []; for (var i=0; i< n+1; i++) { poly[i] = { x:p0.x + r * Math.cos( delta * i ), y:p0.y + r * Math.sin( delta * i ), z:p0.z + 0, w:p0.w } } return poly; } function test_degree_elevation ( degree ) { var d = 150; var curve = [ {x:-d,y:-d,z:-d,w:1}, {x: d,y:-d,z:-d,w:1}, {x: d,y:-d,z: d,w:1}, {x: d,y: d,z: d,w:1} ]; W3D.scale( curve, 1/2, 1/2, 1/2 ); curve = PF.up( curve, degree ); W3D.draw( curve ); W3D.draw( PF.build( curve, 4 ) ); for (var i=0; i< curve.length; i++) W3D.draw_point( curve[i], 4, 10 ); } // CURVES function mycubic ( ctx ) { // a cubic curve built on the scene cube var d = 150; var P4 = [ {x:-d,y:-d,z:-d,w:1}, {x: d,y:-d,z:-d,w:1}, {x: d,y:-d,z: d,w:1}, {x: d,y: d,z: d,w:1} ]; ctx.save(); ctx.beginPath(); ctx.strokeStyle = '#00f'; ctx.lineWidth = 2; W3D.draw( P4 ); // the polygon control ctx.restore(); ctx.save(); ctx.beginPath(); ctx.strokeStyle = '#f00'; ctx.lineWidth = 8; W3D.draw( PF.build( P4, 5 ) ); ctx.restore(); var N = 10; for (var i=0; i< N; i++) { var ptnb = PF.get_PTNB( P4, i/(N-1) ); // {p:p, t:t, n:n, b:b} W3D.draw_PTNB( ptnb, 30 ); // i = 0, 1, ... 9 -> u in [0,1] } } function stretched_cubic ( ctx ) { // a cubic stretched in [0.2,0.8] var d = 150; var P4 = [ {x:-d,y:-d,z:-d,w:1}, {x: d,y:-d,z:-d,w:1}, {x: d,y:-d,z: d,w:1}, {x: d,y: d,z: d,w:1} ]; W3D.draw( PF.build( P4, 4 ) ); ctx.save(); ctx.beginPath(); ctx.strokeStyle = '#f00'; ctx.lineWidth = 6; W3D.draw( PF.build( PF.stretch( P4, 0.2, 0.8) , 4 ) ); ctx.restore(); var p = PF.get_point( P4, 0.5 ); W3D.draw( p ); } function three_circle_arcs ( ctx ) { var N = 40; for (var i=0; i< N; i++) { var u0 = 0, u1 = 1+2.5*i/N; var arc3 = PF_GEO.circle_arc ( 75*i/(N-1), u0, u1, 1 ); W3D.translate( arc3, -75, -150, -150*(1-i/(N-1)) + 150*i/(N-1) ); W3D.draw( PF.build( arc3, 5 ) ); } for (var i=0; i< N; i++) { var u0 = 0, u1 = 1; // +2*i/N; var arc4 = PF_GEO.circle_arc ( 75*i/(N-1), u0, u1, 2 ); W3D.translate( arc4, -75, 75, -150*(1-i/(N-1)) + 150*i/(N-1) ); W3D.draw( PF.build( arc4, 5 ) ); } for (var i=0; i< N; i++) { var u0 = 0, u1 = 1+2.5*i/N; var arc5 = PF_GEO.circle_arc ( 75*i/(N-1), u0, u1, 3 ); W3D.translate( arc5, 75, 75, -150*(1-i/(N-1)) + 150*i/(N-1) ); W3D.draw( PF.build( arc5, 5 ) ); } } function polycurves ( n ) { // a cubic curve rotated around OZ axis var P4 = [{x:-150,y:-150,z:-150,w:1}, {x: 150,y:-150,z:-150,w:1}, {x: 150,y:-150,z: 150,w:1}, {x: 150,y: 150,z: 150,w:1} ]; var Q4 = PF.build( P4, 5 ); for (var j=0; j< n; j++) { W3D.draw( Q4 ); W3D.rotate( Q4, 'OZ', 5 ); W3D.scale( Q4, 0.99, 0.99, 0.99 ); } } function spiral ( dr, dz, f, n ) { // playing with parameters ... var p0 = {x:0,y:0,z:0}; var rad = Math.PI / 180; var spi = []; for (var i=0; i< n+1; i++) { spi[i] = { x:p0.x + dr*i * Math.cos( rad * i ), y:p0.y + dr*i * Math.sin( rad * i ), z:p0.z + dz*i * Math.sin( rad * i * f ), w:1 } } W3D.draw( spi ); } function regular_polygon_torus ( ctx ) { // a regular polygon translated and rotated around OY axis var d = 50; var poly = regular_polygon( {x:-3*d,y:0,z:0,w:1}, d, 5 ); for (var i=0; i< 90; i++) { W3D.rotate( poly, 'OY', 4 ); W3D.draw( poly ); } } function new_spline ( ctx ) { // var d = 150, p = []; p[0] = { nod:{x:-d/2, y:-d, z:0, w:1}, tu:{x: d/2, y:0, z: d/2}}; p[1] = { nod:{x: d/2, y:-d/2, z:0, w:1}, tu:{x: d/2, y:0, z: d/2}}; p[2] = { nod:{x: d, y: d, z:-d, w:1}, tu:{x: 0, y:d/2, z: d/2}}; p[3] = { nod:{x:-d, y: d, z:d, w:1}, tu:{x: 0, y:d/2, z: d/2}}; var spline = PF_GEO.spline_from_nodes( p, true ); for (var i=0; i< spline.length; i++) { W3D.draw( spline[i][0] ); W3D.draw( PF.build( spline[i], 4 ) ); } } // SURFACES var g_ship = [ // used in ship and thick_ship [{x: 3/2, y:0, z:-1/32, w:1}, {x: 3/2, y:-1/8, z:-1/32, w:1}, {x: 3/2, y:-1/4, z:0, w:1}, {x: 3/2, y:-1/8, z:1/32, w:1}, {x: 3/2, y:0, z:1/32, w:1} ], [{x: 2/2, y:0, z:-1/32, w:1}, {x: 2/2, y:-1/8, z:-1/32, w:1}, {x: 2/2, y:-1/4, z:0, w:1}, {x: 2/2, y:-1/8, z:1/32, w:1}, {x: 2/2, y:0, z:1/32, w:1} ], [{x: 1/2, y:0, z:-1/16, w:1}, {x: 1/2, y:-1/2, z:-1/16, w:1}, {x: 1/2, y:-1, z:0, w:1}, {x: 1/2, y:-1/2, z:1/16, w:1}, {x: 1/2, y:0, z:1/16, w:1} ], [{x: 0/2, y:0, z:-1/2, w:1}, {x: 0/2, y:-1/2, z:-2/2, w:1}, {x: 0/2, y:-1, z:0, w:1}, {x: 0/2, y:-1/2, z:2/2, w:1}, {x: 0/2, y:0, z:1/2, w:1} ], [{x:-1/2, y:0, z:-1/2, w:1}, {x:-1/2, y:-1/2, z:-1/2, w:1}, {x:-1/2, y:-1/4, z:0, w:1}, {x:-1/2, y:-1/2, z:1/2, w:1}, {x:-1/2, y:0, z:1/2, w:1} ], [{x:-2/2, y:0, z:-1/4, w:1}, {x:-2/2, y:-1/8, z:-1/4, w:1}, {x:-3/2, y:-1/8, z:0, w:1}, {x:-2/2, y:-1/8, z:1/4, w:1}, {x:-2/2, y:0, z:1/4, w:1} ] ]; function mysurface( ctx, uv ) { // a pS33 surface var d = 150; var pS = [ [ {x:-d, y:-d, z:-d, w:1}, {x:0, y:-d, z: d, w:1}, {x:d, y:-d, z:-d, w:1} ], [ {x:-d, y:0, z:-d, w:1}, {x:0, y:0, z: d, w:1}, {x:d, y:0, z: d, w:1} ], [ {x:-d, y:d, z: d, w:1}, {x:0, y:d, z:-d, w:1}, {x:d, y:d, z: d, w:1} ] ]; W3D.rotate( pS, 'OZ', 90 ); var surf = PF.build( pS, [4, 4] ); W3D.draw( surf, uv ); // two points in the surface : var p0 = {x:0.6,y:0.1}, p1 = {x:0.9,y:0.4}; W3D.draw( PF.get_point( pS, p0 ) ); W3D.draw( PF.get_point( pS, p1 ) ); // surface stretched on the two points var sss = PF.stretch( pS, p0, p1 ); ctx.save(); ctx.beginPath(); ctx.strokeStyle = '#00f'; var uv2 = uv; // (uv == 'u')? 'v' : 'u'; // stretch to be transposed !!! W3D.draw( PF.build( sss, [3, 3] ), uv2 ); ctx.restore(); ctx.save(); ctx.beginPath(); ctx.strokeStyle = '#f00'; ctx.lineWidth = 3; var diag = PF.diag( sss ); W3D.draw( PF.build( diag, 5 ) ); ctx.restore(); } function surf_uv ( ctx, uv ) { var d = 150; var surf = [ [ {x:-d, y:-d, z:-d, w:1}, {x:0, y:-d, z: d, w:1}, {x:d, y:-d, z:-d, w:1} ], [ {x:-d, y:0, z:-d, w:1}, {x:0, y:0, z: d, w:1}, {x:d, y:0, z: d, w:1} ], [ {x:-d, y:d, z: d, w:1}, {x:0, y:d, z:-d, w:1}, {x:d, y:d, z: d, w:1} ] ]; W3D.rotate( surf, 'OZ', 90 ); ctx.save(); ctx.strokeStyle = '#888'; W3D.draw( PF.build( surf, [4, 4] ), uv ); ctx.lineWidth = 2; ctx.strokeStyle = '#f00'; W3D.draw( PF.build( PF.get_subform( surf, 0.25 ), 4 ) ); surf = MAT.ij_ji( surf ); ctx.strokeStyle = '#0f0'; W3D.draw( PF.build( PF.get_subform( surf, 0.33 ), 4 ) ); ctx.restore(); surf = MAT.ij_ji( surf ); // W3D.draw( PF.get_point( surf, {x:0.25, y:0.33} ) ); // draw the point var ptutvn = PF.get_PTUTVN( surf, {x:0.25, y:0.33} ); W3D.draw_PTUTVN( ptutvn, 200 ); // draw the local frame } function diagonal ( ctx, uv ) { var d = 150; var pS = [ [ {x:-d, y:-d*2, z:-d, w:1}, {x:0, y:-d*2, z: d, w:1}, {x:d, y:-d*2, z:-d, w:1} ], [ {x:-d, y:-d, z:-d, w:1}, {x:0, y:-d, z: d, w:1}, {x:d, y:-d, z:-d, w:1} ], [ {x:-d, y:0, z:-d, w:1}, {x:0, y:0, z: d, w:1}, {x:d, y:0, z: d, w:1} ], [ {x:-d, y:d, z: d, w:1}, {x:0, y:d, z:-d, w:1}, {x:d, y:d, z: d, w:1} ], [ {x:-d, y:d*2, z: d, w:1}, {x:0, y:d*2, z:-d, w:1}, {x:d, y:d*2, z: d, w:1} ] ]; W3D.rotate( pS, 'OZ', 90 ); W3D.scale( pS, 0.5, 1, 1 ); var surf = PF.build( pS, [4, 4] ); W3D.draw( surf, uv ); // computing the diagonal of the surface : var diag = PF.diag( pS ) ctx.save(); ctx.beginPath(); ctx.strokeStyle = '#f00'; ctx.lineWidth = 6; W3D.draw( PF.build( diag, 4 ) ); ctx.restore(); } function ship ( ctx, uv ) { var ship = g_ship // g_ship is defined above W3D.rotate( ship, 'OX', 90 ); W3D.scale( ship, 250, 250, 250 ); W3D.translate( ship, -100, 0, 0 ); var curve = [ {x:0.2,y:0.1}, {x:0.2,y:0.9}, {x:0.9,y:0.1}, {x:0.5,y:0.9} ]; ctx.save(); W3D.draw( ship, 'u' ); ctx.lineWidth = 1; ctx.strokeStyle = '#444'; W3D.draw( PF.build( ship, [3, 3] ), ((uv=='v')? 'u':'v') ); ctx.strokeStyle = '#00f'; ctx.lineWidth = 3; W3D.draw( PF.build( PF.diag( ship ), 3 ) ); W3D.scale( ship, 1, -1, 1 ); // -> the other diagonal W3D.draw( PF.build( PF.diag( ship ), 3 ) ); ctx.restore(); } function torus ( ctx, uv ) { // a 1/2 torus patch made with a pS33 var torus = PF_GEO.torus( 100, 50 ); W3D.scale( torus, 1, -1, 1 ); W3D.rotate( torus, 'OY', -90 ); torus = PF.build( torus, [3, 4] ); ctx.save(); ctx.lineWidth = 1; for (var k=0; k< 2; k++) { for (var i=0; i< 4; i++) { ctx.strokeStyle = (k!=0)? '#888' : '#000'; W3D.draw( torus, uv ); W3D.rotate( torus, 'OY', 90 ); } W3D.rotate( torus, 'OZ', 180 ); } ctx.lineWidth = 3; ctx.strokeStyle = '#f00'; W3D.draw( torus, uv ); ctx.restore(); } function ipCubic( ctx, uv ) { // ipCubic embedded in a pS33 // 1) defining : var d = 150; var surf = [ [ {x:-d, y:-d, z:-d, w:1}, {x:0, y:-d, z: d, w:1}, {x:d, y:-d, z:-d, w:1} ], [ {x:-d, y:0, z:-d, w:1}, {x:0, y:0, z: d, w:1}, {x:d, y:0, z: d, w:1} ], [ {x:-d, y:d, z: d, w:1}, {x:0, y:d, z:-d, w:1}, {x:d, y:d, z: d, w:1} ] ]; W3D.rotate( surf, 'OZ', 90 ); var curve = [ {x:0.2,y:0.1}, {x:0.2,y:0.9}, {x:0.9,y:0.1}, {x:0.5,y:0.9} ]; // 2) drawing : ctx.save(); ctx.lineWidth = 1; ctx.strokeStyle = '#888'; W3D.draw( PF.build( surf, [4, 4] ), uv ); // 1) draw surface ctx.strokeStyle = '#00f'; W3D.draw( PF.embed( surf, curve, 0, 3 ) ); // 2) draw control polygon ctx.strokeStyle = '#f00'; ctx.lineWidth = 3; W3D.draw( PF.embed( surf, curve, 4, 0 ) ); // 3) draw cubic curve ctx.restore(); } function lathe ( ctx, uv ) { var r1 = 200; // radius of path circle var u0 = -0.35; // u0= 0.000 and u1=1.000 -> 180° var u1 = 1.5; // u0=-0.707 and u1=1.707 -> 360° var c1 = PF_GEO.circle_arc( 1, u0, u1 ); // radius must be 1 var c2 = [ {x: 30, y:-150, z:0, w:1}, {x: 150, y:-150, z:0, w:1}, {x: -30, y:-120, z:0, w:1}, {x: 30, y: 150, z:0, w:1} ]; var surf = PF_GEO.cross( c1, c2 ); W3D.rotate( surf, 'OX', 90 ); ctx.save(); ctx.strokeStyle = '#888'; W3D.draw( PF.build( surf, [3,4] ), uv ); surf = PF.stretch( surf, {x:0.3,y:0.3}, {x:0.8,y:0.8} ); ctx.strokeStyle = '#00f'; ctx.lineWidth = 2; W3D.draw( PF.build( surf, [3,4] ), uv ); ctx.strokeStyle = '#f00'; ctx.lineWidth = 6; var diag = PF.diag( surf ); W3D.draw( PF.build( PF.diag( surf ), 4 ) ); ctx.restore(); } function pTorus ( ctx, uv ) { var r1 = 200; // radius of path circle var r2 = 50; // radius of section circle var u0 = -0.35; // u0= 0.000 and u1=1.000 -> 180° var u1 = 1.5; // u0=-0.707 and u1=1.707 -> 360° var c1 = PF_GEO.circle_arc( 1, u0, u1 ); // radius must be 1 var c2 = PF_GEO.circle_arc( r2, u0, u1 ); W3D.translate( c2, r1, 0, 0 ); // section circle is translated on OX var surf = PF_GEO.cross( c1, c2 ); ctx.save(); ctx.strokeStyle = '#888'; W3D.draw( PF.build( surf, [3,4] ), uv ); ctx.strokeStyle = '#f00'; ctx.lineWidth = 3; var diag = PF.diag( surf ); W3D.draw( PF.build( PF.diag( surf ), 4 ) ); surf = PF.stretch( surf, {x:0.3,y:0.3}, {x:0.8,y:0.8} ); ctx.strokeStyle = '#00f'; ctx.lineWidth = 2; W3D.draw( PF.build( surf, [3,4] ), uv ); ctx.strokeStyle = '#0f0'; ctx.lineWidth = 6; var diag = PF.diag( surf ); W3D.draw( PF.build( PF.diag( surf ), 4 ) ); ctx.restore(); } function product ( ctx, uv ) { var c1 = [ {x:0.00, y:-1.00, z:0, w:1}, {x:1.00, y:-1.00, z:0, w:1}, {x:0.00, y: 1.00, z:0, w:1}, {x:1.00, y: 1.00, z:0, w:1} ]; W3D.scale( c1, 2, 2, 1 ); var c2 = [ {x: 30, y:-150, z:0, w:1}, {x: 150, y:-150, z:0, w:1}, {x: -30, y:-120, z:0, w:1}, {x: 30, y: 150, z:0, w:1} ]; var surf = PF_GEO.cross( c1, c2 ); W3D.rotate( surf, 'OX', 90 ); ctx.save(); ctx.strokeStyle = '#888'; W3D.draw( PF.build( surf, [3,4] ), uv ); ctx.strokeStyle = '#f00'; ctx.lineWidth = 6; var diag = PF.diag( surf ); W3D.draw( PF.build( PF.diag( surf ), 4 ) ); ctx.restore(); } function tube ( ctx, uv ) { var d = 150; var path = [ {x:-d,y:-d,z:-d,w:1}, {x: d,y:-d,z:-d,w:1}, {x: d,y:-d,z: d,w:1}, {x: d,y: d,z: d,w:1} ]; // var path = PF_GEO.circle_arc ( d, -0.4, 1.4 ); // torus var section = PF_GEO.circle_arc ( 50, -0.4, 1.4 ); var tube = PF_GEO.tube( PF.build( path, 3 ), PF.build( section, 4 ) ); W3D.draw( tube, uv ); ctx.save(); ctx.strokeStyle = '#f00'; ctx.lineWidth = 6; W3D.draw( PF.diag( tube ) ); // surf = pS55 -> diag = pL17 : 17-1 = (5-1)*(5-1) ctx.restore(); // display some TNBs of the path : W3D.draw( PF.build( path, 5 ) ); var N = 10; for (var i=0; i< N; i++) { var ptnb = PF.get_PTNB( path, i/(N-1) ); W3D.draw_PTNB( ptnb, 50 ); } } function tube_spline( ctx, uv ) { // 1) define 3 cubics, an array of 6 nodes with their satellites (12 points) var d = 150, p = []; p[0] = { nod:{x:-d/2, y:-d, z:0, w:1}, tu:{x: d/2, y:0, z: d/2}}; p[1] = { nod:{x: d/2, y:-d/2, z:0, w:1}, tu:{x: d/2, y:0, z: d/2}}; p[2] = { nod:{x: d, y: d, z:-d, w:1}, tu:{x: 0, y:d/2, z: d/2}}; p[3] = { nod:{x:-d, y: d, z:d, w:1}, tu:{x: 0, y:d/2, z: d/2}}; var spline = PF_GEO.spline_from_nodes( p, true ); // 1) draw the tubic spline : for (var i=0; i< spline.length; i++) { var path = spline[i]; var section = PF_GEO.circle_arc ( 10, -0.4, 1.4 ); var tube = PF_GEO.tube( PF.build( path, 4 ), PF.build( section, 2 ) ); W3D.draw( tube, uv ); } // 2) draw the 6 nodes : for (var i=0; i< p.length; i++) W3D.draw( p[i].nod ); // 3) draw the interpolating spline in alternate colors : ctx.save(); ctx.lineWidth = 2; for (var i=0; i< spline.length; i++) { ctx.strokeStyle = (i%2 != 0)? '#0f0' : '#f00'; W3D.draw( PF.build( spline[i], 4 ) ); } ctx.restore(); } function viviani ( ctx, uv ) { // a 1/2 sphere with a Viviani window var sphere = PF_GEO.sphere( 150 ); W3D.scale( sphere, 1, -1, 1 ); W3D.rotate( sphere, 'OY', -90 ); var cyl = PF_GEO.cylinder( 75, 300 ); cyl = PF.stretch( cyl, {x:-4,y:0}, {x:6,y:1} ); W3D.rotate( cyl, 'OX', -90 ); W3D.rotate( cyl, 'OY', -90 ); W3D.translate( cyl, 0, -150, 75 ); W3D.draw( PF.build( cyl, [4,7] ), uv ); ctx.save(); ctx.lineWidth = 1; ctx.strokeStyle = '#888'; W3D.draw( PF.build( cyl, [4,7] ), uv ); W3D.draw( PF.build( sphere, [3, 4] ), uv ); ctx.lineWidth = 3; ctx.strokeStyle = '#f00'; W3D.draw( PF.build( PF.diag( sphere ), 3 ) ); W3D.scale( sphere, -1, 1, 1 ); ctx.lineWidth = 1; ctx.strokeStyle = '#888'; W3D.draw( PF.build( sphere, [3, 4] ), uv ); ctx.lineWidth = 3; ctx.strokeStyle = '#f00'; W3D.draw( PF.build( PF.diag( sphere ), 3 ) ); W3D.scale( sphere, 1, -1, 1 ); ctx.lineWidth = 1; ctx.strokeStyle = '#888'; W3D.draw( PF.build( sphere, [3, 4] ), uv ); ctx.lineWidth = 3; ctx.strokeStyle = '#f00'; W3D.draw( PF.build( PF.diag( sphere ), 3 ) ); W3D.scale( sphere, -1, 1, 1 ); ctx.lineWidth = 1; ctx.strokeStyle = '#888'; W3D.draw( PF.build( sphere, [3, 4] ), uv ); ctx.lineWidth = 3; ctx.strokeStyle = '#f00'; W3D.draw( PF.build( PF.diag( sphere ), 3 ) ); ctx.restore(); } function test_coons ( ctx, uv ) { var d = 150, h = 50; var c1 = [ {x:-d,y:-d,z:0,w:1}, {x:-d/3,y:-d, z:h, w:1}, {x:d/3,y:-d, z:-h,w:1}, {x:d, y:-d,z:0,w:1} ]; var c2 = [ {x:-d,y: d,z:0,w:1}, {x:-d/3,y: d, z:-h,w:1}, {x:d/3,y: d, z:h, w:1}, {x:d, y: d,z:0,w:1} ]; var c3 = [ {x:-d,y:-d,z:0,w:1}, {x:-d, y:-d/3,z:h, w:1}, {x:-d, y:d/3,z:2*h,w:1}, {x:-d,y:d, z:0,w:1} ]; var c4 = [ {x: d,y:-d,z:0,w:1}, {x: d, y:-d/3,z:-2*h,w:1}, {x: d, y:d/3,z:h, w:1}, {x: d,y:d, z:0,w:1} ]; var coons = PF_GEO.coons( c1, c2, c3, c4 ); W3D.draw( PF.build( coons, [3,3] ), uv ); W3D.draw( PF.get_point( coons, {x:0.9, y:0.1} ) ); ctx.save(); ctx.lineWidth = 3; ctx.strokeStyle = '#f00'; W3D.draw( PF.build ( c1, 3 ) ); W3D.draw( PF.build ( c2, 3 ) ); ctx.strokeStyle = '#0f0'; W3D.draw( PF.build ( c3, 3 ) ); W3D.draw( PF.build ( c4, 3 ) ); ctx.strokeStyle = '#00f'; W3D.draw( PF.build ( PF.diag( coons ), 3 ) ); ctx.restore(); } function new_coons ( ctx, uv ) { var d = 150; var c1 = PF_GEO.cubic_from_nodes( { nod:{x:-d, y:-d, z:0, w:1}, tu:{x:d/2, y:0, z:d/2} }, { nod:{x: d, y:-d, z:0, w:1}, tu:{x:d/2, y:0, z:d/2} } ); var c2 = PF_GEO.cubic_from_nodes( { nod:{x:-d, y: d, z:0, w:1}, tu:{x:d/2, y:0, z:d/2} }, { nod:{x: d, y: d, z:0, w:1}, tu:{x:d/2, y:0, z:d/2} } ); var c3 = PF_GEO.cubic_from_nodes( { nod:{x:-d, y:-d, z:0, w:1}, tu:{x:0, y:d/2, z:d/2} }, { nod:{x:-d, y: d, z:0, w:1}, tu:{x:0, y:d/2, z:d/2} } ); var c4 = PF_GEO.cubic_from_nodes( { nod:{x: d, y:-d, z:0, w:1}, tu:{x:0, y:d/2, z:d/2} }, { nod:{x: d, y: d, z:0, w:1}, tu:{x:0, y:d/2, z:d/2} } ); var coons = PF_GEO.coons( c1, c2, c3, c4 ); W3D.draw( PF.build( coons, [3,3] ), uv ); W3D.translate( coons, 0, 2*d, 0 ); W3D.draw( PF.build( coons, [3,3] ), uv ); W3D.translate( coons, -2*d, 0, 0 ); W3D.draw( PF.build( coons, [3,3] ), uv ); W3D.translate( coons, 0, -2*d, 0 ); W3D.draw( PF.build( coons, [3,3] ), uv ); W3D.translate( coons, -2*d, 0, 0 ); W3D.draw( PF.build( coons, [3,3] ), uv ); ctx.save(); ctx.lineWidth = 6; ctx.strokeStyle = '#f00'; W3D.draw( PF.build( c1, 3 ) ); ctx.strokeStyle = '#0f0'; W3D.draw( PF.build( c2, 3 ) ); ctx.strokeStyle = '#00f'; W3D.draw( PF.build( c3, 3 ) ); ctx.strokeStyle = '#0ff'; W3D.draw( PF.build( c4, 3 ) ); ctx.restore(); } function coons_from_splines ( ctx, uv ) { var d = 150, kx = 60, ky = 75, h = 30; var p00 = { nod:{x:-d, y:-d, z: 0, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p01 = { nod:{x: 0, y:-d, z: 0, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p02 = { nod:{x: d, y:-d, z:-d, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p10 = { nod:{x:-d, y: 0, z: d, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p11 = { nod:{x: 0, y: 0, z: 0, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p12 = { nod:{x: d, y: 0, z:-d, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p20 = { nod:{x:-d, y: d, z: d, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p21 = { nod:{x: 0, y: d, z: 0, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var p22 = { nod:{x: d, y: d, z: 0, w:1}, tx:{x:kx, y:0, z:h}, ty:{x:0, y:ky, z:h} }; var s = []; s[0] = PF_GEO.spline_from_nodes( [ {nod:p00.nod, tu:p00.tx}, {nod:p01.nod, tu:p01.tx}, {nod:p02.nod, tu:p02.tx} ], false); s[1] = PF_GEO.spline_from_nodes( [ {nod:p10.nod, tu:p10.tx}, {nod:p11.nod, tu:p11.tx}, {nod:p12.nod, tu:p12.tx} ], false); s[2] = PF_GEO.spline_from_nodes( [ {nod:p20.nod, tu:p20.tx}, {nod:p21.nod, tu:p21.tx}, {nod:p22.nod, tu:p22.tx} ], false); s[3] = PF_GEO.spline_from_nodes( [ {nod:p00.nod, tu:p00.ty}, {nod:p10.nod, tu:p10.ty}, {nod:p20.nod, tu:p20.ty} ], false); s[4] = PF_GEO.spline_from_nodes( [ {nod:p01.nod, tu:p01.ty}, {nod:p11.nod, tu:p11.ty}, {nod:p21.nod, tu:p21.ty} ], false); s[5] = PF_GEO.spline_from_nodes( [ {nod:p02.nod, tu:p02.ty}, {nod:p12.nod, tu:p12.ty}, {nod:p22.nod, tu:p22.ty} ], false); var coons = []; coons[0] = PF_GEO.coons( s[0][0], s[1][0], s[3][0], s[4][0] ); coons[1] = PF_GEO.coons( s[0][1], s[1][1], s[4][0], s[5][0] ); coons[2] = PF_GEO.coons( s[1][0], s[2][0], s[3][1], s[4][1] ); coons[3] = PF_GEO.coons( s[1][1], s[2][1], s[4][1], s[5][1] ); ctx.save(); ctx.lineWidth = 3; for (var i=0; i< s.length; i++) for (var j=0; j< s[i].length; j++) W3D.draw( PF.build( s[i][j], 3 ) ); ctx.lineWidth = 1; ctx.strokeStyle = "#888"; for (var i=0; i< coons.length; i++) W3D.draw( PF.build( coons[i], [3,3] ), uv ); ctx.lineWidth = 6; ctx.strokeStyle = "#f00"; W3D.draw( PF.build( PF.diag( coons[0] ), 3 ) ); W3D.draw( PF.build( PF.diag( coons[3] ), 3 ) ); ctx.restore(); W3D.draw(PF.embed(coons[0],regular_polygon({x:0.5,y:0.5,z:0,w:1}, 0.5, 3), 0, 3)); W3D.draw(PF.embed(coons[1],regular_polygon({x:0.5,y:0.5,z:0,w:1}, 0.4, 4), 0, 3)); W3D.draw(PF.embed(coons[2],regular_polygon({x:0.5,y:0.5,z:0,w:1}, 0.4, 5), 0, 3)); W3D.draw(PF.embed(coons[3],regular_polygon({x:0.5,y:0.5,z:0,w:1}, 0.4, 6), 0, 3)); } // VOLUMES function vol ( ctx, uvw, uv ) { // drawing the 6 faces of a volume var d = 150, h = 50, vol = [ [ [ {x:-d, y:-d, z:-d-h, w:1}, {x:0, y:-d, z: d-h, w:1}, {x:d, y:-d, z:-d-h, w:1} ], [ {x:-d, y:0, z:-d-h, w:1}, {x:0, y:0, z: d-h, w:1}, {x:d, y:0, z: d-h, w:1} ], [ {x:-d, y:d, z: d-h, w:1}, {x:0, y:d, z:-d-h, w:1}, {x:d, y:d, z: d-h, w:1} ] ], [ [ {x:-d, y:-d, z:-d, w:1}, {x:0, y:-d, z: d, w:1}, {x:d, y:-d, z:-d, w:1} ], [ {x:-d, y:0, z:-d, w:1}, {x:0, y:0, z: d, w:1}, {x:d, y:0, z: d, w:1} ], [ {x:-d, y:d, z: d, w:1}, {x:0, y:d, z:-d, w:1}, {x:d, y:d, z: d, w:1} ] ], [ [ {x:-d, y:-d, z:-d+h, w:1}, {x:0, y:-d, z: d+h, w:1}, {x:d, y:-d, z:-d+h, w:1} ], [ {x:-d, y:0, z:-d+h, w:1}, {x:0, y:0, z: d+h, w:1}, {x:d, y:0, z: d+h, w:1} ], [ {x:-d, y:d, z: d+h, w:1}, {x:0, y:d, z:-d+h, w:1}, {x:d, y:d, z: d+h, w:1} ] ] ]; vol = PF.stretch( vol, {x:0.2,y:0.2,z:-0.5}, {x:0.8,y:0.8,z:1.5} ); W3D.draw_vol_faces( PF.build_vol_faces( vol, [3,3]), uv ); // 1) draw volume var curve = [ {x:0.2,y:0.1,z:0}, {x:0.2,y:0.9,z:1}, {x:0.9,y:0.1,z:0}, {x:0.5,y:0.9,z:1} ]; // an embedded cubic ctx.save(); ctx.lineWidth = 1; ctx.strokeStyle = '#00f'; W3D.draw( PF.embed( vol, curve, 0, 3 ) ); // 2) draw control polygon ctx.strokeStyle = '#f00'; ctx.lineWidth = 3; W3D.draw( PF.embed( vol, curve, 4, 0 ) ); // 3) draw cubic curve ctx.restore(); } function vol_surf_curve ( ctx, uvw, uv ) { var d = 150, h = 20; var vol = [ [ [ {x:-d, y:-d, z:-d-h, w:1}, {x:0, y:-d, z: d-h, w:1}, {x:d, y:-d, z:-d-h, w:1} ], [ {x:-d, y:0, z:-d-h, w:1}, {x:0, y:0, z: d-h, w:1}, {x:d, y:0, z: d-h, w:1} ], [ {x:-d, y:d, z: d-h, w:1}, {x:0, y:d, z:-d-h, w:1}, {x:d, y:d, z: d-h, w:1} ] ], [ [ {x:-d, y:-d, z:-d+h, w:1}, {x:0, y:-d, z: d+h, w:1}, {x:d, y:-d, z:-d+h, w:1} ], [ {x:-d, y:0, z:-d+h, w:1}, {x:0, y:0, z: d+h, w:1}, {x:d, y:0, z: d+h, w:1} ], [ {x:-d, y:d, z: d+h, w:1}, {x:0, y:d, z:-d+h, w:1}, {x:d, y:d, z: d+h, w:1} ] ] ]; W3D.rotate( vol, 'OZ', 45 ); ctx.save(); ctx.strokeStyle = '#444'; W3D.draw( PF.build( vol, [4, 4, 1] ), uvw ); var diag_vol = PF.diag( vol ); ctx.strokeStyle = '#00f'; uv = (uv == 'u')? 'v' : 'u'; W3D.draw( PF.build( diag_vol, [3,3] ), uv ); var diag_diag_vol = PF.diag( diag_vol ); ctx.lineWidth = 3; ctx.strokeStyle = '#f00'; W3D.draw( PF.build( diag_diag_vol, 3 ) ); ctx.restore(); } function hairs ( ctx, uv, uvw ) { } function thick_torus ( ctx, uv, uvw ) { var r1 = 200; // radius of path circle var u0 = -0.35; // u0= 0.000 and u1=1.000 -> 180° var u1 = 1.35; // u0=-0.707 and u1=1.707 -> 360° var c1 = PF_GEO.circle_arc( 1, u0, u1 ); // radius must be 1 var r2 = 30; // radius of section circle var c2 = PF_GEO.circle_arc( r2, 0, 1 ); W3D.translate( c2, r1, 0, 0 ); // section circle is translated on OX var surf1 = PF_GEO.cross( c1, c2 ); var r2 = 60; // radius of section circle var c2 = PF_GEO.circle_arc( r2, u0, u1 ); W3D.translate( c2, r1, 0, 0 ); // section circle is translated on OX var surf2 = PF_GEO.cross( c1, c2 ); var vol = [surf1, surf2]; ctx.save(); ctx.strokeStyle = '#888'; W3D.draw( PF.build( vol, [3,4,0] ), uvw ); // W3D.draw_vol_faces( PF.build_vol_faces( vol, [3,4] ), uv ); ctx.strokeStyle = '#f00'; ctx.lineWidth = 6; var diag = PF.diag( PF.diag( vol ) ); W3D.draw( PF.build( diag, 4 ) ); ctx.strokeStyle = '#0f0'; var diag = PF.diag( surf1 ); W3D.draw( PF.build( diag, 4 ) ); ctx.strokeStyle = '#00f'; var diag = PF.diag( surf2 ); W3D.draw( PF.build( diag, 4 ) ); ctx.restore(); } function thick_ship ( ctx, d, uv ) { // 1) defining the extrados shape with 6 polygons, each 5 control points var ship = g_ship // g_ship is defined above // 2) some tunings W3D.rotate( ship, 'OX', 90 ); W3D.scale( ship, 250, -250, 250 ); W3D.translate( ship, -100, 0, 0 ); // 3) drawing the 6 control polygons (x5) W3D.draw( ship, uv ); // 4) building the volume of the shell var vol = PF_GEO.parallel( ship, 2, -d ); // d : distance intrados/extrados // 5) drawing the 6 faces of the shell W3D.draw_vol_faces( PF.build_vol_faces( vol, [1,1] ), uv ); // 6) drawing diagonals of intrados and extrados : W3D.draw( PF.build( PF.diag( vol[0] ), 3 ) ); W3D.draw( PF.build( PF.diag( vol[1] ), 3 ) ); W3D.scale( vol[0], 1,-1,1 ); W3D.scale( vol[1], 1,-1,1 ); W3D.draw( PF.build( PF.diag( vol[0] ), 3 ) ); W3D.draw( PF.build( PF.diag( vol[1] ), 3 ) ); // W3D.scale( vol[1], 1, 1, -1/64 ); // W3D.draw( vol[1], 'v' ); } function display ( ctx, type, uv, uvw ) { switch (type) { // CURVE case 1: mycubic ( ctx ); W3D.text_display( 'A cubic (pL4) built on the cube scene.' ); break; case 2: three_circle_arcs ( ctx ); W3D.text_display( 'Circle arcs made of pL3, pL4, pL5 and variable ends.' ); break; case 3: polycurves ( 48 ); W3D.text_display( 'A cubic (pL4) rotated 48 times.' ); break; case 4: spiral ( 0.08, 0.01, 20, 3600 ); W3D.text_display( 'Playing with a spiral controlled by 4 parameters.' ); break; case 5: regular_polygon_torus ( ctx ); W3D.text_display( 'A regular polygon rotated 90 times, not a volume.' ); break; case 6: new_spline( ctx ); W3D.text_display( 'An interpolating closed cubic spline built on 4 nodes and tangent vectors.' ); break; case 7: tube_spline( ctx, uv ); W3D.text_display( 'A circle tube on an interpolating cubic spline.' ); break; // SURFACE case 8: mysurface ( ctx, uv ); W3D.text_display( 'A pS33 stretched between {0.6,0.1} and {0.9,0.4}, and its diagonal.' ); break; case 9: surf_uv( ctx, uv ); W3D.text_display( 'A pSurface (pS33) and a point with two coordinate lines .'); break; case 10: diagonal ( ctx, uv ); W3D.text_display( 'A pSurface (pS35) and a diagonal.' ); break; case 11: viviani ( ctx, uv ); W3D.text_display( 'Intersection sphere & cylinder gives a Viviani-s curve.'); break; case 12: ship ( ctx, uv ); W3D.text_display( 'A pSurface (pS56), its 6 control polygons and two diagonals.' ); break; case 13: torus( ctx, uv ); W3D.text_display( 'A torus made of 8 pS33 patches.' ); break; case 14: ipCubic( ctx, uv ); W3D.text_display( 'A cubic (pL4) and its control polygon embedded in a (pS33).' ); break; case 15: tube( ctx, uv ); W3D.text_display( 'A tube generated by an open circle arc along a cubic with some of its TNB axis.' ); break; case 16: new_coons ( ctx, uv ); W3D.text_display( 'A Coons surface built on 4 cubic border curves.' ); break; case 17: coons_from_splines ( ctx, uv ); W3D.text_display( 'Coons patches built on 6 cubic-spline curves with embedded regular polygons' ); break; // VOLUME case 18: vol( ctx, uvw, uv ); W3D.text_display( 'A cubic and its control polygon embedded in a stretched pVolume (pV332).' ); break; case 19: vol_surf_curve ( ctx, uvw, uv ); W3D.text_display( 'A pVolume (pV332) and its first and second diagonals.' ); break; case 20: pTorus( ctx, uv ); W3D.text_display( 'Drawing a segment immersed in a pTorus (pS55) open patch (near to be closed).' ); break; case 21: lathe( ctx, uv ); W3D.text_display( 'Lathe on a cubic.' ); break; case 22: hairs ( ctx, uv, uvw ); W3D.text_display( 'A pS33 and a pL4 -> Hairs volume in the wind. Desactivated for a while ...' ); break; case 23: thick_torus ( ctx, uv, uvw ); W3D.text_display( 'A thick pTorus (pV552), the diagonals of the two surfaces and of the volume.' ); break; case 24: thick_ship ( ctx, 30, uv ); W3D.text_display( 'A ship shell with a constant thickness and its intrados/extrados diagonals.' ); break; default: W3D.text_display( 'Sorry, the figure [ type = ' + type + ' ] is undefined !' ); } } return { display:display }; })(); // end PF_TEST //////////////// // END OF MODULI //////////////// //////////////// // 6) DISPLAY SCENE //////////////// function display ( can ) { var rotx = $('rotx').value; var roty = $('roty').value; var rotz = $('rotz').value; var focale = $('focale').value; // infinite -> orthographic projection var scale = $('scale').value; // scale of the scene var type = parseInt( $('type').value ); // choice of the figure to be displayed var uv = $('uv').value; var uvw = $('uvw').value; var ctx = W3D.init2D( can ); W3D.init3D ( rotx, roty, rotz, focale, scale ); // W3D.init3D( 0, 0, 0, 500000, 2 ); PF_TEST.display ( ctx, type, uv, uvw ); } $('mycanvas').innerHTML = display('mycanvas'); °°} °°° DEFINING THE TOOL PALETTE :°°° {div {@ position:absolute; top:165px; left:165px; width:140px; white-space:pre-wrap; }{drag} 1) camera : Rot/X : {input {@ id:rotx; code:code; value:300; background:#ccc; width:50px; text-align:center;}} {slider {@ min:0; max:360; value:300; step:10; input_id:rotx; code_id:code;}} Rot/Y : {input {@ id:roty; code:code; value:0; background:#ccc; width:50px; text-align:center;}} {slider {@ min:0; max:360; value:0; step:10; input_id:roty; code_id:code;}} Rot/Z : {input {@ id:rotz; code:code; value:60; background:#ccc; width:50px; text-align:center;}} {slider {@ min:0; max:360; value:60; step:10; input_id:rotz; code_id:code;}} focale : {input {@ id:focale; code:code; value:500; background:#ccc; width:80px; text-align:center;}} {slider {@ min:350; max:50000; value:500; step:100; input_id:focale; code_id:code;}} scale : {input {@ id:scale; code:code; value:1; background:#ccc; width:30px; text-align:center;}} {slider {@ min:0.1; max:2; value:1; step:0.1; input_id:scale; code_id:code;}} 2) display : srf (u|v|w) : {input {@ id:uv; code:code; value:u; background:#ccc; width:30px; text-align:center;}} vol (u|v|w) : {input {@ id:uvw; code:code; value:w; background:#ccc; width:30px; text-align:center;}} 3) figures : type (1..24) : {input {@ id:type; code:code; value:17; background:#ccc; width:30px; text-align:center;}} {submit {@ code:code; value:draw;}} }