import graph3; import solids; settings.outformat="html"; size(20cm); currentprojection= perspective( camera=(-10.32483,10.32483,10.47533), up=(0.009815727,-0.009815727,0.01963145), target=(-0.32483398445800127,0.32483398445800127,0.47533398445800046), zoom=0.48101709809096993, angle=33.996826322482754, autoadjust=true); currentlight = light( background=white, diffuse=new pen[] {gray(0.8), gray(0.8), gray(0.7)}, specular=new pen[] {gray(0.1), gray(0.1), gray(0.1)}, position=new triple[] {currentprojection.camera, (-5,-5,5), (0,0,10)} ); // =============================== // Parameters // =============================== real L1_Len = 2; real L2_Len = 2; real L3_Len = 2; // ========================================================= // JAVASCRIPT BRIDGE (Modified for Individual Sliders) // ========================================================= javascript(" // =============================== // 1. Storage for joint angles // =============================== window.jointStates = { q1: 0, q2: 0, q3: 0 }; // =============================== // 2. Matrix Math Helpers // =============================== function multiply(A, B) { let C = new Array(16).fill(0); for (let col = 0; col < 4; col++) { for (let row = 0; row < 4; row++) { let sum = 0; for (let k = 0; k < 4; k++) { sum += A[row + k*4] * B[k + col*4]; } C[row + col*4] = sum; } } return C; } function apply(M, p) { return [ M[0]*p[0] + M[4]*p[1] + M[8]*p[2] + M[12], M[1]*p[0] + M[5]*p[1] + M[9]*p[2] + M[13], M[2]*p[0] + M[6]*p[1] + M[10]*p[2] + M[14] ]; } function getRotationZ(deg) { let rad = deg * Math.PI / 180; let c = Math.cos(rad), s = Math.sin(rad); return [c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; } // =============================== // 3. Transform Functions // =============================== window.J1 = function(p, t) { if(!window.L0_OFFSET) return p; return apply(multiply(window.L0_OFFSET, getRotationZ(window.jointStates.q1)), p); }; window.J2 = function(p, t) { if(!window.L1_OFFSET) return p; return apply(multiply(window.L1_OFFSET, getRotationZ(window.jointStates.q2)), p); }; window.J3 = function(p, t) { if(!window.L2_OFFSET) return p; return apply(multiply(window.L2_OFFSET, getRotationZ(window.jointStates.q3)), p); }; // =============================== // 4. UI + Responsive Styling // =============================== window.addEventListener('load', function(){ // Hide default controls let hide = document.createElement('style'); hide.textContent = '.asy-player-controls, #asy-slider { display: none !important; }'; document.head.appendChild(hide); // ---------------------- // Responsive Slider CSS // ---------------------- let style = document.createElement('style'); style.textContent = ` #control-panel { width: min(300px, 80vw); } .control-slider { width: 100%; height: 4vh; } .control-slider::-webkit-slider-runnable-track { height: 1.2vh; background: #ddd; border-radius: 0.6vh; } .control-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 4vh; height: 4vh; margin-top: -1.4vh; border-radius: 50%; background: #4CAF50; } .control-slider::-moz-range-track { height: 1.2vh; background: #ddd; } .control-slider::-moz-range-thumb { width: 4vh; height: 4vh; border-radius: 50%; background: #4CAF50; } `; document.head.appendChild(style); // ---------------------- // Control Panel // ---------------------- let panel = document.createElement('div'); panel.id = 'control-panel'; panel.style.cssText = ` position:fixed; top:2vh; left:2vw; z-index:9999; background:rgba(255,255,255,0.9); padding:2vh; border-radius:10px; font-family:sans-serif; box-shadow:3px 3px 10px rgba(0,0,0,0.2); `; panel.innerHTML = 'Joint Controls'; ['q1','q2','q3'].forEach(joint => { let div = document.createElement('div'); div.style.marginBottom = '10px'; div.innerHTML = `` + `` + ``; let slider = div.querySelector('input'); slider.oninput = function() { window.jointStates[joint] = parseFloat(this.value); document.getElementById('val-'+joint).innerText = this.value + '°'; if (window.updateScene) requestAnimationFrame(window.updateScene); }; panel.appendChild(div); }); document.body.appendChild(panel); // ---------------------- // Title (Top Center) // ---------------------- let title = document.createElement('div'); title.innerHTML = 'RRR Three-Joint Robot Arm'; title.style.cssText = ` position:fixed; top:10px; left:50%; transform:translateX(-50%); font-size:20px; font-family:Arial; color:black; z-index:10000; pointer-events:none; `; document.body.appendChild(title); // ---------------------- // Footer (Bottom Right) // ---------------------- let footer = document.createElement('div'); footer.innerHTML = 'Powered by Asymptote & WebGL'; footer.style.cssText = ` position:fixed; bottom:10px; right:15px; font-size:12px; font-family:Arial; color:darkblue; z-index:10000; pointer-events:none; `; document.body.appendChild(footer); }); "); // =============================== // ASYMPTOTE GEOMETRY & ASSEMBLY // (UNCHANGED PER YOUR REQUEST) // =============================== void joint_r1_f(transform3 T) { real R = 0.35, H = 0.5; pen surfacepen = lightgray; pen edgepen = black + linewidth(0.5mm); triple axis = unit(T*Z - T*O); revolution r = cylinder(T*(-0.5*H*Z), R, H, T*Z - T*O); draw(surface(r), surfacepen); draw(surface(circle(T*(0,0, H/2), R, axis)), surfacepen); draw(circle(T*(0,0, H/2), R, axis), edgepen); draw(surface(circle(T*(0,0,-H/2), R, axis)), surfacepen); draw(circle(T*(0,0,-H/2), R, axis), edgepen); } transform3 joint_r1_m(transform3 T, real angle) { transform3 R = rotate(angle, Z); transform3 Tm = T * R; pen movepen = lightblue; surface box = shift(-0.2*(X+Y) - 0.3*Z) * scale(0.4, 0.4, 0.6) * unitcube; draw(Tm*box, movepen); dot(Tm*(0.2,0,0.3), red); dot(Tm*(0.2,0,-0.3), red); return Tm; } void joint_r2_f(transform3 T) { real R = 0.35, H = 0.5; pen surfacepen = gray(0.85); pen edgepen = black + linewidth(0.5mm); triple axis = unit(T*Z - T*O); revolution r = cylinder(T*(-0.5*H*Z), R, H, axis); draw(surface(r), surfacepen); draw(surface(circle(T*(0,0, H/2), R, axis)), surfacepen); draw(circle(T*(0,0, H/2), R, axis), edgepen); draw(surface(circle(T*(0,0,-H/2), R, axis)), surfacepen); draw(circle(T*(0,0,-H/2), R, axis), edgepen); } transform3 joint_r2_m(transform3 T, real angle) { pen movepen = lightgreen; pen edgepen = black + linewidth(0.5mm); transform3 R = rotate(angle, Z); transform3 Tm = T * R; surface box = shift(-0.2*(X+Y) - 0.3*Z) * scale(0.4, 0.4, 0.6) * unitcube; draw(Tm*box, movepen); path3 p1 = (0,0,0.3)--(0,0,0.35)--(0.4,0,0.35)--(0.4,0,-0.35)--(0,0,-0.35)--(0,0,-0.3); draw(Tm*p1, edgepen); dot(Tm*(0.2,0,0.3), red); dot(Tm*(0.2,0,-0.3), red); return Tm * rotate(90, Y); } void draw_gripper_simple(transform3 T) { pen p = black + linewidth(1.2); real w = 0.1, h = 0.2; draw(T*(-w, 0, 0) -- T*(-w, 0, h), p); draw(T*( w, 0, 0) -- T*( w, 0, h), p); draw(T*(-w, 0, 0) -- T*( w, 0, 0), p); } void attach_coordinates(transform3 T, real length=2, string suffix="") { draw(T*scale3(length)*(O--X), red, Arrow3(size=10)); draw(T*scale3(length)*(O--Y), green, Arrow3(size=10)); draw(T*scale3(length)*(O--Z), blue, Arrow3(size=10)); string sub = (suffix == "") ? "" : "_{" + suffix + "}"; label("$x" + sub + "$", T*scale3(length)*(1.2*X), T*X, red); label("$y" + sub + "$", T*scale3(length)*(1.2*Y), T*Y, green); label("$z" + sub + "$", T*scale3(length)*(1.2*Z), T*Z, blue); } void exportToJS(string name, transform3 T) { string jsCommand = "window." + name + " = [" + string(T[0][0]) + "," + string(T[1][0]) + "," + string(T[2][0]) + "," + string(T[3][0]) + "," + string(T[0][1]) + "," + string(T[1][1]) + "," + string(T[2][1]) + "," + string(T[3][1]) + "," + string(T[0][2]) + "," + string(T[1][2]) + "," + string(T[2][2]) + "," + string(T[3][2]) + "," + string(T[0][3]) + "," + string(T[1][3]) + "," + string(T[2][3]) + "," + string(T[3][3]) + "];"; javascript(jsCommand); } // =============================== // World Frame & Floor // =============================== draw(O--X, red, Arrow3); draw(O--Y, green, Arrow3); draw(O--Z, blue, Arrow3); label("$x$", 1.1*X, N, red); label("$y$", 1.1*Y, N, green); label("$z$", 1.0*Z, E, blue); pen bg=gray(0.9); real r = 2.5; draw(surface((r,r,0)--(-r,r,0)--(-r,-r,0)--(r,-r,0)--cycle),bg,bg,light=nolight); transform3 L0 = shift(0, 0, 2); draw(O--L0*O, linewidth(2)); joint_r1_f(L0); attach_coordinates(L0*shift(0,0,2), 1.5, "1"); // =============================== // Recursive Animation Block // =============================== beginTransform("function(x,t){ return J1(x,t); }", 1); transform3 L1_a = joint_r1_m(identity(4), 0); attach_coordinates(L1_a*shift(0,0,2)*rotate(90,X), 1.3, "2"); transform3 L1_b = L1_a * shift(0,0,L1_Len) * rotate(180, Z); draw(L1_a*O--L1_b*O, linewidth(2)); transform3 L1_c = L1_b * rotate(-90, X); joint_r2_f(L1_c); transform3 L1 = L1_c; beginTransform("function(x,t){ return J2(x,t); }", 1); transform3 L2_a = joint_r2_m(identity(4), 0); attach_coordinates(L2_a*rotate(-90,Z), 1, "3"); transform3 L2_b = L2_a*shift(0,0,L2_Len); draw(L2_a*(0,0,0.4)--L2_b*O, linewidth(2)); joint_r1_f(L2_b); transform3 L2 = L2_b; beginTransform("function(x,t){ return J3(x,t); }", 1); transform3 L3_a = joint_r1_m(identity(4), 0); transform3 L3_b = L3_a*shift(0,0,L3_Len); draw(L3_a*O--L3_b*O, linewidth(2)); draw_gripper_simple(L3_b); attach_coordinates(L3_b*rotate(-90,Z), 0.5, "4"); endTransform(); endTransform(); endTransform(); exportToJS("L0_OFFSET", L0); exportToJS("L1_OFFSET", L1); exportToJS("L2_OFFSET", L2);