Repository navigation
Fit Lab: poke metric follows holdout margin, push-out and occlusion modes (#16) #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // The rule the Measure poke count follows, kept pure so Node can test it. It mirrors the final renderer: | ||
| // - tools/uo-content/occlusion.py (BodyHoldout / blocked_pixels): the body cuts an item pixel only where it is | ||
| // more than `margin` in front of the item; in clothing mode with hide-body on, a same-region contact gets | ||
| // max(margin, hide.inward). | ||
| // - the installed canonical renderer, render_uo_layer.py: HOLDOUT_MARGIN = 0.01 m and BODY_GAP = 0.006 m (push-out), | ||
| // which tools/uo-content/blender_build.py sets to 0 for rigid items and for helm, weapon, shield, bow and quiver. | ||
| export const HOLDOUT_MARGIN = 0.01; | ||
| export const PUSH_OUT_GAP = 0.006; | ||
| const NO_PUSH_OUT = new Set(['helm', 'weapon', 'shield', 'bow', 'quiver']); | ||
|
|
||
| // studioPart is the mapping part's `studio_part`, the value builds.py hands the renderer as spec.part (not the pack | ||
| // part code or the layer name). A part without one is treated as pushed. | ||
| export function pokeRule(fit, studioPart) { | ||
| const mode = fit.occlusion || 'clothing'; | ||
| if (mode === 'none') return { measure: false, mode, allowance: 0, faces: 'none' }; // renderer skips the holdout | ||
| const tolerance = mode === 'clothing' && fit.hide?.enabled ? (fit.hide.inward ?? 0.01) : 0; | ||
| const pushed = fit.bind !== 'rigid' && !NO_PUSH_OUT.has(studioPart); | ||
| return { measure: true, mode, | ||
| allowance: Math.max(HOLDOUT_MARGIN, tolerance) + (pushed ? PUSH_OUT_GAP : 0), | ||
| // clothing: limb/head faces under the item (the torso never holds out); body: the whole body, hide-body ignored | ||
| faces: mode === 'body' ? 'all' : 'under' }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import test from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import { readFileSync } from 'node:fs'; | ||
| import { pokeRule, HOLDOUT_MARGIN, PUSH_OUT_GAP } from './poke-rules.mjs'; | ||
|
|
||
| const fit = (o = {}) => ({ occlusion: 'clothing', bind: 'skinned', hide: { enabled: true, outward: .02, inward: .01 }, ...o }); | ||
| const near = (a, b) => assert.ok(Math.abs(a - b) < 1e-12, `${a} != ${b}`); | ||
|
|
||
| test('clothing: 1 cm margin plus 6 mm push-out for a skinned garment', () => { | ||
| const r = pokeRule(fit(), 'chest'); | ||
| assert.equal(r.measure, true); assert.equal(r.faces, 'under'); | ||
| near(r.allowance, HOLDOUT_MARGIN + PUSH_OUT_GAP); | ||
| }); | ||
| test('a part with no studio_part is treated as pushed', () => { | ||
| near(pokeRule(fit(), undefined).allowance, HOLDOUT_MARGIN + PUSH_OUT_GAP); | ||
| }); | ||
| test('hide-body inward larger than the margin is the contact allowance; off falls back to the margin', () => { | ||
| near(pokeRule(fit({ hide: { enabled: true, inward: .03 } }), 'chest').allowance, .036); | ||
| near(pokeRule(fit({ hide: { enabled: false, inward: .03 } }), 'chest').allowance, .016); | ||
| }); | ||
| test('rigid items get no push-out whatever the studio part', () => { | ||
| assert.equal(pokeRule(fit({ bind: 'rigid' }), 'chest').allowance, HOLDOUT_MARGIN); | ||
| }); | ||
| test('helm, weapon, shield, bow and quiver studio parts get no push-out', () => { | ||
| for (const part of ['helm', 'weapon', 'shield', 'bow', 'quiver']) | ||
| assert.equal(pokeRule(fit(), part).allowance, HOLDOUT_MARGIN); | ||
| }); | ||
| test('pack part codes and layer names do not decide the push-out', () => { | ||
| for (const code of ['hood', 'weapon-sword', 'shield-round', 'backpack', 'OneHanded', 'Helm']) | ||
| near(pokeRule(fit(), code).allowance, HOLDOUT_MARGIN + PUSH_OUT_GAP); | ||
| }); | ||
| test('body mode uses the whole body and ignores the hide-body tolerance', () => { | ||
| const r = pokeRule(fit({ occlusion: 'body' }), 'chest'); | ||
| assert.equal(r.faces, 'all'); near(r.allowance, .016); | ||
| }); | ||
| test('none mode measures nothing, like the renderer', () => { | ||
| assert.equal(pokeRule(fit({ occlusion: 'none' }), 'chest').measure, false); | ||
| }); | ||
| test('cc0 starter mapping: every part gets the allowance the renderer implies', () => { | ||
| const mapping = JSON.parse(readFileSync(new URL('../../../examples/cc0-starter/outfit-mapping.json', import.meta.url))); | ||
| const plain = ['hood', 'hair', 'beard', 'weapon-sword', 'shield-round', 'backpack']; | ||
| const pushedCodes = ['shirt', 'pants', 'cloak']; | ||
| for (const p of mapping.parts) { | ||
| const rigid = p.bind === 'rigid', free = ['helm', 'weapon', 'shield', 'bow', 'quiver'].includes(p.studio_part); | ||
| const r = pokeRule(fit({ bind: p.bind }), p.studio_part); | ||
| near(r.allowance, rigid || free ? .01 : .016); | ||
| if (plain.includes(p.code)) near(r.allowance, .01); | ||
| if (pushedCodes.includes(p.code)) near(r.allowance, .016); | ||
| } | ||
| assert.ok(plain.concat(pushedCodes).every(c => mapping.parts.some(p => p.code === c))); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When measurements use different allowances—for example, a 1.6 cm skinned garment followed by a 1 cm rigid item—all callbacks returned by
pushBack()have the same string representation. Three.js r160 usesonBeforeCompile.toString()as the default custom program cache key, so it reuses the first compiled shader, including that shader's embedded distance literal, for subsequent materials. Measurements after the first allowance therefore use the wrong depth offset; define acustomProgramCacheKeycontaining the allowance or pass the distance through a uniform.Useful? React with 👍 / 👎.