{"id":7208,"date":"2026-10-22T10:20:28","date_gmt":"2026-10-22T04:50:28","guid":{"rendered":"https:\/\/acbtimes.in\/?page_id=7208"},"modified":"2026-10-22T13:23:52","modified_gmt":"2026-10-22T07:53:52","slug":"form-sketching-practice","status":"publish","type":"page","link":"https:\/\/acbtimes.in\/hi\/form-sketching-practice\/","title":{"rendered":"\u0906\u0915\u0943\u0924\u093f \u0930\u0947\u0916\u093e\u0902\u0915\u0928 \u0905\u092d\u094d\u092f\u093e\u0938"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"7208\" class=\"elementor elementor-7208\">\n\t\t\t\t<div class=\"elementor-element elementor-element-722dd20 e-flex e-con-boxed e-con e-parent\" data-id=\"722dd20\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-dd40824 elementor-widget elementor-widget-wp-widget-custom_html\" data-id=\"dd40824\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"wp-widget-custom_html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<div class=\"textwidget custom-html-widget\"><!-- ============================================================\r\n  Form Sketching Practice - 3D shapes you can rotate and draw from\r\n  HOW TO USE IN WORDPRESS: add a \"Custom HTML\" block to your page\r\n  and paste this whole file into it. Nothing else is needed.\r\n  (Uses three.js r128 from cdnjs.cloudflare.com)\r\n============================================================= -->\r\n<div id=\"fsp-app\">\r\n  <h3 class=\"fsp-title\">Form sketching practice<small>Pick a form, turn it, draw it from every angle.<\/small><\/h3>\r\n  <div class=\"fsp-shapes\" id=\"fsp-shapes\"><\/div>\r\n  <div class=\"fsp-stage\">\r\n    \r\n    <div class=\"fsp-ov fsp-name\"><\/div>\r\n    <div class=\"fsp-ov fsp-time\" style=\"display:none\"><\/div>\r\n    <div class=\"fsp-ov fsp-hint\">Drag to rotate. Scroll or pinch to zoom. Twist two fingers to roll. Double-click to reset.<\/div>\r\n  <\/div>\r\n  <div class=\"fsp-tip\" id=\"fsp-tip\"><\/div>\r\n  <div class=\"fsp-controls\">\r\n    <div class=\"fsp-group\"><div class=\"fsp-lbl\">Show<\/div><div class=\"fsp-row\">\r\n      <button type=\"button\" class=\"fsp-chip\" data-tog=\"lines\">Contour lines<\/button>\r\n      <button type=\"button\" class=\"fsp-chip\" data-tog=\"axes\">Axes<\/button>\r\n      <button type=\"button\" class=\"fsp-chip\" data-tog=\"floor\">Floor and shadow<\/button>\r\n      <button type=\"button\" class=\"fsp-chip\" data-tog=\"spin\">Auto-spin<\/button>\r\n    <\/div><\/div>\r\n    <div class=\"fsp-group\"><div class=\"fsp-lbl\">Shading<\/div>\r\n      <div class=\"fsp-seg\" data-seg=\"shade\">\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"soft\">Soft<\/button>\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"dramatic\">Dramatic<\/button>\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"sil\">Silhouette<\/button>\r\n      <\/div><\/div>\r\n    <div class=\"fsp-group\"><div class=\"fsp-lbl\">Perspective<\/div>\r\n      <div class=\"fsp-seg\" data-seg=\"persp\">\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"flat\" title=\"Telephoto, almost no convergence\">Flat<\/button>\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"normal\">Normal<\/button>\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"wide\" title=\"Wide angle, strong foreshortening\">Wide<\/button>\r\n      <\/div><\/div>\r\n    <div class=\"fsp-group\"><div class=\"fsp-lbl\">Background<\/div>\r\n      <div class=\"fsp-seg\" data-seg=\"bg\">\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"light\">Light<\/button>\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"mid\">Mid grey<\/button>\r\n        <button type=\"button\" class=\"fsp-sb\" data-v=\"dark\">Dark<\/button>\r\n      <\/div><\/div>\r\n    <div class=\"fsp-group\"><div class=\"fsp-lbl\">Light<\/div>\r\n      <label class=\"fsp-sl\">Direction<\/label>\r\n      <label class=\"fsp-sl\">Height<\/label>\r\n    <\/div>\r\n    <div class=\"fsp-group\" id=\"fsp-posebox\" hidden><div class=\"fsp-lbl\">Mannequin pose<\/div>\r\n      <\/div>\r\n    <div class=\"fsp-group\"><div class=\"fsp-lbl\">View<\/div><div class=\"fsp-row\">\r\n      <button type=\"button\" class=\"fsp-btn pri\" id=\"fsp-rand\">Random angle<\/button>\r\n      <button type=\"button\" class=\"fsp-btn\" id=\"fsp-reset\">Reset view<\/button>\r\n      <button type=\"button\" class=\"fsp-btn\" id=\"fsp-full\">Full screen<\/button>\r\n    <\/div><\/div>\r\n    <div class=\"fsp-group\"><div class=\"fsp-lbl\">Timed practice (new angle each round)<\/div><div class=\"fsp-row\">\r\n      \r\n        30 sec1 min2 min5 min\r\n      \r\n      <button type=\"button\" class=\"fsp-btn\" id=\"fsp-timer\">Start timer<\/button>\r\n      <button type=\"button\" class=\"fsp-chip on\" data-tog=\"beep\">Beep<\/button>\r\n    <\/div><\/div>\r\n  <\/div>\r\n<\/div>\r\n<script>(function(){\r\nvar root=document.getElementById('fsp-app');\r\nif(!root)return;\r\nfunction start(){\r\n  if(!window.THREE){root.querySelector('.fsp-stage').innerHTML='<p style=\"padding:24px;color:#444\">The 3D library could not be loaded. Check your connection and reload the page.<\/p>';return;}\r\n  try {\r\n  init();\r\n} catch (err) {\r\n  console.error(\"Form Sketching error:\", err);\r\n  root.querySelector('.fsp-stage').innerHTML =\r\n    '<pre style=\"padding:12px;color:#b3261e;white-space:pre-wrap;font-size:12px;\">' +\r\n    String(err && (err.stack || err.message) || err) +\r\n    '<\/pre>';\r\n}\r\n}\r\nif(window.THREE)start();else window.addEventListener('load',start);\r\n\r\nfunction init(){\r\nvar T=THREE,D=Math.PI\/180;\r\nvar $=function(s){return root.querySelector(s);};\r\nvar $$=function(s){return Array.prototype.slice.call(root.querySelectorAll(s));};\r\nvar stage=$('.fsp-stage'),canvas=$('#fsp-canvas');\r\nvar S={shape:'cube',lines:true,axes:false,floor:false,spin:false,beep:true,shade:'soft',persp:'normal',bg:'light',az:-40,el:45,zoom:1};\r\nvar dirty=true,lineObjs=[],J={},poseTarget=null,target=null,dragging=false,vx=0,vy=0,lastMove=0,SH=null;\r\n\r\n\/* ---------- renderer, scene, lights ---------- *\/\r\nvar renderer=new T.WebGLRenderer({canvas:canvas,antialias:true});\r\nrenderer.setPixelRatio(Math.min(window.devicePixelRatio||1,2));\r\nrenderer.shadowMap.enabled=true;renderer.shadowMap.type=T.PCFSoftShadowMap;\r\nvar scene=new T.Scene();scene.background=new T.Color(0xe6e8eb);\r\nvar camera=new T.PerspectiveCamera(40,1,0.5,300);\r\nvar hemi=new T.HemisphereLight(0xffffff,0x8a8f98,0.75);scene.add(hemi);\r\nvar sun=new T.DirectionalLight(0xffffff,0.6);sun.castShadow=true;\r\nsun.shadow.mapSize.set(2048,2048);\r\nvar sc=sun.shadow.camera;sc.left=-6;sc.right=6;sc.top=6;sc.bottom=-6;sc.near=1;sc.far=26;\r\nsun.shadow.bias=-0.0006;sun.shadow.normalBias=0.03;scene.add(sun);\r\nvar floor=new T.Group();\r\nvar ground=new T.Mesh(new T.PlaneGeometry(18,18),new T.ShadowMaterial({opacity:0.3}));\r\nground.rotation.x=-Math.PI\/2;ground.receiveShadow=true;\r\nvar grid=new T.GridHelper(18,18,0x888888,0x888888);grid.material.transparent=true;grid.material.opacity=0.35;\r\nfloor.add(ground,grid);floor.position.y=-2.85;floor.visible=false;scene.add(floor);\r\nvar pivot=new T.Group(),holder=new T.Group();pivot.add(holder);scene.add(pivot);\r\nvar axes=new T.AxesHelper(2.8);axes.material.depthTest=false;axes.renderOrder=5;axes.visible=false;pivot.add(axes);\r\n\r\n\/* ---------- materials ---------- *\/\r\nvar CLAY=new T.MeshStandardMaterial({color:0xcfcac2,roughness:0.95,metalness:0,side:T.DoubleSide,polygonOffset:true,polygonOffsetFactor:1,polygonOffsetUnits:1});\r\nvar DARK=new T.MeshStandardMaterial({color:0x20232a,roughness:0.5});\r\nvar LINE=new T.LineBasicMaterial({color:0x2b2f36,transparent:true,opacity:0.5});\r\nvar ACC=new T.LineBasicMaterial({color:0x2f5bd8});\r\n\r\n\/* ---------- geometry helpers ---------- *\/\r\nfunction smooth(geo){\r\n  geo.computeVertexNormals();\r\n  var p=geo.attributes.position,n=geo.attributes.normal,map={},i,k;\r\n  for(i=0;i<p.count;i++){\r\n    k=Math.round(p.getX(i)*1e4)+','+Math.round(p.getY(i)*1e4)+','+Math.round(p.getZ(i)*1e4);\r\n    var a=map[k]||(map[k]=[0,0,0]);a[0]+=n.getX(i);a[1]+=n.getY(i);a[2]+=n.getZ(i);\r\n  }\r\n  for(i=0;i<p.count;i++){\r\n    k=Math.round(p.getX(i)*1e4)+','+Math.round(p.getY(i)*1e4)+','+Math.round(p.getZ(i)*1e4);\r\n    var b=map[k],l=Math.sqrt(b[0]*b[0]+b[1]*b[1]+b[2]*b[2])||1;\r\n    n.setXYZ(i,b[0]\/l,b[1]\/l,b[2]\/l);\r\n  }\r\n}\r\n\/* Builds grid contour lines from a geometry whose vertices sit on a (cols+1) x (rows+1) grid *\/\r\nfunction gridGeo(geo,cols,rows,o){\r\n  o=o||{};var blocks=o.blocks||1,cs=o.cStep||1,rs=o.rStep||1,p=geo.attributes.position,per=(cols+1)*(rows+1),a=[];\r\n  function pt(b,r,c){var i=b*per+r*(cols+1)+c;a.push(p.getX(i),p.getY(i),p.getZ(i));}\r\n  for(var b=0;b<blocks;b++){\r\n    for(var r=0;r<=rows;r++){if(r%rs===0||r===rows){for(var c=0;c<cols;c++){pt(b,r,c);pt(b,r,c+1);}}}\r\n    for(var c2=0;c2<=cols;c2++){if(c2%cs===0||c2===cols){for(var r2=0;r2<rows;r2++){pt(b,r2,c2);pt(b,r2+1,c2);}}}\r\n  }\r\n  var lg=new T.BufferGeometry();lg.setAttribute('position',new T.Float32BufferAttribute(a,3));return lg;\r\n}\r\nfunction addLines(mesh,lg,mat){\r\n  var l=new T.LineSegments(lg,mat||LINE);l.visible=S.lines&&S.shade!=='sil';lineObjs.push(l);mesh.add(l);return l;\r\n}\r\nfunction mk(geo,o){\r\n  o=o||{};\r\n  var m=new T.Mesh(geo,o.mat||CLAY);m.castShadow=true;m.receiveShadow=true;\r\n  if(o.pos)m.position.set(o.pos[0],o.pos[1],o.pos[2]);\r\n  if(o.rot)m.rotation.set(o.rot[0],o.rot[1],o.rot[2]);\r\n  if(o.scale)m.scale.set(o.scale[0],o.scale[1],o.scale[2]);\r\n  if(o.parent)o.parent.add(m);\r\n  if(o.lineGeo)addLines(m,o.lineGeo);\r\n  else if(o.grid)addLines(m,gridGeo(geo,o.grid[0],o.grid[1],o.gopt));\r\n  else if(o.edges)addLines(m,new T.EdgesGeometry(geo,o.edges));\r\n  return m;\r\n}\r\nvar SPH=new T.SphereGeometry(1,20,14);SPH.userData.keep=true;\r\nvar SPH_L=gridGeo(SPH,20,14,{cStep:2,rStep:2});SPH_L.userData.keep=true;\r\nfunction finGeo(pts,depth){\r\n  var s=new T.Shape();s.moveTo(pts[0][0],pts[0][1]);\r\n  for(var i=1;i<pts.length;i++)s.lineTo(pts[i][0],pts[i][1]);\r\n  s.closePath();\r\n  var g=new T.ExtrudeGeometry(s,{depth:depth,bevelEnabled:false});g.translate(0,0,-depth\/2);return g;\r\n}\r\nfunction ring(fn,n){\r\n  var a=[],i,p,q;\r\n  for(i=0;i<n;i++){p=fn(i\/n*2*Math.PI);q=fn((i+1)\/n*2*Math.PI);a.push(p[0],p[1],p[2],q[0],q[1],q[2]);}\r\n  var g=new T.BufferGeometry();g.setAttribute('position',new T.Float32BufferAttribute(a,3));return g;\r\n}\r\n\r\n\/* ---------- shape builders ---------- *\/\r\nfunction bSphereLike(rad,w,h,fn,gopt){\r\n  var g=new T.Group(),geo=new T.SphereGeometry(1,w,h),p=geo.attributes.position;\r\n  for(var i=0;i<p.count;i++){var r=fn(p.getX(i),p.getY(i),p.getZ(i));p.setXYZ(i,r[0]*rad,r[1]*rad,r[2]*rad);}\r\n  smooth(geo);mk(geo,{parent:g,grid:[w,h],gopt:gopt});return g;\r\n}\r\nfunction bMannequin(){\r\n  var g=new T.Group(),r=new T.Group();g.add(r);r.scale.setScalar(0.85);r.position.y=0.05;J={};\r\n  function jt(par,n,x,y,z){var o=new T.Group();o.position.set(x,y,z);par.add(o);J[n]=o;return o;}\r\n  function el(par,rx,ry,rz,x,y,z){return mk(SPH,{parent:par,scale:[rx,ry,rz],pos:[x,y,z],lineGeo:SPH_L});}\r\n  function cy(par,rt,rb,h,y){return mk(new T.CylinderGeometry(rt,rb,h,14,3),{parent:par,pos:[0,y,0],grid:[14,3],gopt:{cStep:2}});}\r\n  var pel=jt(r,'pelvis',0,0,0);el(pel,0.5,0.3,0.34,0,0,0);\r\n  var tor=jt(r,'torso',0,0.2,0);\r\n  el(tor,0.4,0.3,0.29,0,0.22,0);\r\n  el(tor,0.6,0.55,0.4,0,0.78,0);\r\n  cy(tor,0.13,0.16,0.3,1.38);\r\n  var hd=jt(tor,'head',0,1.5,0);\r\n  el(hd,0.3,0.38,0.34,0,0.32,0.02);\r\n  mk(new T.ConeGeometry(0.06,0.16,10),{parent:hd,pos:[0,0.27,0.38],rot:[Math.PI\/2,0,0]});\r\n  [['A',1],['B',-1]].forEach(function(s){\r\n    var n=s[0],k=s[1];\r\n    var sh=jt(tor,'sh'+n,k*0.62,1.12,0);\r\n    el(sh,0.19,0.19,0.19,0,0,0);\r\n    cy(sh,0.14,0.105,0.8,-0.4);\r\n    var eb=jt(sh,'el'+n,0,-0.8,0);\r\n    el(eb,0.108,0.108,0.108,0,0,0);\r\n    cy(eb,0.1,0.07,0.75,-0.375);\r\n    el(eb,0.075,0.15,0.05,0,-0.88,0);\r\n    var hp=jt(pel,'hip'+n,k*0.27,-0.12,0);\r\n    el(hp,0.2,0.2,0.2,0,0,0);\r\n    cy(hp,0.2,0.14,1.1,-0.55);\r\n    var kn=jt(hp,'kn'+n,0,-1.1,0);\r\n    el(kn,0.14,0.14,0.14,0,0,0);\r\n    cy(kn,0.13,0.085,1.0,-0.5);\r\n    el(kn,0.09,0.09,0.09,0,-1.0,0);\r\n    el(kn,0.1,0.08,0.26,0,-1.08,0.12);\r\n  });\r\n  return g;\r\n}\r\nfunction bFish(){\r\n  var g=new T.Group(),geo=new T.SphereGeometry(1,36,24);\r\n  geo.rotateZ(Math.PI\/2);\r\n  var p=geo.attributes.position;\r\n  for(var i=0;i<p.count;i++){\r\n    var x=p.getX(i),y=p.getY(i),z=p.getZ(i),s=x<0?0.28+0.72*Math.pow(1+x,0.8):1-0.3*x*x;\r\n    p.setXYZ(i,x*1.9,y*0.78*s,z*0.5*s);\r\n  }\r\n  smooth(geo);mk(geo,{parent:g,grid:[36,24],gopt:{cStep:3,rStep:2}});\r\n  mk(finGeo([[0,0],[-0.85,0.8],[-0.55,0],[-0.85,-0.8]],0.05),{parent:g,pos:[-1.78,0,0],edges:25});\r\n  mk(finGeo([[0.6,0],[0.05,0.78],[-0.2,0.45],[-0.75,0.1],[-0.75,0]],0.05),{parent:g,pos:[0,0.55,0],edges:25});\r\n  mk(finGeo([[0.1,0],[-0.5,-0.55],[-0.7,0]],0.05),{parent:g,pos:[-0.2,-0.58,0],edges:25});\r\n  [1,-1].forEach(function(k){\r\n    mk(SPH,{parent:g,scale:[0.5,0.04,0.2],pos:[0.5,-0.3,k*0.5],rot:[0,k*0.5,0],lineGeo:SPH_L});\r\n    mk(SPH,{parent:g,scale:[0.12,0.12,0.12],pos:[1.15,0.22,k*0.3],mat:DARK});\r\n  });\r\n  return g;\r\n}\r\nfunction bLoomis(){\r\n  var R=1.9,L=R*0.75,g=new T.Group(),geo=new T.SphereGeometry(R,48,32),p=geo.attributes.position,flat=[],i;\r\n  for(i=0;i<p.count;i++){var x=p.getX(i);if(Math.abs(x)>L){flat.push([i,x>0?1:-1]);p.setX(i,x>0?L:-L);}}\r\n  smooth(geo);\r\n  var n=geo.attributes.normal;flat.forEach(function(f){n.setXYZ(f[0],f[1],0,0);});\r\n  mk(geo,{parent:g,grid:[48,32],gopt:{cStep:4,rStep:4}});\r\n  var cl=function(v){return Math.max(-L,Math.min(L,v));},rr=R*1.004;\r\n  var m1=new T.Group();g.add(m1);\r\n  addLines(m1,ring(function(a){return [cl(rr*Math.cos(a)),0,rr*Math.sin(a)];},64),ACC);\r\n  addLines(m1,ring(function(a){return [0,rr*Math.sin(a),rr*Math.cos(a)];},64),ACC);\r\n  mk(new T.ConeGeometry(0.22,0.55,14),{parent:g,pos:[0,-0.3,R*0.86],rot:[Math.PI\/2,0,0]});\r\n  return g;\r\n}\r\n\r\nvar SHAPES=[\r\n {id:'cube',name:'Cube',view:[0.5,-0.7],tip:\"<b>Cube.<\/b> Draw it in one-, two- and three-point perspective. Parallel edges must run toward the same vanishing point. Start with the nearest corner, then the edges that leave it.\",\r\n  build:function(){var g=new T.Group();mk(new T.BoxGeometry(2.5,2.5,2.5,2,2,2),{parent:g,grid:[2,2],gopt:{blocks:6}});return g;}},\r\n {id:'cuboid',name:'Cuboid',view:[0.45,-0.65],tip:\"<b>Cuboid.<\/b> Same rules as the cube, but the three edge lengths shrink differently as you turn it. Check which direction looks longest and how much each one is foreshortened.\",\r\n  build:function(){var g=new T.Group();mk(new T.BoxGeometry(3.7,1.7,2.2,2,2,2),{parent:g,grid:[2,2],gopt:{blocks:6}});return g;}},\r\n {id:'sphere',name:'Sphere',view:[0.4,-0.5],tip:\"<b>Sphere.<\/b> Its outline is always a circle. The rings around it are ellipses, and they open up the more you look down on them. Practise the ellipse until it is a single smooth stroke.\",\r\n  build:function(){var g=new T.Group();mk(new T.SphereGeometry(1.75,32,20),{parent:g,grid:[32,20],gopt:{cStep:2,rStep:2}});return g;}},\r\n {id:'cylinder',name:'Cylinder',view:[0.55,-0.6],tip:\"<b>Cylinder.<\/b> Draw the central axis first, then ellipses across it. Ellipses further from your eye level are rounder. The sides are tangent to both ends, not joined at the ellipse corners.\",\r\n  build:function(){var g=new T.Group();mk(new T.CylinderGeometry(1.2,1.2,3.3,32,4),{parent:g,grid:[32,4],gopt:{cStep:4}});return g;}},\r\n {id:'cone',name:'Cone',view:[0.5,-0.6],tip:\"<b>Cone.<\/b> Find the axis, place the base ellipse around it, then draw two tangent lines up to the apex. A cone is a cylinder that shrinks to a point.\",\r\n  build:function(){var g=new T.Group();mk(new T.ConeGeometry(1.6,3.4,32,5),{parent:g,grid:[32,5],gopt:{cStep:4}});return g;}},\r\n {id:'pyramid',name:'Pyramid',view:[0.45,-0.6],tip:\"<b>Pyramid.<\/b> Draw the square base in perspective first. Its diagonals cross at the centre, and the apex sits straight above that point.\",\r\n  build:function(){var g=new T.Group(),geo=new T.ConeGeometry(2.1,3.1,4,4);geo.rotateY(Math.PI\/4);mk(geo,{parent:g,grid:[4,4]});return g;}},\r\n {id:'torus',name:'Torus',view:[0.9,-0.4],tip:\"<b>Torus.<\/b> A ring of overlapping spheres. Sketch the ring as an ellipse, then wrap the tube around it. Cross-contours show how the tube turns.\",\r\n  build:function(){var g=new T.Group();mk(new T.TorusGeometry(1.5,0.55,18,44),{parent:g,grid:[44,18],gopt:{cStep:2,rStep:3}});return g;}},\r\n {id:'egg',name:'Egg',view:[0.4,-0.6],tip:\"<b>Egg.<\/b> Two spheres of different size blended together. Find the long axis first, then decide where the form is widest.\",\r\n  build:function(){return bSphereLike(1.5,36,24,function(x,y,z){var k=1-0.22*y;return [x*k*1.05,y*1.3,z*k*1.05];},{cStep:3,rStep:3});}},\r\n {id:'blob',name:'Organic blob',view:[0.5,-0.6],tip:\"<b>Organic blob.<\/b> A pebble-like form with no straight lines. Practise cross-contour lines that wrap the surface, and find the light, the core shadow and the cast shadow.\",\r\n  build:function(){return bSphereLike(1.55,32,22,function(x,y,z){var r=1+0.17*Math.sin(2.1*x+0.7)*Math.cos(1.7*y)+0.12*Math.sin(2.7*z+1.3*x)+0.08*Math.cos(4*y+2*z);return [x*r*1.12,y*r*0.9,z*r];},{cStep:2,rStep:2});}},\r\n {id:'vase',name:'Vase',view:[0.45,-0.5],tip:\"<b>Vase.<\/b> A stack of ellipses along one straight axis. Draw the axis, add ellipses at the key widths, then connect their edges to make the silhouette.\",\r\n  build:function(){\r\n    var g=new T.Group(),pts=[new T.Vector2(0.001,-1.9)],i;\r\n    for(i=0;i<=28;i++){var t=i\/28,y=-1.9+3.8*t,r=0.55+0.75*Math.sin(Math.PI*Math.pow(t,0.72));if(t>0.9)r+=0.12*(t-0.9)\/0.1;pts.push(new T.Vector2(r,y));}\r\n    var geo=new T.LatheGeometry(pts,40);\r\n    mk(geo,{parent:g,grid:[pts.length-1,40],gopt:{cStep:3,rStep:4}});return g;}},\r\n {id:'loomis',name:'Loomis head',view:[0.15,-0.55],tip:\"<b>Loomis head.<\/b> A ball with the sides sliced off. The blue rings are the brow line and the centre line. The small wedge marks the front. Use them to place the eyes, nose and ears.\",\r\n  build:bLoomis},\r\n {id:'mannequin',name:'Mannequin',view:[0.1,-0.5],upright:true,tip:\"<b>Mannequin.<\/b> See the figure as simple forms: ribcage, pelvis, cylinders for the limbs. Look for the line of action through the whole pose before you add detail. Try the pose list.\",\r\n  build:bMannequin},\r\n {id:'fish',name:'Fish',view:[0.25,-0.8],upright:true,tip:\"<b>Fish.<\/b> An ellipsoid body that tapers toward the tail, with flat wedge fins. Follow the spine curve first, then place the fins and eye along it.\",\r\n  build:bFish}\r\n];\r\n\r\n\/* ---------- mannequin poses (degrees, [x,y,z] per joint) ---------- *\/\r\nvar POSES={\r\n standing:{n:\"Standing\",v:{shA:[0,0,7],shB:[0,0,-7],elA:[-10,0,0],elB:[-10,0,0],hipA:[0,0,2],hipB:[0,0,-2]}},\r\n relaxed:{n:\"Relaxed (weight on one leg)\",v:{pelvis:[0,0,4],torso:[0,0,-5],head:[0,0,2],shA:[8,0,10],shB:[-6,0,-14],elA:[-18,0,0],elB:[-35,0,0],hipA:[0,0,-4],hipB:[-12,0,-2],knA:[2,0,0],knB:[20,0,0]}},\r\n tpose:{n:\"T-pose\",v:{shA:[0,0,90],shB:[0,0,-90]}},\r\n walking:{n:\"Walking\",v:{pelvis:[0,6,0],torso:[0,-10,0],shA:[28,0,6],shB:[-28,0,-6],elA:[-20,0,0],elB:[-35,0,0],hipA:[-28,0,2],hipB:[20,0,-2],knA:[8,0,0],knB:[32,0,0]}},\r\n running:{n:\"Running\",v:{pelvis:[0,8,0],torso:[14,-14,0],head:[-10,0,0],shA:[55,0,10],shB:[-70,0,-10],elA:[-95,0,0],elB:[-95,0,0],hipA:[-75,0,2],hipB:[28,0,-2],knA:[95,0,0],knB:[70,0,0]}},\r\n reaching:{n:\"Reaching up\",v:{torso:[6,0,0],head:[-14,0,0],shA:[-165,0,8],shB:[18,0,-10],elA:[-6,0,0],elB:[-25,0,0],hipA:[-6,0,2],hipB:[10,0,-2],knB:[14,0,0]}},\r\n sitting:{n:\"Sitting\",v:{torso:[-4,0,0],shA:[-30,0,6],shB:[-30,0,-6],elA:[-75,0,0],elB:[-75,0,0],hipA:[-90,0,6],hipB:[-90,0,-6],knA:[90,0,0],knB:[90,0,0]}}\r\n};\r\nvar poseSel=$('#fsp-pose'),curPose='standing';\r\nObject.keys(POSES).forEach(function(k){var o=document.createElement('option');o.value=k;o.textContent=POSES[k].n;poseSel.appendChild(o);});\r\nfunction setPose(name,instant){\r\n  var P=POSES[name];if(!P)return;curPose=name;poseSel.value=name;poseTarget={};\r\n  for(var k in J){var v=P.v[k]||[0,0,0];poseTarget[k]=[v[0]*D,v[1]*D,v[2]*D];}\r\n  if(instant){for(var j in poseTarget){J[j].rotation.set(poseTarget[j][0],poseTarget[j][1],poseTarget[j][2]);}poseTarget=null;}\r\n  dirty=true;\r\n}\r\n\r\n\/* ---------- appearance ---------- *\/\r\nfunction applyLight(){\r\n  var a=S.az*D,e=S.el*D;\r\n  sun.position.set(Math.cos(e)*Math.sin(a)*9,Math.sin(e)*9,Math.cos(e)*Math.cos(a)*9);dirty=true;\r\n}\r\nfunction applyLines(){\r\n  lineObjs.forEach(function(l){l.visible=S.lines&&S.shade!=='sil';});\r\n  axes.visible=S.axes;floor.visible=S.floor;dirty=true;\r\n}\r\nfunction applyShade(){\r\n  var dark=S.bg==='dark';\r\n  if(S.shade==='soft'){hemi.intensity=0.75;sun.intensity=0.6;CLAY.color.set(0xcfcac2);CLAY.emissive.set(0x000000);}\r\n  else if(S.shade==='dramatic'){hemi.intensity=0.1;sun.intensity=1.4;CLAY.color.set(0xcfcac2);CLAY.emissive.set(0x000000);}\r\n  else{hemi.intensity=0;sun.intensity=0;CLAY.color.set(0x000000);CLAY.emissive.set(dark?0xe4e7ec:0x1b1e24);}\r\n  applyLines();\r\n}\r\nfunction applyBg(){\r\n  var map={light:0xe6e8eb,mid:0x9aa1a9,dark:0x1b1d22};\r\n  scene.background.set(map[S.bg]);stage.classList.toggle('dark',S.bg==='dark');\r\n  if(S.bg==='dark'){LINE.color.set(0xeef0f3);LINE.opacity=0.35;grid.material.color.set(0xaaaaaa);}\r\n  else if(S.bg==='mid'){LINE.color.set(0x1b1e24);LINE.opacity=0.55;grid.material.color.set(0x444444);}\r\n  else{LINE.color.set(0x2b2f36);LINE.opacity=0.5;grid.material.color.set(0x888888);}\r\n  applyShade();\r\n}\r\nfunction placeCamera(){\r\n  var fov={flat:14,normal:40,wide:80}[S.persp];\r\n  var w=stage.clientWidth||600,h=stage.clientHeight||400;\r\n  camera.fov=fov;camera.aspect=w\/h;\r\n  var half=Math.atan(Math.tan(fov*D\/2)*Math.min(1,camera.aspect));\r\n  var d=2.9\/Math.sin(half)\/S.zoom;\r\n  camera.position.set(0,0,d);camera.near=Math.max(0.1,d-7);camera.far=d+25;\r\n  camera.updateProjectionMatrix();dirty=true;\r\n}\r\nfunction resize(){\r\n  var w=stage.clientWidth,h=stage.clientHeight;if(!w||!h)return;\r\n  renderer.setSize(w,h,false);placeCamera();\r\n}\r\nfunction zoomBy(f){S.zoom=Math.min(3.5,Math.max(0.5,S.zoom*f));placeCamera();}\r\n\r\n\/* ---------- shape switching ---------- *\/\r\nfunction viewQuat(sh){return new T.Quaternion().setFromEuler(new T.Euler(sh.view[0],sh.view[1],0,'YXZ'));}\r\nfunction randomView(){\r\n  if(SH&&SH.upright){target=new T.Quaternion().setFromEuler(new T.Euler((Math.random()-0.5)*1.0,Math.random()*Math.PI*2,0,'YXZ'));}\r\n  else{\r\n    var u=Math.random(),v=Math.random(),w=Math.random();\r\n    target=new T.Quaternion(Math.sqrt(1-u)*Math.sin(2*Math.PI*v),Math.sqrt(1-u)*Math.cos(2*Math.PI*v),Math.sqrt(u)*Math.sin(2*Math.PI*w),Math.sqrt(u)*Math.cos(2*Math.PI*w));\r\n  }\r\n  vx=vy=0;dirty=true;\r\n}\r\nfunction resetView(){target=viewQuat(SH);S.zoom=1;vx=vy=0;placeCamera();}\r\nfunction setShape(id){\r\n  SH=SHAPES.filter(function(s){return s.id===id;})[0]||SHAPES[0];S.shape=SH.id;\r\n  holder.children.slice().forEach(function(c){\r\n    holder.remove(c);\r\n    c.traverse(function(o){if(o.geometry&&!o.geometry.userData.keep)o.geometry.dispose();});\r\n  });\r\n  lineObjs=[];J={};poseTarget=null;\r\n  holder.add(SH.build());\r\n  applyLines();\r\n  pivot.quaternion.copy(viewQuat(SH));target=null;S.zoom=1;vx=vy=0;placeCamera();\r\n  $$('#fsp-shapes .fsp-chip').forEach(function(b){b.classList.toggle('on',b.getAttribute('data-id')===SH.id);});\r\n  $('.fsp-name').textContent=SH.name;\r\n  $('#fsp-tip').innerHTML=SH.tip;\r\n  var isM=SH.id==='mannequin';$('#fsp-posebox').hidden=!isM;\r\n  if(isM)setPose(curPose,true);\r\n  dirty=true;\r\n}\r\n\r\n\/* ---------- UI wiring ---------- *\/\r\nvar shapesBox=$('#fsp-shapes');\r\nSHAPES.forEach(function(s){\r\n  var b=document.createElement('button');b.type='button';b.className='fsp-chip';b.textContent=s.name;b.setAttribute('data-id',s.id);\r\n  b.addEventListener('click',function(){setShape(s.id);});shapesBox.appendChild(b);\r\n});\r\n$$('[data-tog]').forEach(function(b){\r\n  var k=b.getAttribute('data-tog');b.classList.toggle('on',!!S[k]);\r\n  b.addEventListener('click',function(){S[k]=!S[k];b.classList.toggle('on',S[k]);applyLines();});\r\n});\r\n$$('.fsp-seg').forEach(function(seg){\r\n  var key=seg.getAttribute('data-seg'),btns=Array.prototype.slice.call(seg.querySelectorAll('button'));\r\n  function mark(){btns.forEach(function(x){x.classList.toggle('on',x.getAttribute('data-v')===S[key]);});}\r\n  mark();\r\n  btns.forEach(function(b){b.addEventListener('click',function(){\r\n    S[key]=b.getAttribute('data-v');mark();\r\n    if(key==='shade')applyShade();else if(key==='persp')placeCamera();else applyBg();\r\n  });});\r\n});\r\n$('#fsp-az').addEventListener('input',function(e){S.az=+e.target.value;applyLight();});\r\n$('#fsp-el').addEventListener('input',function(e){S.el=+e.target.value;applyLight();});\r\nposeSel.addEventListener('change',function(){setPose(poseSel.value,false);});\r\n$('#fsp-rand').addEventListener('click',function(){randomView();if(SH.id==='mannequin')randomPose();});\r\n$('#fsp-reset').addEventListener('click',resetView);\r\nvar fsBtn=$('#fsp-full');\r\nif(!(stage.requestFullscreen||stage.webkitRequestFullscreen))fsBtn.style.display='none';\r\nfsBtn.addEventListener('click',function(){\r\n  var fe=document.fullscreenElement||document.webkitFullscreenElement;\r\n  if(fe){(document.exitFullscreen||document.webkitExitFullscreen).call(document);}\r\n  else{(stage.requestFullscreen||stage.webkitRequestFullscreen).call(stage);}\r\n});\r\nfunction randomPose(){\r\n  var keys=Object.keys(POSES).filter(function(k){return k!==curPose;});\r\n  setPose(keys[Math.floor(Math.random()*keys.length)],false);\r\n}\r\n\r\n\/* ---------- practice timer ---------- *\/\r\nvar tm={on:false,end:0,len:60,id:null,ac:null},timeEl=$('.fsp-time'),timerBtn=$('#fsp-timer');\r\nfunction fmt(s){var m=Math.floor(s\/60),r=s%60;return m+':'+(r<10?'0':'')+r;}\r\nfunction beep(){\r\n  if(!S.beep)return;\r\n  try{\r\n    var ac=tm.ac;if(!ac)return;\r\n    var o=ac.createOscillator(),g=ac.createGain(),t=ac.currentTime;\r\n    o.frequency.value=880;g.gain.value=0.001;o.connect(g);g.connect(ac.destination);\r\n    g.gain.exponentialRampToValueAtTime(0.15,t+0.01);g.gain.exponentialRampToValueAtTime(0.001,t+0.4);\r\n    o.start(t);o.stop(t+0.45);\r\n  }catch(e){}\r\n}\r\nfunction tick(){\r\n  var left=Math.ceil((tm.end-Date.now())\/1000);\r\n  if(left<=0){beep();randomView();if(SH.id==='mannequin')randomPose();tm.end=Date.now()+tm.len*1000;left=tm.len;}\r\n  timeEl.textContent=fmt(left);\r\n}\r\nfunction toggleTimer(){\r\n  if(tm.on){\r\n    tm.on=false;clearInterval(tm.id);timeEl.style.display='none';timerBtn.textContent='Start timer';return;\r\n  }\r\n  try{var AC=window.AudioContext||window.webkitAudioContext;if(AC){tm.ac=tm.ac||new AC();if(tm.ac.resume)tm.ac.resume();}}catch(e){}\r\n  tm.len=+$('#fsp-len').value;tm.end=Date.now()+tm.len*1000;tm.on=true;\r\n  timeEl.style.display='block';timerBtn.textContent='Stop timer';tick();tm.id=setInterval(tick,250);\r\n}\r\ntimerBtn.addEventListener('click',toggleTimer);\r\n$('#fsp-len').addEventListener('change',function(){if(tm.on){toggleTimer();toggleTimer();}});\r\n\r\n\/* ---------- pointer \/ touch \/ keyboard rotation ---------- *\/\r\nvar qx=new T.Quaternion(),qy=new T.Quaternion(),qz=new T.Quaternion(),qSpin=new T.Quaternion().setFromAxisAngle(new T.Vector3(0,1,0),0.008);\r\nvar AX=new T.Vector3(1,0,0),AY=new T.Vector3(0,1,0),AZ=new T.Vector3(0,0,1);\r\nvar ptrs={},nPtr=0,lastPinch=null;\r\nfunction rot(dx,dy){\r\n  qy.setFromAxisAngle(AY,dx*0.008);qx.setFromAxisAngle(AX,dy*0.008);\r\n  pivot.quaternion.premultiply(qy).premultiply(qx);dirty=true;\r\n}\r\nfunction pinchInfo(){\r\n  var k=Object.keys(ptrs),a=ptrs[k[0]],b=ptrs[k[1]],dx=b.x-a.x,dy=b.y-a.y;\r\n  return {d:Math.sqrt(dx*dx+dy*dy)||1,a:Math.atan2(dy,dx)};\r\n}\r\ncanvas.addEventListener('pointerdown',function(e){\r\n  try{canvas.setPointerCapture(e.pointerId);}catch(x){}\r\n  if(!ptrs[e.pointerId])nPtr++;\r\n  ptrs[e.pointerId]={x:e.clientX,y:e.clientY};target=null;vx=vy=0;dragging=true;\r\n  if(nPtr===2)lastPinch=pinchInfo();\r\n  canvas.focus({preventScroll:true});\r\n});\r\ncanvas.addEventListener('pointermove',function(e){\r\n  var p=ptrs[e.pointerId];if(!p)return;\r\n  var dx=e.clientX-p.x,dy=e.clientY-p.y;p.x=e.clientX;p.y=e.clientY;\r\n  if(nPtr===1){rot(dx,dy);vx=vx*0.4+dx*0.6;vy=vy*0.4+dy*0.6;lastMove=Date.now();}\r\n  else if(nPtr===2){\r\n    var info=pinchInfo();\r\n    if(lastPinch){\r\n      S.zoom=Math.min(3.5,Math.max(0.5,S.zoom*info.d\/lastPinch.d));\r\n      qz.setFromAxisAngle(AZ,-(info.a-lastPinch.a));pivot.quaternion.premultiply(qz);placeCamera();\r\n    }\r\n    lastPinch=info;\r\n  }\r\n  dirty=true;\r\n});\r\nfunction endPtr(e){\r\n  if(!ptrs[e.pointerId])return;\r\n  delete ptrs[e.pointerId];nPtr=Math.max(0,nPtr-1);lastPinch=null;\r\n  if(nPtr===0){dragging=false;if(Date.now()-lastMove>70){vx=vy=0;}}\r\n  else{vx=vy=0;}\r\n}\r\ncanvas.addEventListener('pointerup',endPtr);\r\ncanvas.addEventListener('pointercancel',endPtr);\r\ncanvas.addEventListener('lostpointercapture',endPtr);\r\ncanvas.addEventListener('wheel',function(e){e.preventDefault();zoomBy(Math.exp(-e.deltaY*0.0015));},{passive:false});\r\ncanvas.addEventListener('dblclick',resetView);\r\ncanvas.addEventListener('contextmenu',function(e){e.preventDefault();});\r\ncanvas.addEventListener('keydown',function(e){\r\n  var m={ArrowLeft:[-14,0],ArrowRight:[14,0],ArrowUp:[0,-14],ArrowDown:[0,14]}[e.key];\r\n  if(m){e.preventDefault();target=null;rot(m[0],m[1]);}\r\n  else if(e.key==='+'||e.key==='='){zoomBy(1.1);}\r\n  else if(e.key==='-'){zoomBy(1\/1.1);}\r\n});\r\n\r\n\/* ---------- main loop (renders only when something changed) ---------- *\/\r\nfunction update(){\r\n  if(target){\r\n    pivot.quaternion.slerp(target,0.14);\r\n    if(pivot.quaternion.angleTo(target)<0.002){pivot.quaternion.copy(target);target=null;}\r\n    dirty=true;\r\n  }\r\n  if(!dragging&&!target){\r\n    if(Math.abs(vx)>0.04||Math.abs(vy)>0.04){rot(vx,vy);vx*=0.93;vy*=0.93;}\r\n    else{vx=vy=0;}\r\n  }\r\n  if(S.spin&&!dragging&&!target){pivot.quaternion.premultiply(qSpin);dirty=true;}\r\n  if(poseTarget){\r\n    var moving=false,k,i,key;\r\n    for(k in poseTarget){\r\n      var o=J[k];if(!o)continue;\r\n      for(i=0;i<3;i++){\r\n        key='xyz'.charAt(i);var d=poseTarget[k][i]-o.rotation[key];\r\n        if(Math.abs(d)>0.002){o.rotation[key]+=d*0.2;moving=true;}else{o.rotation[key]=poseTarget[k][i];}\r\n      }\r\n    }\r\n    dirty=true;if(!moving)poseTarget=null;\r\n  }\r\n}\r\nfunction loop(){\r\n  requestAnimationFrame(loop);\r\n  update();\r\n  if(dirty){renderer.render(scene,camera);dirty=false;}\r\n}\r\n\r\napplyBg();applyLight();\r\nsetShape('cube');\r\nif(window.ResizeObserver){new ResizeObserver(resize).observe(stage);}\r\nwindow.addEventListener('resize',resize);\r\nresize();\r\nloop();\r\n}\r\n})();<\/script><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":7215,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"jnews_page_loop":[],"jnews_single_page":[],"jnews_social_meta":[],"footnotes":""},"class_list":["post-7208","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/pages\/7208","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/comments?post=7208"}],"version-history":[{"count":13,"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/pages\/7208\/revisions"}],"predecessor-version":[{"id":7254,"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/pages\/7208\/revisions\/7254"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/media\/7215"}],"wp:attachment":[{"href":"https:\/\/acbtimes.in\/hi\/wp-json\/wp\/v2\/media?parent=7208"}],"curies":[{"name":"\u0921\u092c\u094d\u0932\u094d\u092f\u0942\u092a\u0940","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}