553 lines
No EOL
21 KiB
Text
553 lines
No EOL
21 KiB
Text
import "luxe: world" for Entity, UIClear, UILayoutMode, UIBehave, UIContain, Assets, Material, UI, UIEvent, UILayoutBehave
|
|
import "luxe: ui" for Control, UIList, UIWindow, UIButton, UIPanel, UIImage, UISlider, UILabel
|
|
import "luxe: ui/field/number" for UINumber
|
|
import "luxe: id" for ID
|
|
import "luxe: draw" for PathStyle
|
|
import "luxe: math" for Math
|
|
import "luxe: color" for Color
|
|
|
|
class ColorPickerData {
|
|
|
|
//parameters - we could make some of this dynamic if we wanted to
|
|
triangle_size {35}
|
|
outer_ring_size {200}
|
|
inner_ring_size {140}
|
|
|
|
r{_r}
|
|
g{_g}
|
|
b{_b}
|
|
|
|
h{_h}
|
|
s{_s}
|
|
v{_v}
|
|
|
|
a{_a}
|
|
|
|
rgba{[_r, _g, _b, _a]}
|
|
hsva{[_h, _s, _v, _a]}
|
|
|
|
toString{"Data - HSV:[%(h), %(s), %(v)] - RGB:[%(r), %(g), %(b)] - A:[%(a)]"}
|
|
|
|
construct new(){
|
|
set_rgba(Color.hex(0xFFAABB))
|
|
}
|
|
|
|
set_rgba(col){set_rgba(col, true)}
|
|
set_rgba(col, update_spaces){
|
|
if(!ColorPicker.approx(_r, col.r)) _r = col.r
|
|
if(!ColorPicker.approx(_g, col.g)) _g = col.g
|
|
if(!ColorPicker.approx(_b, col.b)) _b = col.b
|
|
if(!ColorPicker.approx(_a, col.a)) _a = col.a
|
|
|
|
if(!update_spaces) return
|
|
|
|
var hsv = Color.rgb2hsv(col)
|
|
//be careful not to destroy hue when doing rgb to hsv
|
|
if(!ColorPicker.approx(_h, hsv.x) && !ColorPicker.approx(_s, 0)) _h = hsv.x
|
|
if(!ColorPicker.approx(_s, hsv.y)) _s = hsv.y
|
|
if(!ColorPicker.approx(_v, hsv.z)) _v = hsv.z
|
|
}
|
|
|
|
set_hsva(col){set_hsva(col, true)}
|
|
set_hsva(col, update_spaces){
|
|
if(!ColorPicker.approx(_h, col.x)) _h = col.x
|
|
if(!ColorPicker.approx(_s, col.y)) _s = col.y
|
|
if(!ColorPicker.approx(_v, col.z)) _v = col.z
|
|
if(!ColorPicker.approx(_a, col.a)) _a = col.a
|
|
|
|
if(!update_spaces) return
|
|
|
|
var rgb = Color.hsv2rgb(col)
|
|
if(!ColorPicker.approx(_r, rgb.r)) _r = rgb.r
|
|
if(!ColorPicker.approx(_g, rgb.g)) _g = rgb.g
|
|
if(!ColorPicker.approx(_b, rgb.b)) _b = rgb.b
|
|
}
|
|
|
|
set_rgba_component(index: Num, value: Num){
|
|
if(index == 0){
|
|
set_rgba([value, _g, _b, _a])
|
|
} else if(index == 1){
|
|
set_rgba([_r, value, _b, _a])
|
|
} else if(index == 2){
|
|
set_rgba([_r, _g, value, _a])
|
|
} else if(index == 3){
|
|
set_rgba([_r, _g, _b, value])
|
|
}
|
|
}
|
|
|
|
get_rgba_component(index: Num){
|
|
if(index == 0){
|
|
return _r
|
|
} else if(index == 1){
|
|
return _g
|
|
} else if(index == 2){
|
|
return _b
|
|
} else if(index == 3){
|
|
return _a
|
|
}
|
|
}
|
|
|
|
set_hsva_component(index: Num, value: Num){
|
|
if(index == 0){
|
|
set_hsva([value, _s, _v, _a])
|
|
} else if(index == 1){
|
|
set_hsva([_h, value, _v, _a])
|
|
} else if(index == 2){
|
|
set_hsva([_h, _s, value, _a])
|
|
} else if(index == 3){
|
|
set_hsva([_h, _s, _v, value])
|
|
}
|
|
}
|
|
|
|
get_hsva_component(index: Num){
|
|
if(index == 0){
|
|
return _h
|
|
} else if(index == 1){
|
|
return _s
|
|
} else if(index == 2){
|
|
return _v
|
|
} else if(index == 3){
|
|
return _a
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class ColorPicker{
|
|
static create(ui: Entity) : Control{
|
|
var data = ColorPickerData.new()
|
|
|
|
//setup root
|
|
var panel = UIWindow.create(ui)
|
|
Control.set_size(panel, 350, 500)
|
|
Control.set_id(panel, "panel.%(ID.unique())")
|
|
//Control.set_state_data(panel, base_color) //turns out UIWindows use their own state, whoops
|
|
|
|
var color_view = Control.create(ui)
|
|
Control.set_behave(color_view, UIBehave.fill)
|
|
Control.set_margin(color_view, 0, 40, 0, 0)
|
|
Control.child_add(panel, color_view)
|
|
Control.set_id(panel, "color_view.%(ID.unique())")
|
|
Control.set_state_data(color_view, data)
|
|
|
|
var hsv_view = Control.create(ui)
|
|
Control.set_behave(hsv_view, UIBehave.fill)
|
|
Control.set_margin(hsv_view, 0, 0, 0, 0)
|
|
Control.set_contain(hsv_view, UIContain.column | UIContain.start)
|
|
Control.child_add(color_view, hsv_view)
|
|
Control.set_id(panel, "hsv_view.%(ID.unique())")
|
|
|
|
var wheel = create_hsv_wheel(ui, color_view)
|
|
Control.set_behave(wheel, UIBehave.left | UIBehave.top)
|
|
Control.set_margin(wheel, 8, 8, 0, 0)
|
|
Control.child_add(hsv_view, wheel)
|
|
|
|
var component_choice = Control.create(ui)
|
|
Control.set_contain(component_choice, UIContain.row | UIContain.start)
|
|
Control.set_behave(component_choice, UIBehave.hfill | UIBehave.left | UIBehave.top)
|
|
Control.set_margin(component_choice, 8, 8, 0, 0)
|
|
Control.set_size(component_choice, 0, 32)
|
|
Control.child_add(hsv_view, component_choice)
|
|
|
|
var rgba_button = UIButton.create(ui)
|
|
Control.set_behave(rgba_button, UIBehave.left)
|
|
UIButton.set_text(rgba_button, "rgb")
|
|
Control.child_add(component_choice, rgba_button)
|
|
|
|
var hsva_button = UIButton.create(ui)
|
|
Control.set_behave(hsva_button, UIBehave.left)
|
|
UIButton.set_text(hsva_button, "hsv")
|
|
Control.child_add(component_choice, hsva_button)
|
|
|
|
var rgba_components = rgba_values(ui, color_view)
|
|
Control.child_add(hsv_view, rgba_components)
|
|
|
|
var hsva_components = hsva_values(ui, color_view)
|
|
Control.set_visible(hsva_components, false)
|
|
Control.child_add(hsv_view, hsva_components)
|
|
|
|
Control.set_events(rgba_button) {|event|
|
|
if(event.type == UIEvent.press){
|
|
Control.set_visible(rgba_components, true)
|
|
Control.set_visible(hsva_components, false)
|
|
}
|
|
}
|
|
|
|
Control.set_events(hsva_button) {|event|
|
|
if(event.type == UIEvent.press){
|
|
Control.set_visible(rgba_components, false)
|
|
Control.set_visible(hsva_components, true)
|
|
}
|
|
}
|
|
|
|
//todo: next steps: hex input(s?)
|
|
|
|
return panel
|
|
}
|
|
|
|
static rgba_values(ui: UI, color_view: Control) {
|
|
var rgba_components = Control.create(ui)
|
|
Control.set_contain(rgba_components, UIContain.column | UIContain.hfit | UIContain.vfit)
|
|
Control.set_behave(rgba_components, UIBehave.hfill)
|
|
Control.set_margin(rgba_components, 0, 8, 0, 0)
|
|
|
|
var red = color_component(ui, "R", 0, color_view, "rgb")
|
|
Control.child_add(rgba_components, red)
|
|
var green = color_component(ui, "G", 1, color_view, "rgb")
|
|
Control.child_add(rgba_components, green)
|
|
var blue = color_component(ui, "B", 2, color_view, "rgb")
|
|
Control.child_add(rgba_components, blue)
|
|
var alpha = color_component(ui, "A", 3, color_view, "rgb")
|
|
Control.child_add(rgba_components, alpha)
|
|
|
|
return rgba_components
|
|
}
|
|
|
|
static hsva_values(ui: UI, color_view: Control) {
|
|
var rgba_components = Control.create(ui)
|
|
Control.set_contain(rgba_components, UIContain.column | UIContain.hfit | UIContain.vfit)
|
|
Control.set_behave(rgba_components, UIBehave.hfill)
|
|
Control.set_margin(rgba_components, 0, 8, 0, 0)
|
|
|
|
var red = color_component(ui, "H", 0, color_view, "hsv")
|
|
Control.child_add(rgba_components, red)
|
|
var green = color_component(ui, "S", 1, color_view, "hsv")
|
|
Control.child_add(rgba_components, green)
|
|
var blue = color_component(ui, "V", 2, color_view, "hsv")
|
|
Control.child_add(rgba_components, blue)
|
|
var alpha = color_component(ui, "A", 3, color_view, "hsv")
|
|
Control.child_add(rgba_components, alpha)
|
|
|
|
return rgba_components
|
|
}
|
|
|
|
static color_component (ui: Entity, name: String, index: Num, color_view: Control, space: String){
|
|
var data: ColorPickerData = Control.get_state_data(color_view)
|
|
|
|
var base = Control.create(ui)
|
|
Control.set_size(base, 0, 32)
|
|
Control.set_behave(base, UIBehave.top | UIBehave.left | UILayoutBehave.hfill)
|
|
Control.set_contain(base, UIContain.row)
|
|
Control.set_margin(base, 0, 0, 0, 0)
|
|
Control.set_id(base, "component.%(name).%(ID.unique())")
|
|
|
|
var label = UILabel.create(ui)
|
|
UILabel.set_text(label, name)
|
|
UILabel.set_text_size(label, 20)
|
|
Control.set_size(label, 20, 32)
|
|
Control.set_behave(label, UIBehave.left | UIBehave.vfill)
|
|
Control.set_margin(label, 8, 0, 8, 0)
|
|
Control.child_add(base, label)
|
|
|
|
var number = UINumber.create(ui)
|
|
UINumber.set_validation(number) {|input|
|
|
return Math.fixed(input)
|
|
}
|
|
Control.set_behave(number, UIBehave.top | UIBehave.left)
|
|
Control.set_margin(number, 0, 0, 0, 0)
|
|
Control.set_size(number, 72, 32)
|
|
Control.child_add(base, number)
|
|
|
|
var slider = UISlider.create(ui)
|
|
UISlider.set_min(slider, 0)
|
|
UISlider.set_max(slider, 1)
|
|
UISlider.set_value(slider, 0)
|
|
Control.set_behave(slider, UILayoutBehave.top | UILayoutBehave.bottom | UILayoutBehave.hfill)
|
|
Control.set_margin(slider, 8, 8, 8, 8)
|
|
Control.child_add(base, slider)
|
|
|
|
if(space == "rgb"){
|
|
UINumber.set_value(number, data.get_rgba_component(index))
|
|
UISlider.set_value(slider, data.get_rgba_component(index))
|
|
} else if(space == "hsv"){
|
|
UINumber.set_value(number, data.get_hsva_component(index))
|
|
UISlider.set_value(slider, data.get_hsva_component(index))
|
|
}
|
|
|
|
Control.set_events(number) { |event: UIEvent|
|
|
if(event.type == UIEvent.change){
|
|
if(approx(UISlider.get_value(slider), event.change, 0.001)) return
|
|
UI.events_emit(base, UIEvent.change, event.change)
|
|
}
|
|
}
|
|
|
|
Control.set_events(slider) { |event: UIEvent|
|
|
if(event.type == UIEvent.change){
|
|
if(approx(UINumber.get_value(number), event.change, 0.001)) return
|
|
UI.events_emit(base, UIEvent.change, event.change)
|
|
}
|
|
}
|
|
|
|
Control.set_events(color_view){|event|
|
|
if(event.type == UIEvent.change){
|
|
if(space == "rgb"){
|
|
UISlider.set_value(slider, Math.fixed(event.change.get_rgba_component(index)))
|
|
UINumber.set_value(number, Math.fixed(event.change.get_rgba_component(index)))
|
|
} else if(space == "hsv"){
|
|
UISlider.set_value(slider, Math.fixed(event.change.get_hsva_component(index)))
|
|
UINumber.set_value(number, Math.fixed(event.change.get_hsva_component(index)))
|
|
}
|
|
}
|
|
}
|
|
|
|
Control.set_events(base) {|event|
|
|
if(event.type == UIEvent.change){
|
|
var color = Control.get_state_data(color_view)
|
|
if(space == "rgb"){
|
|
if(approx(event.change, color.get_rgba_component(index))) return
|
|
color.set_rgba_component(index, event.change)
|
|
} else if(space == "hsv"){
|
|
if(approx(event.change, color.get_hsva_component(index))) return
|
|
color.set_hsva_component(index, event.change)
|
|
}
|
|
Control.set_state_data(color_view, color)
|
|
UI.events_emit(color_view, UIEvent.change, color)
|
|
}
|
|
}
|
|
|
|
return base
|
|
}
|
|
|
|
static create_hsv_wheel(ui: Entity, data_root: Control) : Control {
|
|
var data: ColorPickerData = Control.get_state_data(data_root)
|
|
var base_value_gamma = 1.6
|
|
var base_sat_gamma = 0.8
|
|
|
|
var color_wheel = Control.create(ui)
|
|
Control.set_size(color_wheel, data.outer_ring_size, data.outer_ring_size)
|
|
//Control.child_add(parent, color_wheel)
|
|
//the wheel has a bunch of state to have stuff be more solid (for example not reset hue when going to [0,0,0,_]) and interaction state
|
|
Control.set_state_data(color_wheel, {"ring":null, "triangle":null})
|
|
Control.set_process(color_wheel){|control, state, event, x,y,w,h|
|
|
//this might not be nessecary anymore?
|
|
if(event.control != control) return
|
|
if(event.type == UIEvent.move){
|
|
x = Control.get_pos_x_abs(control)
|
|
y = Control.get_pos_y_abs(control)
|
|
var center = [x + w/2, y + h/2]
|
|
if(state["ring"] == "captured"){ //if we're editing the ring(hue)
|
|
//hue from angle
|
|
var diff = [event.x - center.x, event.y - center.y]
|
|
var angle = Math.atan2(-diff.x, diff.y) + Num.pi
|
|
var hue = angle / Num.tau
|
|
|
|
//change relevant values
|
|
var picker_state: ColorPickerData = Control.get_state_data(data_root)
|
|
var hsv = [hue, picker_state.s, picker_state.v, picker_state.a]
|
|
picker_state.set_hsva(hsv)
|
|
UI.events_emit(data_root, UIEvent.change, picker_state)
|
|
} else if(state["triangle"] == "captured"){ //if we're editing the triangle (saturation & value)
|
|
var picker_state: ColorPickerData = Control.get_state_data(data_root)
|
|
var diff = [event.x - center.x, event.y - center.y] //position rel to center
|
|
Math.rotate(diff, 0, 0, picker_state.h * -360) //follow triangle rotation
|
|
//constrain to triangle
|
|
var triangle_size = picker_state.triangle_size
|
|
constrain_to_triangle(diff, triangle_size)
|
|
|
|
//get saturation based on distance to 2 edges
|
|
var saturation = (-diff.y + triangle_size) / ((-diff.y + triangle_size) + (Math.dot2D(diff, dir_vec(Math.radians(30), 1)) + triangle_size))
|
|
saturation = saturation.pow(base_sat_gamma)
|
|
saturation = saturation.clamp(0, 1)
|
|
|
|
//similarly get value based on distance to other edge
|
|
var value = Math.dot2D(diff, dir_vec(Math.radians(150), 1))
|
|
value = value + triangle_size
|
|
value = value / (triangle_size * 3)
|
|
value = 1 - value
|
|
value = value.pow(base_value_gamma)
|
|
value = value.clamp(0, 1)
|
|
|
|
//calculate and apply relevant values
|
|
var hsv = [picker_state.h, saturation, value, picker_state.a]
|
|
picker_state.set_hsva(hsv)
|
|
UI.events_emit(data_root, UIEvent.change, picker_state)
|
|
} else { //if we're not editing anything, lets check what we're hovering over!
|
|
var picker_state: ColorPickerData = Control.get_state_data(data_root)
|
|
var distance = Math.dist2D(center, event)
|
|
//first reset hover state (can only be hover bc we checked for captured)
|
|
state["ring"] = null
|
|
state["triangle"] = null
|
|
//then find actual state
|
|
if(distance > picker_state.inner_ring_size/2 && distance < picker_state.outer_ring_size/2){
|
|
state["ring"] = "hover"
|
|
} else if(distance < picker_state.inner_ring_size/2) { //todo: triangle not round
|
|
state["triangle"] = "hover"
|
|
}
|
|
}
|
|
} else if(event.type == UIEvent.press && event.button == 1) {
|
|
//if we click, check if we're hovering over anything and if so, change it to captured
|
|
var hover_ring = state["ring"] != null //ring is hover (or captured)
|
|
var hover_tri = state["triangle"] != null //triangle is hover (or captured)
|
|
if(hover_ring || hover_tri) UI.capture(control) //if any of both happens, capture this
|
|
if(hover_ring) state["ring"] = "captured"
|
|
if(hover_tri) state["triangle"] = "captured"
|
|
} else if(event.type == UIEvent.release && event.button == 1) { //if we release a click, let go
|
|
var picker_state: ColorPickerData = Control.get_state_data(data_root)
|
|
x = Control.get_pos_x_abs(control)
|
|
y = Control.get_pos_y_abs(control)
|
|
var center = [x + w/2, y + h/2]
|
|
//todo: avoid code dupe with hover code
|
|
var distance = Math.dist2D(center, event)
|
|
//first reset hover state (can only be hover bc we checked for captured)
|
|
state["ring"] = null
|
|
state["triangle"] = null
|
|
//then find actual state
|
|
if(distance > picker_state.inner_ring_size/2 && distance < picker_state.outer_ring_size/2){
|
|
state["ring"] = "hover"
|
|
} else if(distance < picker_state.inner_ring_size/2) { //todo: triangle not round
|
|
state["triangle"] = "hover"
|
|
}
|
|
UI.uncapture(control)
|
|
}
|
|
}
|
|
Control.set_allow_input(color_wheel, true)
|
|
|
|
//ring visuals, interresting stuff happens in shader
|
|
var color_ring = UIImage.create(ui)
|
|
Control.set_size(color_ring, data.outer_ring_size, data.outer_ring_size)
|
|
Control.set_contain(color_ring, UIContain.justify)
|
|
var color_ring_mat = Material.create("materials/color_ring")
|
|
Material.set_input(color_ring_mat, "wheel.outer_distance", 0.5)
|
|
Material.set_input(color_ring_mat, "wheel.inner_distance", 0.5 * data.inner_ring_size / data.outer_ring_size)
|
|
UIImage.set_material(color_ring, color_ring_mat)
|
|
Control.child_add(color_wheel, color_ring)
|
|
|
|
//triangle visual, interresting stuff happens in shader
|
|
var color_triangle = UIImage.create(ui)
|
|
Control.set_margin(color_triangle, 30, 30, 0, 0)
|
|
Control.set_size(color_triangle, data.triangle_size * 4, data.triangle_size*4)
|
|
Control.set_contain(color_triangle, UIContain.justify)
|
|
var color_tri_mat = Material.create("materials/color_triangle")
|
|
Material.set_input(color_tri_mat, "triangle.value_gamma", base_value_gamma)
|
|
Material.set_input(color_tri_mat, "triangle.saturation_gamma", base_sat_gamma)
|
|
Material.set_input(color_tri_mat, "triangle.hue", data.h)
|
|
UIImage.set_material(color_triangle, color_tri_mat)
|
|
UIImage.set_angle(color_triangle, data.h * -360)
|
|
Control.child_add(color_wheel, color_triangle)
|
|
//rotates image when color wheel hue updates
|
|
Control.set_events(data_root) {|event|
|
|
if(event.type == UIEvent.change){
|
|
var hue = event.change.h
|
|
UIImage.set_angle(color_triangle, hue * -360)
|
|
Material.set_input(color_tri_mat, "triangle.hue", hue)
|
|
}
|
|
}
|
|
|
|
//overlay does everything thats easier done with direct UI draw functions
|
|
var color_wheel_overlay = Control.create(ui)
|
|
Control.set_behave(color_wheel_overlay, UIBehave.fill)
|
|
Control.set_margin(color_wheel_overlay, 0, 0, 0, 0)
|
|
Control.set_render(color_wheel_overlay){|control, state, x, y, w, h|
|
|
//prep data
|
|
var depth = UI.draw_depth_of(control, 0)
|
|
var center = [x + w/2, y + h/2]
|
|
var style: PathStyle = PathStyle.new()
|
|
|
|
var picker_state: ColorPickerData = Control.get_state_data(data_root)
|
|
var color = picker_state.rgba
|
|
color.a = 1
|
|
var hue = picker_state.h
|
|
var saturation = picker_state.s.pow(1/base_sat_gamma)
|
|
var value = picker_state.v.pow(1/base_value_gamma)
|
|
|
|
//draw hue ring borders
|
|
style.color = [0.22, 0.22, 0.22, 1]
|
|
style.thickness = 3
|
|
UI.draw_ring(control, center.x, center.y, depth, picker_state.outer_ring_size/2, picker_state.outer_ring_size/2, 0, 360, 8, style)
|
|
UI.draw_ring(control, center.x, center.y, depth, picker_state.inner_ring_size/2, picker_state.inner_ring_size/2, 0, 360, 8, style)
|
|
|
|
//draw hue ring color rect
|
|
var wheel_data = Control.get_state_data(color_wheel)
|
|
style.thickness = 2
|
|
if(wheel_data["ring"] != null) {
|
|
style.color = [0.5, 0.5, 0.5, 1]
|
|
} else {
|
|
style.color = [0.22, 0.22, 0.22, 1]
|
|
}
|
|
var size = [20, 30]
|
|
var angle_degree = hue * -360
|
|
var angle_radian = hue * -Num.tau
|
|
var avg_radius = (picker_state.inner_ring_size + picker_state.outer_ring_size) / 2 / 2
|
|
var offset = [-angle_radian.sin * avg_radius - size.x/2, -angle_radian.cos * avg_radius - size.y/2]
|
|
UI.draw_quad(control, center.x + offset.x, center.y + offset.y, depth, size.x, size.y, angle_degree, color)
|
|
UI.draw_rect(control, center.x + offset.x, center.y + offset.y, depth, size.x, size.y, angle_degree, style)
|
|
|
|
//draw triangle color dot
|
|
style.thickness = 2
|
|
if(wheel_data["triangle"] != null) {
|
|
style.color = [0.5, 0.5, 0.5, 1]
|
|
} else {
|
|
style.color = [0.22, 0.22, 0.22, 1]
|
|
}
|
|
var triangle_size = picker_state.triangle_size
|
|
var v_dist = (1 - value) * (triangle_size * 3) - triangle_size //distance in "value direction" in triangle
|
|
var v_dir = dir_vec(Math.radians(150), 1) //"value direction"
|
|
var v_norm = [-v_dir.y, v_dir.x] //direction orthogonal to "value direction"
|
|
var width = value * triangle_size * 1.73205080756 //width (along value ortho) at current value value
|
|
var pos = [v_dir.x * v_dist + (saturation*2-1) * width * v_norm.x, v_dir.y * v_dist + (saturation*2-1) * width * v_norm.y]
|
|
//adjust for rotation
|
|
Math.rotate(pos, 0, 0, hue * 360)
|
|
var dot_size = 7
|
|
UI.draw_circle(control, center.x + pos.x, center.y + pos.y, depth, dot_size, dot_size, 0, 360, 8, color)
|
|
UI.draw_ring(control, center.x + pos.x, center.y + pos.y, depth, dot_size, dot_size, 0, 360, 8, style)
|
|
}
|
|
Control.child_add(color_wheel, color_wheel_overlay)
|
|
|
|
return color_wheel
|
|
}
|
|
|
|
static approx_rgb(one: Color, other: Color, epsilson: Num) : Bool{
|
|
return approx(one.x, other.x, epsilson) && approx(one.y, other.y, epsilson) && approx(one.z, other.z, epsilson)
|
|
}
|
|
|
|
|
|
static approx(one: Num, other: Num) : Bool{
|
|
return approx(one, other, 0.001)
|
|
}
|
|
|
|
static approx(one: Num, other: Num, epsilon: Num) : Bool{
|
|
if(!(one is Num && other is Num)) return false
|
|
return (one - other).abs < epsilon
|
|
}
|
|
|
|
//if this goes into the actual engine and not editor code this should take degree
|
|
//but I like radians better :)
|
|
static dir_vec(angle: Num, length: Num) : List{
|
|
return [angle.cos * length, angle.sin * length]
|
|
}
|
|
|
|
static constrain_to_triangle(pos: List, size: Num){
|
|
//size is the inner radius, so we get half the base by multiplying with tan(60°)
|
|
var base_width = size * 1.73205
|
|
//first, we do the cheap calculation for [0, 1]
|
|
var edge_dist = -(pos.y - size)
|
|
if(edge_dist < 0){
|
|
pos.y = size
|
|
pos.x = pos.x.clamp(-base_width, base_width)
|
|
return
|
|
}
|
|
//then, for the diagonal edges
|
|
var edge_normal = dir_vec(Math.radians(30), 1)
|
|
//first we check the distance from the edge
|
|
edge_dist = Math.dot2D(pos, edge_normal)
|
|
if(edge_dist < -size){
|
|
//if the dot is outside the edge also get the position along the edge tangent so we can constrain it
|
|
var edge_tangent = [edge_normal.y, -edge_normal.x]
|
|
var tangent_dist = Math.dot2D(pos, edge_tangent).clamp(-base_width, base_width)
|
|
//then construct the constrained position at the edge, but keeping the relative, constrained, tangent position
|
|
pos.x = edge_normal.x * -size + edge_tangent.x * tangent_dist
|
|
pos.y = edge_normal.y * -size + edge_tangent.y * tangent_dist
|
|
return
|
|
}
|
|
//same as prev
|
|
edge_normal = dir_vec(Math.radians(150), 1)
|
|
edge_dist = Math.dot2D(pos, edge_normal)
|
|
if(edge_dist < -size){
|
|
var edge_tangent = [edge_normal.y, -edge_normal.x]
|
|
var tangent_dist = Math.dot2D(pos, edge_tangent).clamp(-base_width, base_width)
|
|
pos.x = edge_normal.x * -size + edge_tangent.x * tangent_dist
|
|
pos.y = edge_normal.y * -size + edge_tangent.y * tangent_dist
|
|
return
|
|
}
|
|
}
|
|
} |