Init
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
# Edit2d
|
||||||
|
|
||||||
|
Edit2d is my attempt to make a box2d editor, where i can create, simulate, edit and save them using a single tool
|
||||||
|
My main goal is to create game using it
|
||||||
|
It's created using folloing tools
|
||||||
|
|
||||||
|
1. Odin programming language
|
||||||
|
2. GLFW for input handling and graphics
|
||||||
|
3. Dear ImGui for user interfaces
|
||||||
|
4. Box2d
|
||||||
|
|
||||||
|
## Current Progress
|
||||||
|
|
||||||
|
Currently it can create, edit and save all kinds of bodies and shape
|
||||||
|
|
||||||
|
## Working on
|
||||||
|
|
||||||
|
Create edit and save different kinds of joints
|
||||||
|
|
||||||
+237
@@ -0,0 +1,237 @@
|
|||||||
|
package ion
|
||||||
|
|
||||||
|
import b2 "vendor:box2d"
|
||||||
|
import array "core:container/small_array"
|
||||||
|
|
||||||
|
static_index :: i32
|
||||||
|
|
||||||
|
static_index_global :: struct
|
||||||
|
{
|
||||||
|
index : i32,
|
||||||
|
level : string,
|
||||||
|
offset : b2.Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file contains code to handle box2d stuffs of the game code
|
||||||
|
Don't put game's logic here
|
||||||
|
*/
|
||||||
|
|
||||||
|
revolt_joint_def :: struct
|
||||||
|
{
|
||||||
|
using def : b2.RevoluteJointDef,
|
||||||
|
|
||||||
|
//Everything else can be stored in the def
|
||||||
|
entity_a, entity_b : static_index,
|
||||||
|
}
|
||||||
|
|
||||||
|
distance_joint_def :: struct
|
||||||
|
{
|
||||||
|
using def : b2.DistanceJointDef,
|
||||||
|
|
||||||
|
//Everything else can be stored in the def
|
||||||
|
entity_a, entity_b : static_index,
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_world :: struct
|
||||||
|
{
|
||||||
|
world_id : b2.WorldId,
|
||||||
|
|
||||||
|
//This in engine code?
|
||||||
|
static_indexes : map[static_index]int `cbor:"-"`,
|
||||||
|
relations : map[^static_index][dynamic]static_index_global `cbor:"-"`,
|
||||||
|
relations_serializeable : map[ static_index][dynamic]static_index_global,
|
||||||
|
|
||||||
|
revolute_joint_defs : [dynamic]revolt_joint_def,
|
||||||
|
distant_joint_defs : [dynamic]distance_joint_def,
|
||||||
|
|
||||||
|
revolute_joints : [dynamic]b2.JointId,
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_entity_flags_enum :: enum u64 {
|
||||||
|
POLYGON_IS_BOX,
|
||||||
|
MULTI_BODIES,
|
||||||
|
MULTI_SHAPES,
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_entity_flags :: bit_set[engine_entity_flags_enum]
|
||||||
|
|
||||||
|
engine_entity_def :: struct {
|
||||||
|
body_def : b2.BodyDef,
|
||||||
|
shape_def : b2.ShapeDef,
|
||||||
|
shape_type : b2.ShapeType,
|
||||||
|
|
||||||
|
|
||||||
|
radius, scale : f32,
|
||||||
|
centers : [2]b2.Vec2,
|
||||||
|
size : b2.Vec2,
|
||||||
|
is_loop : bool,
|
||||||
|
vertices : array.Small_Array(b2.MAX_POLYGON_VERTICES, b2.Vec2),
|
||||||
|
name_buf : [255]u8 `fmt:"-" cbor:"-"`,
|
||||||
|
|
||||||
|
entity_flags : engine_entity_flags,
|
||||||
|
|
||||||
|
index : static_index,
|
||||||
|
|
||||||
|
body_count : int,
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_entity :: struct {
|
||||||
|
body_id : b2.BodyId,
|
||||||
|
shape_id : b2.ShapeId,
|
||||||
|
|
||||||
|
//This is if the entity has multiple bodies
|
||||||
|
bodies : [dynamic]b2.BodyId,
|
||||||
|
shapes : [dynamic]b2.ShapeId,
|
||||||
|
joints : [dynamic]b2.JointId,
|
||||||
|
entity_flags : engine_entity_flags,
|
||||||
|
index : ^static_index,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
engine_entity_single_body :: proc(def : ^engine_entity_def, world_id: b2.WorldId, index : i32) -> engine_entity
|
||||||
|
{
|
||||||
|
|
||||||
|
def := def
|
||||||
|
|
||||||
|
new_entity : engine_entity
|
||||||
|
|
||||||
|
|
||||||
|
if def.index != 0
|
||||||
|
{
|
||||||
|
new_entity.index = new(static_index)
|
||||||
|
new_entity.index^ = def.index
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entity.body_id = b2.CreateBody(world_id, def.body_def)
|
||||||
|
|
||||||
|
switch def.shape_type{
|
||||||
|
|
||||||
|
case .circleShape:
|
||||||
|
{
|
||||||
|
def.radius *= def.scale
|
||||||
|
circle := b2.Circle{ radius = def.radius }
|
||||||
|
|
||||||
|
def.scale = 1
|
||||||
|
|
||||||
|
new_entity.shape_id = b2.CreateCircleShape(new_entity.body_id, def.shape_def, circle)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .capsuleShape:
|
||||||
|
{
|
||||||
|
def.radius *= def.scale
|
||||||
|
def.centers[0] *= def.scale
|
||||||
|
def.centers[1] *= def.scale
|
||||||
|
|
||||||
|
def.scale = 1
|
||||||
|
|
||||||
|
capsule := b2.Capsule{
|
||||||
|
center1 = def.centers[0],
|
||||||
|
center2 = def.centers[1],
|
||||||
|
radius = def.radius
|
||||||
|
}
|
||||||
|
|
||||||
|
new_entity.shape_id = b2.CreateCapsuleShape(
|
||||||
|
new_entity.body_id,
|
||||||
|
def.shape_def,
|
||||||
|
capsule
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case .chainSegmentShape:
|
||||||
|
{
|
||||||
|
chain_def := b2.DefaultChainDef()
|
||||||
|
verts :[dynamic]b2.Vec2
|
||||||
|
|
||||||
|
for &v in array.slice(&def.vertices){
|
||||||
|
v *= def.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for v in array.slice(&def.vertices){
|
||||||
|
//If it's not a looped chain then it needs two defination
|
||||||
|
|
||||||
|
if !def.is_loop do append(&verts, v)
|
||||||
|
|
||||||
|
append(&verts, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := array.slice(&def.vertices)
|
||||||
|
|
||||||
|
chain_def.points = &verts[0]
|
||||||
|
chain_def.count = i32(len(verts))
|
||||||
|
chain_def.isLoop = def.is_loop
|
||||||
|
|
||||||
|
c := b2.CreateChain(new_entity.body_id, chain_def)
|
||||||
|
|
||||||
|
shapes_data :[10]b2.ShapeId
|
||||||
|
shapes := b2.Body_GetShapes(new_entity.body_id, shapes_data[:])
|
||||||
|
|
||||||
|
for shape in shapes{
|
||||||
|
b2.Shape_SetUserData(shape, rawptr(uintptr(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
def.scale = 1
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
case .segmentShape:
|
||||||
|
{
|
||||||
|
for &v in array.slice(&def.vertices){
|
||||||
|
v *= def.scale
|
||||||
|
}
|
||||||
|
segment : b2.Segment = {point1 = array.get(def.vertices, 0), point2 = array.get(def.vertices, 2)}
|
||||||
|
|
||||||
|
new_entity.shape_id = b2.CreateSegmentShape(new_entity.body_id, def.shape_def, segment)
|
||||||
|
def.scale = 1
|
||||||
|
|
||||||
|
}
|
||||||
|
case .polygonShape:
|
||||||
|
{
|
||||||
|
poly : b2.Polygon
|
||||||
|
|
||||||
|
if .POLYGON_IS_BOX in def.entity_flags
|
||||||
|
{
|
||||||
|
def.size *= def.scale
|
||||||
|
poly = b2.MakeBox(def.size.x, def.size.y)
|
||||||
|
def.scale = 1
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
//def.size *= def.scale
|
||||||
|
|
||||||
|
for &v in array.slice(&def.vertices){
|
||||||
|
v *= def.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
points := make([dynamic]b2.Vec2, 0)
|
||||||
|
|
||||||
|
for p, i in array.slice(&def.vertices){
|
||||||
|
if i >= int(def.vertices.len) do break
|
||||||
|
append_elem(&points, p)
|
||||||
|
}
|
||||||
|
sort_points_ccw(points[:])
|
||||||
|
|
||||||
|
hull := b2.ComputeHull(points[:])
|
||||||
|
poly = b2.MakePolygon(hull, 0)
|
||||||
|
delete(points)
|
||||||
|
def.scale = 1
|
||||||
|
}
|
||||||
|
new_entity.shape_id = b2.CreatePolygonShape(new_entity.body_id, def.shape_def, poly)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if def.shape_type != .chainSegmentShape{
|
||||||
|
b2.Shape_SetUserData(new_entity.shape_id, rawptr(uintptr(index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_entity
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+409
@@ -0,0 +1,409 @@
|
|||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
EditMode :: enum
|
||||||
|
{
|
||||||
|
ENTITY,
|
||||||
|
VERTICES,
|
||||||
|
OVERVIEW,
|
||||||
|
}
|
||||||
|
|
||||||
|
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_revolt_joint : revolt_joint_def,
|
||||||
|
|
||||||
|
curr_joint_joint : distance_joint_def,
|
||||||
|
|
||||||
|
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})
|
||||||
|
{
|
||||||
|
|
||||||
|
//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 , {})
|
||||||
|
{
|
||||||
|
|
||||||
|
if im.CollapsingHeader("Revolute Joints")
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package ion
|
||||||
|
|
||||||
|
|
||||||
|
import "core:encoding/cbor"
|
||||||
|
import im "shared:odin-imgui"
|
||||||
|
import "shared:odin-imgui/imgui_impl_glfw"
|
||||||
|
import "shared:odin-imgui/imgui_impl_opengl3"
|
||||||
|
import gl "vendor:OpenGL"
|
||||||
|
import "vendor:glfw"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
engine_state :: struct
|
||||||
|
{
|
||||||
|
window : glfw.WindowHandle,
|
||||||
|
draw : Draw,
|
||||||
|
restart, pause : bool,
|
||||||
|
substep_count : u32,
|
||||||
|
|
||||||
|
//Must be set before calling ion_init
|
||||||
|
width, height : i32,
|
||||||
|
title : cstring,
|
||||||
|
time : f32,
|
||||||
|
tex_line : u32,
|
||||||
|
drop_callback : glfw.DropProc,
|
||||||
|
|
||||||
|
input : input_state,
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_KEYS :: 512
|
||||||
|
|
||||||
|
input_state :: struct
|
||||||
|
{
|
||||||
|
mouse_wheel : [2]f64,
|
||||||
|
mouse : [2]f64,
|
||||||
|
mouse_prev : [2]f64,
|
||||||
|
|
||||||
|
curr, prev : [MAX_KEYS]bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This will only be called once to initilize the engine
|
||||||
|
|
||||||
|
initilize graphics library, glfw, callbacks
|
||||||
|
*/
|
||||||
|
engine_init :: proc(state: ^engine_state)
|
||||||
|
{
|
||||||
|
|
||||||
|
assert(glfw.Init() == true)
|
||||||
|
|
||||||
|
glfw.WindowHint(glfw.SCALE_TO_MONITOR, 1)
|
||||||
|
|
||||||
|
state.window = glfw.CreateWindow(state.width, state.height, state.title, nil, nil)
|
||||||
|
|
||||||
|
assert(state.window != nil)
|
||||||
|
|
||||||
|
glfw.MakeContextCurrent(state.window)
|
||||||
|
glfw.SwapInterval(1)
|
||||||
|
gl.load_up_to(4, 5, glfw.gl_set_proc_address)
|
||||||
|
|
||||||
|
im.CHECKVERSION()
|
||||||
|
im.CreateContext()
|
||||||
|
|
||||||
|
io := im.GetIO()
|
||||||
|
|
||||||
|
io.ConfigFlags += {
|
||||||
|
.NavEnableKeyboard,
|
||||||
|
.NavEnableGamepad,
|
||||||
|
.DpiEnableScaleFonts,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
im.StyleColorsClassic()
|
||||||
|
|
||||||
|
style := im.GetStyle()
|
||||||
|
style.ChildBorderSize = 0.
|
||||||
|
style.ChildRounding = 6
|
||||||
|
style.TabRounding = 6
|
||||||
|
style.FrameRounding = 6
|
||||||
|
style.GrabRounding = 6
|
||||||
|
style.WindowRounding = 6
|
||||||
|
style.PopupRounding = 6
|
||||||
|
|
||||||
|
imgui_impl_glfw.InitForOpenGL(state.window, true)
|
||||||
|
imgui_impl_opengl3.Init("#version 150")
|
||||||
|
|
||||||
|
state.draw.cam = camera_init()
|
||||||
|
|
||||||
|
display_w, display_h := glfw.GetFramebufferSize(state.window)
|
||||||
|
state.draw.cam.width = display_w
|
||||||
|
state.draw.cam.height = display_h
|
||||||
|
state.draw.cam.zoom = 15
|
||||||
|
state.draw.show_ui = true
|
||||||
|
|
||||||
|
draw_create(&state.draw, &state.draw.cam)
|
||||||
|
|
||||||
|
cbor.tag_register_type({
|
||||||
|
marshal = proc(_: ^cbor.Tag_Implementation, e: cbor.Encoder, v: any) -> cbor.Marshal_Error {
|
||||||
|
cbor._encode_u8(e.writer, 201, .Tag) or_return
|
||||||
|
return nil;
|
||||||
|
},
|
||||||
|
unmarshal = proc(_: ^cbor.Tag_Implementation, d: cbor.Decoder, _: cbor.Tag_Number, v: any) -> (cbor.Unmarshal_Error) {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, 201, rawptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
update_frame :: proc(state: ^engine_state)
|
||||||
|
{
|
||||||
|
state.input.mouse_wheel = {}
|
||||||
|
glfw.PollEvents()
|
||||||
|
|
||||||
|
keyboard_update(state)
|
||||||
|
|
||||||
|
gl.ClearColor(0.4, 0.5, 0.6, 1.0)
|
||||||
|
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||||
|
|
||||||
|
cam := &state.draw.cam
|
||||||
|
cam.width, cam.height = glfw.GetWindowSize(state.window)
|
||||||
|
|
||||||
|
state.width , state.height = glfw.GetFramebufferSize(state.window)
|
||||||
|
gl.Viewport(0, 0, state.width, state.height)
|
||||||
|
|
||||||
|
imgui_impl_opengl3.NewFrame()
|
||||||
|
imgui_impl_glfw.NewFrame()
|
||||||
|
im.NewFrame()
|
||||||
|
}
|
||||||
|
|
||||||
|
end_frame :: proc(state: ^engine_state)
|
||||||
|
{
|
||||||
|
im.Render()
|
||||||
|
imgui_impl_opengl3.RenderDrawData(im.GetDrawData())
|
||||||
|
glfw.SwapBuffers(state.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup :: proc(state: ^engine_state)
|
||||||
|
{
|
||||||
|
imgui_impl_opengl3.Shutdown()
|
||||||
|
imgui_impl_glfw.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_should_close :: proc(state : ^engine_state) -> b32
|
||||||
|
{
|
||||||
|
return glfw.WindowShouldClose(state.window)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
keyboard_update :: proc(state: ^engine_state)
|
||||||
|
{
|
||||||
|
state.input.mouse_prev = state.input.mouse
|
||||||
|
|
||||||
|
state.input.mouse.x, state.input.mouse.y = glfw.GetCursorPos(state.window)
|
||||||
|
|
||||||
|
state.input.prev = state.input.curr
|
||||||
|
|
||||||
|
//Update current states
|
||||||
|
|
||||||
|
for key in glfw.KEY_SPACE ..< MAX_KEYS
|
||||||
|
{
|
||||||
|
state.input.curr[key] = glfw.GetKey(state.window, i32(key)) == glfw.PRESS
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in 0..<glfw.KEY_SPACE
|
||||||
|
{
|
||||||
|
state.input.curr[key] = glfw.GetMouseButton(state.window, i32(key)) == glfw.PRESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is_key_down :: #force_inline proc(state: ^engine_state, key : i32) -> bool{
|
||||||
|
return state.input.curr[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_key_pressed :: #force_inline proc(state: ^engine_state, key : i32) -> bool{
|
||||||
|
return state.input.curr[key] && !state.input.prev[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_key_released :: #force_inline proc(state: ^engine_state, key : i32) -> bool{
|
||||||
|
return !state.input.curr[key] && state.input.prev[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package ion
|
||||||
|
|
||||||
|
import "core:slice"
|
||||||
|
import b2 "vendor:box2d"
|
||||||
|
|
||||||
|
|
||||||
|
saturate :: proc(f : f32) -> f32 {
|
||||||
|
return (f < 0.0) ? 0.0 : (f > 1.0) ? 1.0 : f
|
||||||
|
}
|
||||||
|
|
||||||
|
f32_to_u8_sat :: proc(val : f32) -> u8 {
|
||||||
|
|
||||||
|
sat := saturate(val)
|
||||||
|
sat *= 255
|
||||||
|
sat += 0.5
|
||||||
|
|
||||||
|
ret := cast(u8)sat
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float4_to_u32 :: proc(color : [4]f32) -> u32 {
|
||||||
|
out : u32
|
||||||
|
out = u32(f32_to_u8_sat(color.a)) << 24
|
||||||
|
out |= u32(f32_to_u8_sat(color.r)) << 16
|
||||||
|
out |= u32(f32_to_u8_sat(color.g)) << 8
|
||||||
|
out |= u32(f32_to_u8_sat(color.b))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
u32_to_float4 :: proc(color : u32) -> [4]f32 {
|
||||||
|
ret : [4]f32
|
||||||
|
ret.a = f32((color >> 24) & 0xFF) / 255.0
|
||||||
|
ret.r = f32((color >> 16) & 0xFF) / 255.0
|
||||||
|
ret.g = f32((color >> 8) & 0xFF) / 255.0
|
||||||
|
ret.b = f32((color) & 0xFF) / 255.0
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
centroid :: proc(points: []b2.Vec2) -> b2.Vec2{
|
||||||
|
center := b2.Vec2{0,0}
|
||||||
|
for p in points do center += p
|
||||||
|
center /= f32(len(points))
|
||||||
|
return center
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cross :: proc(o, a, b : b2.Vec2) -> f32{
|
||||||
|
return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
//For sorting
|
||||||
|
curr_center : b2.Vec2
|
||||||
|
|
||||||
|
sort_points_ccw :: proc(points : []b2.Vec2){
|
||||||
|
if len(points) == 0 do return
|
||||||
|
|
||||||
|
curr_center = centroid(points)
|
||||||
|
slice.sort_by(points , proc(a, b: b2.Vec2) -> bool{
|
||||||
|
c := cross(curr_center, a, b)
|
||||||
|
|
||||||
|
if abs(c) < 1e-7{
|
||||||
|
return b2.Distance(curr_center, a) < b2.Distance(curr_center, b)
|
||||||
|
}
|
||||||
|
return c > 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
FlipDirection :: enum {
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
Both, // Flip both horizontally and vertically
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
flip_points :: proc(points: []b2.Vec2, direction : FlipDirection){
|
||||||
|
for &vertex, i in points{
|
||||||
|
switch direction {
|
||||||
|
case .Horizontal:
|
||||||
|
points[i] = b2.Vec2{-vertex.x, vertex.y}
|
||||||
|
case .Vertical:
|
||||||
|
points[i] = b2.Vec2{vertex.x, -vertex.y}
|
||||||
|
case .Both:
|
||||||
|
points[i] = b2.Vec2{-vertex.x, -vertex.y}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#version 330
|
||||||
|
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
uniform float time;
|
||||||
|
uniform vec2 resolution;
|
||||||
|
uniform vec3 baseColor;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//A simple pseudo-random function
|
||||||
|
float random(vec2 st){
|
||||||
|
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5853123);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
vec2 uv = gl_FragCoord.xy / resolution.xy;
|
||||||
|
|
||||||
|
//Create some noise
|
||||||
|
float noise = random(uv + time * 0.5);
|
||||||
|
|
||||||
|
//Adjust these values to control the intensity and color of the grain
|
||||||
|
float grainIntensity = 0.01;
|
||||||
|
|
||||||
|
//Mix the base color with the noise
|
||||||
|
vec3 color = baseColor + vec3(noise * grainIntensity);
|
||||||
|
|
||||||
|
FragColor = vec4(color, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#version 330
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_position;
|
||||||
|
|
||||||
|
void main(void){
|
||||||
|
gl_Position = vec4(v_position, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 f_position;
|
||||||
|
in vec4 f_color;
|
||||||
|
in float f_thickness;
|
||||||
|
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
float radius = 1.0;
|
||||||
|
|
||||||
|
//distance to circle
|
||||||
|
vec2 w = f_position;
|
||||||
|
float dw = length(w);
|
||||||
|
float d = abs(dw - radius);
|
||||||
|
|
||||||
|
fragColor = vec4(f_color.rgb, smoothstep(f_thickness, 0.0, d));
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform mat4 projectionMatrix;
|
||||||
|
uniform float pixelScale;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_localPosition;
|
||||||
|
layout(location = 1) in vec2 v_instancePosition;
|
||||||
|
layout(location = 2) in float v_instanceRadius;
|
||||||
|
layout(location = 3) in vec4 v_instanceColor;
|
||||||
|
|
||||||
|
|
||||||
|
out vec2 f_position;
|
||||||
|
out vec4 f_color;
|
||||||
|
out float f_thickness;
|
||||||
|
|
||||||
|
|
||||||
|
void main(){
|
||||||
|
f_position = v_localPosition;
|
||||||
|
f_color = v_instanceColor;
|
||||||
|
float radius = v_instanceRadius;
|
||||||
|
|
||||||
|
//resolution.y = pixelScale * radius
|
||||||
|
|
||||||
|
f_thickness = 3.0f / (pixelScale * radius);
|
||||||
|
|
||||||
|
vec2 p = vec2(radius * v_localPosition.x, radius * v_localPosition.y) + v_instancePosition;
|
||||||
|
|
||||||
|
gl_Position = projectionMatrix * vec4(p, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Erin Catto
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 f_position;
|
||||||
|
in vec4 f_color;
|
||||||
|
in float f_length;
|
||||||
|
in float f_thickness;
|
||||||
|
|
||||||
|
out vec4 color;
|
||||||
|
|
||||||
|
// Thanks to baz and kolyan3040 for help on this shader
|
||||||
|
// todo this can be optimized a bit, keeping some terms for clarity
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Alpha_compositing
|
||||||
|
vec4 blend_colors(vec4 front,vec4 back)
|
||||||
|
{
|
||||||
|
vec3 cSrc = front.rgb;
|
||||||
|
float alphaSrc = front.a;
|
||||||
|
vec3 cDst = back.rgb;
|
||||||
|
float alphaDst = back.a;
|
||||||
|
|
||||||
|
vec3 cOut = cSrc * alphaSrc + cDst * alphaDst * (1.0 - alphaSrc);
|
||||||
|
float alphaOut = alphaSrc + alphaDst * (1.0 - alphaSrc);
|
||||||
|
|
||||||
|
// remove alpha from rgb
|
||||||
|
cOut = cOut / alphaOut;
|
||||||
|
|
||||||
|
return vec4(cOut, alphaOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// radius in unit quad
|
||||||
|
float radius = 0.5 * (2.0 - f_length);
|
||||||
|
|
||||||
|
vec4 borderColor = f_color;
|
||||||
|
vec4 fillColor = 0.6f * borderColor;
|
||||||
|
|
||||||
|
vec2 v1 = vec2(-0.5 * f_length, 0);
|
||||||
|
vec2 v2 = vec2(0.5 * f_length, 0);
|
||||||
|
|
||||||
|
// distance to line segment
|
||||||
|
vec2 e = v2 - v1;
|
||||||
|
vec2 w = f_position - v1;
|
||||||
|
float we = dot(w, e);
|
||||||
|
vec2 b = w - e * clamp(we / dot(e, e), 0.0, 1.0);
|
||||||
|
float dw = length(b);
|
||||||
|
|
||||||
|
// SDF union of capsule and line segment
|
||||||
|
float d = min(dw, abs(dw - radius));
|
||||||
|
|
||||||
|
// roll the fill alpha down at the border
|
||||||
|
vec4 back = vec4(fillColor.rgb, fillColor.a * smoothstep(radius + f_thickness, radius, dw));
|
||||||
|
|
||||||
|
// roll the border alpha down from 1 to 0 across the border thickness
|
||||||
|
vec4 front = vec4(borderColor.rgb, smoothstep(f_thickness, 0.0f, d));
|
||||||
|
|
||||||
|
color = blend_colors(front, back);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Erin Catto
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform mat4 projectionMatrix;
|
||||||
|
uniform float pixelScale;
|
||||||
|
|
||||||
|
layout(location=0) in vec2 v_localPosition;
|
||||||
|
layout(location=1) in vec4 v_instanceTransform;
|
||||||
|
layout(location=2) in float v_instanceRadius;
|
||||||
|
layout(location=3) in float v_instanceLength;
|
||||||
|
layout(location=4) in vec4 v_instanceColor;
|
||||||
|
|
||||||
|
out vec2 f_position;
|
||||||
|
out vec4 f_color;
|
||||||
|
out float f_length;
|
||||||
|
out float f_thickness;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
f_position = v_localPosition;
|
||||||
|
f_color = v_instanceColor;
|
||||||
|
|
||||||
|
float radius = v_instanceRadius;
|
||||||
|
float length = v_instanceLength;
|
||||||
|
|
||||||
|
// scale quad large enough to hold capsule
|
||||||
|
float scale = radius + 0.5 * length;
|
||||||
|
|
||||||
|
// quad range of [-1, 1] implies normalize radius and length
|
||||||
|
f_length = length / scale;
|
||||||
|
|
||||||
|
// resolution.y = pixelScale * scale
|
||||||
|
f_thickness = 3.0f / (pixelScale * scale);
|
||||||
|
|
||||||
|
float x = v_instanceTransform.x;
|
||||||
|
float y = v_instanceTransform.y;
|
||||||
|
float c = v_instanceTransform.z;
|
||||||
|
float s = v_instanceTransform.w;
|
||||||
|
vec2 p = vec2(scale * v_localPosition.x, scale * v_localPosition.y);
|
||||||
|
p = vec2((c * p.x - s * p.y) + x, (s * p.x + c * p.y) + y);
|
||||||
|
gl_Position = projectionMatrix * vec4(p, 0.0, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Erin Catto
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 f_position;
|
||||||
|
in vec4 f_color;
|
||||||
|
in float f_thickness;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Alpha_compositing
|
||||||
|
vec4 blend_colors(vec4 front, vec4 back)
|
||||||
|
{
|
||||||
|
vec3 cSrc = front.rgb;
|
||||||
|
float alphaSrc = front.a;
|
||||||
|
vec3 cDst = back.rgb;
|
||||||
|
float alphaDst = back.a;
|
||||||
|
|
||||||
|
vec3 cOut = cSrc * alphaSrc + cDst * alphaDst * (1.0 - alphaSrc);
|
||||||
|
float alphaOut = alphaSrc + alphaDst * (1.0 - alphaSrc);
|
||||||
|
cOut = cOut / alphaOut;
|
||||||
|
|
||||||
|
return vec4(cOut, alphaOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// radius in unit quad
|
||||||
|
float radius = 1.0;
|
||||||
|
|
||||||
|
// distance to axis line segment
|
||||||
|
vec2 e = vec2(radius, 0);
|
||||||
|
vec2 w = f_position;
|
||||||
|
float we = dot(w, e);
|
||||||
|
vec2 b = w - e * clamp(we / dot(e, e), 0.0, 1.0);
|
||||||
|
float da = length(b);
|
||||||
|
|
||||||
|
// distance to circle
|
||||||
|
float dw = length(w);
|
||||||
|
float dc = abs(dw - radius);
|
||||||
|
|
||||||
|
// union of circle and axis
|
||||||
|
float d = min(da, dc);
|
||||||
|
|
||||||
|
vec4 borderColor = f_color;
|
||||||
|
vec4 fillColor = 0.6f * borderColor;
|
||||||
|
|
||||||
|
// roll the fill alpha down at the border
|
||||||
|
vec4 back = vec4(fillColor.rgb, fillColor.a * smoothstep(radius + f_thickness, radius, dw));
|
||||||
|
|
||||||
|
// roll the border alpha down from 1 to 0 across the border thickness
|
||||||
|
vec4 front = vec4(borderColor.rgb, smoothstep(f_thickness, 0.0f, d));
|
||||||
|
|
||||||
|
fragColor = blend_colors(front, back);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Erin Catto
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform mat4 projectionMatrix;
|
||||||
|
uniform float pixelScale;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_localPosition;
|
||||||
|
layout(location = 1) in vec4 v_instanceTransform;
|
||||||
|
layout(location = 2) in float v_instanceRadius;
|
||||||
|
layout(location = 3) in vec4 v_instanceColor;
|
||||||
|
|
||||||
|
out vec2 f_position;
|
||||||
|
out vec4 f_color;
|
||||||
|
out float f_thickness;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
f_position = v_localPosition;
|
||||||
|
f_color = v_instanceColor;
|
||||||
|
float radius = v_instanceRadius;
|
||||||
|
|
||||||
|
// resolution.y = pixelScale * radius
|
||||||
|
f_thickness = 3.0f / (pixelScale * radius);
|
||||||
|
|
||||||
|
float x = v_instanceTransform.x;
|
||||||
|
float y = v_instanceTransform.y;
|
||||||
|
float c = v_instanceTransform.z;
|
||||||
|
float s = v_instanceTransform.w;
|
||||||
|
vec2 p = vec2(radius * v_localPosition.x, radius * v_localPosition.y);
|
||||||
|
p = vec2((c * p.x - s * p.y) + x, (s * p.x + c * p.y) + y);
|
||||||
|
gl_Position = projectionMatrix * vec4(p, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Erin Catto
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 f_position;
|
||||||
|
in vec2 f_points[8];
|
||||||
|
flat in int f_count;
|
||||||
|
in float f_radius;
|
||||||
|
in vec4 f_color;
|
||||||
|
in float f_thickness;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Alpha_compositing
|
||||||
|
vec4 blend_colors(vec4 front, vec4 back)
|
||||||
|
{
|
||||||
|
vec3 cSrc = front.rgb;
|
||||||
|
float alphaSrc = front.a;
|
||||||
|
vec3 cDst = back.rgb;
|
||||||
|
float alphaDst = back.a;
|
||||||
|
|
||||||
|
vec3 cOut = cSrc * alphaSrc + cDst * alphaDst * (1.0 - alphaSrc);
|
||||||
|
float alphaOut = alphaSrc + alphaDst * (1.0 - alphaSrc);
|
||||||
|
|
||||||
|
// remove alpha from rgb
|
||||||
|
cOut = cOut / alphaOut;
|
||||||
|
|
||||||
|
return vec4(cOut, alphaOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
float cross2d(in vec2 v1, in vec2 v2)
|
||||||
|
{
|
||||||
|
return v1.x * v2.y - v1.y * v2.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signed distance function for convex polygon
|
||||||
|
float sdConvexPolygon(in vec2 p, in vec2[8] v, in int count)
|
||||||
|
{
|
||||||
|
// Initial squared distance
|
||||||
|
float d = dot(p - v[0], p - v[0]);
|
||||||
|
|
||||||
|
// Consider query point inside to start
|
||||||
|
float side = -1.0;
|
||||||
|
int j = count - 1;
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
// Distance to a polygon edge
|
||||||
|
vec2 e = v[i] - v[j];
|
||||||
|
vec2 w = p - v[j];
|
||||||
|
float we = dot(w, e);
|
||||||
|
vec2 b = w - e * clamp(we / dot(e, e), 0.0, 1.0);
|
||||||
|
float bb = dot(b, b);
|
||||||
|
|
||||||
|
// Track smallest distance
|
||||||
|
if (bb < d)
|
||||||
|
{
|
||||||
|
d = bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the query point is outside any edge then it is outside the entire polygon.
|
||||||
|
// This depends on the CCW winding order of points.
|
||||||
|
float s = cross2d(w, e);
|
||||||
|
if (s >= 0.0)
|
||||||
|
{
|
||||||
|
side = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
j = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return side * sqrt(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 borderColor = f_color;
|
||||||
|
vec4 fillColor = 0.6f * borderColor;
|
||||||
|
|
||||||
|
float dw = sdConvexPolygon(f_position, f_points, f_count);
|
||||||
|
float d = abs(dw - f_radius);
|
||||||
|
|
||||||
|
// roll the fill alpha down at the border
|
||||||
|
vec4 back = vec4(fillColor.rgb, fillColor.a * smoothstep(f_radius + f_thickness, f_radius, dw));
|
||||||
|
|
||||||
|
// roll the border alpha down from 1 to 0 across the border thickness
|
||||||
|
vec4 front = vec4(borderColor.rgb, smoothstep(f_thickness, 0.0f, d));
|
||||||
|
|
||||||
|
fragColor = blend_colors(front, back);
|
||||||
|
|
||||||
|
// todo debugging
|
||||||
|
// float resy = 3.0f / f_thickness;
|
||||||
|
|
||||||
|
// if (resy < 539.9f)
|
||||||
|
// {
|
||||||
|
// fragColor = vec4(1, 0, 0, 1);
|
||||||
|
// }
|
||||||
|
// else if (resy > 540.1f)
|
||||||
|
// {
|
||||||
|
// fragColor = vec4(0, 1, 0, 1);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// fragColor = vec4(0, 0, 1, 1);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Erin Catto
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#version 330
|
||||||
|
|
||||||
|
uniform mat4 projectionMatrix;
|
||||||
|
uniform float pixelScale;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 v_localPosition;
|
||||||
|
layout(location = 1) in vec4 v_instanceTransform;
|
||||||
|
layout(location = 2) in vec4 v_instancePoints12;
|
||||||
|
layout(location = 3) in vec4 v_instancePoints34;
|
||||||
|
layout(location = 4) in vec4 v_instancePoints56;
|
||||||
|
layout(location = 5) in vec4 v_instancePoints78;
|
||||||
|
layout(location = 6) in int v_instanceCount;
|
||||||
|
layout(location = 7) in float v_instanceRadius;
|
||||||
|
layout(location = 8) in vec4 v_instanceColor;
|
||||||
|
|
||||||
|
out vec2 f_position;
|
||||||
|
out vec4 f_color;
|
||||||
|
out vec2 f_points[8];
|
||||||
|
flat out int f_count;
|
||||||
|
out float f_radius;
|
||||||
|
out float f_thickness;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
f_position = v_localPosition;
|
||||||
|
f_color = v_instanceColor;
|
||||||
|
|
||||||
|
f_radius = v_instanceRadius;
|
||||||
|
f_count = v_instanceCount;
|
||||||
|
|
||||||
|
f_points[0] = v_instancePoints12.xy;
|
||||||
|
f_points[1] = v_instancePoints12.zw;
|
||||||
|
f_points[2] = v_instancePoints34.xy;
|
||||||
|
f_points[3] = v_instancePoints34.zw;
|
||||||
|
f_points[4] = v_instancePoints56.xy;
|
||||||
|
f_points[5] = v_instancePoints56.zw;
|
||||||
|
f_points[6] = v_instancePoints78.xy;
|
||||||
|
f_points[7] = v_instancePoints78.zw;
|
||||||
|
|
||||||
|
// Compute polygon AABB
|
||||||
|
vec2 lower = f_points[0];
|
||||||
|
vec2 upper = f_points[0];
|
||||||
|
for (int i = 1; i < v_instanceCount; ++i)
|
||||||
|
{
|
||||||
|
lower = min(lower, f_points[i]);
|
||||||
|
upper = max(upper, f_points[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 center = 0.5 * (lower + upper);
|
||||||
|
vec2 width = upper - lower;
|
||||||
|
float maxWidth = max(width.x, width.y);
|
||||||
|
|
||||||
|
float scale = f_radius + 0.5 * maxWidth;
|
||||||
|
float invScale = 1.0 / scale;
|
||||||
|
|
||||||
|
// Shift and scale polygon points so they fit in 2x2 quad
|
||||||
|
for (int i = 0; i < f_count; ++i)
|
||||||
|
{
|
||||||
|
f_points[i] = invScale * (f_points[i] - center);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale radius as well
|
||||||
|
f_radius = invScale * f_radius;
|
||||||
|
|
||||||
|
// resolution.y = pixelScale * scale
|
||||||
|
f_thickness = 3.0f / (pixelScale * scale);
|
||||||
|
|
||||||
|
// scale up and transform quad to fit polygon
|
||||||
|
float x = v_instanceTransform.x;
|
||||||
|
float y = v_instanceTransform.y;
|
||||||
|
float c = v_instanceTransform.z;
|
||||||
|
float s = v_instanceTransform.w;
|
||||||
|
vec2 p = vec2(scale * v_localPosition.x, scale * v_localPosition.y) + center;
|
||||||
|
p = vec2((c * p.x - s * p.y) + x, (s * p.x + c * p.y) + y);
|
||||||
|
gl_Position = projectionMatrix * vec4(p, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user