Files
Edit2D/interface.odin
T
SamratGhale 8ed94fac32 Joints
2026-03-12 12:54:13 +05:45

434 lines
9.9 KiB
Odin

package ion
import "core:slice"
import "core:container/small_array"
import "core:fmt"
import im "shared:odin-imgui"
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
{
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,
vertex_index : ^i32,
edit_mode : EditMode,
curr_joint_index : i32,
curr_joint_type : b2.JointType,
curr_static_index : static_index_global,
}
interface_body_def_editor :: proc(def: ^engine_entity_def)
{
if im.BeginCombo("Body Type", fmt.ctprint(def.body_def.type))
{
for type in b2.BodyType
{
if im.Selectable(fmt.ctprint(type), def.body_def.type == type) do def.body_def.type = type
}
im.EndCombo()
}
im.SliderFloat2("Position", &def.body_def.position, -50, 50)
angle := RAD2DEG * b2.Rot_GetAngle(def.body_def.rotation)
if im.SliderFloat("Rotation", &angle, 0, 359)
{
rad := DEG2RAD * angle
def.body_def.rotation = b2.MakeRot(rad)
}
im.SliderFloat2("Linear velocity", &def.body_def.linearVelocity, 0, 500)
im.SliderFloat("Angular velocity", &def.body_def.angularVelocity, 0, 500)
im.SliderFloat("Linear Damping", &def.body_def.linearDamping, 0, 500)
im.SliderFloat("Angular Damping", &def.body_def.angularDamping, 0, 500)
im.SliderFloat("Gravity Scale", &def.body_def.gravityScale, 0, 100)
im.Checkbox("Fixed rotation", &def.body_def.fixedRotation)
if im.InputText("Body Name", cstring(&def.name_buf[0]), 255) {
def.body_def.name = cstring(&def.name_buf[0])
}
}
interface_shape_def_editor :: proc(def: ^engine_entity_def) -> bool
{
shape_def := &def.shape_def
if im.BeginCombo("Shape Type", fmt.ctprint(def.shape_type)) {
for type in b2.ShapeType
{
if im.Selectable(fmt.ctprint(type), def.shape_type == type)
{
def.shape_type = type
}
}
im.EndCombo()
}
switch def.shape_type {
case .circleShape:
{
im.SliderFloat("radius", &def.radius, 0, 40)
}
case .polygonShape:
{
im.SliderFloat2("Size", &def.size, -500, 500)
}
case .capsuleShape:
{
im.SliderFloat2("Center 1", &def.centers[0], -100, 100)
im.SliderFloat2("Center 2", &def.centers[0], -100, 100)
im.SliderFloat("Radius", &def.radius, 0, 40)
}
case .chainSegmentShape:
{
im.Checkbox("is loop", &def.is_loop)
}
case .segmentShape:
{
//TODO
}
}
im.SliderFloat("Density", &def.shape_def.density, 0, 100)
if im.Button("Flip horizontally") do flip_points(small_array.slice(&def.vertices), .Horizontal)
if im.Button("Flip Vertically ") do flip_points(small_array.slice(&def.vertices), .Vertical)
if im.TreeNode("Events and contacts") {
im.Checkbox("Is sensor", &def.shape_def.isSensor)
im.Checkbox("Enable Sensor Events", &def.shape_def.enableSensorEvents)
im.Checkbox("Enable Contact Events", &def.shape_def.enableContactEvents)
im.Checkbox("Enable Hit Events", &def.shape_def.enableHitEvents)
im.Checkbox("Enable Presolve Events", &def.shape_def.enablePreSolveEvents)
im.Checkbox("Invoke contact Creation", &def.shape_def.invokeContactCreation)
im.Checkbox("Update body mass ", &def.shape_def.updateBodyMass)
im.TreePop()
}
if im.TreeNode("Material") {
im.Separator()
im.SliderFloat("Friction", &def.shape_def.material.friction, 0, 1)
im.SliderFloat("Restitution", &def.shape_def.material.restitution, 0, 1)
im.SliderFloat("Rolling Resistance", &def.shape_def.material.rollingResistance, 0, 1)
im.SliderFloat("Tangent Speed", &def.shape_def.material.tangentSpeed, 0, 1)
im.InputInt("User material id", &def.shape_def.material.userMaterialId)
//Colorpicker
if im.TreeNode("Color") {
color_f32 := u32_to_float4(def.shape_def.material.customColor)
if im.ColorPicker4("Custom Color", &color_f32, {.Uint8, .InputRGB}) {
def.shape_def.material.customColor = float4_to_u32(color_f32)
}
im.TreePop()
}
im.Separator()
im.TreePop()
}
return false
}
interface_draw_options :: proc(state: ^engine_state) {
if im.BeginTabItem("Controls") {
debug_draw := &state.draw.debug_draw
im.Checkbox("Shapes", &debug_draw.drawShapes)
im.Checkbox("Joints", &debug_draw.drawJoints)
im.Checkbox("Joint Extras", &debug_draw.drawJointExtras)
im.Checkbox("Bounds", &debug_draw.drawBounds)
im.Checkbox("Contact Points", &debug_draw.drawContacts)
im.Checkbox("Contact Normals", &debug_draw.drawContactNormals)
im.Checkbox("Contact Inpulses", &debug_draw.drawContactImpulses)
im.Checkbox("Contact Features", &debug_draw.drawContactFeatures)
im.Checkbox("Friction Inpulses", &debug_draw.drawFrictionImpulses)
im.Checkbox("Mass ", &debug_draw.drawMass)
im.Checkbox("Body Names", &debug_draw.drawBodyNames)
im.Checkbox("Graph Colors", &debug_draw.drawGraphColors)
im.Checkbox("Islands ", &debug_draw.drawIslands)
im.SliderFloat("Rotation", &state.draw.cam.rotation, 0, 360)
im.EndTabItem()
}
}
interface_edit_static_index :: proc(interface:^interface_state, def: ^engine_entity_def) -> bool
{
curr_index := &interface.curr_static_index
entity := interface.entities[interface.selected_entity^]
level := interface.world
if level.relations[entity.index] == nil
{
level.relations[entity.index] = {}
}
indexes := &level.relations[entity.index]
if im.InputInt("Index Value", &def.index) do return true
ret := false
if def.index != 0
{
//For now only select from current room
if im.BeginCombo("Edit Select index", fmt.ctprint(curr_index.index))
{
for index in level.static_indexes
{
if im.Selectable(fmt.ctprint(index), curr_index.index == index)
{
curr_index.index = index
}
}
im.EndCombo()
}
if curr_index.index != 0
{
if indexes != nil
{
if im.Button("Add relation")
{
if !slice.contains(indexes[:], interface.curr_static_index)
{
append(indexes, interface.curr_static_index)
}
}
}
}
if indexes != nil{
for val, i in indexes
{
im.Text("%d", val.index)
im.SameLine()
if im.Button("Delete") {
ordered_remove(indexes, i)
}
}
}
}
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
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)
{
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)
{
joint_def.entity_b = i
}
}
im.EndCombo()
}
//Now box2d
im.SliderFloat2("localAnchorA", &joint_def.localAnchorA, -5, 5)
im.SliderFloat2("localAnchorB", &joint_def.localAnchorB, -5, 5)
//Convert to degree to radian
im.SliderFloat("Reference 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.Checkbox("Enable Limit", &joint_def.enableLimit)
im.InputFloat("Lower Angle", &joint_def.lowerAngle)
im.InputFloat("Upper Angle", &joint_def.upperAngle)
im.Checkbox("Enable Motor", &joint_def.enableMotor)
im.InputFloat("Moror Torque", &joint_def.maxMotorTorque)
im.InputFloat("Moror Speed", &joint_def.motorSpeed)
im.InputFloat("Draw Size", &joint_def.drawSize)
im.Checkbox("Collide Connected", &joint_def.collideConnected)
if im.Button("Add joint")
{
//append(&level.revolute_joint_defs, interface.curr_revolt_joint)
return true
}
return false
}
*/
interface_entity :: proc(interface: ^interface_state) -> bool
{
entity_selected := interface.selected_entity^ != -1
if entity_selected
{
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
{
contains := flag in def.entity_flags
if im.Checkbox(fmt.ctprint(flag), &contains)
{
def.entity_flags ~= {flag}
}
}
im.Separator()
if im.CollapsingHeader("Shape Edit")
{
interface_shape_def_editor(def)
}
im.Separator()
if im.CollapsingHeader("Body Edit")
{
interface_body_def_editor(def)
}
if im.CollapsingHeader("Static Index")
{
ret |= interface_edit_static_index(interface, def)
}
im.EndTabItem()
}
if im.BeginTabItem("Joints", nil , {})
{
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()
}
return def^ != def_old || ret
}else{
return false
}
}
interface_all :: proc(interface: ^interface_state) -> bool
{
ret := false
if im.Begin("Box2d interface")
{
if im.BeginTabBar("Tabs")
{
if interface_entity(interface) do ret = true
interface_draw_options(interface.state)
im.EndTabBar()
}
}
im.End()
return ret
}