Pixel-Composer/scripts/BBMOD_Gizmo/BBMOD_Gizmo.gml
2023-04-23 16:47:33 +02:00

1224 lines
37 KiB
Text

/// @enum Enumeration of edit spaces.
enum BBMOD_EEditSpace
{
/// @member Edit instances in world-space.
Global,
/// @member Edit instance relatively to its transformation.
Local,
/// @member Total number of members of this enum.
SIZE,
};
/// @enum Enumeration of edit types.
enum BBMOD_EEditType
{
/// @member Translate selected instances.
Position,
/// @member Rotate selected instances.
Rotation,
/// @member Scale selected instances.
Scale,
/// @member Total number of members of this enum.
SIZE,
};
/// @enum Enumeration of edit axes.
enum BBMOD_EEditAxis
{
/// @member No edit.
None = 0,
/// @member Edit on X axis.
X = $1,
/// @member Edit on Y axis.
Y = $10,
/// @member Edit on Z axis.
Z = $100,
/// @member Edit on all axes.
All = $111,
};
/// @func BBMOD_Gizmo([_size])
///
/// @extends BBMOD_Class
///
/// @desc A gizmo for transforming instances.
///
/// @param {Real} [_size] The size of the gizmo. Default value is 10 units.
///
/// @note This requries synchronnous loading of models, therefore it cannot
/// be used on platforms like HTML5, which require asynchronnous loading.
/// You also **must** use {@link BBMOD_Camera} for the gizmo to work properly!
function BBMOD_Gizmo(_size=10.0)
: BBMOD_Class() constructor
{
BBMOD_CLASS_GENERATED_BODY;
static Class_destroy = destroy;
/// @var {Array<Struct.BBMOD_Model>} Gizmo models for individual edit modes.
/// @note Please note that these are not loaded asynchronnously, therefore
/// the gizmo cannot be used on platforms that require asynchronnous loading,
/// like HTML5!
/// @see BBMOD_EEditType
/// @readonly
static Models = undefined;
/// @var {Array<Struct.BBMOD_Material>} Materials used when mouse-picking
/// the gizmo.
static MaterialsSelect = undefined;
if (Models == undefined)
{
var _shaderSelect = new BBMOD_BaseShader(
BBMOD_ShGizmoSelect, BBMOD_VFORMAT_DEFAULT);
var _materialSelect = new BBMOD_BaseMaterial(_shaderSelect);
_materialSelect.BaseOpacity = sprite_get_texture(BBMOD_SprGizmo, 1);
MaterialsSelect = [_materialSelect];
var _shader = new BBMOD_BaseShader(BBMOD_ShGizmo, BBMOD_VFORMAT_DEFAULT);
var _material = new BBMOD_BaseMaterial(_shader);
_material.BaseOpacity = sprite_get_texture(BBMOD_SprGizmo, 0);
var _modelMove = new BBMOD_Model("Data/BBMOD/Models/GizmoMove.bbmod")
.freeze();
// TODO: Fix gizmo model
_modelMove.RootNode.Transform = new BBMOD_DualQuaternion();
_modelMove.Materials[@ 0] = _material;
var _modelScale = new BBMOD_Model("Data/BBMOD/Models/GizmoScale.bbmod")
.freeze();
_modelScale.Materials[@ 0] = _material;
var _modelRotate = new BBMOD_Model("Data/BBMOD/Models/GizmoRotate.bbmod")
.freeze();
_modelRotate.Materials[@ 0] = _material;
Models = [
_modelMove,
_modelRotate,
_modelScale,
];
}
/// @var {Bool} If `true` then the gizmo is editing selected instances.
IsEditing = false;
/// @var {Struct.BBMOD_Vec2} Screen-space coordinates to lock the mouse
/// cursor at or `undefined`.
/// @private
__mouseLockAt = undefined;
/// @var {Struct.BBMOD_Vec3} World-space offset from the mouse to the gizmo
/// or `undefined`.
/// @private
__mouseOffset = undefined;
/// @var {Constant.Cursor} The cursor used before editing started.
/// @private
__cursorBackup = undefined;
/// @var {Bool} Enables snapping to grid when moving objects. Default value
/// is `true`.
/// @see BBMOD_Gizmo.GridSize
EnableGridSnap = true;
/// @var {Struct.BBMOD_Vec3} The size of the grid. Default value is
/// `(1, 1, 1)`.
/// @see BBMOD_Gizmo.EnableGridSnap
GridSize = new BBMOD_Vec3(1.0);
/// @var {Bool} Enables angle snapping when rotating objects. Default value
/// is `true`.
/// @see BBMOD_Gizmo.AngleSnap
EnableAngleSnap = true;
/// @var {Real} Angle snapping size. Default value is 1.
/// @see BBMOD_Gizmo.EnableAngleSnap
AngleSnap = 1.0;
/// @var {Real} Determines the space in which are the selected instances
/// transformed. Use values from {@link BBMOD_EEditSpace}.
EditSpace = BBMOD_EEditSpace.Global;
/// @var {Real} Determines how are the selected instances transformed
/// (translated/rotated/scaled. Use values from {@link BBMOD_EEditType}.
EditType = BBMOD_EEditType.Position;
/// @var {Real} Determines on which axes are the selected instances edited.
/// Use values from {@link BBMOD_EEditAxis}.
EditAxis = BBMOD_EEditAxis.None;
/// @var {Constant.MouseButton} The mouse button used for dragging the gizmo.
/// Default is `mb_left`.
ButtonDrag = mb_left;
/// @var {Constant.VirtualKey} The virtual key used to switch to the next
/// edit type. Default is `vk_tab`.
/// @see BBMOD_Gizmo.EditType
KeyNextEditType = vk_tab;
/// @var {Constant.VirtualKey} The virtual key used to switch to the next
/// edit space. Default is `vk_space`.
/// @see BBMOD_Gizmo.EditSpace
KeyNextEditSpace = vk_space;
/// @var {Constant.VirtualKey} The virtual key used to increase
/// speed of editing (e.g. rotate objects by a larger angle). Default is
/// `vk_shift`.
KeyEditFaster = vk_shift;
/// @var {Constant.VirtualKey} The virtual key used to decrease
/// speed of editing (e.g. rotate objects by a smaller angle). Default is
/// `vk_control`.
KeyEditSlower = vk_control;
/// @var {Constant.VirtualKey} The virtual key used to cancel editing and
/// revert changes. Default is `vk_escape`.
KeyCancel = vk_escape;
/// @var {Constant.VirtualKey} The virtual key used to ignore grid and
/// angle snapping when they are enabled. Default is `vk_alt`.
/// @see BBMOD_Gizmo.EnableGridSnap
/// @see BBMOD_Gizmo.EnableAngleSnap
KeyIgnoreSnap = vk_alt;
/// @var {Real} The size of the gizmo. Default value is 10.
Size = _size;
/// @var {Struct.BBMOD_Vec3} The gizmo's position in world-space.
/// @readonly
Position = new BBMOD_Vec3();
/// @var {Struct.BBMOD_Vec3} The gizmo's position in world-space before
/// editing started or `undefined`.
/// @private
__positionBackup = undefined;
/// @var {Struct.BBMOD_Vec3} The gizmo's rotation in euler angles.
Rotation = new BBMOD_Vec3();
/// @var {Id.DsList<Id.Instance>} A list of selected instances.
/// @readonly
Selected = ds_list_create();
/// @var {Id.DsList<Struct>} A list of additional data required for editing
/// instances, e.g. their original offset from the gizmo, rotation and scale.
/// @private
__instanceData = ds_list_create();
/// @var {Struct.BBMOD_Vec3} The current scaling factor of selected instances.
/// @private
__scaleBy = new BBMOD_Vec3(0.0);
/// @var {Struct.BBMOD_Vec3} The current euler angles we are rotating selected
/// instances by.
/// @private
__rotateBy = new BBMOD_Vec3(0.0);
/// @var {Function} A function that the gizmo uses to check whether an instance
/// exists. Must take the instance as the first argument and return a bool.
/// Defaults a function that returns the result of `instance_exists`.
InstanceExists = function (_instance) {
gml_pragma("forceinline");
return instance_exists(_instance);
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// global matrix. Normally this is an identity matrix. If the instance is
/// attached to another instance for example, then this will be that
/// instance's transformation matrix. Must take the instance as the first
/// argument and return a {@link BBMOD_Matrix}. Defaults to a function that
/// always returns an identity matrix.
GetInstanceGlobalMatrix = function (_instance) {
gml_pragma("forceinline");
return new BBMOD_Matrix();
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// position on the X axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that returns the instance's `x`
/// variable.
GetInstancePositionX = function (_instance) {
gml_pragma("forceinline");
return _instance.x;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// position on the X axis. Must take the instance as the first argument and
/// its new position on the X axis as the second argument. Defaults to a
/// function that assings the new position to the instance's `x` variable.
SetInstancePositionX = function (_instance, _x) {
gml_pragma("forceinline");
_instance.x = _x;
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// position on the Y axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that returns the instance's `y`
/// variable.
GetInstancePositionY = function (_instance) {
gml_pragma("forceinline");
return _instance.y;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// position on the Y axis. Must take the instance as the first argument and
/// its new position on the Y axis as the second argument. Defaults to a
/// function that assings the new position to the instance's `y` variable.
SetInstancePositionY = function (_instance, _y) {
gml_pragma("forceinline");
_instance.y = _y;
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// position on the Z axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that returns the instance's `z`
/// variable.
GetInstancePositionZ = function (_instance) {
gml_pragma("forceinline");
return _instance.z;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// position on the Z axis. Must take the instance as the first argument and
/// its new position on the Z axis as the second argument. Defaults to a
/// function that assings the new position to the instance's `Z` variable.
SetInstancePositionZ = function (_instance, _z) {
gml_pragma("forceinline");
_instance.z = _z;
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// rotation on the X axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that always returns 0.
GetInstanceRotationX = function (_instance) {
gml_pragma("forceinline");
return 0.0;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// rotation on the X axis. Must take the instance as the first argument and
/// its new rotation on the X axis as the second argument. Defaults to a
/// function that does not do anything.
SetInstanceRotationX = function (_instance, _x) {
gml_pragma("forceinline");
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// rotation on the Y axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that always returns 0.
GetInstanceRotationY = function (_instance) {
gml_pragma("forceinline");
return 0.0;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// rotation on the Y axis. Must take the instance as the first argument and
/// its new rotation on the Y axis as the second argument. Defaults to a
/// function that does not do anything.
SetInstanceRotationY = function (_instance, _y) {
gml_pragma("forceinline");
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// rotation on the Z axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that returns the instance's
/// `image_angle` variable.
GetInstanceRotationZ = function (_instance) {
gml_pragma("forceinline");
return _instance.image_angle;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// rotation on the Z axis. Must take the instance as the first argument and
/// its new rotation on the Z axis as the second argument. Defaults to a
/// function that assings the new rotation to the instance's `image_angle`
/// variable.
SetInstanceRotationZ = function (_instance, _z) {
gml_pragma("forceinline");
_instance.image_angle = _z;
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// scale on the X axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that returns the instance's
/// `image_xscale` variable.
GetInstanceScaleX = function (_instance) {
gml_pragma("forceinline");
return _instance.image_xscale;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// scale on the X axis. Must take the instance as the first argument and
/// its new scale on the X axis as the second argument. Defaults to a
/// function that assings the new scale to the instance's `image_xscale`
/// variable.
SetInstanceScaleX = function (_instance, _x) {
gml_pragma("forceinline");
_instance.image_xscale = _x;
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// scale on the Y axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that returns the instance's
/// `image_yscale` variable.
GetInstanceScaleY = function (_instance) {
gml_pragma("forceinline");
return _instance.image_yscale;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// scale on the Y axis. Must take the instance as the first argument and
/// its new scale on the Y axis as the second argument. Defaults to a
/// function that assings the new scale to the instance's `image_yscale`
/// variable.
SetInstanceScaleY = function (_instance, _y) {
gml_pragma("forceinline");
_instance.image_yscale = _y;
};
/// @var {Function} A function that the gizmo uses to retrieve an instance's
/// scale on the Z axis. Must take the instance as the first argument and
/// return a real. Defaults to a function that always returns 1.
GetInstanceScaleZ = function (_instance) {
gml_pragma("forceinline");
return 1.0;
};
/// @var {Function} A function that the gizmo uses to change an instance's
/// scale on the Z axis. Must take the instance as the first argument and
/// its new scale on the Z axis as the second argument. Defaults to a
/// function that does not do anything.
SetInstanceScaleZ = function (_instance, _z) {
gml_pragma("forceinline");
};
/// @func get_instance_position_vec3(_instance)
///
/// @desc Retrieves an instance's position as {@link BBMOD_Vec3}.
///
/// @param {Id.Instance} _instance The ID of the instance.
///
/// @return {Struct.BBMOD_Vec3} The instance's position.
static get_instance_position_vec3 = function (_instance) {
gml_pragma("forceinline");
return new BBMOD_Vec3(
GetInstancePositionX(_instance),
GetInstancePositionY(_instance),
GetInstancePositionZ(_instance));
};
/// @func set_instance_position_vec3(_instance, _position)
///
/// @desc Changes an instance's position using a {@link BBMOD_Vec3}.
///
/// @param {Id.Instance} _instance The ID of the instance.
/// @param {Struct.BBMOD_Vec3} _position The new position of the instance.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static set_instance_position_vec3 = function (_instance, _position) {
gml_pragma("forceinline");
SetInstancePositionX(_instance, _position.X);
SetInstancePositionY(_instance, _position.Y);
SetInstancePositionZ(_instance, _position.Z);
return self;
};
/// @func get_instance_rotation_vec3(_instance)
///
/// @desc Retrieves an instance's rotation as {@link BBMOD_Vec3}.
///
/// @param {Id.Instance} _instance The ID of the instance.
///
/// @return {Struct.BBMOD_Vec3} The instance's rotation in euler angles.
static get_instance_rotation_vec3 = function (_instance) {
gml_pragma("forceinline");
return new BBMOD_Vec3(
GetInstanceRotationX(_instance),
GetInstanceRotationY(_instance),
GetInstanceRotationZ(_instance));
};
/// @func set_instance_rotation_vec3(_instance, _rotation)
///
/// @desc Changes an instance's rotation using a {@link BBMOD_Vec3}.
///
/// @param {Id.Instance} _instance The ID of the instance.
/// @param {Struct.BBMOD_Vec3} _rotation The new rotation of the instance
/// in euler angles.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static set_instance_rotation_vec3 = function (_instance, _rotation) {
gml_pragma("forceinline");
SetInstanceRotationX(_instance, _rotation.X);
SetInstanceRotationY(_instance, _rotation.Y);
SetInstanceRotationZ(_instance, _rotation.Z);
return self;
};
/// @func get_instance_scale_vec3(_instance)
///
/// @desc Retrieves an instance's scale as {@link BBMOD_Vec3}.
///
/// @param {Id.Instance} _instance The ID of the instance.
///
/// @return {Struct.BBMOD_Vec3} The instance's scale.
static get_instance_scale_vec3 = function (_instance) {
gml_pragma("forceinline");
return new BBMOD_Vec3(
GetInstanceScaleX(_instance),
GetInstanceScaleY(_instance),
GetInstanceScaleZ(_instance));
};
/// @func set_instance_scale_vec3(_instance, _scale)
///
/// @desc Changes an instance's scale using a {@link BBMOD_Vec3}.
///
/// @param {Id.Instance} _instance The ID of the instance.
/// @param {Struct.BBMOD_Vec3} _scale The new scale of the instance.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static set_instance_scale_vec3 = function (_instance, _scale) {
gml_pragma("forceinline");
SetInstanceScaleX(_instance, _scale.X);
SetInstanceScaleY(_instance, _scale.Y);
SetInstanceScaleZ(_instance, _scale.Z);
return self;
};
/// @func select(_instance)
///
/// @desc Adds an instance to selection.
///
/// @param {Id.Instance} _instance The instance to select.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static select = function (_instance) {
gml_pragma("forceinline");
if (!is_selected(_instance))
{
ds_list_add(Selected, _instance);
ds_list_add(__instanceData, {
Offset: new BBMOD_Vec3(),
Rotation: new BBMOD_Vec3(),
Scale: new BBMOD_Vec3(),
});
}
return self;
};
/// @func is_selected(_instance)
///
/// @desc Checks whether an instance is selected.
///
/// @param {Id.Instance} _instance The instance to check.
///
/// @return {Bool} Returns `true` if the instance is selected.
static is_selected = function (_instance) {
gml_pragma("forceinline");
return (ds_list_find_index(Selected, _instance) != -1);
};
/// @func unselect(_instance)
///
/// @desc Removes an instance from selection.
///
/// @param {Id.Instance} _instance The instance to unselect.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static unselect = function (_instance) {
gml_pragma("forceinline");
var _index = ds_list_find_index(Selected, _instance);
if (_index != -1)
{
ds_list_delete(Selected, _index);
ds_list_delete(__instanceData, _index);
}
return self;
};
/// @func toggle_select(_instance)
///
/// @desc Unselects an instance if it's selected, or selects if it isn't.
///
/// @param {Id.Instance} _instance The instance to toggle selection of.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static toggle_select = function (_instance) {
gml_pragma("forceinline");
if (is_selected(_instance))
{
unselect(_instance);
}
else
{
select(_instance);
}
return self;
};
/// @func clear_selection()
///
/// @desc Removes all instances from selection.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static clear_selection = function () {
gml_pragma("forceinline");
ds_list_clear(Selected);
ds_list_clear(__instanceData);
return self;
};
/// @func intersect_ray_plane(_origin, _direction, _plane, _normal)
///
/// @desc Intersects a ray with a plane.
///
/// @param {Struct.BBMOD_Vec3} _origin The ray origin.
/// @param {Struct.BBMOD_Vec3} _direction The ray direction.
/// @param {Struct.BBMOD_Vec3} _plane The plane origin.
/// @param {Struct.BBMOD_Vec3} _normal The plane normal.
///
/// @return {Struct.BBMOD_Vec3} The point of intersection or `undefined`.
///
/// @private
static intersect_ray_plane = function (_origin, _direction, _plane, _normal) {
var _dot = _direction.Dot(_normal);
if (_dot == 0.0)
{
return undefined;
}
var _t = -(_origin.Sub(_plane).Dot(_normal) / _dot);
return _origin.Add(_direction.Scale(_t));
};
/// @func update_position()
///
/// @desc Updates the gizmo's position, based on its selected instances.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
static update_position = function () {
var _size = ds_list_size(Selected);
var _posX = 0.0;
var _posY = 0.0;
var _posZ = 0.0;
for (var i = _size - 1; i >= 0; --i)
{
var _instance = Selected[| i];
if (!InstanceExists(_instance))
{
ds_list_delete(Selected, i);
ds_list_delete(__instanceData, i);
--_size;
continue;
}
_posX += GetInstancePositionX(_instance);
_posY += GetInstancePositionY(_instance);
_posZ += GetInstancePositionZ(_instance);
}
if (_size > 0)
{
_posX /= _size;
_posY /= _size;
_posZ /= _size;
Position.Set(_posX, _posY, _posZ);
if (EditSpace == BBMOD_EEditSpace.Local)
{
var _lastSelected = Selected[| _size - 1];
Rotation.Set(
GetInstanceRotationX(_lastSelected),
GetInstanceRotationY(_lastSelected),
GetInstanceRotationZ(_lastSelected));
}
else
{
Rotation.Set(0.0, 0.0, 0.0);
}
}
return self;
};
/// @func update(_deltaTime)
///
/// @desc Updates the gizmo. Should be called every frame.
///
/// @param {Real} _deltaTime How much time has passed since the last frame
/// (in microseconds).
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
///
/// @note This requires you to use a {@link BBMOD_BaseCamera} and it will
/// not do anything if its [apply](./BBMOD_BaseCamera.apply.html) method has
/// not been called yet!
static update = function (_deltaTime) {
if (!global.__bbmodCameraCurrent)
{
return self;
}
////////////////////////////////////////////////////////////////////////
//
// Not editing or finished editing
//
if (!IsEditing || !mouse_check_button(ButtonDrag))
{
if (KeyNextEditType != undefined
&& keyboard_check_pressed(KeyNextEditType))
{
if (++EditType >= BBMOD_EEditType.SIZE)
{
EditType = 0;
}
}
if (KeyNextEditSpace != undefined
&& keyboard_check_pressed(KeyNextEditSpace))
{
if (++EditSpace >= BBMOD_EEditSpace.SIZE)
{
EditSpace = 0;
}
}
// Compute gizmo's new position
var _size = ds_list_size(Selected);
var _posX = 0.0;
var _posY = 0.0;
var _posZ = 0.0;
for (var i = _size - 1; i >= 0; --i)
{
var _instance = Selected[| i];
if (!InstanceExists(_instance))
{
ds_list_delete(Selected, i);
ds_list_delete(__instanceData, i);
--_size;
continue;
}
_posX += GetInstancePositionX(_instance);
_posY += GetInstancePositionY(_instance);
_posZ += GetInstancePositionZ(_instance);
}
if (_size > 0)
{
_posX /= _size;
_posY /= _size;
_posZ /= _size;
Position.Set(_posX, _posY, _posZ);
if (EditSpace == BBMOD_EEditSpace.Local)
{
var _lastSelected = Selected[| _size - 1];
var _mat = GetInstanceGlobalMatrix(_lastSelected);
var _mat2 = new BBMOD_Matrix().RotateEuler(get_instance_rotation_vec3(_lastSelected));
var _mat3 = _mat2.Mul(_mat);
var _euler = _mat3.ToEuler();
Rotation.FromArray(_euler);
}
else
{
Rotation.Set(0.0, 0.0, 0.0);
}
}
// Store instance data
for (var i = _size - 1; i >= 0; --i)
{
var _instance = Selected[| i];
var _data = __instanceData[| i];
_data.Offset = get_instance_position_vec3(_instance).Sub(Position);
_data.Rotation = get_instance_rotation_vec3(_instance);
_data.Scale = get_instance_scale_vec3(_instance);
}
// Clear properties used when editing
IsEditing = false;
__mouseOffset = undefined;
__mouseLockAt = undefined;
__positionBackup = undefined;
if (__cursorBackup != undefined)
{
window_set_cursor(__cursorBackup);
__cursorBackup = undefined;
}
__scaleBy = new BBMOD_Vec3(0.0);
__rotateBy = new BBMOD_Vec3(0.0);
return self;
}
////////////////////////////////////////////////////////////////////////
//
// Editing
//
var _mouseX = window_mouse_get_x();
var _mouseY = window_mouse_get_y();
if (!__mouseLockAt)
{
__mouseLockAt = new BBMOD_Vec2(_mouseX, _mouseY);
__cursorBackup = window_get_cursor();
}
var _quaternionGizmo = new BBMOD_Quaternion().FromEuler(Rotation.X, Rotation.Y, Rotation.Z);
var _forwardGizmo = _quaternionGizmo.Rotate(BBMOD_VEC3_FORWARD);
var _rightGizmo = _quaternionGizmo.Rotate(BBMOD_VEC3_RIGHT);
var _upGizmo = _quaternionGizmo.Rotate(BBMOD_VEC3_UP);
var _matRot = [
_forwardGizmo.X, _forwardGizmo.Y, _forwardGizmo.Z, 0.0,
_rightGizmo.X, _rightGizmo.Y, _rightGizmo.Z, 0.0,
_upGizmo.X, _upGizmo.Y, _upGizmo.Z, 0.0,
0.0, 0.0, 0.0, 1.0,
];
var _matRotInverse = [
_forwardGizmo.X, _rightGizmo.X, _upGizmo.X, 0.0,
_forwardGizmo.Y, _rightGizmo.Y, _upGizmo.Y, 0.0,
_forwardGizmo.Z, _rightGizmo.Z, _upGizmo.Z, 0.0,
0.0, 0.0, 0.0, 1.0,
];
////////////////////////////////////////////////////////////////////////
// Handle editing
switch (EditType)
{
case BBMOD_EEditType.Position:
if (!__positionBackup)
{
__positionBackup = Position.Clone();
}
var _planeNormal;
switch (EditAxis)
{
case BBMOD_EEditAxis.X:
var _dot1 = _rightGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
var _dot2 = _upGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
_planeNormal = (abs(_dot1) > abs(_dot2)) ? _rightGizmo : _upGizmo;
break;
case BBMOD_EEditAxis.Y:
var _dot1 = _forwardGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
var _dot2 = _upGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
_planeNormal = (abs(_dot1) > abs(_dot2)) ? _forwardGizmo : _upGizmo;
break;
case BBMOD_EEditAxis.Z:
var _dot1 = _forwardGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
var _dot2 = _rightGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
_planeNormal = (abs(_dot1) > abs(_dot2)) ? _forwardGizmo : _rightGizmo;
break;
case BBMOD_EEditAxis.All:
_planeNormal = global.__bbmodCameraCurrent.get_forward();
break;
}
var _mouseWorld = intersect_ray_plane(
global.__bbmodCameraCurrent.Position,
global.__bbmodCameraCurrent.screen_point_to_vec3(new BBMOD_Vec2(_mouseX, _mouseY), global.__bbmodRendererCurrent),
__positionBackup,
_planeNormal);
if (_mouseWorld)
{
var _snap = (EnableGridSnap && !keyboard_check(KeyIgnoreSnap));
if (EditAxis == BBMOD_EEditAxis.All)
{
if (!__mouseOffset)
{
__mouseOffset = _mouseWorld.Sub(Position);
}
Position = _mouseWorld.Add(__mouseOffset);
}
else
{
if (!__mouseOffset)
{
__mouseOffset = _mouseWorld;
}
var _diff = _mouseWorld.Sub(__mouseOffset);
if (EditAxis & BBMOD_EEditAxis.X)
{
var _moveX = _forwardGizmo.Scale(_diff.Dot(_forwardGizmo));
if (_snap
&& EditSpace == BBMOD_EEditSpace.Local
&& GridSize.X != 0.0)
{
var _moveXLength = _moveX.Length();
if (_moveXLength > 0.0)
{
var _s = round(_moveXLength / GridSize.X) * GridSize.X;
_moveX = _moveX.Normalize().Scale(_s);
}
}
Position = __positionBackup.Add(_moveX);
}
if (EditAxis & BBMOD_EEditAxis.Y)
{
var _moveY = _rightGizmo.Scale(_diff.Dot(_rightGizmo));
if (_snap
&& EditSpace == BBMOD_EEditSpace.Local
&& GridSize.Y != 0.0)
{
var _moveYLength = _moveY.Length();
if (_moveYLength > 0.0)
{
var _s = round(_moveYLength / GridSize.Y) * GridSize.Y;
_moveY = _moveY.Normalize().Scale(_s);
}
}
Position = __positionBackup.Add(_moveY);
}
if (EditAxis & BBMOD_EEditAxis.Z)
{
var _moveZ = _upGizmo.Scale(_diff.Dot(_upGizmo));
if (_snap
&& EditSpace == BBMOD_EEditSpace.Local
&& GridSize.Z != 0.0)
{
var _moveZLength = _moveZ.Length();
if (_moveZLength > 0.0)
{
var _s = round(_moveZLength / GridSize.Z) * GridSize.Z;
_moveZ = _moveZ.Normalize().Scale(_s);
}
}
Position = __positionBackup.Add(_moveZ);
}
}
if (_snap
&& (EditSpace == BBMOD_EEditSpace.Global
|| EditAxis == BBMOD_EEditAxis.All))
{
if (GridSize.X != 0.0)
{
Position.X = round(Position.X / GridSize.X) * GridSize.X;
}
if (GridSize.Y != 0.0)
{
Position.Y = round(Position.Y / GridSize.Y) * GridSize.Y;
}
if (GridSize.Z != 0.0)
{
Position.Z = round(Position.Z / GridSize.Z) * GridSize.Z;
}
}
}
break;
case BBMOD_EEditType.Rotation:
var _planeNormal = ((EditAxis == BBMOD_EEditAxis.X) ? _forwardGizmo
: ((EditAxis == BBMOD_EEditAxis.Y) ? _rightGizmo
: _upGizmo));
var _mouseWorld = intersect_ray_plane(
global.__bbmodCameraCurrent.Position,
global.__bbmodCameraCurrent.screen_point_to_vec3(new BBMOD_Vec2(_mouseX, _mouseY), global.__bbmodRendererCurrent),
Position,
_planeNormal);
if (_mouseWorld)
{
if (!__mouseOffset)
{
__mouseOffset = _mouseWorld;
}
var _v1 = __mouseOffset.Sub(Position);
var _v2 = _mouseWorld.Sub(Position);
var _angle = darctan2(_v2.Cross(_v1).Dot(_planeNormal), _v1.Dot(_v2));
switch (EditAxis)
{
case BBMOD_EEditAxis.X:
__rotateBy.X = _angle;
break;
case BBMOD_EEditAxis.Y:
__rotateBy.Y = _angle;
break;
case BBMOD_EEditAxis.Z:
__rotateBy.Z = _angle;
break;
}
}
break;
case BBMOD_EEditType.Scale:
var _planeNormal;
switch (EditAxis)
{
case BBMOD_EEditAxis.X:
var _dot1 = _rightGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
var _dot2 = _upGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
_planeNormal = (abs(_dot1) > abs(_dot2)) ? _rightGizmo : _upGizmo;
break;
case BBMOD_EEditAxis.Y:
var _dot1 = _forwardGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
var _dot2 = _upGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
_planeNormal = (abs(_dot1) > abs(_dot2)) ? _forwardGizmo : _upGizmo;
break;
case BBMOD_EEditAxis.Z:
var _dot1 = _forwardGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
var _dot2 = _rightGizmo.Dot(global.__bbmodCameraCurrent.get_forward());
_planeNormal = (abs(_dot1) > abs(_dot2)) ? _forwardGizmo : _rightGizmo;
break;
case BBMOD_EEditAxis.All:
_planeNormal = global.__bbmodCameraCurrent.get_forward();
break;
}
var _mouseWorld = intersect_ray_plane(
global.__bbmodCameraCurrent.Position,
global.__bbmodCameraCurrent.screen_point_to_vec3(new BBMOD_Vec2(_mouseX, _mouseY), global.__bbmodRendererCurrent),
Position,
_planeNormal);
if (_mouseWorld && __mouseOffset)
{
var _mul = (keyboard_check(KeyEditFaster) ? 5.0
: (keyboard_check(KeyEditSlower) ? 0.1
: 1.0));
var _diff = _mouseWorld.Sub(__mouseOffset).Scale(_mul);
if (EditAxis == BBMOD_EEditAxis.All)
{
var _diffX = _diff.Mul(_forwardGizmo.Abs()).Dot(_forwardGizmo);
var _diffY = _diff.Mul(_rightGizmo.Abs()).Dot(_rightGizmo);
var _scaleBy = (abs(_diffX) > abs(_diffY)) ? _diffX : _diffY;
__scaleBy.X += _scaleBy;
__scaleBy.Y += _scaleBy;
__scaleBy.Z += _scaleBy;
}
else
{
if (EditAxis & BBMOD_EEditAxis.X)
{
__scaleBy.X += _diff.Mul(_forwardGizmo.Abs()).Dot(_forwardGizmo);
}
if (EditAxis & BBMOD_EEditAxis.Y)
{
__scaleBy.Y += _diff.Mul(_rightGizmo.Abs()).Dot(_rightGizmo);
}
if (EditAxis & BBMOD_EEditAxis.Z)
{
__scaleBy.Z += _diff.Mul(_upGizmo.Abs()).Dot(_upGizmo);
}
}
}
__mouseOffset = _mouseWorld;
break;
}
////////////////////////////////////////////////////////////////////////
// Cancel editing?
if (keyboard_check_pressed(KeyCancel))
{
if (__positionBackup)
{
__positionBackup.Copy(Position);
}
__rotateBy.Set(0.0, 0.0, 0.0);
__scaleBy.Set(0.0, 0.0, 0.0);
IsEditing = false;
}
////////////////////////////////////////////////////////////////////////
// Apply to selected instances
var _size = ds_list_size(Selected);
for (var i = _size - 1; i >= 0; --i)
{
var _instance = Selected[| i];
if (!InstanceExists(_instance))
{
ds_list_delete(Selected, i);
ds_list_delete(__instanceData, i);
--_size;
continue;
}
var _data = __instanceData[| i];
var _positionOffset = _data.Offset;
var _rotationStored = _data.Rotation;
var _scaleStored = _data.Scale;
// Get local basis
var _quaternionInstance = new BBMOD_Quaternion().FromEuler(
GetInstanceRotationX(_instance),
GetInstanceRotationY(_instance),
GetInstanceRotationZ(_instance));
var _forwardInstance = _quaternionInstance.Rotate(BBMOD_VEC3_FORWARD);
var _rightInstance = _quaternionInstance.Rotate(BBMOD_VEC3_RIGHT);
var _upInstance = _quaternionInstance.Rotate(BBMOD_VEC3_UP);
// Apply rotation
var _matGlobal = GetInstanceGlobalMatrix(_instance);
var _matGlobalInv = _matGlobal.Inverse();
var _rotateByX = __rotateBy.X;
var _rotateByY = __rotateBy.Y;
var _rotateByZ = __rotateBy.Z;
if (EnableAngleSnap
&& AngleSnap != 0.0
&& !keyboard_check(KeyIgnoreSnap))
{
_rotateByX = floor(__rotateBy.X / AngleSnap) * AngleSnap;
_rotateByY = floor(__rotateBy.Y / AngleSnap) * AngleSnap;
_rotateByZ = floor(__rotateBy.Z / AngleSnap) * AngleSnap;
}
var _temp = new BBMOD_Vec4(_forwardGizmo.X, _forwardGizmo.Y, _forwardGizmo.Z, 0.0).Transform(_matGlobalInv.Raw);
var _forwardGlobal = new BBMOD_Vec3(_temp.X, _temp.Y, _temp.Z);
var _temp = new BBMOD_Vec4(_rightGizmo.X, _rightGizmo.Y, _rightGizmo.Z, 0.0).Transform(_matGlobalInv.Raw);
var _rightGlobal = new BBMOD_Vec3(_temp.X, _temp.Y, _temp.Z);
var _temp = new BBMOD_Vec4(_upGizmo.X, _upGizmo.Y, _upGizmo.Z, 0.0).Transform(_matGlobalInv.Raw);
var _upGlobal = new BBMOD_Vec3(_temp.X, _temp.Y, _temp.Z);
var _rotMatrix = new BBMOD_Matrix().RotateEuler(_rotationStored);
if (_rotateByX != 0.0)
{
var _quaternionX = new BBMOD_Quaternion().FromAxisAngle(_forwardGlobal, _rotateByX);
_positionOffset = _quaternionX.Rotate(_positionOffset);
_rotMatrix = _rotMatrix.RotateQuat(_quaternionX);
}
if (_rotateByY != 0.0)
{
var _quaternionY = new BBMOD_Quaternion().FromAxisAngle(_rightGlobal, _rotateByY);
_positionOffset = _quaternionY.Rotate(_positionOffset);
_rotMatrix = _rotMatrix.RotateQuat(_quaternionY);
}
if (_rotateByZ != 0.0)
{
var _quaternionZ = new BBMOD_Quaternion().FromAxisAngle(_upGlobal, _rotateByZ);
_positionOffset = _quaternionZ.Rotate(_positionOffset);
_rotMatrix = _rotMatrix.RotateQuat(_quaternionZ);
}
var _rotArray = _rotMatrix.ToEuler();
SetInstanceRotationX(_instance, _rotArray[0]);
SetInstanceRotationY(_instance, _rotArray[1]);
SetInstanceRotationZ(_instance, _rotArray[2]);
// Apply scale
var _scaleNew = _scaleStored.Clone();
var _scaleOld = _scaleNew.Clone();
// Scale on X
_scaleNew.X += __scaleBy.X * abs(_forwardGlobal.Dot(_forwardInstance));
_scaleNew.Y += __scaleBy.X * abs(_forwardGlobal.Dot(_rightInstance));
_scaleNew.Z += __scaleBy.X * abs(_forwardGlobal.Dot(_upInstance));
// Scale on Y
_scaleNew.X += __scaleBy.Y * abs(_rightGlobal.Dot(_forwardInstance));
_scaleNew.Y += __scaleBy.Y * abs(_rightGlobal.Dot(_rightInstance));
_scaleNew.Z += __scaleBy.Y * abs(_rightGlobal.Dot(_upInstance));
// Scale on Z
_scaleNew.X += __scaleBy.Z * abs(_upGlobal.Dot(_forwardInstance));
_scaleNew.Y += __scaleBy.Z * abs(_upGlobal.Dot(_rightInstance));
_scaleNew.Z += __scaleBy.Z * abs(_upGlobal.Dot(_upInstance));
// Scale offset
var _vI = matrix_transform_vertex(_matRotInverse, _positionOffset.X, _positionOffset.Y, _positionOffset.Z);
var _vIRot = matrix_transform_vertex(
matrix_build(
0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
(1.0 / max(_scaleOld.X, 0.0001)) * (_scaleOld.X + __scaleBy.X),
(1.0 / max(_scaleOld.Y, 0.0001)) * (_scaleOld.Y + __scaleBy.Y),
(1.0 / max(_scaleOld.Z, 0.0001)) * (_scaleOld.Z + __scaleBy.Z)),
_vI[0], _vI[1], _vI[2]);
var _v = matrix_transform_vertex(_matRot, _vIRot[0], _vIRot[1], _vIRot[2]);
// Apply scale and position
set_instance_scale_vec3(_instance, _scaleNew);
SetInstancePositionX(_instance, Position.X + _v[0]);
SetInstancePositionY(_instance, Position.Y + _v[1]);
SetInstancePositionZ(_instance, Position.Z + _v[2]);
}
return self;
};
/// @func submit([_materials])
///
/// @desc Immediately submits the gizmo for rendering.
///
/// @param {Array<Struct.BBMOD_Material>} [_materials] Materials to use or
/// `undefined`.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
///
/// @note This changes the world matrix based on the gizmo's position and size!
static submit = function (_materials=undefined) {
gml_pragma("forceinline");
(new BBMOD_Matrix())
.Scale(new BBMOD_Vec3(Size))
.RotateEuler(Rotation)
.Translate(Position)
.ApplyWorld();
Models[EditType].submit(_materials);
return self;
};
/// @func render([_materials])
///
/// @desc Enqueues the gizmo for rendering.
///
/// @param {Array<Struct.BBMOD_Material>} [_materials] Materials to use or
/// `undefined`.
///
/// @return {Struct.BBMOD_Gizmo} Returns `self`.
///
/// @note This changes the world matrix based on the gizmo's position and size!
static render = function (_materials=undefined) {
gml_pragma("forceinline");
new BBMOD_Matrix()
.Scale(new BBMOD_Vec3(Size))
.RotateEuler(Rotation)
.Translate(Position)
.ApplyWorld();
Models[EditType].render(_materials);
return self;
};
static destroy = function () {
Class_destroy();
ds_list_destroy(Selected);
ds_list_destroy(__instanceData);
return undefined;
};
}