강화: 프롬프트 카메라/포즈 다양성 규칙 보강
This commit is contained in:
@@ -331,15 +331,15 @@ const CAMERA_VARIANTS = {
|
|||||||
|
|
||||||
const CAMERA_ORIENTATION_RULES = {
|
const CAMERA_ORIENTATION_RULES = {
|
||||||
normal:
|
normal:
|
||||||
"Use one angle from side/rear/low/high per render. Low-angle and high-angle shots should be used at least 40% of the time across the deck.",
|
"Use only side/rear/low/high framing. Never use a pure front-on head-on lock. Across one character's 5 stages, make sure at least one low-angle and one high-angle shot appear.",
|
||||||
"light-damage":
|
"light-damage":
|
||||||
"Prioritize low-angle, rear, side, or high-angle framing. Frontal reads are discouraged unless the face identity would otherwise be lost.",
|
"Prioritize low-angle, rear, side, and high-angle framing. Frontal reads are disallowed unless strict identity continuity would otherwise fail.",
|
||||||
"medium-damage":
|
"medium-damage":
|
||||||
"Use energetic side/rear/low/high framing; force low-angle and high-angle alternation across nearby cards.",
|
"Use energetic side/rear/low/high framing; force low-angle and high-angle alternation so adjacent stages do not share the same vertical framing.",
|
||||||
"heavy-damage":
|
"heavy-damage":
|
||||||
"Prioritize low-angle, side, high-angle, and rear framing to emphasize impact chaos; high/low angles must dominate this stage.",
|
"Prioritize low-angle, side, high-angle, and rear framing to emphasize impact chaos; high/low angles should dominate this stage.",
|
||||||
defeated:
|
defeated:
|
||||||
"Prioritize side/back/low/high framing for collapse and pain continuity; avoid frontal composition unless continuity breaks.",
|
"Prioritize side/rear/back/low/high framing for collapse and pain continuity; frontal composition only if face continuity cannot be preserved.",
|
||||||
};
|
};
|
||||||
|
|
||||||
const POSE_ORIENTATION_VARIANTS = {
|
const POSE_ORIENTATION_VARIANTS = {
|
||||||
@@ -581,10 +581,37 @@ function pickDiverseOption(list, seed, stage, step) {
|
|||||||
return list[index];
|
return list[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeToDirectionDistance(length, from, to) {
|
||||||
|
return (to - from + length) % length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function firstIndexForOrientation(plan, wanted) {
|
||||||
|
for (let i = 0; i < plan.length; i += 1) {
|
||||||
|
if (normalizeOrientation(plan[i]) === wanted) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextDifferentIndex(plan, startIndex, bannedOrientation) {
|
||||||
|
for (let i = 1; i <= plan.length; i += 1) {
|
||||||
|
const index = (startIndex + i) % plan.length;
|
||||||
|
const candidate = normalizeOrientation(plan[index]);
|
||||||
|
if (candidate !== bannedOrientation && candidate !== "front") {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return startIndex;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeOrientation(orientation) {
|
function normalizeOrientation(orientation) {
|
||||||
return orientation === "front" ? "side" : orientation;
|
return orientation === "front" ? "side" : orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cameraHistoryByCharacter = new Map();
|
||||||
|
const poseHistoryByCharacter = new Map();
|
||||||
|
|
||||||
function pickCameraForStage(spec, character, stage) {
|
function pickCameraForStage(spec, character, stage) {
|
||||||
const orientationPlan = CAMERA_ORIENTATION_PLAN[spec.label] ?? CAMERA_ORIENTATION_PLAN.normal;
|
const orientationPlan = CAMERA_ORIENTATION_PLAN[spec.label] ?? CAMERA_ORIENTATION_PLAN.normal;
|
||||||
const orientationSeed = charSeed(`${character.id}-${character.scene}-${spec.label}`);
|
const orientationSeed = charSeed(`${character.id}-${character.scene}-${spec.label}`);
|
||||||
@@ -598,10 +625,36 @@ function pickCameraForStage(spec, character, stage) {
|
|||||||
}
|
}
|
||||||
resolvedIndex = (resolvedIndex + 1) % orientationPlan.length;
|
resolvedIndex = (resolvedIndex + 1) % orientationPlan.length;
|
||||||
}
|
}
|
||||||
const orientation = normalizeOrientation(orientationPlan[resolvedIndex]);
|
const historyKey = character.id;
|
||||||
|
const history = cameraHistoryByCharacter.get(historyKey) ?? [];
|
||||||
|
const needLow = stage >= 3 && !history.includes("low");
|
||||||
|
const needHigh = stage >= 4 && !history.includes("high");
|
||||||
|
if (needLow) {
|
||||||
|
const lowIndex = firstIndexForOrientation(orientationPlan, "low");
|
||||||
|
if (lowIndex !== -1) {
|
||||||
|
resolvedIndex = (resolvedIndex + normalizeToDirectionDistance(orientationPlan.length, resolvedIndex, lowIndex)) % orientationPlan.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needHigh) {
|
||||||
|
const highIndex = firstIndexForOrientation(orientationPlan, "high");
|
||||||
|
if (highIndex !== -1) {
|
||||||
|
resolvedIndex = (resolvedIndex + normalizeToDirectionDistance(orientationPlan.length, resolvedIndex, highIndex)) % orientationPlan.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (history.length > 0) {
|
||||||
|
resolvedIndex = nextDifferentIndex(orientationPlan, resolvedIndex, history[history.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalOrientation = normalizeOrientation(orientationPlan[resolvedIndex]);
|
||||||
|
history.push(finalOrientation);
|
||||||
|
if (history.length > 8) {
|
||||||
|
history.shift();
|
||||||
|
}
|
||||||
|
cameraHistoryByCharacter.set(historyKey, history);
|
||||||
|
|
||||||
const stagePool =
|
const stagePool =
|
||||||
(CAMERA_VARIANTS[spec.label] ?? CAMERA_VARIANTS.normal)[orientation] ??
|
(CAMERA_VARIANTS[spec.label] ?? CAMERA_VARIANTS.normal)[finalOrientation] ??
|
||||||
CAMERA_VARIANTS.normal[orientation] ??
|
CAMERA_VARIANTS.normal[finalOrientation] ??
|
||||||
CAMERA_VARIANTS.normal.side;
|
CAMERA_VARIANTS.normal.side;
|
||||||
const phrase = pickDiverseOption(
|
const phrase = pickDiverseOption(
|
||||||
stagePool,
|
stagePool,
|
||||||
@@ -609,7 +662,7 @@ function pickCameraForStage(spec, character, stage) {
|
|||||||
stage,
|
stage,
|
||||||
17,
|
17,
|
||||||
);
|
);
|
||||||
const angleAddonPool = LOW_HIGH_CAMERA_PHRASES[orientation];
|
const angleAddonPool = LOW_HIGH_CAMERA_PHRASES[finalOrientation];
|
||||||
const extraPhrase = angleAddonPool
|
const extraPhrase = angleAddonPool
|
||||||
? pickDiverseOption(
|
? pickDiverseOption(
|
||||||
angleAddonPool,
|
angleAddonPool,
|
||||||
@@ -620,7 +673,7 @@ function pickCameraForStage(spec, character, stage) {
|
|||||||
: null;
|
: null;
|
||||||
const anglePhrase = extraPhrase === null ? phrase : `${phrase} ${extraPhrase}`;
|
const anglePhrase = extraPhrase === null ? phrase : `${phrase} ${extraPhrase}`;
|
||||||
return {
|
return {
|
||||||
orientation,
|
orientation: finalOrientation,
|
||||||
phrase: anglePhrase,
|
phrase: anglePhrase,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -633,12 +686,24 @@ function pickPoseModifier(spec, character, stage, orientation) {
|
|||||||
POSE_ORIENTATION_VARIANTS.normal?.[normalizedOrientation] ??
|
POSE_ORIENTATION_VARIANTS.normal?.[normalizedOrientation] ??
|
||||||
[];
|
[];
|
||||||
const posePool = orientationBonus.concat(list);
|
const posePool = orientationBonus.concat(list);
|
||||||
return pickDiverseOption(
|
const pick = pickDiverseOption(
|
||||||
posePool,
|
posePool,
|
||||||
charSeed(`${character.id}-${character.pose}-${character.weapon}-${orientation}`),
|
charSeed(`${character.id}-${character.pose}-${character.weapon}-${orientation}`),
|
||||||
stage,
|
stage,
|
||||||
19,
|
19,
|
||||||
);
|
);
|
||||||
|
const historyKey = character.id;
|
||||||
|
const history = poseHistoryByCharacter.get(historyKey) ?? [];
|
||||||
|
let selected = pick;
|
||||||
|
if (history.length > 0 && selected === history[history.length - 1]) {
|
||||||
|
selected = posePool[(posePool.indexOf(pick) + 1) % posePool.length];
|
||||||
|
}
|
||||||
|
history.push(selected);
|
||||||
|
if (history.length > 12) {
|
||||||
|
history.shift();
|
||||||
|
}
|
||||||
|
poseHistoryByCharacter.set(historyKey, history);
|
||||||
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
function promptFor(character, stage) {
|
function promptFor(character, stage) {
|
||||||
@@ -659,6 +724,8 @@ function promptFor(character, stage) {
|
|||||||
const camera = pickCameraForStage(spec, character, stage);
|
const camera = pickCameraForStage(spec, character, stage);
|
||||||
const angleControl =
|
const angleControl =
|
||||||
"Angle strictness: this render must use side/rear/low/high framing first; direct frontal composition is disallowed unless the model absolutely cannot preserve face identity.";
|
"Angle strictness: this render must use side/rear/low/high framing first; direct frontal composition is disallowed unless the model absolutely cannot preserve face identity.";
|
||||||
|
const angleConstraint =
|
||||||
|
"Do not output front-facing framing for default action shots. Use side/rear/low/high angles as primary composition, and rotate the view family between shots so the same character does not repeat the exact same camera axis.";
|
||||||
const poseModifier = pickPoseModifier(spec, character, stage, camera.orientation);
|
const poseModifier = pickPoseModifier(spec, character, stage, camera.orientation);
|
||||||
return [
|
return [
|
||||||
"Use case: stylized-concept",
|
"Use case: stylized-concept",
|
||||||
@@ -674,6 +741,7 @@ function promptFor(character, stage) {
|
|||||||
`Facial expression: ${spec.expression}.`,
|
`Facial expression: ${spec.expression}.`,
|
||||||
`Camera/view variation: ${camera.orientation} angle. Composition directive: ${camera.phrase}.`,
|
`Camera/view variation: ${camera.orientation} angle. Composition directive: ${camera.phrase}.`,
|
||||||
angleControl,
|
angleControl,
|
||||||
|
angleConstraint,
|
||||||
"Across the character's five damage stages, rotate between side, rear, low-angle, and high-angle views as the primary framing, and only use frontal composition when the identity cannot be preserved.",
|
"Across the character's five damage stages, rotate between side, rear, low-angle, and high-angle views as the primary framing, and only use frontal composition when the identity cannot be preserved.",
|
||||||
`Camera constraint: ${CAMERA_ORIENTATION_RULES[spec.label] ?? CAMERA_ORIENTATION_RULES.normal}`,
|
`Camera constraint: ${CAMERA_ORIENTATION_RULES[spec.label] ?? CAMERA_ORIENTATION_RULES.normal}`,
|
||||||
`Adult visual appeal: ${character.appealFocus}; ${adultAppeal}.`,
|
`Adult visual appeal: ${character.appealFocus}; ${adultAppeal}.`,
|
||||||
|
|||||||
Reference in New Issue
Block a user