2dcefeb54f
* Added checkbox for editor to parent spawned nodes to selected node (and renamed the option above it) * Convert tabs to spaces. * Curly braces * More formatting. * Formatting * Changed default setting to false. * Removed redundant null check * Update EditorSpawn.as Co-authored-by: 1vanK <1vanK@users.noreply.github.com>
454 lines
16 KiB
ActionScript
454 lines
16 KiB
ActionScript
// Urho3D spawn editor
|
|
|
|
LineEdit@ positionOffsetX;
|
|
LineEdit@ positionOffsetY;
|
|
LineEdit@ positionOffsetZ;
|
|
LineEdit@ randomRotationX;
|
|
LineEdit@ randomRotationY;
|
|
LineEdit@ randomRotationZ;
|
|
LineEdit@ randomScaleMinEdit;
|
|
LineEdit@ randomScaleMaxEdit;
|
|
LineEdit@ numberSpawnedObjectsEdit;
|
|
LineEdit@ spawnRadiusEdit;
|
|
LineEdit@ spawnCountEdit;
|
|
|
|
Window@ spawnWindow;
|
|
Vector3 positionOffset = Vector3(0, 0, 0);
|
|
Vector3 randomRotation = Vector3(0, 0, 0);
|
|
float randomScaleMin = 1;
|
|
float randomScaleMax = 1;
|
|
uint spawnCount = 1;
|
|
float spawnRadius = 0;
|
|
bool useNormal = true;
|
|
bool alignToAABBBottom = true;
|
|
bool spawnOnSelection = false;
|
|
bool parentToSelection = false;
|
|
uint numberSpawnedObjects = 1;
|
|
Array<String> spawnedObjectsNames;
|
|
|
|
void CreateSpawnEditor()
|
|
{
|
|
if (spawnWindow !is null)
|
|
return;
|
|
|
|
spawnWindow = LoadEditorUI("UI/EditorSpawnWindow.xml");
|
|
ui.root.AddChild(spawnWindow);
|
|
spawnWindow.opacity = uiMaxOpacity;
|
|
|
|
int height = Min(ui.root.height - 60, 500);
|
|
spawnWindow.SetSize(300, height);
|
|
CenterDialog(spawnWindow);
|
|
|
|
HideSpawnEditor();
|
|
SubscribeToEvent(spawnWindow.GetChild("CloseButton", true), "Released", "HideSpawnEditor");
|
|
positionOffsetX = spawnWindow.GetChild("PositionOffset.x", true);
|
|
positionOffsetY = spawnWindow.GetChild("PositionOffset.y", true);
|
|
positionOffsetZ = spawnWindow.GetChild("PositionOffset.z", true);
|
|
positionOffsetX.text = String(positionOffset.x);
|
|
positionOffsetY.text = String(positionOffset.y);
|
|
positionOffsetZ.text = String(positionOffset.z);
|
|
randomRotationX = spawnWindow.GetChild("RandomRotation.x", true);
|
|
randomRotationY = spawnWindow.GetChild("RandomRotation.y", true);
|
|
randomRotationZ = spawnWindow.GetChild("RandomRotation.z", true);
|
|
randomRotationX.text = String(randomRotation.x);
|
|
randomRotationY.text = String(randomRotation.y);
|
|
randomRotationZ.text = String(randomRotation.z);
|
|
|
|
randomScaleMinEdit = spawnWindow.GetChild("RandomScaleMin", true);
|
|
randomScaleMaxEdit = spawnWindow.GetChild("RandomScaleMax", true);
|
|
randomScaleMinEdit.text = String(randomScaleMin);
|
|
randomScaleMaxEdit.text = String(randomScaleMax);
|
|
CheckBox@ useNormalToggle = spawnWindow.GetChild("UseNormal", true);
|
|
useNormalToggle.checked = useNormal;
|
|
CheckBox@ alignToAABBBottomToggle = spawnWindow.GetChild("AlignToAABBBottom", true);
|
|
alignToAABBBottomToggle.checked = alignToAABBBottom;
|
|
CheckBox@ spawnOnSelectionToggle = spawnWindow.GetChild("SpawnOnSelected", true);
|
|
spawnOnSelectionToggle.checked = spawnOnSelection;
|
|
CheckBox@ parentToSelectionToggle = spawnWindow.GetChild("ParentToSelected", true);
|
|
parentToSelectionToggle.checked = parentToSelection;
|
|
|
|
numberSpawnedObjectsEdit = spawnWindow.GetChild("NumberSpawnedObjects", true);
|
|
numberSpawnedObjectsEdit.text = String(numberSpawnedObjects);
|
|
|
|
spawnRadiusEdit = spawnWindow.GetChild("SpawnRadius", true);
|
|
spawnCountEdit = spawnWindow.GetChild("SpawnCount", true);
|
|
spawnRadiusEdit.text = String(spawnRadius);
|
|
spawnCountEdit.text = String(spawnCount);
|
|
|
|
SubscribeToEvent(positionOffsetX, "TextChanged", "EditPositionOffset");
|
|
SubscribeToEvent(positionOffsetY, "TextChanged", "EditPositionOffset");
|
|
SubscribeToEvent(positionOffsetZ, "TextChanged", "EditPositionOffset");
|
|
SubscribeToEvent(randomRotationX, "TextChanged", "EditRandomRotation");
|
|
SubscribeToEvent(randomRotationY, "TextChanged", "EditRandomRotation");
|
|
SubscribeToEvent(randomRotationZ, "TextChanged", "EditRandomRotation");
|
|
SubscribeToEvent(randomScaleMinEdit, "TextChanged", "EditRandomScale");
|
|
SubscribeToEvent(randomScaleMaxEdit, "TextChanged", "EditRandomScale");
|
|
SubscribeToEvent(spawnRadiusEdit, "TextChanged", "EditSpawnRadius");
|
|
SubscribeToEvent(spawnCountEdit, "TextChanged", "EditSpawnCount");
|
|
SubscribeToEvent(useNormalToggle, "Toggled", "ToggleUseNormal");
|
|
SubscribeToEvent(alignToAABBBottomToggle, "Toggled", "ToggleAlignToAABBBottom");
|
|
SubscribeToEvent(spawnOnSelectionToggle, "Toggled", "ToggleSpawnOnSelected");
|
|
SubscribeToEvent(parentToSelectionToggle, "Toggled", "ToggleParentToSelected");
|
|
SubscribeToEvent(numberSpawnedObjectsEdit, "TextFinished", "UpdateNumberSpawnedObjects");
|
|
SubscribeToEvent(spawnWindow.GetChild("SetSpawnMode", true), "Released", "SetSpawnMode");
|
|
RefreshPickedObjects();
|
|
}
|
|
|
|
bool ToggleSpawnEditor()
|
|
{
|
|
if (spawnWindow.visible == false)
|
|
ShowSpawnEditor();
|
|
else
|
|
HideSpawnEditor();
|
|
return true;
|
|
}
|
|
|
|
void ShowSpawnEditor()
|
|
{
|
|
spawnWindow.visible = true;
|
|
spawnWindow.BringToFront();
|
|
}
|
|
|
|
void HideSpawnEditor()
|
|
{
|
|
spawnWindow.visible = false;
|
|
}
|
|
|
|
void PickSpawnObject()
|
|
{
|
|
@resourcePicker = GetResourcePicker(StringHash("Node"));
|
|
if (resourcePicker is null)
|
|
return;
|
|
|
|
String lastPath = resourcePicker.lastPath;
|
|
if (lastPath.empty)
|
|
lastPath = sceneResourcePath;
|
|
CreateFileSelector(localization.Get("Pick ") + resourcePicker.typeName, "OK", "Cancel", lastPath, resourcePicker.filters, resourcePicker.lastFilter, false);
|
|
SubscribeToEvent(uiFileSelector, "FileSelected", "PickSpawnObjectDone");
|
|
}
|
|
|
|
void EditPositionOffset(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
LineEdit@ edit = eventData["Element"].GetPtr();
|
|
positionOffset = Vector3(positionOffsetX.text.ToFloat(), positionOffsetY.text.ToFloat(), positionOffsetZ.text.ToFloat());
|
|
UpdateHierarchyItem(editorScene);
|
|
}
|
|
|
|
void EditRandomRotation(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
LineEdit@ edit = eventData["Element"].GetPtr();
|
|
randomRotation = Vector3(randomRotationX.text.ToFloat(), randomRotationY.text.ToFloat(), randomRotationZ.text.ToFloat());
|
|
UpdateHierarchyItem(editorScene);
|
|
}
|
|
|
|
void EditRandomScale(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
LineEdit@ edit = eventData["Element"].GetPtr();
|
|
randomScaleMin = randomScaleMinEdit.text.ToFloat();
|
|
randomScaleMax = randomScaleMaxEdit.text.ToFloat();
|
|
UpdateHierarchyItem(editorScene);
|
|
}
|
|
|
|
void ToggleUseNormal(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
useNormal = cast<CheckBox>(eventData["Element"].GetPtr()).checked;
|
|
}
|
|
|
|
void ToggleAlignToAABBBottom(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
alignToAABBBottom = cast<CheckBox>(eventData["Element"].GetPtr()).checked;
|
|
}
|
|
|
|
void ToggleSpawnOnSelected(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
spawnOnSelection = cast<CheckBox>(eventData["Element"].GetPtr()).checked;
|
|
}
|
|
|
|
void ToggleParentToSelected(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
parentToSelection = cast<CheckBox>(eventData["Element"].GetPtr()).checked;
|
|
}
|
|
|
|
void UpdateNumberSpawnedObjects(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
LineEdit@ edit = eventData["Element"].GetPtr();
|
|
numberSpawnedObjects = edit.text.ToUInt();
|
|
edit.text = String(numberSpawnedObjects);
|
|
RefreshPickedObjects();
|
|
}
|
|
|
|
void EditSpawnRadius(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
LineEdit@ edit = eventData["Element"].GetPtr();
|
|
spawnRadius = edit.text.ToFloat();
|
|
}
|
|
|
|
void EditSpawnCount(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
LineEdit@ edit = eventData["Element"].GetPtr();
|
|
spawnCount = edit.text.ToUInt();
|
|
}
|
|
|
|
void RefreshPickedObjects()
|
|
{
|
|
spawnedObjectsNames.Resize(numberSpawnedObjects);
|
|
ListView@ list = spawnWindow.GetChild("SpawnedObjects", true);
|
|
list.RemoveAllItems();
|
|
|
|
for (uint i = 0; i < numberSpawnedObjects; ++i)
|
|
{
|
|
UIElement@ parent = CreateAttributeEditorParentWithSeparatedLabel(list, "Object " +(i+1), i, 0, false);
|
|
|
|
UIElement@ container = UIElement();
|
|
container.SetLayout(LM_HORIZONTAL, 4, IntRect(10, 0, 4, 0));
|
|
container.SetFixedHeight(ATTR_HEIGHT);
|
|
parent.AddChild(container);
|
|
|
|
LineEdit@ nameEdit = CreateAttributeLineEdit(container, null, i, 0);
|
|
nameEdit.name = "TextureNameEdit" + String(i);
|
|
|
|
Button@ pickButton = CreateResourcePickerButton(container, null, i, 0, "smallButtonPick");
|
|
SubscribeToEvent(pickButton, "Released", "PickSpawnedObject");
|
|
nameEdit.text = spawnedObjectsNames[i];
|
|
|
|
SubscribeToEvent(nameEdit, "TextFinished", "EditSpawnedObjectName");
|
|
}
|
|
}
|
|
|
|
void EditSpawnedObjectName(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
LineEdit@ nameEdit = eventData["Element"].GetPtr();
|
|
int index = nameEdit.vars["Index"].GetUInt();
|
|
String resourceName = VerifySpawnedObjectFile(nameEdit.text);
|
|
nameEdit.text = resourceName;
|
|
spawnedObjectsNames[index] = resourceName;
|
|
}
|
|
|
|
String VerifySpawnedObjectFile(const String&in resourceName)
|
|
{
|
|
File@ file = cache.GetFile(resourceName);
|
|
if(file !is null)
|
|
return resourceName;
|
|
else
|
|
return String();
|
|
}
|
|
|
|
void PickSpawnedObject(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
UIElement@ button = eventData["Element"].GetPtr();
|
|
resourcePickIndex = button.vars["Index"].GetUInt();
|
|
CreateFileSelector("Pick spawned object", "Pick", "Cancel", uiNodePath, uiSceneFilters, uiNodeFilter);
|
|
|
|
SubscribeToEvent(uiFileSelector, "FileSelected", "PickSpawnedObjectNameDone");
|
|
}
|
|
|
|
void PickSpawnedObjectNameDone(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
StoreResourcePickerPath();
|
|
CloseFileSelector();
|
|
|
|
if (!eventData["OK"].GetBool())
|
|
{
|
|
@resourcePicker = null;
|
|
return;
|
|
}
|
|
|
|
String resourceName = GetResourceNameFromFullName(eventData["FileName"].GetString());
|
|
spawnedObjectsNames[resourcePickIndex] = VerifySpawnedObjectFile(resourceName);
|
|
@resourcePicker = null;
|
|
RefreshPickedObjects();
|
|
}
|
|
|
|
void SetSpawnMode(StringHash eventType, VariantMap& eventData)
|
|
{
|
|
editMode = EDIT_SPAWN;
|
|
}
|
|
|
|
void PlaceObject(Vector3 spawnPosition, Vector3 normal)
|
|
{
|
|
Quaternion spawnRotation;
|
|
if (useNormal)
|
|
spawnRotation = Quaternion(Vector3(0, 1, 0), normal);
|
|
spawnRotation = Quaternion(Random(-randomRotation.x, randomRotation.x),
|
|
Random(-randomRotation.y, randomRotation.y), Random(-randomRotation.z, randomRotation.z)) * spawnRotation;
|
|
|
|
Node@ parent = null;
|
|
if (parentToSelection && selectedNodes.length > 0)
|
|
parent = selectedNodes[0];
|
|
|
|
int number = RandomInt(0, spawnedObjectsNames.length);
|
|
File@ file = cache.GetFile(spawnedObjectsNames[number]);
|
|
Node@ spawnedObject = InstantiateNodeFromFile(file, spawnPosition + (spawnRotation * positionOffset), spawnRotation, Random(randomScaleMin, randomScaleMax), parent);
|
|
if (spawnedObject is null)
|
|
{
|
|
spawnedObjectsNames[number] = spawnedObjectsNames[spawnedObjectsNames.length - 1];
|
|
--numberSpawnedObjects;
|
|
RefreshPickedObjects();
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool GetSpawnPosition(const Ray&in cameraRay, float maxDistance, Vector3&out position, Vector3&out normal, float randomRadius = 0.0,
|
|
bool allowNoHit = true)
|
|
{
|
|
if (pickMode < PICK_RIGIDBODIES && editorScene.octree !is null)
|
|
{
|
|
RayQueryResult result = editorScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY,
|
|
0x7fffffff);
|
|
if (result.drawable !is null)
|
|
{
|
|
if (randomRadius > 0)
|
|
{
|
|
Vector3 basePosition = RandomizeSpawnPosition(result.position, randomRadius);
|
|
basePosition.y += randomRadius;
|
|
result = editorScene.octree.RaycastSingle(Ray(basePosition, Vector3(0, -1, 0)), RAY_TRIANGLE, randomRadius * 2.0,
|
|
DRAWABLE_GEOMETRY, 0x7fffffff);
|
|
if (result.drawable !is null)
|
|
{
|
|
position = result.position;
|
|
normal = result.normal;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
position = result.position;
|
|
normal = result.normal;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else if (editorScene.physicsWorld !is null)
|
|
{
|
|
// If we are not running the actual physics update, refresh collisions before raycasting
|
|
if (!runUpdate)
|
|
editorScene.physicsWorld.UpdateCollisions();
|
|
|
|
PhysicsRaycastResult result = editorScene.physicsWorld.RaycastSingle(cameraRay, maxDistance);
|
|
|
|
if (result.body !is null)
|
|
{
|
|
if (randomRadius > 0)
|
|
{
|
|
Vector3 basePosition = RandomizeSpawnPosition(result.position, randomRadius);
|
|
basePosition.y += randomRadius;
|
|
result = editorScene.physicsWorld.RaycastSingle(Ray(basePosition, Vector3(0, -1, 0)), randomRadius * 2.0);
|
|
if (result.body !is null)
|
|
{
|
|
position = result.position;
|
|
normal = result.normal;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
position = result.position;
|
|
normal = result.normal;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
position = cameraRay.origin + cameraRay.direction * maxDistance;
|
|
normal = Vector3(0, 1, 0);
|
|
return allowNoHit;
|
|
}
|
|
|
|
bool GetSpawnPositionOnNode(const Ray&in cameraRay, float maxDistance, Vector3&out position, Vector3&out normal, Node@ node, float randomRadius = 0.0,
|
|
bool allowNoHit = true)
|
|
{
|
|
if (pickMode < PICK_RIGIDBODIES && editorScene.octree !is null)
|
|
{
|
|
Array<RayQueryResult> results = editorScene.octree.Raycast(cameraRay, RAY_TRIANGLE, maxDistance, DRAWABLE_GEOMETRY, 0x7fffffff);
|
|
|
|
if (!results.empty)
|
|
{
|
|
RayQueryResult result = results[0];
|
|
|
|
for(uint i = 0; i < results.length; i++)
|
|
{
|
|
if (results[i].node is node)
|
|
{
|
|
result = results[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (randomRadius > 0)
|
|
{
|
|
Vector3 basePosition = RandomizeSpawnPosition(result.position, randomRadius);
|
|
basePosition.y += randomRadius;
|
|
Array<RayQueryResult> randomResults = editorScene.octree.Raycast(Ray(basePosition, Vector3(0, -1, 0)), RAY_TRIANGLE, randomRadius * 2.0, DRAWABLE_GEOMETRY, 0x7fffffff);
|
|
|
|
if (randomResults.length == 0)
|
|
{
|
|
position = result.position;
|
|
normal = result.normal;
|
|
return true;
|
|
}
|
|
|
|
result = randomResults[0];
|
|
|
|
// Find node in results
|
|
for(uint i = 0; i < randomResults.length; i++)
|
|
{
|
|
if (randomResults[i].node is node)
|
|
{
|
|
result = randomResults[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
position = result.position;
|
|
normal = result.normal;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
position = result.position;
|
|
normal = result.normal;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
position = cameraRay.origin + cameraRay.direction * maxDistance;
|
|
normal = Vector3(0, 1, 0);
|
|
return allowNoHit;
|
|
}
|
|
|
|
Vector3 RandomizeSpawnPosition(const Vector3&in position, float randomRadius)
|
|
{
|
|
float angle = Random() * 360.0;
|
|
float distance = Random() * randomRadius;
|
|
return position + Quaternion(0, angle, 0) * Vector3(0, 0, distance);
|
|
}
|
|
|
|
void SpawnObject()
|
|
{
|
|
Node@ selectedNode = null;
|
|
|
|
if (selectedNodes.length > 0)
|
|
selectedNode = selectedNodes[0];
|
|
|
|
if (spawnedObjectsNames.length == 0)
|
|
return;
|
|
|
|
IntRect view = activeViewport.viewport.rect;
|
|
|
|
for (uint i = 0; i < spawnCount; ++i)
|
|
{
|
|
Ray cameraRay = GetActiveViewportCameraRay();
|
|
Vector3 position, normal;
|
|
bool result = false;
|
|
|
|
if (spawnOnSelection && selectedNode !is null)
|
|
result = GetSpawnPositionOnNode(cameraRay, camera.farClip, position, normal, selectedNode, spawnRadius, false);
|
|
else
|
|
result = GetSpawnPosition(cameraRay, camera.farClip, position, normal, spawnRadius, false);
|
|
|
|
if (result)
|
|
PlaceObject(position, normal);
|
|
}
|
|
}
|