1010#include " util/GraphicUtils.h"
1111
1212#include < cmath>
13+ #include < cfloat>
1314
1415using namespace doriax ;
1516
@@ -377,6 +378,24 @@ void editor::SceneRender::update(std::vector<Entity> selEntities, std::vector<En
377378 }
378379 }
379380
381+ // Override gizmo for selected instance within instanced mesh
382+ if (selectedInstanceIndex >= 0 && selEntities.size () == 1 && selEntities[0 ] == selectedInstanceEntity){
383+ InstancedMeshComponent* instmesh = scene->findComponent <InstancedMeshComponent>(selectedInstanceEntity);
384+ Transform* instTransform = scene->findComponent <Transform>(selectedInstanceEntity);
385+ if (instmesh && instTransform && selectedInstanceIndex < (int )instmesh->instances .size ()){
386+ OBB instOBB = getInstanceOBB (selectedInstanceEntity, selectedInstanceIndex);
387+ if (!instOBB.isNull ()){
388+ const InstanceData& inst = instmesh->instances [selectedInstanceIndex];
389+ Quaternion instWorldRotation = getInstanceWorldRotation (*instTransform, *instmesh, inst);
390+ Vector3 instCenter = instOBB.getCenter ();
391+ toolslayer.updateGizmo (camera, instCenter, instWorldRotation, scale, instOBB, mouseRay, mouseClicked, anchorData, anchorArea);
392+ // Replace selection outline with the instance OBB
393+ selBB.clear ();
394+ selBB.push_back (instOBB);
395+ }
396+ }
397+ }
398+
380399 if (selBB.size () > 0 ) {
381400 updateSelLines (selBB);
382401 }
@@ -386,7 +405,7 @@ void editor::SceneRender::update(std::vector<Entity> selEntities, std::vector<En
386405 // Determine selLines visibility: hide for single entity with OBJECT2D gizmo or empty selBB
387406 bool showSelLines = selectionVisibility;
388407 if (!multipleEntitiesSelected && (toolslayer.getGizmoSelected () == GizmoSelected::OBJECT2D || selBB.size () == 0 )) {
389- if (selectedTileIndex < 0 ) {
408+ if (selectedTileIndex < 0 && selectedInstanceIndex < 0 ) {
390409 showSelLines = false ;
391410 }
392411 }
@@ -402,7 +421,8 @@ void editor::SceneRender::update(std::vector<Entity> selEntities, std::vector<En
402421 if (toolslayer.getGizmoSelected () == GizmoSelected::OBJECT2D && selEntities.size () == 1 ){
403422 bool isTilemap = scene->getSignature (selEntities[0 ]).test (scene->getComponentId <TilemapComponent>());
404423 bool isCamera = scene->getSignature (selEntities[0 ]).test (scene->getComponentId <CameraComponent>());
405- toolslayer.setShowObject2DRects ((!isTilemap || selectedTileIndex >= 0 ) && !isCamera);
424+ bool isInstancedMesh = scene->getSignature (selEntities[0 ]).test (scene->getComponentId <InstancedMeshComponent>());
425+ toolslayer.setShowObject2DRects ((!isTilemap || selectedTileIndex >= 0 ) && !isCamera && !isInstancedMesh);
406426 }
407427
408428 uilayer.setViewGizmoImageVisible (true );
@@ -506,6 +526,39 @@ void editor::SceneRender::mouseClickEvent(float x, float y, std::vector<Entity>
506526 }
507527 }
508528 }
529+
530+ // Store instance start state for instance sub-selection drag
531+ if (selectedInstanceIndex >= 0 && selEntities.size () == 1 && selEntities[0 ] == selectedInstanceEntity){
532+ InstancedMeshComponent* instmesh = scene->findComponent <InstancedMeshComponent>(selectedInstanceEntity);
533+ Transform* instTransform = scene->findComponent <Transform>(selectedInstanceEntity);
534+ if (instmesh && instTransform && selectedInstanceIndex < (int )instmesh->instances .size ()){
535+ const InstanceData& inst = instmesh->instances [selectedInstanceIndex];
536+ instanceStartPosition = inst.position ;
537+ instanceStartRotation = inst.rotation ;
538+ instanceStartScale = inst.scale ;
539+
540+ OBB instOBB = getInstanceOBB (selectedInstanceEntity, selectedInstanceIndex);
541+ if (!instOBB.isNull ()){
542+ // Gizmo is centered on the instance OBB center (world space); its
543+ // orientation must match getInstanceOBB (see getInstanceWorldRotation).
544+ gizmoStartPosition = instOBB.getCenter ();
545+ cursorStartOffset = gizmoStartPosition - rretrun.point ;
546+ rotationStartOffset = getInstanceWorldRotation (*instTransform, *instmesh, inst);
547+
548+ // Build the actual instance world matrix (same formula the renderer uses
549+ // in non-billboard mode) so drag math operates on the instance directly.
550+ Matrix4 instanceLocalMatrix = Matrix4::translateMatrix (inst.position )
551+ * inst.rotation .getRotationMatrix ()
552+ * Matrix4::scaleMatrix (inst.scale );
553+ Matrix4 instanceWorldMatrix = instTransform->modelMatrix * instanceLocalMatrix;
554+
555+ Matrix4 gizmoM = Matrix4::translateMatrix (gizmoStartPosition)
556+ * rotationStartOffset.getRotationMatrix ()
557+ * Matrix4::scaleMatrix (Vector3 (1 , 1 , 1 ));
558+ objectMatrixOffset[selectedInstanceEntity] = gizmoM.inverse () * instanceWorldMatrix;
559+ }
560+ }
561+ }
509562 }
510563
511564}
@@ -539,12 +592,50 @@ void editor::SceneRender::mouseDragEvent(float x, float y, float origX, float or
539592
540593 SceneProject* sceneProject = project->getScene (sceneId);
541594
595+ bool isInstanceDrag = selectedInstanceIndex >= 0 && selEntities.size () == 1
596+ && entity == selectedInstanceEntity
597+ && scene->findComponent <InstancedMeshComponent>(entity) != nullptr ;
598+
599+ auto emitInstanceTransformCmd = [&](const Matrix4& worldMatrix){
600+ Matrix4 instLocalMatrix = transform->modelMatrix .inverse () * worldMatrix;
601+ Vector3 newPos, newScale;
602+ Quaternion newRot;
603+ instLocalMatrix.decomposeStandard (newPos, newScale, newRot);
604+
605+ std::string prefix = " instances[" + std::to_string (selectedInstanceIndex) + " ]" ;
606+ MultiPropertyCmd* multiCmd = new MultiPropertyCmd ();
607+ GizmoSelected gz = toolslayer.getGizmoSelected ();
608+ if (gz == GizmoSelected::TRANSLATE || gz == GizmoSelected::OBJECT2D ){
609+ multiCmd->addPropertyCmd <Vector3>(project, sceneProject->id , entity, ComponentType::InstancedMeshComponent, prefix + " .position" , newPos);
610+ }else if (gz == GizmoSelected::ROTATE ){
611+ multiCmd->addPropertyCmd <Quaternion>(project, sceneProject->id , entity, ComponentType::InstancedMeshComponent, prefix + " .rotation" , newRot);
612+ }else if (gz == GizmoSelected::SCALE ){
613+ multiCmd->addPropertyCmd <Vector3>(project, sceneProject->id , entity, ComponentType::InstancedMeshComponent, prefix + " .scale" , newScale);
614+ }
615+ lastCommand = multiCmd;
616+ };
617+
542618 if (rretrun){
543619
544620 toolslayer.mouseDrag (rretrun.point );
545621
546622 Transform* transformParent = scene->findComponent <Transform>(transform->parent );
547623
624+ // Emit a transform command for the active drag target (instance or entity).
625+ // worldMatrix is the new object world matrix built by the current gizmo branch.
626+ auto applyObjectTransform = [&](const Matrix4& worldMatrix){
627+ if (toolslayer.getGizmoSideSelected () == GizmoSideSelected::NONE ) return ;
628+ if (isInstanceDrag){
629+ emitInstanceTransformCmd (worldMatrix);
630+ }else {
631+ Matrix4 m = worldMatrix;
632+ if (transformParent){
633+ m = transformParent->modelMatrix .inverse () * m;
634+ }
635+ lastCommand = new ObjectTransformCmd (project, sceneProject->id , entity, m);
636+ }
637+ };
638+
548639 if (toolslayer.getGizmoSelected () == GizmoSelected::TRANSLATE ){
549640 Vector3 deltaPos = gizmoRMatrix.inverse () * ((rretrun.point + cursorStartOffset) - gizmoStartPosition);
550641
@@ -577,13 +668,7 @@ void editor::SceneRender::mouseDragEvent(float x, float y, float origX, float or
577668 Matrix4 gizmoMatrix = Matrix4::translateMatrix (newPos) * gizmoRMatrix * Matrix4::scaleMatrix (Vector3 (1 ,1 ,1 ));
578669 Matrix4 objMatrix = gizmoMatrix * objectMatrixOffset[entity];
579670
580- if (transformParent){
581- objMatrix = transformParent->modelMatrix .inverse () * objMatrix;
582- }
583-
584- if (toolslayer.getGizmoSideSelected () != GizmoSideSelected::NONE ){
585- lastCommand = new ObjectTransformCmd (project, sceneProject->id , entity, objMatrix);
586- }
671+ applyObjectTransform (objMatrix);
587672 }
588673
589674 if (toolslayer.getGizmoSelected () == GizmoSelected::ROTATE ){
@@ -605,13 +690,7 @@ void editor::SceneRender::mouseDragEvent(float x, float y, float origX, float or
605690 Matrix4 gizmoMatrix = Matrix4::translateMatrix (gizmoPosition) * newRot.getRotationMatrix () * Matrix4::scaleMatrix (Vector3 (1 ,1 ,1 ));
606691 Matrix4 objMatrix = gizmoMatrix * objectMatrixOffset[entity];
607692
608- if (transformParent){
609- objMatrix = transformParent->modelMatrix .inverse () * objMatrix;
610- }
611-
612- if (toolslayer.getGizmoSideSelected () != GizmoSideSelected::NONE ){
613- lastCommand = new ObjectTransformCmd (project, sceneProject->id , entity, objMatrix);
614- }
693+ applyObjectTransform (objMatrix);
615694 }
616695
617696 if (toolslayer.getGizmoSelected () == GizmoSelected::SCALE ){
@@ -647,13 +726,7 @@ void editor::SceneRender::mouseDragEvent(float x, float y, float origX, float or
647726 Matrix4 gizmoMatrix = Matrix4::translateMatrix (gizmoPosition) * gizmoRMatrix * Matrix4::scaleMatrix (newScale);
648727 Matrix4 objMatrix = gizmoMatrix * objectMatrixOffset[entity];
649728
650- if (transformParent){
651- objMatrix = transformParent->modelMatrix .inverse () * objMatrix;
652- }
653-
654- if (toolslayer.getGizmoSideSelected () != GizmoSideSelected::NONE ){
655- lastCommand = new ObjectTransformCmd (project, sceneProject->id , entity, objMatrix);
656- }
729+ applyObjectTransform (objMatrix);
657730 }
658731
659732 if (toolslayer.getGizmoSelected () == GizmoSelected::OBJECT2D ){
@@ -780,6 +853,22 @@ void editor::SceneRender::mouseDragEvent(float x, float y, float origX, float or
780853 lastCommand = multiCmd;
781854 }
782855 }
856+ }else if (isInstanceDrag){
857+ // Instance CENTER drag (position only) for OBJECT2D
858+ if (toolslayer.getGizmo2DSideSelected () == Gizmo2DSideSelected::CENTER ){
859+ Vector3 newPos = gizmoRMatrix.inverse () * ((rretrun.point + cursorStartOffset) - gizmoStartPosition);
860+ newPos = gizmoStartPosition + (gizmoRMatrix * newPos);
861+ if (displaySettings.snapToGrid ) {
862+ float spacing = displaySettings.gridSpacing2D ;
863+ if (spacing > 0 .0f ) {
864+ newPos.x = std::round (newPos.x / spacing) * spacing;
865+ newPos.y = std::round (newPos.y / spacing) * spacing;
866+ }
867+ }
868+ Matrix4 gizmoMatrix = Matrix4::translateMatrix (newPos) * gizmoRMatrix * Matrix4::scaleMatrix (Vector3 (1 ,1 ,1 ));
869+ Matrix4 objMatrix = gizmoMatrix * objectMatrixOffset[entity];
870+ emitInstanceTransformCmd (objMatrix);
871+ }
783872 }else {
784873 // Original entity-level OBJECT2D handling
785874 bool isSprite = scene->getComponentArray <SpriteComponent>()->hasEntity (entity);
@@ -1001,6 +1090,83 @@ OBB editor::SceneRender::getTileOBB(Entity entity, int tileIndex){
10011090 return transform.modelMatrix * tileAABB.getOBB ();
10021091}
10031092
1093+ void editor::SceneRender::selectInstance (Entity entity, int instanceIndex){
1094+ selectedInstanceEntity = entity;
1095+ selectedInstanceIndex = instanceIndex;
1096+ }
1097+
1098+ void editor::SceneRender::clearInstanceSelection (){
1099+ selectedInstanceEntity = 0 ;
1100+ selectedInstanceIndex = -1 ;
1101+ }
1102+
1103+ Quaternion editor::SceneRender::getInstanceWorldRotation (const Transform& transform, const InstancedMeshComponent& instmesh, const InstanceData& inst) const {
1104+ // Billboard mode overrides per-instance rotation at render time, so mirror that
1105+ // here by not composing inst.rotation into the picking/gizmo orientation.
1106+ if (instmesh.instancedBillboard ){
1107+ return transform.worldRotation ;
1108+ }
1109+ return transform.worldRotation * inst.rotation ;
1110+ }
1111+
1112+ OBB editor::SceneRender::getInstanceOBB (Entity entity, int instanceIndex){
1113+ if (!scene->getComponentArray <InstancedMeshComponent>()->hasEntity (entity)) return OBB ();
1114+ if (!scene->getComponentArray <MeshComponent>()->hasEntity (entity)) return OBB ();
1115+ if (!scene->getComponentArray <Transform>()->hasEntity (entity)) return OBB ();
1116+
1117+ InstancedMeshComponent& instmesh = scene->getComponent <InstancedMeshComponent>(entity);
1118+ MeshComponent& mesh = scene->getComponent <MeshComponent>(entity);
1119+ Transform& transform = scene->getComponent <Transform>(entity);
1120+
1121+ if (instanceIndex < 0 || instanceIndex >= (int )instmesh.instances .size ()) return OBB ();
1122+
1123+ const InstanceData& inst = instmesh.instances [instanceIndex];
1124+ // For billboard meshes the per-instance rotation is ignored at render time, so
1125+ // skip it here too to keep the OBB aligned with what is actually drawn.
1126+ Matrix4 rotMatrix = instmesh.instancedBillboard
1127+ ? Matrix4 ()
1128+ : inst.rotation .getRotationMatrix ();
1129+ Matrix4 instanceMatrix = Matrix4::translateMatrix (inst.position )
1130+ * rotMatrix
1131+ * Matrix4::scaleMatrix (inst.scale );
1132+
1133+ AABB localAABB = mesh.verticesAABB ;
1134+ if (localAABB.isNull () || (localAABB.getMinimum () == Vector3::ZERO && localAABB.getMaximum () == Vector3::ZERO )){
1135+ // Fallback to a small unit cube around origin so the instance is still selectable
1136+ localAABB = AABB (Vector3 (-0 .5f , -0 .5f , -0 .5f ), Vector3 (0 .5f , 0 .5f , 0 .5f ));
1137+ }
1138+
1139+ return (transform.modelMatrix * instanceMatrix) * localAABB.getOBB ();
1140+ }
1141+
1142+ int editor::SceneRender::hitTestInstance (Entity entity, float x, float y){
1143+ if (!scene->getComponentArray <InstancedMeshComponent>()->hasEntity (entity)) return -1 ;
1144+ if (!scene->getComponentArray <MeshComponent>()->hasEntity (entity)) return -1 ;
1145+ if (!scene->getComponentArray <Transform>()->hasEntity (entity)) return -1 ;
1146+
1147+ InstancedMeshComponent& instmesh = scene->getComponent <InstancedMeshComponent>(entity);
1148+
1149+ Ray ray = camera->screenToRay (x, y);
1150+
1151+ // Only instances within maxInstances are actually rendered, so restrict picking to them.
1152+ int renderedCount = (int )std::min<size_t >(instmesh.instances .size (), instmesh.maxInstances );
1153+
1154+ int bestIndex = -1 ;
1155+ float bestDistance = FLT_MAX ;
1156+ for (int i = 0 ; i < renderedCount; i++){
1157+ if (!instmesh.instances [i].visible ) continue ;
1158+ OBB instOBB = getInstanceOBB (entity, i);
1159+ if (instOBB.isNull ()) continue ;
1160+ RayReturn rr = ray.intersects (instOBB);
1161+ if (rr && rr.distance < bestDistance){
1162+ bestDistance = rr.distance ;
1163+ bestIndex = i;
1164+ }
1165+ }
1166+
1167+ return bestIndex;
1168+ }
1169+
10041170void editor::SceneRender::setupCameraIcon (CameraObjects& co){
10051171 TextureData iconData;
10061172 iconData.loadTextureFromMemory (camera_icon_png, camera_icon_png_len);
0 commit comments