This commit is contained in:
syuilo 2026-05-20 15:21:02 +09:00
commit 5d17b557c1
5 changed files with 51 additions and 32 deletions

View file

@ -57,8 +57,10 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-if="!controller.grabbing.value && controller.selected.value != null" @click="removeSelectedObject"><i class="ti ti-trash"></i> (X)</MkButton>
</template>
<MkButton v-if="controller.isSitting.value" @click="controller.standUp()">降りる (Q)</MkButton>
<template v-for="interaction in interacions" :key="interaction.id">
<MkButton inline @click="interaction.fn()">{{ interaction.label }}{{ interaction.isPrimary ? ' (E)' : '' }}</MkButton>
<template v-if="controller.selected.value != null">
<template v-for="interaction in controller.selected.value.interacions" :key="interaction.id">
<MkButton inline @click="controller.interact(interaction.id)">{{ interaction.label }}{{ interaction.isPrimary ? ' (E)' : '' }}</MkButton>
</template>
</template>
</div>
@ -114,13 +116,6 @@ const props = defineProps<{
const canvasKey = ref(0); // canvaskey
const canvas = useTemplateRef('canvas');
const interacions = shallowRef<{
id: string;
label: string;
isPrimary: boolean;
fn: () => void;
}[]>([]);
function resize() {
controller.resize();
}
@ -309,19 +304,6 @@ onMounted(async () => {
isModified.value = true;
});
//watch(controller.selected, (v) => {
// if (v == null) {
// interacions.value = [];
// } else {
// interacions.value = Object.entries(v.objectEntity.instance.interactions).map(([interactionId, interactionInfo]) => ({
// id: interactionId,
// label: interactionInfo.label,
// isPrimary: v.objectEntity.instance.primaryInteraction === interactionId,
// fn: interactionInfo.fn,
// }));
// }
//});
if (joyStickEl.value != null) {
const joyStick = new Joystick(joyStickEl.value!, { radiusPx: joyStickRadiusPx });
joyStick.on('start', (vector) => {

View file

@ -30,9 +30,14 @@ export class RoomController extends EngineControllerBase<RoomEngine> {
public isRoomLightOn = ref(true);
public grabbing = ref<{ forInstall: boolean } | null>(null);
public gridSnapping = ref({ enabled: true, scale: cm(4) });
public selected = ref<{
public selected = shallowRef<{
objectId: string;
objectState: RoomStateObject;
interacions: {
id: string;
label: string;
isPrimary: boolean;
}[];
} | null>(null);
public roomState: ShallowRef<RoomState>;
@ -194,4 +199,8 @@ export class RoomController extends EngineControllerBase<RoomEngine> {
}
this.isRoomLightOn.value = !this.isRoomLightOn.value;
}
public interact(id: string) {
this.call('interact', [this.selected.value!.objectId, id]);
}
}

View file

@ -117,6 +117,11 @@ export class RoomEngine extends EngineBase<{
selected: {
objectId: string;
objectState: RoomStateObject;
interacions: {
id: string;
label: string;
isPrimary: boolean;
}[];
} | null;
}) => void;
'changeGrabbingState': (ctx: { grabbing: { forInstall: boolean } | null }) => void;
@ -179,7 +184,19 @@ export class RoomEngine extends EngineBase<{
}
set selected(v) {
this._selected = v;
this.ev('changeSelectedState', { selected: v == null ? null : { objectId: v.objectId, objectState: v.objectState } });
if (v == null) {
this.ev('changeSelectedState', { selected: null });
return;
}
this.ev('changeSelectedState', { selected: {
objectId: v.objectId,
objectState: v.objectState,
interacions: Object.entries(v.objectEntity.instance.interactions).map(([interactionId, interactionInfo]) => ({
id: interactionId,
label: interactionInfo.label,
isPrimary: v.objectEntity.instance.primaryInteraction === interactionId,
})),
} });
}
private time: 0 | 1 | 2 = 0; // 0: 昼, 1: 夕, 2: 夜
@ -903,6 +920,9 @@ export class RoomEngine extends EngineBase<{
id: args.id,
timer: this.timer, // TODO: 家具が撤去された後も動作し続けるのをどうにかする
graphicsQuality: this.graphicsQuality,
sitChair: () => {
this.sitChair(args.id);
},
stickyMarkerMeshUpdated: (mesh) => {
// TODO
//// stickyな子の位置を更新
@ -1359,17 +1379,16 @@ export class RoomEngine extends EngineBase<{
this.grabbingCtx = null;
}
public interact(oid: string) {
public interact(oid: string, iid: string | null = null) {
const o = this.roomState.installedObjects.find(o => o.id === oid)!;
const objDef = getObjectDef(o.type);
const entity = this.objectEntities.get(o.id)!;
if (objDef.isChair) {
this.sitChair(o.id);
} else {
if (iid == null) {
if (entity.instance.primaryInteraction != null) {
entity.instance.interactions[entity.instance.primaryInteraction].fn();
}
} else {
entity.instance.interactions[iid].fn();
}
}

View file

@ -140,13 +140,14 @@ export type ObjectDef<OpSc extends OptionsSchema | undefined = undefined> = {
fixParticleSystem: (ps: BABYLON.ParticleSystem) => void;
};
lc: BABYLON.ClusteredLightContainer | null;
root: BABYLON.Mesh;
root: BABYLON.TransformNode;
options: OpSc extends undefined ? ConvertedOptions : Readonly<GetConvertedOptionsSchemaValues<NonNullable<OpSc>>>;
model: ModelManager;
id: string;
timer: Timer;
graphicsQuality: number;
stickyMarkerMeshUpdated?: (mesh: BABYLON.Mesh) => void;
sitChair?: () => void;
}) => RoomObjectInstance<OpSc extends undefined ? ConvertedOptions : GetConvertedOptionsSchemaValues<NonNullable<OpSc>>> | Promise<RoomObjectInstance<OpSc extends undefined ? ConvertedOptions : GetConvertedOptionsSchemaValues<NonNullable<OpSc>>>>; // TODO: createInstanceをasyncにするのではなく、別にreadyみたいなものを返させる
};

View file

@ -29,7 +29,7 @@ export const chair = defineObject({
hasCollisions: true,
isChair: true,
canPreMeshesMerging: true,
createInstance: ({ model, options }) => {
createInstance: ({ model, options, sitChair }) => {
const primaryMaterial = model.findMaterial('__X_PRIMARY__');
const secondaryMaterial = model.findMaterial('__X_SECONDARY__');
@ -51,7 +51,15 @@ export const chair = defineObject({
applyPrimaryColor();
applySecondaryColor();
},
interactions: {},
interactions: {
sit: {
label: 'Sit',
fn: () => {
sitChair?.();
},
},
},
primaryInteraction: 'sit',
};
},
});