From 8ed94fac32824360583c67a6dd1a0ee5135a24cd Mon Sep 17 00:00:00 2001 From: SamratGhale Date: Thu, 12 Mar 2026 12:54:13 +0545 Subject: [PATCH] Joints --- entity.odin | 23 ++++-- interface.odin | 64 +++++++++++------ interface_joints.odin | 162 ++++++++++++++++++++++++++++++++++++++++++ ion.odin | 1 - 4 files changed, 222 insertions(+), 28 deletions(-) create mode 100644 interface_joints.odin diff --git a/entity.odin b/entity.odin index d41fbbc..262c71e 100644 --- a/entity.odin +++ b/entity.odin @@ -17,20 +17,24 @@ static_index_global :: struct Don't put game's logic here */ +joint_common :: struct +{ + entity_a, entity_b : static_index, + bodyIdA, bodyIdB : b2.BodyId, +} + revolt_joint_def :: struct { - using def : b2.RevoluteJointDef, - //Everything else can be stored in the def entity_a, entity_b : static_index, + + using def : b2.RevoluteJointDef, } distance_joint_def :: struct { - using def : b2.DistanceJointDef, - - //Everything else can be stored in the def entity_a, entity_b : static_index, + using def : b2.DistanceJointDef, } engine_world :: struct @@ -42,10 +46,15 @@ engine_world :: struct relations : map[^static_index][dynamic]static_index_global `cbor:"-"`, relations_serializeable : map[ static_index][dynamic]static_index_global, + /* + Seems okay to put the joint defs in engine rather than in game because + we don't add more attributes in joints in the game + + Can be changed later without requireing refactor + */ revolute_joint_defs : [dynamic]revolt_joint_def, distant_joint_defs : [dynamic]distance_joint_def, - - revolute_joints : [dynamic]b2.JointId, + joints : [dynamic]b2.JointId `cbor:"-"`, } engine_entity_flags_enum :: enum u64 { diff --git a/interface.odin b/interface.odin index 8d0d52f..4bc75f4 100644 --- a/interface.odin +++ b/interface.odin @@ -1,16 +1,18 @@ package ion -import "base:runtime" import "core:slice" import "core:container/small_array" import "core:fmt" import im "shared:odin-imgui" -import "vendor:glfw" import b2 "vendor:box2d" /* This library will only account for box2d's entities editing It only deals with one world_id, which means typically one level + +All the interface follows a pattern +i.e. It takes interface_state pointer and returns a boolean indicating weather the world needs to be reloaded + */ EditMode :: enum @@ -18,23 +20,23 @@ EditMode :: enum ENTITY, VERTICES, OVERVIEW, + JOINT, } interface_state :: struct { - entity_defs: [dynamic]^engine_entity_def, - entities: [dynamic]^engine_entity, - selected_entity: ^i32, - world: ^engine_world, - state: ^engine_state, - + entity_defs : [dynamic]^engine_entity_def, + entities : [dynamic]^engine_entity, + selected_entity : ^i32, + world : ^engine_world, + state : ^engine_state, + vertex_index : ^i32, edit_mode : EditMode, - - curr_revolt_joint : revolt_joint_def, - curr_joint_joint : distance_joint_def, - + curr_joint_index : i32, + curr_joint_type : b2.JointType, + curr_static_index : static_index_global, } @@ -251,15 +253,15 @@ interface_edit_static_index :: proc(interface:^interface_state, def: ^engine_ent return false } +/* interface_edit_revolute_joint :: proc(interface: ^interface_state) -> bool { - //Select static index and then get bodyId from it //If chain shapre then allow choosing index level := interface.world - joint_def := &interface.curr_revolt_joint + joint_def := interface.curr_revolt_joint if im.BeginCombo("Index A", fmt.ctprint(joint_def.entity_a)) { @@ -317,13 +319,14 @@ interface_edit_revolute_joint :: proc(interface: ^interface_state) -> bool if im.Button("Add joint") { - append(&level.revolute_joint_defs, interface.curr_revolt_joint) + //append(&level.revolute_joint_defs, interface.curr_revolt_joint) return true } return false } +*/ interface_entity :: proc(interface: ^interface_state) -> bool @@ -333,14 +336,14 @@ interface_entity :: proc(interface: ^interface_state) -> bool if entity_selected { - def := interface.entity_defs[interface.selected_entity^] + def := interface.entity_defs[interface.selected_entity^] def_old := def^ ret := false if im.BeginTabItem("Entity", nil, {.Leading}) { - + interface.edit_mode = .ENTITY //Flags for flag in engine_entity_flags_enum { @@ -375,12 +378,26 @@ interface_entity :: proc(interface: ^interface_state) -> bool if im.BeginTabItem("Joints", nil , {}) { - - if im.CollapsingHeader("Revolute Joints") + interface.edit_mode = .JOINT + + if im.BeginCombo("Joint type", fmt.ctprint(interface.curr_joint_type)) + { + for type in b2.JointType + { + if im.Selectable(fmt.ctprint(type), type == interface.curr_joint_type) do interface.curr_joint_type = type + } + im.EndCombo() + } + + + if interface.curr_joint_type == .distanceJoint + { + ret |= interface_edit_distance_joint(interface) + } + if interface.curr_joint_type == .revoluteJoint { ret |= interface_edit_revolute_joint(interface) } - im.EndTabItem() } @@ -407,3 +424,10 @@ interface_all :: proc(interface: ^interface_state) -> bool im.End() return ret } + + + + + + + diff --git a/interface_joints.odin b/interface_joints.odin new file mode 100644 index 0000000..150dc9b --- /dev/null +++ b/interface_joints.odin @@ -0,0 +1,162 @@ +package ion + +import "core:fmt" +import b2 "vendor:box2d" +import im "shared:odin-imgui" + +/* + TODO: + Delete joints + Angles in degree +*/ + +/* + All joints have bodyIdA and bodyIdB +*/ +interface_edit_joint_common :: proc(joint_def : ^joint_common, interface: ^interface_state) -> bool +{ + level := interface.world + { + if joint_def.entity_a in level.static_indexes{ + entity_a := interface.entity_defs[level.static_indexes[joint_def.entity_a]] + points_add(&interface.state.draw.points, entity_a.body_def.position, 20.0, b2.HexColor.Plum) + } + if joint_def.entity_b in level.static_indexes{ + entity_b := interface.entity_defs[level.static_indexes[joint_def.entity_b]] + points_add(&interface.state.draw.points, entity_b.body_def.position, 20.0, b2.HexColor.Plum) + } + } + + /* + Set body A and Body B on the basis of static index so that it can pesist after restart + */ + + if im.BeginCombo("Index A", fmt.ctprint(joint_def.entity_a)) + { + for i in level.static_indexes + { + if im.Selectable(fmt.ctprint(i), i == joint_def.entity_a) do joint_def.entity_a = i + } + im.EndCombo() + } + + im.Separator() + + if im.BeginCombo("Index B", fmt.ctprint(joint_def.entity_b)) + { + for i in level.static_indexes + { + if im.Selectable(fmt.ctprint(i), i == joint_def.entity_b) do joint_def.entity_b = i + } + im.EndCombo() + } + return false +} + + +interface_edit_distance_joint :: proc( interface : ^interface_state ) -> bool +{ + level := interface.world + interface.curr_joint_type = .distanceJoint + + if len(level.distant_joint_defs) == 0 do im.Text("No distance joint created, Click add to create new") + + if im.Button("Create new joint") + { + append(&level.distant_joint_defs, distance_joint_def{def = b2.DefaultDistanceJointDef()}) + + interface.curr_joint_index = i32(len(level.distant_joint_defs)) + } + + //Select index + { + if im.BeginCombo("Select joint", fmt.ctprint(interface.curr_joint_index)) + { + for i in 0..= i32(len(level.distant_joint_defs)) do return false + + joint_def := &level.distant_joint_defs[interface.curr_joint_index] + old_def := joint_def^ + + if interface_edit_joint_common(cast(^joint_common)joint_def, interface) do return true + + /* + Highlight the bodies here + */ + + im.SliderFloat2("localAnchorA", &joint_def.localAnchorA, -5, 5) + im.SliderFloat2("localAnchorB", &joint_def.localAnchorB, -5, 5) + im.SliderFloat("Rest length", &joint_def.length, 0, 100) + im.Checkbox("Enable Spring", &joint_def.enableSpring) + im.InputFloat("Hertz ", &joint_def.hertz) + im.InputFloat("Damping Ratio", &joint_def.dampingRatio) + im.Checkbox("Enable Limit", &joint_def.enableLimit) + im.InputFloat("Min length", &joint_def.minLength) + im.InputFloat("Max length", &joint_def.maxLength) + im.Checkbox("Enable Motor", &joint_def.enableMotor) + im.InputFloat("Moror Torque", &joint_def.maxMotorForce) + im.InputFloat("Moror Speed", &joint_def.motorSpeed) + im.Checkbox("Collide Connected", &joint_def.collideConnected) + + return old_def != joint_def^ +} + + +interface_edit_revolute_joint :: proc( interface : ^interface_state ) -> bool +{ + level := interface.world + interface.curr_joint_type = .revoluteJoint + + if len(level.revolute_joint_defs) == 0 do im.Text("No revolute joint created, Click add to create new") + + if im.Button("Create new joint") + { + append(&level.revolute_joint_defs, revolt_joint_def{def = b2.DefaultRevoluteJointDef()}) + + interface.curr_joint_index = i32(len(level.revolute_joint_defs)) + } + + //Select index + if im.BeginCombo("Select joint", fmt.ctprint(interface.curr_joint_index)) + { + for i in 0..= i32(len(level.revolute_joint_defs)) do return false + + joint_def := &level.revolute_joint_defs[interface.curr_joint_index] + old_def := joint_def^ + + if interface_edit_joint_common(cast(^joint_common)joint_def, interface) do return true + + /* + Highlight the bodies here + */ + + im.SliderFloat2("localAnchorA", &joint_def.localAnchorA, -5, 5) + im.SliderFloat2("localAnchorB", &joint_def.localAnchorB, -5, 5) + im.SliderFloat("Refresh angle", &joint_def.referenceAngle, 0, 100) + im.SliderFloat("Target angle", &joint_def.targetAngle, 0, 100) + im.Checkbox("Enable Spring", &joint_def.enableSpring) + im.InputFloat("Hertz ", &joint_def.hertz) + im.InputFloat("Damping Ratio", &joint_def.dampingRatio) + im.InputFloat("Lower Angle", &joint_def.lowerAngle) + im.InputFloat("Upper Angle", &joint_def.upperAngle) + im.InputFloat("Max Motor Limit", &joint_def.maxMotorTorque) + im.InputFloat("Motor Speed", &joint_def.motorSpeed) + im.InputFloat("Draw Size", &joint_def.drawSize) + im.Checkbox("Enable Motor", &joint_def.enableMotor) + im.Checkbox("Enable Limit", &joint_def.enableLimit) + im.Checkbox("Collide Connected", &joint_def.collideConnected) + + return old_def != joint_def^ +} \ No newline at end of file diff --git a/ion.odin b/ion.odin index 8765e2c..6daa650 100644 --- a/ion.odin +++ b/ion.odin @@ -40,7 +40,6 @@ input_state :: struct /* This will only be called once to initilize the engine - initilize graphics library, glfw, callbacks */ engine_init :: proc(state: ^engine_state)