import {z} from 'zod' export type DirectionalAccessoryAxes = {forward?: number | undefined, up?: number | undefined, out?: number | undefined}; export type DirectionalAccessoryFitPayload = {model: string, item: string, name?: string | undefined, kind?: ('blade' | 'gun' | 'staff' | 'bow' | 'shield' | 'helmet' | 'hat' | 'back' | 'other') | undefined, point?: ('right_hand' | 'left_hand' | 'head' | 'back' | 'hips') | undefined, bone?: string | undefined, size?: number | undefined, move?: DirectionalAccessoryAxes | undefined, turn?: DirectionalAccessoryAxes | undefined, flip?: boolean | undefined, grip?: boolean | undefined, preview?: boolean | undefined, preview_preset_id?: string | undefined}; export type CreateApiAssetsSpriteDirectionalAccessoryFitArgs = {body: DirectionalAccessoryFitPayload}; export type CreateApiAssetsSpriteDirectionalAccessoryFitOptions = {baseUrl: string, ApiKey?: string | undefined, fetch?: typeof fetch | undefined}; export const directionalAccessoryAxes = z.object({forward: z.number().optional(), up: z.number().optional(), out: z.number().optional()}); export const directionalAccessoryPlacement = z.object({bone: z.string().optional(), point: z.string().optional(), kind: z.string().optional(), flipped: z.boolean().optional(), grip: z.boolean().optional(), base_scale: z.number().optional(), position: z.array(z.number()).optional(), quaternion: z.array(z.number()).optional(), scale: z.array(z.number()).optional()}); export const directionalAccessoryChecks = z.object({sink: z.number().optional(), inside: z.number().optional(), grip_gap: z.number().optional(), through: z.number().optional()}); export const directionalSpriteRender = z.object({style_id: z.string().optional(), style_version: z.number().int().optional(), style_params: z.object({}).optional(), perspective: z.string().optional(), direction: z.string().optional(), animation_name: z.string().optional(), fps: z.number().optional(), keep_horizontal_travel: z.boolean().optional(), keep_vertical_travel: z.boolean().optional()}); export const directionalPreview = z.object({direction: z.string().optional(), frame_urls: z.array(z.string()).optional(), width: z.number().int().optional(), height: z.number().int().optional(), num_frames: z.number().int().optional(), fps: z.number().optional(), render_3d: directionalSpriteRender.optional()}); export const directionalAccessoryFitResult = z.object({kind: z.string().optional(), point: z.string().optional(), bone: z.string().optional(), size: z.number().optional(), move: directionalAccessoryAxes.optional(), turn: directionalAccessoryAxes.optional(), flip: z.boolean().optional(), grip: z.boolean().optional(), placement: directionalAccessoryPlacement.optional(), checks: directionalAccessoryChecks.optional(), points: z.array(z.object({point: z.string().optional(), bone: z.string().optional()})).optional(), model_url: z.string().optional(), previews: z.array(directionalPreview).optional(), preview_error: z.string().optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Fit an accessory to a rigged 3D model for Directional Sprites: an item GLB (a sword, a gun, a staff, a bow, a shield, a helmet, a hat, a backpack...) held by one bone of the model - a hand, the head, the back or the hips. Nothing is saved. The item is first placed automatically for its `kind` (a sword by its handle, a gun by its pistol grip, a shield on the forearm, a helmet over the head, a backpack on the back), and the hand closes round it. Adjust it relative to that automatic fit with `size` (% of it), `move` (% of the character's height along forward/up/out) and `turn` (degrees about those axes), and call again with the new values until it looks right: each call starts from the automatic fit, so send the full adjustment every time. The response has the `placement` to pass to attachDirectionalAccessories, `checks` (how far the item sinks into the body, how far its grip is from the palm, how much of the head pokes through a helmet, each in % of the character's height or of its vertices), and, unless `preview` is false, `model_url` (the model holding the item, a GLB kept 7 days) and `previews`: frames of the model walking with it, seen from SW and SE, like previewDirectionalSprites' (which this shares: one preview at a time, a newer one cancels the older, which then comes back without previews). Free (no credits) and synchronous; a fit takes a few seconds with previews. Requires an API key (user scope). * * POST /assets/sprite/directional-accessory-fit */ export const createApiAssetsSpriteDirectionalAccessoryFit = async (args: CreateApiAssetsSpriteDirectionalAccessoryFitArgs, options: CreateApiAssetsSpriteDirectionalAccessoryFitOptions) => { const {baseUrl, fetch: fetchFn = fetch, ApiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (ApiKey !== undefined) headers.set("Authorization", ApiKey) const url = `${baseUrl.replace(/\/+$/, '')}/assets/sprite/directional-accessory-fit${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(directionalAccessoryFitResult, await res.json()) };