Skip to content

Commit bfff067

Browse files
Improve hints when inspecting or positioning an object in 3D (#8707)
1 parent 94a542d commit bfff067

2 files changed

Lines changed: 143 additions & 15 deletions

File tree

newIDE/app/src/EditorFunctions/EditorFunctions.spec.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,7 @@ describe('editorFunctions', () => {
16851685
expect(result.success).toBe(true);
16861686
expect(result.message).toEqual(
16871687
expect.stringContaining(
1688-
'Created 2 new instances of object "Player" using point brush at 50, 60 on layer "base" (size 64x64, rotation 45°, opacity 128/255, z-order 5).'
1688+
'Created 2 new instances of object "Player" using point brush at 50, 60 on layer "base" (size 64x64, rotation 45°, opacity 128/255, z-order 5, origin at this position, each occupies X 50 to 114, Y 60 to 124).'
16891689
)
16901690
);
16911691
});
@@ -1724,12 +1724,59 @@ describe('editorFunctions', () => {
17241724
expect(result.success).toBe(true);
17251725
expect(result.message).toEqual(
17261726
expect.stringContaining(
1727-
'Created 1 new instance of object "Player" using point brush at 10, 20, 30 on layer "base" (size 8x16x24, rotation (15°, 30°, 45°)).'
1727+
'Created 1 new instance of object "Player" using point brush at 10, 20, 30 on layer "base" (size 8x16x24, rotation (15°, 30°, 45°), origin at this position, each occupies X 10 to 18, Y 20 to 36, Z 30 to 54).'
17281728
)
17291729
);
17301730
});
17311731
});
17321732

