diff --git a/docs/kit-reference.md b/docs/kit-reference.md index 378017e..1d23d3a 100644 --- a/docs/kit-reference.md +++ b/docs/kit-reference.md @@ -58,6 +58,7 @@ b2kStart | `b2kQuickStart [gy]` | One call: world + gravity + card-edge walls + start the loop. | | `b2kStart` / `b2kStop` | Begin / end the simulation loop. | | `b2kPause` / `b2kResume` | Freeze / resume stepping without tearing down. | +| `b2kStepOnce` | Advance exactly one fixed step (even while paused) — drives a Step button. | | `b2kIsRunning()` | True while the loop is stepping (not stopped or paused). | | `b2kAddWalls` | Static walls around the current card edges. | | `b2kAddGround [screenY]` | A static floor across the card (optionally at a given Y). | @@ -188,6 +189,11 @@ helpers or to `b2kRemoveJoint`. | `b2kRopeLength(joint)` → pixels | Current distance-joint length. | | `b2kSpring joint, hertz [,damping]` | Make a rope (distance) joint springy. | | `b2kWeldSpring joint, hertz [,damping]` | Make a weld springy (0 hertz = rigid). | +| `b2kMotorOff joint` | Turn a hinge motor off (free swing). | +| `b2kHingeLimitOff joint` | Remove a hinge's angle limits. | +| `b2kSliderMotorOff joint` | Turn a slider motor off. | +| `b2kSliderLimitOff joint` | Remove a slider's travel limits. | +| `b2kWheelMotorOff joint` | Turn a wheel motor off. | ## Drag & events diff --git a/examples/box2dxt-contraption-builder.livecodescript b/examples/box2dxt-contraption-builder.livecodescript index 2cc70dc..d58fb0f 100644 --- a/examples/box2dxt-contraption-builder.livecodescript +++ b/examples/box2dxt-contraption-builder.livecodescript @@ -1003,6 +1003,28 @@ command b2kWheelSpring pJoint, pHertz, pDamping b2WheelEnableSpring pJoint, true, pHertz, pDamping end b2kWheelSpring +-- OFF switches: the b2k… joint wrappers above only ever turn a feature ON, so +-- these let callers disable a motor or limit again (free swing / unbounded slide). +command b2kMotorOff pJoint + if pJoint is not empty and pJoint > 0 then b2RevoluteEnableMotor pJoint, false, 0, 0 +end b2kMotorOff + +command b2kHingeLimitOff pJoint + if pJoint is not empty and pJoint > 0 then b2RevoluteEnableLimit pJoint, false, 0, 0 +end b2kHingeLimitOff + +command b2kSliderMotorOff pJoint + if pJoint is not empty and pJoint > 0 then b2PrismaticEnableMotor pJoint, false, 0, 0 +end b2kSliderMotorOff + +command b2kSliderLimitOff pJoint + if pJoint is not empty and pJoint > 0 then b2PrismaticEnableLimit pJoint, false, 0, 0 +end b2kSliderLimitOff + +command b2kWheelMotorOff pJoint + if pJoint is not empty and pJoint > 0 then b2WheelEnableMotor pJoint, false, 0, 0 +end b2kWheelMotorOff + command b2kRemoveJoint pJoint if pJoint is not empty and pJoint > 0 then b2DestroyJoint pJoint end b2kRemoveJoint @@ -1072,6 +1094,20 @@ on b2kStep pGen send ("b2kStep " & sGen) to me in 16 milliseconds end b2kStep +-- Advance exactly one fixed step, even while paused (drives a Step button). +command b2kStepOnce + if not sRunning then exit b2kStepOnce + b2Step sWorld, (1 / 60), sSub + if sDragging then + b2MouseSetTarget sDragJoint, b2kToWorldX(the mouseH), b2kToWorldY(the mouseV) + end if + lock screen + b2kSync + b2kDispatchContacts + if sFrameObj is not empty then dispatch "b2kFrame" to sFrameObj + unlock screen +end b2kStepOnce + command b2kSync local tRef, tBody, tWx, tWy, tDead, d put empty into tDead @@ -1243,6 +1279,9 @@ local gRingN, gRingX, gRingY, gRingF local gDynParts, gDynDirty -- selection / inspector / onboarding / stress-test bookkeeping local gSelPart, gHoverId, gOnbStep, gStressCount +local gSelJoint -- selected joint index (1..gJN), empty if none +local gInspTab -- active inspector tab: shape | physics | motion | special +local gGravityMode -- world gravity preset: normal|low|zero|reverse|sideways local gPreRun -- snapshot of the build, taken when Run starts constant kShapeTools = "drag,box,ball,capsule,poly,image,anchor,delete,duplicate" @@ -1278,6 +1317,10 @@ constant kFanW = 130, kFanH = 150, kFanForce = 900 constant kMagnetRange = 220, kMagnetForce = 1400, kMagnetMinDist = 16 constant kStressBatch = 50 constant kSizeMin = 16, kSizeMax = 240 +-- inspector: rows shown per tab, dimension nudge (px), joint click radius (px) +constant kPropRows = 10, kDimStep = 8, kJointHitR = 16 +-- numeric settings a user may type an exact value for (toggles/colour stay stepper-only) +constant kEntryKeys = "width,height,length,thickness,diameter,size,angle,bounce,density,friction,gravity,lindamp,angdamp,launchspeed,launchdir,spinrate,blastradius,blastpower,fanforce,fansize,magforce,magrange" -- ===================================================================== -- Lifecycle @@ -1312,6 +1355,9 @@ on startCB put empty into gFps put the milliseconds into gFpsTime put empty into gSelPart + put empty into gSelJoint + put "shape" into gInspTab + put "normal" into gGravityMode put empty into gHoverId put 0 into gStressCount resetSceneState @@ -1468,17 +1514,22 @@ on makeInspector put kTopBarH + kAccentH into tTop makeBar "ui_inspbg", tL, tTop, tWide, tHigh - kStatusH, "26,28,35" makeLabel "ui_insp_title", tL + 12, tTop + 10, tWide - 12, tTop + 34, "Inspector", 14, "236,238,245", true - makeField "ui_insp_body", tL + 12, tTop + 40, tWide - 12, tTop + 150, 11, "190,194,206" + makeField "ui_insp_body", tL + 12, tTop + 40, tWide - 12, tTop + 118, 11, "190,194,206" + -- settings tabs: group a part's many settings so each pane stays short + makeAction "tab_shape", "Shape", tL + 12, tTop + 124, 50, 24 + makeAction "tab_physics", "Physics", tL + 64, tTop + 124, 58, 24 + makeAction "tab_motion", "Motion", tL + 124, tTop + 124, 52, 24 + makeAction "tab_special", "Special", tL + 178, tTop + 124, 58, 24 -- a clearly-marked, divided settings block so adjustable values stand out makeBar "ui_settings_div", tL + 12, tTop + 156, tWide - 12, tTop + 157, "60,66,80" makeLabel "ui_settings_hdr", tL + 12, tTop + 162, tWide - 12, tTop + 182, "PART SETTINGS", 11, "120,220,150", true makeField "ui_settings_hint", tL + 12, tTop + 186, tWide - 12, tTop + 250, 11, "150,154,166" put tTop + 186 into tRy - repeat with tI = 1 to 8 - makeField ("ui_prop_lbl_" & tI), tL + 12, tRy + 3, tWide - 92, tRy + 27, 11, "232,236,246" + repeat with tI = 1 to kPropRows + makeField ("ui_prop_lbl_" & tI), tL + 12, tRy + 3, tWide - 92, tRy + 25, 11, "232,236,246" makePropBtn ("prop_dec_" & tI), "−", tWide - 84, tRy, 32, 26 makePropBtn ("prop_inc_" & tI), "+", tWide - 44, tRy, 32, 26 - add 30 to tRy + add 28 to tRy end repeat add 6 to tRy makeBar "ui_options_div", tL + 12, tRy, tWide - 12, tRy + 1, "60,66,80" @@ -1491,8 +1542,14 @@ on makeInspector makeLabel "ui_lbl_speed", tL + 12, tRy + 2, tWide - 92, tRy + 22, "Motor speed", 11, "168,172,184", false makeAction "spddn", "−", tWide - 84, tRy, 32, 26 makeAction "spdup", "+", tWide - 44, tRy, 32, 26 + add 34 to tRy + makeAction "gravity", "Gravity: Normal", tL + 12, tRy, kInspectorW - 24, 26 + add 32 to tRy + makeAction "pause", "Pause", tL + 12, tRy, 108, 26 + makeAction "step", "Step", tL + 128, tRy, 80, 26 clearPropRows refreshSpeedLabel + refreshGravityLabel end makeInspector -- A prominent accent-green +/- button for adjusting a part's settings. @@ -1691,6 +1748,15 @@ on mouseDown jointClick tx, ty exit mouseDown end if + -- Drag tool: a click on a joint marker selects that joint for editing. + if gTool is "drag" then + local tJHit + put jointUnderClick(tx, ty) into tJHit + if tJHit is not empty then + selectJoint tJHit + exit mouseDown + end if + end if put partUnderClick(tx, ty) into tHit if gTool is "delete" then if tHit is not empty then @@ -1765,6 +1831,11 @@ on handleUiClick hideRecipesMenu exit handleUiClick end if + -- Click a settings value to type an exact number (steppers stay on the +/- keys). + if "ui_prop_lbl_" is in tName then + editPropValue (the uPropKey of the target) + exit handleUiClick + end if -- Picking a build tool while the simulation runs drops you back into Build. if ("ui_tool_" is in tName or "ui_joint_" is in tName) and gMode is "run" then toggleMode if "ui_tool_" is in tName then @@ -1772,12 +1843,14 @@ on handleUiClick put empty into gJointTool resetPend deselectPart + deselectJoint put "Tool: " & friendlyKind(gTool) into gStatus showInspectorForTool else if "ui_joint_" is in tName then put the uJointId of the target into gJointTool resetPend deselectPart + deselectJoint put "Joint: " & gJointTool & " — click part A, then part B." into gStatus showInspectorForTool else if "ui_act_" is in tName then @@ -1793,6 +1866,10 @@ on handleAction pId adjustProp pId exit handleAction end if + if char 1 to 4 of pId is "tab_" then + selectInspTab (char 5 to -1 of pId) + exit handleAction + end if switch pId case "mode" toggleMode @@ -1829,6 +1906,15 @@ on handleAction pId put "Motor speed: " & gMotorSpeed & " deg/sec." into gStatus refreshSpeedLabel break + case "gravity" + cycleGravity + break + case "pause" + togglePause + break + case "step" + stepOnce + break case "recipes" toggleRecipesMenu break @@ -1862,6 +1948,18 @@ on handleAction pId hideRecipesMenu buildRecipeMagnetCatch break + case "catapult" + hideRecipesMenu + buildRecipeCatapult + break + case "cradle" + hideRecipesMenu + buildRecipeCradle + break + case "dominoes" + hideRecipesMenu + buildRecipeDominoes + break case "stress" hideRecipesMenu stressTest @@ -1881,9 +1979,11 @@ on toggleMode put "run" into gMode put empty into gJointTool resetPend + deselectJoint put true into gMotorsRunning armFuses b2kStart -- fresh loop: gravity, motors, mouse drag + armLaunches -- fire each part's initial push/spin put "Run mode — gravity, motors and special parts are live. Drag bodies with the mouse." into gStatus else put "build" into gMode @@ -1893,6 +1993,7 @@ on toggleMode put "Build mode — arrange parts and connect joints, then press Run." into gStatus end if highlightAll + refreshPauseLabel end toggleMode -- The part under a click: prefer the actual control clicked, then fall back to @@ -2328,6 +2429,8 @@ function connectJoint pKind, pA, pB, pX, pY, pMotorSpeed b2kWheelMotor tJ, pMotorSpeed, kWheelTorque end if put pMotorSpeed into gJMotor[gJN] + set the uMotorOn of graphic ("cb_joint_" & gJN) to true + set the uMotorSpeed of graphic ("cb_joint_" & gJN) to pMotorSpeed end if return true end connectJoint @@ -2375,6 +2478,18 @@ on recordJoint pKind, pJoint, pA, pB, pX, pY, pVis set the foregroundColor of graphic tG to "200,206,220" end if end if + -- default editable joint settings, stored on the marker (like parts' u-props) + set the uMotorOn of graphic tG to false + set the uMotorSpeed of graphic tG to gMotorSpeed + set the uMotorPower of graphic tG to motorPowerDefault(pKind) + set the uLimitOn of graphic tG to false + set the uLimitLo of graphic tG to jointLimitLoDefault(pKind) + set the uLimitHi of graphic tG to jointLimitHiDefault(pKind) + set the uSpringHz of graphic tG to jointSpringHzDefault(pKind) + set the uSpringDamp of graphic tG to jointSpringDampDefault(pKind) + set the uMinLen of graphic tG to 0 + set the uMaxLen of graphic tG to 200 + set the uRangeSet of graphic tG to false end recordJoint -- Redraw every joint marker. dot = pivot bead on B; rod = arm from a fixed pivot @@ -2515,6 +2630,7 @@ on duplicatePart pCtrl end duplicatePart on removeJointAt pIndex + if gSelJoint is pIndex then deselectJoint try b2kRemoveJoint gJHandle[pIndex] end try @@ -2564,6 +2680,7 @@ on clearJoints end clearJoints on resetJointArrays + put empty into gSelJoint put 0 into gJN put empty into gJKind put empty into gJHandle @@ -2632,6 +2749,7 @@ end buildSampleSwing function serializeText local tOut, tP, tI put "CB2" & cr into tOut + put "world" & tab & gGravityMode & cr after tOut repeat for each line tP in gParts if tP is empty then next repeat try @@ -2682,9 +2800,17 @@ function partSpecial pCtrl put empty into tS if kindHasBody(tKind) then put addKV(tS, "fric=" & (the uFriction of pCtrl)) into tS + put addKV(tS, "dens=" & (the uDensity of pCtrl)) into tS put addKV(tS, "grav=" & (the uGravity of pCtrl)) into tS put addKV(tS, "frot=" & (the uFixedRot of pCtrl is true)) into tS put addKV(tS, "btype=" & (the uBodyType of pCtrl)) into tS + put addKV(tS, "ldmp=" & (the uLinDamp of pCtrl)) into tS + put addKV(tS, "admp=" & (the uAngDamp of pCtrl)) into tS + put addKV(tS, "bul=" & (the uBullet of pCtrl is true)) into tS + put addKV(tS, "ang=" & round(b2kAngle(pCtrl))) into tS + put addKV(tS, "lspd=" & (the uLaunchSpeed of pCtrl)) into tS + put addKV(tS, "ldir=" & (the uLaunchDir of pCtrl)) into tS + put addKV(tS, "spin=" & (the uSpinRate of pCtrl)) into tS end if switch tKind case "fan" @@ -2739,7 +2865,8 @@ function jointLine pIndex if gJCb[pIndex] is not empty then put the uPartId of gJCb[pIndex] into tIdB end try return "joint" & tab & gJKind[pIndex] & tab & tIdA & tab & tIdB & tab & \ - gJPx[pIndex] & tab & gJPy[pIndex] & tab & gJMotor[pIndex] + gJPx[pIndex] & tab & gJPy[pIndex] & tab & gJMotor[pIndex] & tab & \ + jointSpecial(pIndex) end jointLine on loadContraption @@ -2773,8 +2900,9 @@ end loadContraption on rebuildFromText pData local tLine, tRec, tMap, tNew local tId, tKind, tX, tY, tW, tH, tBounce, tColor, tFileRef, tSpecial - local tIdA, tIdB, tPx, tPy, tMotor, tA, tB + local tIdA, tIdB, tPx, tPy, tMotor, tA, tB, tJSpecial clearAll + put "normal" into gGravityMode put empty into tMap lock screen repeat for each line tLine in pData @@ -2808,18 +2936,24 @@ on rebuildFromText pData put item 5 of tLine into tPx put item 6 of tLine into tPy put item 7 of tLine into tMotor + put item 8 of tLine into tJSpecial set the itemDelimiter to comma put tMap[tIdA] into tA if tIdB is 0 then put empty into tB else put tMap[tIdB] into tB if tA is not empty and (tB is not empty or tKind is "hinge") then - get connectJoint(tKind, tA, tB, tPx, tPy, tMotor) + if connectJoint(tKind, tA, tB, tPx, tPy, tMotor) then applyJointSpecial gJN, tJSpecial end if + else if tRec is "world" then + put item 2 of tLine into gGravityMode + set the itemDelimiter to comma else set the itemDelimiter to comma end if end repeat set the itemDelimiter to comma + applyGravityMode + refreshGravityLabel renderBuild unlock screen end rebuildFromText @@ -3074,6 +3208,11 @@ on applyPartSpecial pCtrl, pSpecial set the uFriction of pCtrl to tV b2kSetFriction pCtrl, tV end if + put specialValue(pSpecial, "dens") into tV + if tV is not empty then + set the uDensity of pCtrl to tV + b2kSetDensity pCtrl, tV + end if put specialValue(pSpecial, "grav") into tV if tV is not empty then set the uGravity of pCtrl to tV @@ -3087,6 +3226,30 @@ on applyPartSpecial pCtrl, pSpecial set the uBodyType of pCtrl to "static" b2kSetType pCtrl, "static" end if + put specialValue(pSpecial, "ldmp") into tV + if tV is not empty then + set the uLinDamp of pCtrl to tV + b2kSetDamping pCtrl, tV, empty + end if + put specialValue(pSpecial, "admp") into tV + if tV is not empty then + set the uAngDamp of pCtrl to tV + b2kSetDamping pCtrl, empty, tV + end if + if specialValue(pSpecial, "bul") is "true" then + set the uBullet of pCtrl to true + b2kSetBullet pCtrl, true + end if + put specialValue(pSpecial, "lspd") into tV + if tV is not empty then set the uLaunchSpeed of pCtrl to tV + put specialValue(pSpecial, "ldir") into tV + if tV is not empty then set the uLaunchDir of pCtrl to tV + put specialValue(pSpecial, "spin") into tV + if tV is not empty then set the uSpinRate of pCtrl to tV + put specialValue(pSpecial, "ang") into tV + if tV is not empty and tV is not 0 then + b2kMoveTo pCtrl, item 1 of the loc of pCtrl, item 2 of the loc of pCtrl, tV + end if end if switch tKind case "fan" @@ -3378,6 +3541,7 @@ end deleteByPrefix on selectPart pCtrl if pCtrl is empty then exit selectPart deselectPart + deselectJoint put pCtrl into gSelPart try set the uSelFg of pCtrl to the foregroundColor of pCtrl @@ -3413,7 +3577,8 @@ on hoverHelp pId end hoverHelp on restoreInspector - if gSelPart is not empty then showPartInspector gSelPart + if gSelJoint is not empty then showJointInspector gSelJoint + else if gSelPart is not empty then showPartInspector gSelPart else showInspectorForTool end restoreInspector @@ -3425,39 +3590,50 @@ end setInspector -- Show a selected part's name, what it does, and the values you can change. on showPartInspector pCtrl if pCtrl is empty then exit showPartInspector - local tKind, tKey, tRow + local tKind, tKey, tRow, tAvail put the uKind of pCtrl into tKind setInspector ("Selected: " & niceName(tKind)), (line 2 of toolHelp(tKind)) clearPropRows - put 0 into tRow + put partTabs(tKind) into tAvail + if tAvail is empty then + setSettingsHint "This part has no adjustable settings." + exit showPartInspector + end if set the itemDelimiter to comma + if gInspTab is not among the items of tAvail then put item 1 of tAvail into gInspTab + refreshInspTabs tAvail + put 0 into tRow repeat for each item tKey in partProps(tKind) + if propGroup(tKey) is not gInspTab then next repeat add 1 to tRow - if tRow > 8 then exit repeat + if tRow > kPropRows then exit repeat showPropRow tRow, propLabel(pCtrl, tKey), tKey end repeat if tRow > 0 then setSettingsHint empty -- rows are showing; hide the prompt - else setSettingsHint "This part has no adjustable settings." + else setSettingsHint "Nothing to adjust on this tab." end showPartInspector -- The ordered list of adjustable settings shown for each kind of part. function partProps pKind switch pKind case "box" + return "width,height,color,angle,bounce,density,friction,gravity,fixedrot,bodytype,lindamp,angdamp,bullet,material,launchspeed,launchdir,spinrate" + case "image" + return "width,height,angle,bounce,density,friction,gravity,fixedrot,bodytype,lindamp,angdamp,bullet,material,launchspeed,launchdir,spinrate" case "ball" + return "diameter,color,bounce,density,friction,gravity,fixedrot,bodytype,lindamp,angdamp,bullet,material,launchspeed,launchdir,spinrate" case "capsule" + return "length,thickness,color,angle,bounce,density,friction,gravity,fixedrot,bodytype,lindamp,angdamp,bullet,material,launchspeed,launchdir,spinrate" case "poly" - return "size,color,bounce,density,friction,gravity,fixedrot,bodytype" - case "image" - return "size,bounce,density,friction,gravity,fixedrot,bodytype" + return "size,color,angle,bounce,density,friction,gravity,fixedrot,bodytype,lindamp,angdamp,bullet,material,launchspeed,launchdir,spinrate" case "balloon" - return "size,color,bounce,density,friction,gravity" + return "diameter,color,bounce,density,friction,gravity" case "bomb" - return "size,color,bounce,density,blastradius,blastpower,bombfuse" + return "diameter,color,bounce,density,blastradius,blastpower,bombfuse" case "plate" - return "size,color,friction" + return "width,height,color,angle,friction" case "anchor" - return "size,color" + return "width,height,color" case "fan" return "fandir,fanforce,fansize,color" case "magnet" @@ -3471,6 +3647,18 @@ function propLabel pCtrl, pKey switch pKey case "size" return "Size: " & (the width of pCtrl) & " px" + case "width" + return "Width: " & (the width of pCtrl) & " px" + case "height" + return "Height: " & (the height of pCtrl) & " px" + case "length" + return "Length: " & (the width of pCtrl) & " px" + case "thickness" + return "Thickness: " & (the height of pCtrl) & " px" + case "diameter" + return "Diameter: " & (the width of pCtrl) & " px" + case "angle" + return "Angle: " & round(b2kAngle(pCtrl)) & " deg" case "color" return "Colour (cycle with +/−)" case "bounce" @@ -3487,6 +3675,20 @@ function propLabel pCtrl, pKey case "bodytype" if the uBodyType of pCtrl is "static" then return "Movement: fixed in place" return "Movement: free-moving" + case "lindamp" + return "Air drag (move): " & numOr(the uLinDamp of pCtrl, 0) + case "angdamp" + return "Air drag (spin): " & numOr(the uAngDamp of pCtrl, 0) + case "bullet" + return "Fast-collision: " & yesNo(the uBullet of pCtrl is true) + case "material" + return "Material: " & materialNameOf(pCtrl) + case "launchspeed" + return "Launch speed: " & numOr(the uLaunchSpeed of pCtrl, 0) & " px/s" + case "launchdir" + return "Launch angle: " & numOr(the uLaunchDir of pCtrl, 0) & " deg" + case "spinrate" + return "Launch spin: " & numOr(the uSpinRate of pCtrl, 0) & " deg/s" case "blastradius" return "Blast size: " & (the uBlastRadius of pCtrl) case "blastpower" @@ -3511,9 +3713,10 @@ end propLabel on clearPropRows local tI - repeat with tI = 1 to 8 + repeat with tI = 1 to kPropRows hidePropRow tI end repeat + hideInspTabs setSettingsHint "Select a part on the stage to adjust its settings here." end clearPropRows @@ -3526,6 +3729,7 @@ end hidePropRow on showPropRow pRow, pLabel, pKey if there is a field ("ui_prop_lbl_" & pRow) then put pLabel into field ("ui_prop_lbl_" & pRow) + set the uPropKey of field ("ui_prop_lbl_" & pRow) to pKey set the visible of field ("ui_prop_lbl_" & pRow) to true end if if there is a button ("ui_act_prop_dec_" & pRow) then @@ -3540,22 +3744,45 @@ end showPropRow -- A +/- click on an inspector property row. on adjustProp pId - if gSelPart is empty then exit adjustProp local tDir if char 6 to 8 of pId is "inc" then put 1 into tDir else put -1 into tDir + if gSelJoint is not empty then + adjustJointProp gSelJoint, (the uPropKey of the target), tDir + showJointInspector gSelJoint + exit adjustProp + end if + if gSelPart is empty then exit adjustProp adjustPartProp gSelPart, (the uPropKey of the target), tDir showPartInspector gSelPart if gMode is "build" then renderBuild end adjustProp on adjustPartProp pCtrl, pKey, pDir - local tB, tD, tF, tNewDir, tMf, tMr, tFr, tG, tBr, tPow, tFs, tIdx, tPal, tCnt + local tB, tD, tF, tNewDir, tMf, tMr, tFr, tG, tBr, tPow, tFs, tIdx, tPal, tCnt, tLd, tAd, tLs, tSr switch pKey case "size" if pDir > 0 then resizePart pCtrl, 1.15 else resizePart pCtrl, 0.87 break + case "width" + setPartDimension pCtrl, "w", (the width of pCtrl) + pDir * kDimStep + break + case "height" + setPartDimension pCtrl, "h", (the height of pCtrl) + pDir * kDimStep + break + case "length" + setPartDimension pCtrl, "w", (the width of pCtrl) + pDir * kDimStep + break + case "thickness" + setPartDimension pCtrl, "h", (the height of pCtrl) + pDir * kDimStep + break + case "diameter" + setPartDimension pCtrl, "d", (the width of pCtrl) + pDir * kDimStep + break + case "angle" + setPartAngle pCtrl, round(b2kAngle(pCtrl)) + pDir * 15 + break case "color" put partSwatches() into tPal put the number of lines of tPal into tCnt @@ -3608,6 +3835,42 @@ on adjustPartProp pCtrl, pKey, pDir else set the uBodyType of pCtrl to "static" b2kSetType pCtrl, (the uBodyType of pCtrl) break + case "lindamp" + put the uLinDamp of pCtrl into tLd + if tLd is empty then put 0 into tLd + put tLd + pDir * 0.25 into tLd + if tLd < 0 then put 0 into tLd + if tLd > 5 then put 5 into tLd + set the uLinDamp of pCtrl to tLd + b2kSetDamping pCtrl, tLd, empty + break + case "angdamp" + put the uAngDamp of pCtrl into tAd + if tAd is empty then put 0 into tAd + put tAd + pDir * 0.25 into tAd + if tAd < 0 then put 0 into tAd + if tAd > 5 then put 5 into tAd + set the uAngDamp of pCtrl to tAd + b2kSetDamping pCtrl, empty, tAd + break + case "bullet" + set the uBullet of pCtrl to not (the uBullet of pCtrl is true) + b2kSetBullet pCtrl, (the uBullet of pCtrl is true) + break + case "material" + cycleMaterial pCtrl, pDir + break + case "launchspeed" + put numOr(the uLaunchSpeed of pCtrl, 0) + pDir * 50 into tLs + set the uLaunchSpeed of pCtrl to clampRange(tLs, 0, 1500) + break + case "launchdir" + set the uLaunchDir of pCtrl to ((numOr(the uLaunchDir of pCtrl, 0) + pDir * 15 + 360) mod 360) + break + case "spinrate" + put numOr(the uSpinRate of pCtrl, 0) + pDir * 45 into tSr + set the uSpinRate of pCtrl to clampRange(tSr, -720, 720) + break case "blastradius" put the uBlastRadius of pCtrl into tBr if tBr is empty then put kBombRadius into tBr @@ -3742,6 +4005,935 @@ function swatchIndexOf pCtrl, pPal return 0 end swatchIndexOf +-- ===================================================================== +-- Inspector tabs, exact-value entry, per-axis sizing, and joint settings +-- ===================================================================== + +-- Which settings tab a property key belongs to. +function propGroup pKey + if pKey is among the items of "width,height,length,thickness,diameter,size,angle,color" then return "shape" + if pKey is among the items of "bounce,density,friction,gravity,fixedrot,bodytype,lindamp,angdamp,bullet,material" then return "physics" + if pKey is among the items of "launchspeed,launchdir,spinrate" then return "motion" + return "special" +end propGroup + +-- The tabs that actually have settings for this kind (so empty tabs stay hidden). +function partTabs pKind + local tKey, tHasShape, tHasPhys, tHasMotion, tHasSpec, tOut + put false into tHasShape + put false into tHasPhys + put false into tHasMotion + put false into tHasSpec + set the itemDelimiter to comma + repeat for each item tKey in partProps(pKind) + switch propGroup(tKey) + case "shape" + put true into tHasShape + break + case "physics" + put true into tHasPhys + break + case "motion" + put true into tHasMotion + break + case "special" + put true into tHasSpec + break + end switch + end repeat + put empty into tOut + if tHasShape then put "shape" into tOut + if tHasPhys then put addItem(tOut, "physics") into tOut + if tHasMotion then put addItem(tOut, "motion") into tOut + if tHasSpec then put addItem(tOut, "special") into tOut + return tOut +end partTabs + +-- Append an item to a comma list (small sibling of addKV for ; lists). +function addItem pList, pItem + if pList is empty then return pItem + return pList & "," & pItem +end addItem + +on selectInspTab pTab + put pTab into gInspTab + if gSelPart is not empty then showPartInspector gSelPart +end selectInspTab + +-- Show only the tabs a part offers, highlighting the active one. +on refreshInspTabs pAvail + local tT + set the itemDelimiter to comma + repeat for each item tT in "shape,physics,motion,special" + if there is not a button ("ui_act_tab_" & tT) then next repeat + if tT is among the items of pAvail then + set the visible of button ("ui_act_tab_" & tT) to true + if tT is gInspTab then + set the backgroundColor of button ("ui_act_tab_" & tT) to "72,190,130" + set the textColor of button ("ui_act_tab_" & tT) to "255,255,255" + else + set the backgroundColor of button ("ui_act_tab_" & tT) to "44,48,58" + set the textColor of button ("ui_act_tab_" & tT) to "196,200,212" + end if + else + set the visible of button ("ui_act_tab_" & tT) to false + end if + end repeat +end refreshInspTabs + +on hideInspTabs + local tT + set the itemDelimiter to comma + repeat for each item tT in "shape,physics,motion,special" + if there is a button ("ui_act_tab_" & tT) then set the visible of button ("ui_act_tab_" & tT) to false + end repeat +end hideInspTabs + +-- A custom-property value, or a fallback when it has never been set. +function numOr pV, pDefault + if pV is empty then return pDefault + return pV +end numOr + +function clampRange pV, pLo, pHi + if pV < pLo then return pLo + if pV > pHi then return pHi + return pV +end clampRange + +-- Keep a part dimension within the allowed pixel range. +function clampDim pV + if pV is empty or pV is not a number then return kSizeMin + put round(pV) into pV + if pV < kSizeMin then return kSizeMin + if pV > kSizeMax then return kSizeMax + return pV +end clampDim + +-- Set ONE axis of a part to an exact pixel size, then re-fit its physics shape on +-- the same body so joints survive. pAxis is "w", "h", or "d" (both, for balls). +on setPartDimension pCtrl, pAxis, pValue + local tKind, tCx, tCy, tW, tH + put the uKind of pCtrl into tKind + put item 1 of the loc of pCtrl into tCx + put item 2 of the loc of pCtrl into tCy + put the width of pCtrl into tW + put the height of pCtrl into tH + put clampDim(pValue) into pValue + if pAxis is "w" then put pValue into tW + else if pAxis is "h" then put pValue into tH + else if pAxis is "d" then + put pValue into tW + put pValue into tH + end if + set the width of pCtrl to tW + set the height of pCtrl to tH + set the loc of pCtrl to tCx & "," & tCy + if kindHasBody(tKind) then + b2kReshape pCtrl, shapeOfKind(tKind) + reapplyMaterial pCtrl + end if +end setPartDimension + +-- Click a settings value to type an exact number (parts or joints). +on editPropValue pKey + if pKey is empty then exit editPropValue + if gSelJoint is not empty then + editJointValue pKey + exit editPropValue + end if + if gSelPart is empty then exit editPropValue + set the itemDelimiter to comma + if pKey is not among the items of kEntryKeys then exit editPropValue + ask ("Set " & propEditName(gSelPart, pKey) & ":") with currentPropValue(gSelPart, pKey) + if it is empty or the result is "Cancel" then exit editPropValue + if it is not a number then + put "Please type a number." into gStatus + updateHud + exit editPropValue + end if + applyPropValue gSelPart, pKey, it + showPartInspector gSelPart + if gMode is "build" then renderBuild +end editPropValue + +-- A readable name for the entry dialog, taken from the row label (before the colon). +function propEditName pCtrl, pKey + local tL + put propLabel(pCtrl, pKey) into tL + if ":" is in tL then return char 1 to (offset(":", tL) - 1) of tL + return pKey +end propEditName + +function currentPropValue pCtrl, pKey + switch pKey + case "width" + case "length" + case "diameter" + case "size" + return the width of pCtrl + case "height" + case "thickness" + return the height of pCtrl + case "angle" + return round(b2kAngle(pCtrl)) + case "bounce" + return numOr(the uBounce of pCtrl, 0) + case "density" + return numOr(the uDensity of pCtrl, 1) + case "friction" + return numOr(the uFriction of pCtrl, 0.4) + case "gravity" + return numOr(the uGravity of pCtrl, 1) + case "lindamp" + return numOr(the uLinDamp of pCtrl, 0) + case "angdamp" + return numOr(the uAngDamp of pCtrl, 0) + case "launchspeed" + return numOr(the uLaunchSpeed of pCtrl, 0) + case "launchdir" + return numOr(the uLaunchDir of pCtrl, 0) + case "spinrate" + return numOr(the uSpinRate of pCtrl, 0) + case "blastradius" + return numOr(the uBlastRadius of pCtrl, kBombRadius) + case "blastpower" + return numOr(the uBlastPower of pCtrl, kBombPower) + case "fanforce" + return numOr(the uFanForce of pCtrl, kFanForce) + case "fansize" + return numOr(the uFanScale of pCtrl, 1) + case "magforce" + return numOr(the uMagForce of pCtrl, kMagnetForce) + case "magrange" + return numOr(the uMagRange of pCtrl, kMagnetRange) + end switch + return empty +end currentPropValue + +-- Apply an exact typed value to a part (clamped), syncing physics like the steppers. +on applyPropValue pCtrl, pKey, pVal + switch pKey + case "width" + case "length" + setPartDimension pCtrl, "w", pVal + break + case "height" + case "thickness" + setPartDimension pCtrl, "h", pVal + break + case "diameter" + setPartDimension pCtrl, "d", pVal + break + case "angle" + setPartAngle pCtrl, pVal + break + case "size" + if (the width of pCtrl) >= 1 then resizePart pCtrl, (clampDim(pVal) / (the width of pCtrl)) + break + case "bounce" + put clampRange(pVal, 0, 0.9) into pVal + set the uBounce of pCtrl to pVal + b2kSetBounce pCtrl, pVal + break + case "density" + put clampRange(pVal, 0.3, 3) into pVal + set the uDensity of pCtrl to pVal + b2kSetDensity pCtrl, pVal + break + case "friction" + put clampRange(pVal, 0, 2) into pVal + set the uFriction of pCtrl to pVal + b2kSetFriction pCtrl, pVal + break + case "gravity" + put clampRange(pVal, -2, 3) into pVal + set the uGravity of pCtrl to pVal + b2kSetGravityScale pCtrl, pVal + break + case "lindamp" + put clampRange(pVal, 0, 5) into pVal + set the uLinDamp of pCtrl to pVal + b2kSetDamping pCtrl, pVal, empty + break + case "angdamp" + put clampRange(pVal, 0, 5) into pVal + set the uAngDamp of pCtrl to pVal + b2kSetDamping pCtrl, empty, pVal + break + case "launchspeed" + set the uLaunchSpeed of pCtrl to clampRange(pVal, 0, 1500) + break + case "launchdir" + set the uLaunchDir of pCtrl to ((pVal + 360) mod 360) + break + case "spinrate" + set the uSpinRate of pCtrl to clampRange(pVal, -720, 720) + break + case "blastradius" + set the uBlastRadius of pCtrl to clampRange(pVal, 60, 500) + break + case "blastpower" + set the uBlastPower of pCtrl to clampRange(pVal, 200, 2000) + break + case "fanforce" + set the uFanForce of pCtrl to clampRange(pVal, 0, 3000) + break + case "fansize" + set the uFanScale of pCtrl to clampRange(pVal, 0.5, 2.5) + drawFanShape pCtrl + break + case "magforce" + set the uMagForce of pCtrl to clampRange(pVal, 100, 4000) + break + case "magrange" + set the uMagRange of pCtrl to clampRange(pVal, 60, 520) + break + end switch +end applyPropValue + +-- ---- Per-joint inspector -------------------------------------------- + +-- The on-screen point that selects a joint (its marker bead / link midpoint). +function jointHandleLoc pIndex + local tVis, tG, tA, tB + put gJVis[pIndex] into tVis + put "cb_joint_" & pIndex into tG + if there is not a graphic tG then return empty + if tVis is "dot" then return the loc of graphic tG + if tVis is "rod" then + put line 1 of the points of graphic tG into tA + put line 2 of the points of graphic tG into tB + if tA is empty or tB is empty then return the loc of graphic tG + return midOf(tA, tB) + end if + if gJCa[pIndex] is empty then return empty + put the loc of gJCa[pIndex] into tA + if gJCb[pIndex] is empty then put tA into tB + else put the loc of gJCb[pIndex] into tB + return midOf(tA, tB) +end jointHandleLoc + +function midOf pA, pB + return round((item 1 of pA + item 1 of pB) / 2) & "," & round((item 2 of pA + item 2 of pB) / 2) +end midOf + +-- The live joint whose handle is nearest the click (within kJointHitR), or empty. +function jointUnderClick pX, pY + local tI, tBest, tBestD, tH, tD + put empty into tBest + put empty into tBestD + if gJN < 1 then return empty + repeat with tI = 1 to gJN + if gJKind[tI] is empty then next repeat + put jointHandleLoc(tI) into tH + if tH is empty then next repeat + put sqrt((pX - item 1 of tH)^2 + (pY - item 2 of tH)^2) into tD + if tD <= kJointHitR and (tBest is empty or tD < tBestD) then + put tI into tBest + put tD into tBestD + end if + end repeat + return tBest +end jointUnderClick + +on selectJoint pIndex + if pIndex is empty then exit selectJoint + if gJKind[pIndex] is empty then exit selectJoint + deselectPart + deselectJoint + put pIndex into gSelJoint + local tG + put "cb_joint_" & pIndex into tG + if there is a graphic tG then + try + set the uSelLine of graphic tG to the lineSize of graphic tG + set the uSelFg of graphic tG to the foregroundColor of graphic tG + set the lineSize of graphic tG to (the lineSize of graphic tG) + 3 + set the foregroundColor of graphic tG to "255,255,255" + end try + end if + showJointInspector pIndex +end selectJoint + +on deselectJoint + if gSelJoint is empty then exit deselectJoint + local tG + put "cb_joint_" & gSelJoint into tG + if there is a graphic tG then + try + set the lineSize of graphic tG to the uSelLine of graphic tG + set the foregroundColor of graphic tG to the uSelFg of graphic tG + end try + end if + put empty into gSelJoint + clearPropRows +end deselectJoint + +-- Show a selected joint's name, what it does, and the values you can change. +on showJointInspector pIndex + if pIndex is empty then exit showJointInspector + if gJKind[pIndex] is empty then exit showJointInspector + local tKind, tKey, tRow, tMk + put gJKind[pIndex] into tKind + put the long id of graphic ("cb_joint_" & pIndex) into tMk + setInspector ("Selected: " & niceName(tKind)), (line 2 of toolHelp(tKind)) + clearPropRows + hideInspTabs + put 0 into tRow + set the itemDelimiter to comma + repeat for each item tKey in jointProps(tKind) + add 1 to tRow + if tRow > kPropRows then exit repeat + showPropRow tRow, jointPropLabel(tMk, tKind, tKey), tKey + end repeat + if tRow > 0 then setSettingsHint empty + else setSettingsHint "This joint has no adjustable settings." +end showJointInspector + +-- The ordered list of adjustable settings shown for each kind of joint. +function jointProps pKind + switch pKind + case "hinge" + return "jmotoron,jmotorspeed,jmotorpower,jlimiton,jlimitlo,jlimithi" + case "slider" + return "jmotoron,jmotorspeed,jmotorpower,jlimiton,jlimitlo,jlimithi" + case "wheel" + return "jmotoron,jmotorspeed,jmotorpower,jspringhz,jspringdamp" + case "rope" + return "jminlen,jmaxlen,jspringhz,jspringdamp" + case "weld" + return "jspringhz,jspringdamp" + end switch + return empty +end jointProps + +-- The human-readable "Name: value" shown on a joint property row. +function jointPropLabel pMk, pKind, pKey + local tUnit + if pKind is "slider" then put " px/s" into tUnit + else put " deg/s" into tUnit + switch pKey + case "jmotoron" + return "Motor: " & onOff(the uMotorOn of pMk is true) + case "jmotorspeed" + return "Motor speed: " & numOr(the uMotorSpeed of pMk, gMotorSpeed) & tUnit + case "jmotorpower" + return "Motor power: " & numOr(the uMotorPower of pMk, motorPowerDefault(pKind)) + case "jlimiton" + return "Limits: " & onOff(the uLimitOn of pMk is true) + case "jlimitlo" + if pKind is "slider" then return "Limit min: " & numOr(the uLimitLo of pMk, -100) & " px" + return "Limit min: " & numOr(the uLimitLo of pMk, -45) & " deg" + case "jlimithi" + if pKind is "slider" then return "Limit max: " & numOr(the uLimitHi of pMk, 100) & " px" + return "Limit max: " & numOr(the uLimitHi of pMk, 45) & " deg" + case "jspringhz" + return "Springiness: " & numOr(the uSpringHz of pMk, 0) & " Hz" + case "jspringdamp" + return "Spring damping: " & numOr(the uSpringDamp of pMk, 0.5) + case "jminlen" + return "Min length: " & numOr(the uMinLen of pMk, 0) & " px" + case "jmaxlen" + return "Max length: " & numOr(the uMaxLen of pMk, 200) & " px" + end switch + return pKey +end jointPropLabel + +function onOff pFlag + if pFlag is true then return "on" + return "off" +end onOff + +on adjustJointProp pIndex, pKey, pDir + local tMk, tV + put the long id of graphic ("cb_joint_" & pIndex) into tMk + switch pKey + case "jmotoron" + set the uMotorOn of tMk to not (the uMotorOn of tMk is true) + applyJointMotor pIndex + break + case "jmotorspeed" + put numOr(the uMotorSpeed of tMk, gMotorSpeed) + pDir * 30 into tV + set the uMotorSpeed of tMk to clampRange(tV, kMotorMin, kMotorMax) + applyJointMotor pIndex + break + case "jmotorpower" + put numOr(the uMotorPower of tMk, motorPowerDefault(gJKind[pIndex])) + pDir * 100 into tV + set the uMotorPower of tMk to clampRange(tV, 50, 6000) + applyJointMotor pIndex + break + case "jlimiton" + set the uLimitOn of tMk to not (the uLimitOn of tMk is true) + applyJointLimit pIndex + break + case "jlimitlo" + if gJKind[pIndex] is "slider" then put numOr(the uLimitLo of tMk, -100) + pDir * 20 into tV + else put numOr(the uLimitLo of tMk, -45) + pDir * 15 into tV + set the uLimitLo of tMk to tV + applyJointLimit pIndex + break + case "jlimithi" + if gJKind[pIndex] is "slider" then put numOr(the uLimitHi of tMk, 100) + pDir * 20 into tV + else put numOr(the uLimitHi of tMk, 45) + pDir * 15 into tV + set the uLimitHi of tMk to tV + applyJointLimit pIndex + break + case "jspringhz" + put numOr(the uSpringHz of tMk, 0) + pDir * 1 into tV + set the uSpringHz of tMk to clampRange(tV, 0, 15) + applyJointSpring pIndex + break + case "jspringdamp" + put numOr(the uSpringDamp of tMk, 0.5) + pDir * 0.1 into tV + set the uSpringDamp of tMk to clampRange(tV, 0, 1) + applyJointSpring pIndex + break + case "jminlen" + put numOr(the uMinLen of tMk, 0) + pDir * 20 into tV + set the uMinLen of tMk to clampRange(tV, 0, 800) + set the uRangeSet of tMk to true + applyJointRange pIndex + break + case "jmaxlen" + put numOr(the uMaxLen of tMk, 200) + pDir * 20 into tV + set the uMaxLen of tMk to clampRange(tV, 20, 800) + set the uRangeSet of tMk to true + applyJointRange pIndex + break + end switch +end adjustJointProp + +-- Push the marker's stored motor setting to the live joint (kind-specific call). +on applyJointMotor pIndex + local tKind, tJ, tMk, tOn, tSpd, tPow + put gJKind[pIndex] into tKind + put gJHandle[pIndex] into tJ + put the long id of graphic ("cb_joint_" & pIndex) into tMk + put (the uMotorOn of tMk is true) into tOn + put numOr(the uMotorSpeed of tMk, gMotorSpeed) into tSpd + put numOr(the uMotorPower of tMk, motorPowerDefault(tKind)) into tPow + switch tKind + case "hinge" + if tOn then b2kMotor tJ, tSpd, tPow + else b2kMotorOff tJ + break + case "slider" + if tOn then b2kSliderMotor tJ, tSpd, tPow + else b2kSliderMotorOff tJ + break + case "wheel" + if tOn then b2kWheelMotor tJ, tSpd, tPow + else b2kWheelMotorOff tJ + break + end switch + if tOn then put tSpd into gJMotor[pIndex] + else put 0 into gJMotor[pIndex] +end applyJointMotor + +on applyJointLimit pIndex + local tKind, tJ, tMk, tOn, tLo, tHi + put gJKind[pIndex] into tKind + put gJHandle[pIndex] into tJ + put the long id of graphic ("cb_joint_" & pIndex) into tMk + put (the uLimitOn of tMk is true) into tOn + if tKind is "slider" then + put numOr(the uLimitLo of tMk, -100) into tLo + put numOr(the uLimitHi of tMk, 100) into tHi + if tOn then b2kSliderLimit tJ, tLo, tHi + else b2kSliderLimitOff tJ + else if tKind is "hinge" then + put numOr(the uLimitLo of tMk, -45) into tLo + put numOr(the uLimitHi of tMk, 45) into tHi + if tOn then b2kHingeLimit tJ, tLo, tHi + else b2kHingeLimitOff tJ + end if +end applyJointLimit + +on applyJointSpring pIndex + local tKind, tJ, tMk, tHz, tDamp + put gJKind[pIndex] into tKind + put gJHandle[pIndex] into tJ + put the long id of graphic ("cb_joint_" & pIndex) into tMk + put numOr(the uSpringHz of tMk, 0) into tHz + put numOr(the uSpringDamp of tMk, 0.5) into tDamp + switch tKind + case "rope" + if tHz > 0 then b2kSpring tJ, tHz, tDamp + else b2DistanceEnableSpring tJ, false, 0, 0 -- 0 Hz = rigid rope again + break + case "weld" + b2kWeldSpring tJ, tHz, tDamp -- 0 Hz = rigid weld + break + case "wheel" + b2kWheelSpring tJ, tHz, tDamp + break + end switch +end applyJointSpring + +on applyJointRange pIndex + local tJ, tMk + put gJHandle[pIndex] into tJ + put the long id of graphic ("cb_joint_" & pIndex) into tMk + b2kRopeRange tJ, numOr(the uMinLen of tMk, 0), numOr(the uMaxLen of tMk, 200) +end applyJointRange + +-- Click a joint value to type an exact number. +on editJointValue pKey + if gSelJoint is empty then exit editJointValue + set the itemDelimiter to comma + if pKey is among the items of "jmotoron,jlimiton" then exit editJointValue -- toggles only + local tMk + put the long id of graphic ("cb_joint_" & gSelJoint) into tMk + ask ("Set " & jointEditName(pKey) & ":") with jointValueOf(tMk, pKey) + if it is empty or the result is "Cancel" then exit editJointValue + if it is not a number then + put "Please type a number." into gStatus + updateHud + exit editJointValue + end if + applyJointValue gSelJoint, pKey, it + showJointInspector gSelJoint +end editJointValue + +function jointValueOf pMk, pKey + switch pKey + case "jmotorspeed" + return numOr(the uMotorSpeed of pMk, gMotorSpeed) + case "jmotorpower" + return numOr(the uMotorPower of pMk, 1000) + case "jlimitlo" + return numOr(the uLimitLo of pMk, 0) + case "jlimithi" + return numOr(the uLimitHi of pMk, 0) + case "jspringhz" + return numOr(the uSpringHz of pMk, 0) + case "jspringdamp" + return numOr(the uSpringDamp of pMk, 0.5) + case "jminlen" + return numOr(the uMinLen of pMk, 0) + case "jmaxlen" + return numOr(the uMaxLen of pMk, 200) + end switch + return empty +end jointValueOf + +function jointEditName pKey + switch pKey + case "jmotorspeed" + return "motor speed" + case "jmotorpower" + return "motor power" + case "jlimitlo" + return "limit minimum" + case "jlimithi" + return "limit maximum" + case "jspringhz" + return "springiness (Hz)" + case "jspringdamp" + return "spring damping (0-1)" + case "jminlen" + return "minimum length (px)" + case "jmaxlen" + return "maximum length (px)" + end switch + return pKey +end jointEditName + +on applyJointValue pIndex, pKey, pVal + local tMk + put the long id of graphic ("cb_joint_" & pIndex) into tMk + switch pKey + case "jmotorspeed" + set the uMotorSpeed of tMk to clampRange(pVal, kMotorMin, kMotorMax) + applyJointMotor pIndex + break + case "jmotorpower" + set the uMotorPower of tMk to clampRange(pVal, 50, 6000) + applyJointMotor pIndex + break + case "jlimitlo" + set the uLimitLo of tMk to pVal + applyJointLimit pIndex + break + case "jlimithi" + set the uLimitHi of tMk to pVal + applyJointLimit pIndex + break + case "jspringhz" + set the uSpringHz of tMk to clampRange(pVal, 0, 15) + applyJointSpring pIndex + break + case "jspringdamp" + set the uSpringDamp of tMk to clampRange(pVal, 0, 1) + applyJointSpring pIndex + break + case "jminlen" + set the uMinLen of tMk to clampRange(pVal, 0, 800) + set the uRangeSet of tMk to true + applyJointRange pIndex + break + case "jmaxlen" + set the uMaxLen of tMk to clampRange(pVal, 20, 800) + set the uRangeSet of tMk to true + applyJointRange pIndex + break + end switch +end applyJointValue + +-- Pack a joint's editable settings as "key=value;…" (per-kind) for Save. +function jointSpecial pIndex + local tKind, tMk, tS + put gJKind[pIndex] into tKind + if tKind is empty then return empty + put the long id of graphic ("cb_joint_" & pIndex) into tMk + put empty into tS + if tKind is "hinge" or tKind is "slider" or tKind is "wheel" then + put addKV(tS, "mon=" & (the uMotorOn of tMk is true)) into tS + put addKV(tS, "msp=" & numOr(the uMotorSpeed of tMk, gMotorSpeed)) into tS + put addKV(tS, "mpw=" & numOr(the uMotorPower of tMk, motorPowerDefault(tKind))) into tS + end if + if tKind is "hinge" or tKind is "slider" then + put addKV(tS, "lon=" & (the uLimitOn of tMk is true)) into tS + put addKV(tS, "llo=" & numOr(the uLimitLo of tMk, 0)) into tS + put addKV(tS, "lhi=" & numOr(the uLimitHi of tMk, 0)) into tS + end if + if tKind is "rope" or tKind is "weld" or tKind is "wheel" then + put addKV(tS, "shz=" & numOr(the uSpringHz of tMk, 0)) into tS + put addKV(tS, "sdm=" & numOr(the uSpringDamp of tMk, 0.5)) into tS + end if + if tKind is "rope" and (the uRangeSet of tMk is true) then + put addKV(tS, "rng=true") into tS + put addKV(tS, "rmn=" & numOr(the uMinLen of tMk, 0)) into tS + put addKV(tS, "rmx=" & numOr(the uMaxLen of tMk, 200)) into tS + end if + return tS +end jointSpecial + +-- Restore a joint's editable settings (from Load); empty string = leave defaults. +on applyJointSpecial pIndex, pSpecial + if pSpecial is empty then exit applyJointSpecial + if gJKind[pIndex] is empty then exit applyJointSpecial + local tMk, tV + put the long id of graphic ("cb_joint_" & pIndex) into tMk + put specialValue(pSpecial, "mon") into tV + if tV is not empty then set the uMotorOn of tMk to (tV is "true") + put specialValue(pSpecial, "msp") into tV + if tV is not empty then set the uMotorSpeed of tMk to tV + put specialValue(pSpecial, "mpw") into tV + if tV is not empty then set the uMotorPower of tMk to tV + put specialValue(pSpecial, "lon") into tV + if tV is not empty then set the uLimitOn of tMk to (tV is "true") + put specialValue(pSpecial, "llo") into tV + if tV is not empty then set the uLimitLo of tMk to tV + put specialValue(pSpecial, "lhi") into tV + if tV is not empty then set the uLimitHi of tMk to tV + put specialValue(pSpecial, "shz") into tV + if tV is not empty then set the uSpringHz of tMk to tV + put specialValue(pSpecial, "sdm") into tV + if tV is not empty then set the uSpringDamp of tMk to tV + applyJointMotor pIndex + applyJointLimit pIndex + applyJointSpring pIndex + if specialValue(pSpecial, "rng") is "true" then + set the uRangeSet of tMk to true + put specialValue(pSpecial, "rmn") into tV + if tV is not empty then set the uMinLen of tMk to tV + put specialValue(pSpecial, "rmx") into tV + if tV is not empty then set the uMaxLen of tMk to tV + applyJointRange pIndex + end if +end applyJointSpecial + +-- Defaults for a joint marker's editable settings, by joint kind. +function jointLimitLoDefault pKind + if pKind is "slider" then return -100 + return -45 +end jointLimitLoDefault + +function jointLimitHiDefault pKind + if pKind is "slider" then return 100 + return 45 +end jointLimitHiDefault + +function jointSpringHzDefault pKind + if pKind is "wheel" then return 5 + return 0 +end jointSpringHzDefault + +function jointSpringDampDefault pKind + if pKind is "wheel" then return 0.7 + return 0.5 +end jointSpringDampDefault + +-- ---- Rotation, material presets, launch, gravity, pause ------------- + +-- Re-seat a part at an exact angle (degrees), keeping its position. Joints survive. +on setPartAngle pCtrl, pDeg + local tCx, tCy + if not kindHasBody(the uKind of pCtrl) then exit setPartAngle + put item 1 of the loc of pCtrl into tCx + put item 2 of the loc of pCtrl into tCy + put (((round(pDeg)) mod 360) + 360) mod 360 into pDeg + b2kMoveTo pCtrl, tCx, tCy, pDeg + renderBuild +end setPartAngle + +-- A material is "friction,density,bounce". Presets give parts a quick feel. +function materialPreset pName + switch pName + case "Ice" + return "0.02,0.9,0.05" + case "Rubber" + return "0.9,1,0.85" + case "Metal" + return "0.3,2.6,0.05" + case "Wood" + return "0.5,0.7,0.2" + end switch + return "0.4,1,0.2" -- Default +end materialPreset + +-- Name the preset a part currently matches, or "Custom". +function materialNameOf pCtrl + local tName, tP, tFr, tD, tB + put numOr(the uFriction of pCtrl, 0.4) into tFr + put numOr(the uDensity of pCtrl, 1) into tD + put numOr(the uBounce of pCtrl, 0.2) into tB + set the itemDelimiter to comma + repeat for each item tName in "Default,Ice,Rubber,Metal,Wood" + put materialPreset(tName) into tP + if tFr is item 1 of tP and tD is item 2 of tP and tB is item 3 of tP then return tName + end repeat + return "Custom" +end materialNameOf + +-- Step to the next/previous material preset and apply its values. +on cycleMaterial pCtrl, pDir + local tOrder, tI, tName, tP + put "Default,Ice,Rubber,Metal,Wood" into tOrder + set the itemDelimiter to comma + put itemOffset(materialNameOf(pCtrl), tOrder) + pDir into tI + if tI < 1 then put the number of items of tOrder into tI + if tI > the number of items of tOrder then put 1 into tI + put item tI of tOrder into tName + put materialPreset(tName) into tP + set the uFriction of pCtrl to item 1 of tP + b2kSetFriction pCtrl, item 1 of tP + set the uDensity of pCtrl to item 2 of tP + b2kSetDensity pCtrl, item 2 of tP + set the uBounce of pCtrl to item 3 of tP + b2kSetBounce pCtrl, item 3 of tP +end cycleMaterial + +-- Apply each part's saved launch velocity and spin (called when Run starts). +on armLaunches + local tP, tSpeed, tDir, tSpin + repeat for each line tP in gParts + if tP is empty then next repeat + if not kindHasBody(the uKind of tP) then next repeat + put numOr(the uLaunchSpeed of tP, 0) into tSpeed + put numOr(the uLaunchDir of tP, 0) into tDir + put numOr(the uSpinRate of tP, 0) into tSpin + if tSpeed > 0 then b2kSetVelocity tP, tSpeed * cos(tDir * kPI / 180), - tSpeed * sin(tDir * kPI / 180) + if tSpin is not 0 then b2kSpin tP, tSpin + end repeat +end armLaunches + +-- World gravity presets (metres/sec^2; screen down is negative y). +on applyGravityMode + switch gGravityMode + case "low" + b2kSetGravity 0, -3.5 + break + case "zero" + b2kSetGravity 0, 0 + break + case "reverse" + b2kSetGravity 0, 10 + break + case "sideways" + b2kSetGravity 9, -4 + break + default + b2kSetGravity 0, -10 + end switch +end applyGravityMode + +on cycleGravity + local tOrder, tI + put "normal,low,zero,reverse,sideways" into tOrder + set the itemDelimiter to comma + put itemOffset(gGravityMode, tOrder) + 1 into tI + if tI < 1 or tI > the number of items of tOrder then put 1 into tI + put item tI of tOrder into gGravityMode + applyGravityMode + refreshGravityLabel + put "Gravity: " & gravityModeName(gGravityMode) & "." into gStatus + updateHud +end cycleGravity + +on refreshGravityLabel + if there is a button "ui_act_gravity" then set the label of button "ui_act_gravity" to "Gravity: " & gravityModeName(gGravityMode) +end refreshGravityLabel + +function gravityModeName pMode + switch pMode + case "low" + return "Low (moon)" + case "zero" + return "Zero-G" + case "reverse" + return "Reverse" + case "sideways" + return "Sideways" + end switch + return "Normal" +end gravityModeName + +on togglePause + if gMode is not "run" then + put "Pause works while the simulation is running — press Run first." into gStatus + updateHud + exit togglePause + end if + if b2kIsRunning() then + b2kPause + put "Paused. Use Step to advance one frame, or Pause again to resume." into gStatus + else + b2kResume + put "Resumed." into gStatus + end if + refreshPauseLabel + updateHud +end togglePause + +on stepOnce + if gMode is not "run" then + put "Step works while paused during Run — press Run first." into gStatus + updateHud + exit stepOnce + end if + if b2kIsRunning() then b2kPause -- stepping implies a paused sim + b2kStepOnce + refreshPauseLabel + put "Stepped one frame." into gStatus + updateHud +end stepOnce + +on refreshPauseLabel + if there is not a button "ui_act_pause" then exit refreshPauseLabel + if gMode is "run" and not b2kIsRunning() then set the label of button "ui_act_pause" to "Resume" + else set the label of button "ui_act_pause" to "Pause" +end refreshPauseLabel + +-- Default motor strength by joint kind (torque for hinge/wheel, force for slider). +function motorPowerDefault pKind + if pKind is "hinge" then return kHingeTorque + if pKind is "wheel" then return kWheelTorque + return 1000 +end motorPowerDefault + function frictionLabel pV if pV is empty then put 0.4 into pV if pV <= 0 then return "none (slippery)" @@ -3963,7 +5155,7 @@ on buildRecipesMenu put the width of this card into tWide put the height of this card into tHigh put 280 into tW - put 336 into tH + put 448 into tH put (tWide - tW) div 2 into tL put (tHigh - tH) div 2 into tT makeBar "ui_recipescrim", 0, 0, tWide, tHigh, "10,11,15" @@ -3985,6 +5177,12 @@ on buildRecipesMenu makeRecipeBtn "cart", "Motor Cart", tL + 16, tBy, tW - 32 add 34 to tBy makeRecipeBtn "swing", "Pendulum Swing", tL + 16, tBy, tW - 32 + add 34 to tBy + makeRecipeBtn "catapult", "Catapult", tL + 16, tBy, tW - 32 + add 34 to tBy + makeRecipeBtn "cradle", "Newton's Cradle", tL + 16, tBy, tW - 32 + add 34 to tBy + makeRecipeBtn "dominoes", "Domino Run", tL + 16, tBy, tW - 32 add 40 to tBy makeRecipeBtn "stress", "Stress Test (+" & kStressBatch & " bodies)", tL + 16, tBy, tW - 32 add 40 to tBy @@ -4095,6 +5293,74 @@ on buildRecipeMagnetCatch updateHud end buildRecipeMagnetCatch +-- A heavy block drops on one arm of a hinged lever and flings a ball off the other. +on buildRecipeCatapult + if gMode is "run" then toggleMode + clearAll + local tCx, tBaseY, tFulcrum, tArm, tBall, tWeight + put (gArenaL + gArenaR) div 2 into tCx + put gArenaB - 70 into tBaseY + put placeAnchor(tCx, tBaseY) into tFulcrum + put placeBox(tCx, tBaseY - 16, 200, 14, "180,150,90") into tArm + set the uDensity of tArm to 0.6 + b2kSetDensity tArm, 0.6 + get connectJoint("hinge", tArm, tFulcrum, tCx, tBaseY - 16, 0) + put placeBall(tCx - 84, tBaseY - 36, 30, "232,182,92") into tBall + put placeBox(tCx + 78, tBaseY - 172, 54, 54, "214,92,92") into tWeight + set the uDensity of tWeight to 2.6 + b2kSetDensity tWeight, 2.6 + renderBuild + put "Catapult — press Run: the heavy block drops on the right arm and flings the ball off the left." into gStatus + updateHud +end buildRecipeCatapult + +-- Five equal pendulums in a touching row; the raised end ball kicks the chain. +on buildRecipeCradle + if gMode is "run" then toggleMode + clearAll + local tCx, tTopY, tI, tX, tBall, tAnchor, tDiam, tLen + put (gArenaL + gArenaR) div 2 into tCx + put gArenaT + 70 into tTopY + put 38 into tDiam + put 150 into tLen + repeat with tI = 1 to 5 + put tCx + (tI - 3) * tDiam into tX + put placeAnchor(tX, tTopY) into tAnchor + if tI is 1 then + put placeBall(tX - 106, tTopY + 106, tDiam, "120,200,232") into tBall -- raised 45°, same rope length + else + put placeBall(tX, tTopY + tLen, tDiam, "120,200,232") into tBall + end if + set the uBounce of tBall to 0 + b2kSetBounce tBall, 0 + set the uDensity of tBall to 2 + b2kSetDensity tBall, 2 + get connectJoint("rope", tBall, tAnchor, tX, tTopY, 0) + end repeat + renderBuild + put "Newton's Cradle — press Run: the raised ball swings down and kicks the far ball out." into gStatus + updateHud +end buildRecipeCradle + +-- A launched ball rolls into a line of standing dominoes (shows off Launch + Run). +on buildRecipeDominoes + if gMode is "run" then toggleMode + clearAll + local tBaseY, tX, tI, tStartX, tBall + put gArenaB - 6 into tBaseY + put gArenaL + 90 into tStartX + repeat with tI = 1 to 12 + put tStartX + tI * 34 into tX + get placeBox(tX, tBaseY - 34, 12, 60, "206,124,206") + end repeat + put placeBall(tStartX - 16, tBaseY - 40, 34, "232,182,92") into tBall + set the uLaunchSpeed of tBall to 320 + set the uLaunchDir of tBall to 0 + renderBuild + put "Domino Run — press Run: the ball is launched into the line and the dominoes cascade." into gStatus + updateHud +end buildRecipeDominoes + -- Drop a batch of random bodies and run, to find where the frame rate gives out. on stressTest local tI, tX, tY diff --git a/src/box2dxt-kit.livecodescript b/src/box2dxt-kit.livecodescript index 2d1d586..8a2518b 100644 --- a/src/box2dxt-kit.livecodescript +++ b/src/box2dxt-kit.livecodescript @@ -974,6 +974,28 @@ command b2kWheelSpring pJoint, pHertz, pDamping b2WheelEnableSpring pJoint, true, pHertz, pDamping end b2kWheelSpring +-- OFF switches: the b2k… joint wrappers above only ever turn a feature ON, so +-- these let callers disable a motor or limit again (free swing / unbounded slide). +command b2kMotorOff pJoint + if pJoint is not empty and pJoint > 0 then b2RevoluteEnableMotor pJoint, false, 0, 0 +end b2kMotorOff + +command b2kHingeLimitOff pJoint + if pJoint is not empty and pJoint > 0 then b2RevoluteEnableLimit pJoint, false, 0, 0 +end b2kHingeLimitOff + +command b2kSliderMotorOff pJoint + if pJoint is not empty and pJoint > 0 then b2PrismaticEnableMotor pJoint, false, 0, 0 +end b2kSliderMotorOff + +command b2kSliderLimitOff pJoint + if pJoint is not empty and pJoint > 0 then b2PrismaticEnableLimit pJoint, false, 0, 0 +end b2kSliderLimitOff + +command b2kWheelMotorOff pJoint + if pJoint is not empty and pJoint > 0 then b2WheelEnableMotor pJoint, false, 0, 0 +end b2kWheelMotorOff + command b2kRemoveJoint pJoint if pJoint is not empty and pJoint > 0 then b2DestroyJoint pJoint end b2kRemoveJoint @@ -1043,6 +1065,20 @@ on b2kStep pGen send ("b2kStep " & sGen) to me in 16 milliseconds end b2kStep +-- Advance exactly one fixed step, even while paused (drives a Step button). +command b2kStepOnce + if not sRunning then exit b2kStepOnce + b2Step sWorld, (1 / 60), sSub + if sDragging then + b2MouseSetTarget sDragJoint, b2kToWorldX(the mouseH), b2kToWorldY(the mouseV) + end if + lock screen + b2kSync + b2kDispatchContacts + if sFrameObj is not empty then dispatch "b2kFrame" to sFrameObj + unlock screen +end b2kStepOnce + command b2kSync local tRef, tBody, tWx, tWy, tDead, d put empty into tDead