1733+
describe('describe_instances (position semantics)', () => {
1734+
let project: gdProject;
1735+
let testScene: gdLayout;
1736+
1737+
beforeEach(() => {
1738+
// $FlowFixMe[invalid-constructor]
1739+
project = new gd.ProjectHelper.createNewGDJSProject();
1740+
testScene = project.insertNewLayout('TestScene', 0);
1741+
testScene.getObjects().insertNewObject(project, 'Sprite', 'Player', 0);
1742+
});
1743+
1744+
afterEach(() => {
1745+
project.delete();
1746+
});
1747+
1748+
it('exposes z (even when 0), object size info and position semantics', async () => {
1749+
const instance = testScene
1750+
.getInitialInstances()
1751+
.insertNewInitialInstance();
1752+
instance.setObjectName('Player');
1753+
instance.setX(100);
1754+
instance.setY(200);
1755+
instance.setHasCustomSize(true);
1756+
instance.setHasCustomDepth(true);
1757+
instance.setCustomWidth(32);
1758+
instance.setCustomHeight(48);
1759+
instance.setCustomDepth(64);
1760+
1761+
const result = await editorFunctions.describe_instances.launchFunction({
1762+
...makeFakeLaunchFunctionOptionsWithProject(project),
1763+
args: { scene_name: 'TestScene' },
1764+
});
1765+
1766+
expect(result.success).toBe(true);
1767+
const instances = result.instances || [];
1768+
expect(instances).toHaveLength(1);
1769+
expect(instances[0].z).toBe(0);
1770+
expect(instances[0].width).toBe(32);
1771+
expect(instances[0].height).toBe(48);
1772+
expect(instances[0].depth).toBe(64);
1773+
expect(result.positionSemantics).toEqual(
1774+
expect.stringContaining('origin, NOT its center')
1775+
);
1776+
expect(result.objectSizeInfo).toEqual({ Player: null });
1777+
});
1778+
});
1779+
17331780
describe('change_scene_properties_layers_effects_groups (echo new values)', () => {
17341781
let project: gdProject;
17351782
let testScene: gdLayout;

newIDE/app/src/EditorFunctions/index.js

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ export type EditorFunctionGenericOutput = {|
137137
// Default size, origin and center of the object(s) being operated on, keyed by object name:
138138
objectSizeInfo?: { [string]: ObjectSizeInfo | null },
139139

140+
// Explanation of the coordinate semantics of `instances` positions:
141+
positionSemantics?: string,
142+
140143
hints?: Array<HintEntry>,
141144

142145
// Set to true when the function call was aborted mid-execution (e.g. the AI
@@ -381,6 +384,45 @@ const injectObjectSizeInfo = (
381384
return output;
382385
};
383386

387+
const INSTANCE_POSITION_SEMANTICS_MESSAGE =
388+
'Each instance x;y;z is its origin, NOT its center. Unless `objectSizeInfo` indicates a custom origin, the origin is the minimum corner: an instance occupies x to x+width, y to y+height and (in 3D) z to z+depth, so its center is at position + size/2. To center an instance A on top of an instance B: A.x = B.x + (B.width - A.width)/2, A.y = B.y + (B.height - A.height)/2, A.z = B.z + B.depth.';
389+
390+
const getOccupiedSpaceDescription = (
391+
position: $ReadOnlyArray<number>,
392+
size: $ReadOnlyArray<number>,
393+
objectSizeInfo: ObjectSizeInfo | null
394+
): string => {
395+
const round = (value: number) => Math.round(value * 100) / 100;
396+
const axes = ['X', 'Y', 'Z'];
397+
const originOffsets = [0, 0, 0];
398+
if (objectSizeInfo) {
399+
const defaultSizes = [
400+
objectSizeInfo.width,
401+
objectSizeInfo.height,
402+
objectSizeInfo.depth,
403+
];
404+
const origins = [
405+
objectSizeInfo.originX,
406+
objectSizeInfo.originY,
407+
objectSizeInfo.originZ,
408+
];
409+
for (let i = 0; i < size.length; i++) {
410+
const defaultSize = defaultSizes[i];
411+
const origin = origins[i];
412+
// Origin offsets are given for the default size - scale them to the actual size.
413+
if (origin && defaultSize) {
414+
originOffsets[i] = origin * (size[i] / defaultSize);
415+
}
416+
}
417+
}
418+
return size
419+
.map((sizeOnAxis, i) => {
420+
const min = position[i] - originOffsets[i];
421+
return `${axes[i]} ${round(min)} to ${round(min + sizeOnAxis)}`;
422+
})
423+
.join(', ');
424+
};
425+
384426
const makeGenericSuccess = (message: string): EditorFunctionGenericOutput => ({
385427
success: true,
386428
message,
@@ -2488,6 +2530,7 @@ const describeInstances: EditorFunction = {
24882530
const initialInstances = layout.getInitialInstances();
24892531

24902532
const instances = [];
2533+
const objectSizeInfoByName: { [string]: ObjectSizeInfo | null } = {};
24912534

24922535
// For each layer
24932536
mapFor(0, layout.getLayersCount(), i => {
@@ -2511,8 +2554,15 @@ const describeInstances: EditorFunction = {
25112554
object = globalObjects.getObject(objectName);
25122555
}
25132556

2514-
const defaultSize = object
2557+
const sizeInfo = object
25152558
? getObjectSizeInfo(object, project, PixiResourcesLoader)
2559+
: null;
2560+
if (object && !(objectName in objectSizeInfoByName)) {
2561+
objectSizeInfoByName[objectName] = sizeInfo;
2562+
}
2563+
2564+
const defaultSize = object
2565+
? sizeInfo
25162566
: { width: 0, height: 0, depth: 0 };
25172567

25182568
const width = instance.hasCustomSize()
@@ -2537,6 +2587,8 @@ const describeInstances: EditorFunction = {
25372587
// Replace persistentUuid by id:
25382588
persistentUuid: undefined,
25392589
id: instance.getPersistentUuid().slice(0, 10),
2590+
// The serializer omits z when it's 0 - always expose it for 3D objects:
2591+
z: depth !== null ? instance.getZ() : undefined,
25402592
// Actual computed dimensions (accounting for default size when no custom size is set):
25412593
width,
25422594
height,
@@ -2550,20 +2602,16 @@ const describeInstances: EditorFunction = {
25502602
);
25512603
});
25522604

2605+
const result: EditorFunctionGenericOutput = {
2606+
success: true,
2607+
instances: instances,
2608+
instancesForSceneNamed: scene_name,
2609+
positionSemantics: INSTANCE_POSITION_SEMANTICS_MESSAGE,
2610+
};
25532611
if (objectNames.size > 0) {
2554-
return {
2555-
success: true,
2556-
instances: instances,
2557-
instancesForSceneNamed: scene_name,
2558-
instancesOnlyForObjectsNamed: [...objectNames].sort().join(','),
2559-
};
2560-
} else {
2561-
return {
2562-
success: true,
2563-
instances: instances,
2564-
instancesForSceneNamed: scene_name,
2565-
};
2612+
result.instancesOnlyForObjectsNamed = [...objectNames].sort().join(',');
25662613
}
2614+
return injectObjectSizeInfo(result, objectSizeInfoByName);
25672615
},
25682616
modifiesProject: false,
25692617
};
@@ -3028,6 +3076,22 @@ const put2dInstances: EditorFunction = {
30283076
attrs.push(`opacity ${instancesOpacity}/255`);
30293077
if (instances_z_order !== null)
30303078
attrs.push(`z-order ${instances_z_order}`);
3079+
const effectiveSize = instancesSize
3080+
? instancesSize
3081+
: objectSizeInfo &&
3082+
objectSizeInfo.width !== null &&
3083+
objectSizeInfo.height !== null
3084+
? [objectSizeInfo.width, objectSizeInfo.height]
3085+
: null;
3086+
if (brush_kind === 'point' && effectiveSize) {
3087+
attrs.push(
3088+
`origin at this position, each occupies ${getOccupiedSpaceDescription(
3089+
brushPosition,
3090+
effectiveSize,
3091+
objectSizeInfo
3092+
)}`
3093+
);
3094+
}
30313095
changes.push(
30323096
`Created ${newInstancesCount} new instance${
30333097
newInstancesCount > 1 ? 's' : ''
@@ -3595,6 +3659,23 @@ const put3dInstances: EditorFunction = {
35953659
instancesRotationArray[1]
35963660
}°, ${instancesRotationArray[2]}°)`
35973661
);
3662+
const effectiveSize = instancesSizeArray
3663+
? instancesSizeArray
3664+
: objectSizeInfo &&
3665+
objectSizeInfo.width !== null &&
3666+
objectSizeInfo.height !== null &&
3667+
objectSizeInfo.depth !== null
3668+
? [objectSizeInfo.width, objectSizeInfo.height, objectSizeInfo.depth]
3669+
: null;
3670+
if (brush_kind === 'point' && effectiveSize) {
3671+
attrs.push(
3672+
`origin at this position, each occupies ${getOccupiedSpaceDescription(
3673+
brushPosition,
3674+
effectiveSize,
3675+
objectSizeInfo
3676+
)}`
3677+
);
3678+
}
35983679
changes.push(
35993680
`Created ${newInstancesCount} new instance${
36003681
newInstancesCount > 1 ? 's' : ''

0 commit comments

Comments
 (0)