arcanumcube
Version:
1 lines • 492 kB
Source Map (JSON)
{"version":3,"file":"arcanumcube.cjs","sources":["../src/core.ts","../src/skins.ts","../src/materials.ts","../src/webgl.ts","../src/index.ts"],"sourcesContent":["// cube size\nexport const CUBE_SIZE = 3;\nexport const SIDE_MAX = CUBE_SIZE - 1;\nexport const SIDE_MIN = 0;\nexport const SIDE_MIDDLE = Math.floor(CUBE_SIZE / 2);\n\n// The color of the sticker on the face of each cube\nexport const STICKER_COLOR = {\n UP: 0,\n FRONT: 1,\n RIGHT: 2,\n DOWN: 3,\n BACK: 4,\n LEFT: 5,\n PLAIN: 6,\n};\nexport type StickerColor = (typeof STICKER_COLOR)[keyof typeof STICKER_COLOR];\n\nexport const CUBE = {\n AXIS: 'axis',\n CENTER: 'center',\n EDGE: 'edge',\n CORNER: 'corner',\n STICKER: 'sticker',\n} as const;\n\nconst CUBETYPE_LIST = [CUBE.AXIS, CUBE.CENTER, CUBE.EDGE, CUBE.CORNER] as const;\nexport type CubeType = (typeof CUBE)[keyof typeof CUBE];\n\nconst FACE_LIST: string[] = ['U', 'F', 'R', 'D', 'B', 'L'] as const;\nexport const FACE: Record<string, number> = Object.assign(\n {},\n ...FACE_LIST.map((k, i) => ({ [k]: i })),\n);\nexport type Face = (typeof FACE)[keyof typeof FACE];\n\nexport const TWIST = {\n U: 'U',\n UR: \"U'\",\n F: 'F',\n FR: \"F'\",\n R: 'R',\n RR: \"R'\",\n D: 'D',\n DR: \"D'\",\n B: 'B',\n BR: \"B'\",\n L: 'L',\n LR: \"L'\",\n M: 'M',\n MR: \"M'\",\n E: 'E',\n ER: \"E'\",\n S: 'S',\n SR: \"S'\",\n U2: 'U2',\n F2: 'F2',\n R2: 'R2',\n D2: 'D2',\n B2: 'B2',\n L2: 'L2',\n M2: 'M2',\n E2: 'E2',\n S2: 'S2',\n} as const;\nexport type Twist = (typeof TWIST)[keyof typeof TWIST];\n\nexport const SINGLE_TWIST_LIST = [\n TWIST.U,\n TWIST.UR,\n TWIST.F,\n TWIST.FR,\n TWIST.R,\n TWIST.RR,\n TWIST.D,\n TWIST.DR,\n TWIST.B,\n TWIST.BR,\n TWIST.L,\n TWIST.LR,\n TWIST.M,\n TWIST.MR,\n TWIST.E,\n TWIST.ER,\n TWIST.S,\n TWIST.SR,\n] as const;\n\nexport const DOUBLE_TWIST_LIST = [\n TWIST.U2,\n TWIST.F2,\n TWIST.R2,\n TWIST.D2,\n TWIST.B2,\n TWIST.L2,\n TWIST.M2,\n TWIST.E2,\n TWIST.S2,\n] as const;\n\nexport const TWIST_LIST = [...SINGLE_TWIST_LIST, ...DOUBLE_TWIST_LIST];\n\nexport const CUBE_ANGLES: Twist[][][][] = [\n [\n // down 3x3 cubes\n [\n [TWIST.B], // [0, 0, 90]\n [TWIST.B2], // [0, 0, 180]\n [TWIST.B2], // [0, 0, 180]\n ],\n [\n [TWIST.UR, TWIST.SR], // [0, 90, 90]\n [TWIST.M2], // [180, 0, 0]\n [TWIST.U, TWIST.S], // [0, -90, -90]\n ],\n [\n [TWIST.L2], // [180, 0, 0]\n [TWIST.M2], // [180, 0, 0]\n [TWIST.L, TWIST.UR, TWIST.F], // [90, 90, -90]\n ],\n ],\n [\n // middle 3x3 cubes\n [\n [TWIST.B], // [0, 0, 90]\n [TWIST.MR], // [-90, 0, 0]\n [TWIST.BR], // [0, 0, -90]\n ],\n [\n [TWIST.SR], // [0, 0, 90]\n [], // [0, 0, 0] original axis\n [TWIST.S], // [0, 0, -90]\n ],\n [\n [TWIST.M, TWIST.FR], // [90, 0, 90]\n [TWIST.M], // [90, 0, 0]\n [TWIST.M, TWIST.F], // [90, 0, -90]\n ],\n ],\n [\n // up 3x3 cubes\n [\n [], // [0, 0, 0] original corner\n [], // [0, 0, 0] original edge\n [TWIST.U], // [0, -90, 0]\n ],\n [\n [TWIST.UR], // [0, 90, 0]\n [], // [0, 0, 0] original center\n [TWIST.U], // [0, -90, 0]\n ],\n [\n [TWIST.UR], // [0, 90, 0]\n [TWIST.U2], // [0, 180, 0]\n [TWIST.U2], // [0, 180, 0]\n ],\n ],\n] as const;\n\nexport const TWIST_RULE: Record<\n Twist,\n { axis: [x: number, y: number, z: number]; levels: number[]; steps: number }\n> = {\n [TWIST.U]: { axis: [0, -1, 0], levels: [2], steps: 1 },\n [TWIST.UR]: { axis: [0, 1, 0], levels: [2], steps: 1 },\n [TWIST.F]: { axis: [0, 0, -1], levels: [2], steps: 1 },\n [TWIST.FR]: { axis: [0, 0, 1], levels: [2], steps: 1 },\n [TWIST.R]: { axis: [-1, 0, 0], levels: [2], steps: 1 },\n [TWIST.RR]: { axis: [1, 0, 0], levels: [2], steps: 1 },\n [TWIST.D]: { axis: [0, 1, 0], levels: [0], steps: 1 },\n [TWIST.DR]: { axis: [0, -1, 0], levels: [0], steps: 1 },\n [TWIST.B]: { axis: [0, 0, 1], levels: [0], steps: 1 },\n [TWIST.BR]: { axis: [0, 0, -1], levels: [0], steps: 1 },\n [TWIST.L]: { axis: [1, 0, 0], levels: [0], steps: 1 },\n [TWIST.LR]: { axis: [-1, 0, 0], levels: [0], steps: 1 },\n [TWIST.M]: { axis: [1, 0, 0], levels: [1], steps: 1 },\n [TWIST.MR]: { axis: [-1, 0, 0], levels: [1], steps: 1 },\n [TWIST.E]: { axis: [0, 1, 0], levels: [1], steps: 1 },\n [TWIST.ER]: { axis: [0, -1, 0], levels: [1], steps: 1 },\n [TWIST.S]: { axis: [0, 0, -1], levels: [1], steps: 1 },\n [TWIST.SR]: { axis: [0, 0, 1], levels: [1], steps: 1 },\n [TWIST.U2]: { axis: [0, -1, 0], levels: [2], steps: 2 },\n [TWIST.F2]: { axis: [0, 0, -1], levels: [2], steps: 2 },\n [TWIST.R2]: { axis: [-1, 0, 0], levels: [2], steps: 2 },\n [TWIST.D2]: { axis: [0, 1, 0], levels: [0], steps: 2 },\n [TWIST.B2]: { axis: [0, 0, 1], levels: [0], steps: 2 },\n [TWIST.L2]: { axis: [1, 0, 0], levels: [0], steps: 2 },\n [TWIST.M2]: { axis: [1, 0, 0], levels: [1], steps: 2 },\n [TWIST.E2]: { axis: [0, 1, 0], levels: [1], steps: 2 },\n [TWIST.S2]: { axis: [0, 0, -1], levels: [1], steps: 2 },\n} as const;\n\n// convert from cube and face to sticker index\nexport function getStickerIndex(x: number, y: number, z: number, face: Face): number {\n let [px, py] = [0, 0];\n\n if (face === FACE.U) {\n px = x;\n py = z;\n } else if (face === FACE.D) {\n px = x;\n py = SIDE_MAX - z;\n } else if (face === FACE.F) {\n px = x;\n py = SIDE_MAX - y;\n } else if (face === FACE.B) {\n px = SIDE_MAX - x;\n py = SIDE_MAX - y;\n } else if (face === FACE.R) {\n px = SIDE_MAX - z;\n py = SIDE_MAX - y;\n } else if (face === FACE.L) {\n px = z;\n py = SIDE_MAX - y;\n }\n\n return face * CUBE_SIZE * CUBE_SIZE + py * CUBE_SIZE + px;\n}\n\n// convert from sticker index to cube and face\nexport function getCubeFromStickerIndex(\n index: number,\n): [x: number, y: number, z: number, face: Face] {\n const face = Math.floor(index / (CUBE_SIZE * CUBE_SIZE));\n const f = index - face * (CUBE_SIZE * CUBE_SIZE);\n const py = Math.floor(f / CUBE_SIZE);\n const px = f % CUBE_SIZE;\n let [x, y, z] = [0, 0, 0];\n\n if (face === FACE.U) {\n x = px;\n z = py;\n } else if (face === FACE.D) {\n x = px;\n z = SIDE_MAX - py;\n } else if (face === FACE.F) {\n x = px;\n y = SIDE_MAX - py;\n } else if (face === FACE.B) {\n x = SIDE_MAX - px;\n y = SIDE_MAX - py;\n } else if (face === FACE.R) {\n z = SIDE_MAX - px;\n y = SIDE_MAX - py;\n } else if (face === FACE.L) {\n z = px;\n y = SIDE_MAX - py;\n }\n\n return [x, y, z, face];\n}\n\nexport function getRandomTwistList(steps: number = 0) {\n // decide twist steps\n const t = steps === 0 ? Math.floor(Math.random() * (30 - 15 + 1)) + 15 : steps;\n const len = SINGLE_TWIST_LIST.length;\n const isOffsetting = (a: string, b: string): boolean => {\n return a !== b && (a + \"'\").substring(0, 2) === (b + \"'\").substring(0, 2);\n };\n\n const list: Twist[] = [];\n let prev = '';\n for (let i = 0; i < t; i++) {\n let s: Twist;\n while (isOffsetting((s = SINGLE_TWIST_LIST[Math.floor(Math.random() * len)]), prev));\n list.push(s);\n prev = s;\n }\n\n return list;\n}\n\n// list of faces surrounding each face\nconst SIDE_FACES = Object.freeze([\n [1, 2, 4, 5],\n [2, 0, 5, 3],\n [0, 1, 3, 4],\n [2, 1, 5, 4],\n [0, 2, 3, 5],\n [1, 0, 4, 3],\n]);\n\n// get the initial surface state\n// up: the side that comes up\n// front: front side\nfunction getInitialState(up: number, front: number): StickerColor[] {\n const down = (up + 3) % 6;\n const side = SIDE_FACES[up];\n\n const state = new Array(CUBE_SIZE * CUBE_SIZE).fill(up);\n for (let i = 0; i < 4; i++) {\n if (i == 2) {\n const downs = [...Array(CUBE_SIZE * CUBE_SIZE)].fill(down);\n state.push(...downs);\n }\n const sides = [...Array(CUBE_SIZE * CUBE_SIZE)].fill(side[(front + i) % 4]);\n state.push(...sides);\n }\n\n return state;\n}\n\n// determine if array elements match\nfunction isSameArrays(arr1: number[], arr2: number[]): boolean {\n if (arr1.length !== arr2.length) return false;\n\n return !arr1.some((v, i) => v !== arr2[i]);\n}\n\n// get a list with all 6 sides\n// (there are 24 patterns in total, 6 types for the top side and 4 types\n// for the front side)\nconst GOAL_STATE_LIST: StickerColor[][] = [];\nfor (let up = 0; up < 6; up++) {\n for (let front = 0; front < 4; front++) {\n GOAL_STATE_LIST.push(getInitialState(up, front));\n }\n}\nObject.freeze(GOAL_STATE_LIST);\n\n// returns whether the goal state is met.\n// that is, it returns whether it is resolved or not.\nexport function MatchGoalState(state: StickerColor[]): boolean {\n return GOAL_STATE_LIST.some((s) => isSameArrays(state, s));\n}\n\n// type of sticker\nexport type Sticker = {\n face: Face;\n color: StickerColor;\n};\n\n/** One small cube class that makes up the Arcanum Cube */\nexport class Cube {\n type: string;\n position: { x: number; y: number; z: number };\n\n protected _stickers: Sticker[];\n protected _initialPosition: { x: number; y: number; z: number };\n\n constructor(x: number, y: number, z: number) {\n this._stickers = [];\n this._initialPosition = { x, y, z };\n this.position = { x, y, z };\n\n // calc faces\n let faces = 0;\n if (x === SIDE_MAX) faces++;\n if (x === SIDE_MIN) faces++;\n if (y === SIDE_MAX) faces++;\n if (y === SIDE_MIN) faces++;\n if (z === SIDE_MAX) faces++;\n if (z === SIDE_MIN) faces++;\n\n this.type = CUBETYPE_LIST[faces];\n }\n\n getStickers(): Sticker[] {\n return this._stickers;\n }\n\n init() {\n const { x, y, z } = this.position;\n const angles = CUBE_ANGLES[y][z][x];\n\n // init cube location\n this._stickers = [];\n if (this.type === CUBE.CENTER) {\n this._stickers = [{ face: FACE.U, color: STICKER_COLOR.PLAIN }];\n } else if (this.type === CUBE.EDGE) {\n this._stickers = [\n { face: FACE.U, color: STICKER_COLOR.PLAIN },\n { face: FACE.B, color: STICKER_COLOR.PLAIN },\n ];\n } else if (this.type === CUBE.CORNER) {\n this._stickers = [\n { face: FACE.U, color: STICKER_COLOR.PLAIN },\n { face: FACE.B, color: STICKER_COLOR.PLAIN },\n { face: FACE.L, color: STICKER_COLOR.PLAIN },\n ];\n }\n\n // rotate sticker face\n this.rotateStickerFace(angles, false, true);\n }\n\n reset() {\n const Faces: Face[] = [FACE.U, FACE.B, FACE.L];\n const { x, y, z } = this._initialPosition;\n this.position = { x, y, z };\n const angles = CUBE_ANGLES[y][z][x];\n\n // init cube faces\n for (let i = 0; i < this._stickers.length; i++) {\n this._stickers[i].face = Faces[i];\n this._stickers[i].color = STICKER_COLOR.PLAIN;\n }\n\n this.rotateStickerFace(angles, false, true);\n }\n\n rotateStickerFace(twists: Twist[], reverse: boolean = false, init: boolean = false) {\n const rotateMap: Face[][] = [\n /* x */\n [FACE.U, FACE.F, FACE.D, FACE.B],\n /* y */\n [FACE.F, FACE.R, FACE.B, FACE.L],\n /* z */\n [FACE.U, FACE.L, FACE.D, FACE.R],\n ];\n\n function getNext(face: Face, axis_index: number, twist: Twist): Face {\n const { axis, steps } = TWIST_RULE[twist];\n const map = rotateMap[axis_index];\n const i = map.indexOf(face);\n if (i < 0) return face; // no rotation\n const angle = axis[axis_index];\n const i2 = (i + 4 + (reverse ? -1 : 1) * angle * steps) % 4;\n return map[i2];\n }\n\n for (const sticker of this._stickers) {\n for (const r of twists) {\n sticker.face = getNext(sticker.face, 0, r);\n sticker.face = getNext(sticker.face, 1, r);\n sticker.face = getNext(sticker.face, 2, r);\n }\n if (init && sticker.color === STICKER_COLOR.PLAIN) {\n sticker.color = sticker.face;\n }\n }\n }\n}\n\n/** Arcanum Cube object class */\nexport class ArcanumCube {\n /** output debug information to console */\n debug: boolean;\n\n /** history of twisting */\n protected _history: Twist[];\n\n /** cube objects matrix */\n protected _matrix: Cube[][][];\n\n constructor(options?: { debug?: boolean }) {\n this.debug = false;\n this._history = [];\n this._matrix = [];\n\n if (options) {\n if (options.debug != null) {\n this.debug = options.debug;\n }\n }\n }\n\n init() {\n this._matrix = [];\n\n // set 3x3x3 cubes\n const yarray: Cube[][][] = [];\n for (let y = SIDE_MIN; y <= SIDE_MAX; y++) {\n const zarray: Cube[][] = [];\n for (let z = SIDE_MIN; z <= SIDE_MAX; z++) {\n const xarray: Cube[] = [];\n for (let x = SIDE_MIN; x <= SIDE_MAX; x++) {\n const cube = new Cube(x, y, z);\n cube.init();\n xarray.push(cube);\n }\n zarray.push(xarray);\n }\n yarray.push(zarray);\n }\n\n this._matrix = yarray;\n }\n\n reset() {\n const list = [];\n for (let y = SIDE_MIN; y <= SIDE_MAX; y++) {\n for (let z = SIDE_MIN; z <= SIDE_MAX; z++) {\n for (let x = SIDE_MIN; x <= SIDE_MAX; x++) {\n const cube = this._matrix[y][z][x];\n cube.reset();\n list.push(cube);\n }\n }\n }\n\n list.forEach((cube) => {\n const n = cube.position;\n this._matrix[n.y][n.z][n.x] = cube;\n });\n\n this._history = [];\n }\n\n // twist randomly several steps\n scramble(steps?: number) {\n const list = getRandomTwistList(steps);\n if (this.debug) console.log('Scramble: ' + list.join(', '));\n this.twist(list, false);\n }\n\n undo(steps: number = 1) {\n const list = this.getUndoList(steps);\n this.twist(list, true);\n }\n\n isSolved(): boolean {\n return MatchGoalState(this.getStickerColors());\n }\n\n getHistory(): Twist[] {\n return this._history;\n }\n\n getUndoList(steps: number = 1): Twist[] {\n let t = steps;\n if (t < 0) t = 0;\n if (t > this._history.length) t = this._history.length;\n return this._history.slice(-t).reverse();\n }\n\n twist(twist: Twist | Twist[], reverse = false) {\n if (Array.isArray(twist)) {\n if (twist.length == 0) return;\n for (const c of twist) {\n this._twist(c, reverse);\n }\n } else {\n this._twist(twist, reverse);\n }\n }\n\n private _twist(twist: Twist, reverse: boolean) {\n this.rotateMatrix(twist, reverse);\n if (this.debug) {\n this.dumpStickers();\n }\n if (reverse) {\n this._history.pop();\n } else {\n this._history.push(twist);\n }\n\n if (this.debug) console.log(this._history.join(' '));\n }\n\n rotateMatrix(twist: Twist, reverse: boolean = false) {\n const rule = TWIST_RULE[twist];\n const axis = rule.axis;\n const map: number[][] = new Array(3);\n for (const l of rule.levels) {\n let i = axis.indexOf(-1);\n if (i === -1) i = axis.indexOf(1);\n const s = (reverse ? -1 : 1) * axis[i];\n map[i] = Array(8).fill(l);\n map[(i + s + 3) % 3] = [0, 1, 2, 2, 2, 1, 0, 0];\n map[(i - s + 3) % 3] = [0, 0, 0, 1, 2, 2, 2, 1];\n\n const [x, y, z] = map;\n const tmp: Cube[] = [];\n for (let i = 0; i < 8; i++) {\n const cube = this._matrix[y[i]][z[i]][x[i]];\n cube.rotateStickerFace([twist], reverse);\n tmp.push(cube);\n }\n for (let i = 0; i < 8; i++) {\n const i2 = (i + rule.steps * 2) % 8;\n tmp[i].position = { x: x[i2], y: y[i2], z: z[i2] };\n this._matrix[y[i2]][z[i2]][x[i2]] = tmp[i];\n }\n }\n }\n\n getStickerColors(): StickerColor[] {\n const list: StickerColor[] = new Array(CUBE_SIZE * CUBE_SIZE * 6);\n\n for (let y = SIDE_MIN; y <= SIDE_MAX; y++) {\n for (let z = SIDE_MIN; z <= SIDE_MAX; z++) {\n for (let x = SIDE_MIN; x <= SIDE_MAX; x++) {\n const cube = this._matrix[y][z][x];\n const stickers = cube.getStickers();\n for (const sticker of stickers) {\n const index = getStickerIndex(x, y, z, sticker.face);\n list[index] = sticker.color;\n }\n }\n }\n }\n\n return list;\n }\n\n dumpStickers() {\n const list = this.getStickerColors();\n\n const STYLE = 'background-color:black; padding:1px 1px;';\n const Col2Str: Record<StickerColor, string> = {\n [STICKER_COLOR.UP]: 'color:white; ' + STYLE,\n [STICKER_COLOR.FRONT]: 'color:#00d800; ' + STYLE,\n [STICKER_COLOR.RIGHT]: 'color:#d80000; ' + STYLE,\n [STICKER_COLOR.DOWN]: 'color:yellow; ' + STYLE,\n [STICKER_COLOR.BACK]: 'color:#0000d8; ' + STYLE,\n [STICKER_COLOR.LEFT]: 'color:#ff8000; ' + STYLE,\n };\n const RESET = 'background-color:none; padding:0px 0px;';\n const result: string[] = [];\n const attrs: string[] = [];\n const getLine = (faces: Face[]) => {\n const line: string[] = [];\n for (let py = 0; py < 3; py++) {\n for (const f of faces) {\n if (f === -1) {\n // indent\n line.push(' ');\n } else {\n line.push('%c\\u25a0 %c\\u25a0 %c\\u25a0%c ');\n const index = f * CUBE_SIZE * CUBE_SIZE + py * CUBE_SIZE;\n attrs.push(Col2Str[list[index + 0]]);\n attrs.push(Col2Str[list[index + 1]]);\n attrs.push(Col2Str[list[index + 2]]);\n attrs.push(RESET);\n }\n }\n line.push('\\n');\n }\n line.push('\\n');\n result.push(line.join(''));\n };\n\n getLine([-1, FACE.U]);\n getLine([FACE.L, FACE.F, FACE.R, FACE.B]);\n getLine([-1, FACE.D]);\n console.log(result.join('\\n'), ...attrs);\n }\n}\n\nconst STICKER_PERMUTATION_GROUP_MAP: Record<Twist | string, number[][]> =\n makeStickerPermutationGroupMap();\n\nfunction makeStickerPermutationGroupMap(): Record<Twist | string, number[][]> {\n const result: Record<Twist | string, number[][]> = {};\n TWIST_LIST.forEach((twist) => {\n const tmp = getCubePermutationGroup(<Twist>twist).stickers;\n result[twist] = [tmp.before, tmp.after];\n });\n return result;\n}\n\nexport function getNextStickerColors(stickers: StickerColor[], twist: Twist): StickerColor[] {\n const result = [...stickers]; // clone\n const [bef, aft] = STICKER_PERMUTATION_GROUP_MAP[twist];\n for (let i = 0; i < bef.length; i++) {\n result[aft[i]] = stickers[bef[i]];\n }\n return result;\n}\n\nexport function getArrayForTensor(stickers: StickerColor[]) {\n const array: number[][][] = [...Array(3)].map(() =>\n [...Array(3)].map(() => Array(6 * 6).fill(0)),\n );\n\n // 3 x 3 stickers with by 6 faces x 6 colors\n for (let i = 0; i < stickers.length; i++) {\n const face = Math.floor(i / (CUBE_SIZE * CUBE_SIZE));\n const c = i - face * (CUBE_SIZE * CUBE_SIZE);\n const row = Math.floor(c / CUBE_SIZE);\n const column = c - row * CUBE_SIZE;\n const color = stickers[i];\n array[row][column][color * 6 /* faces */ + face] = 1;\n }\n\n return array;\n}\n\nexport function getCubePermutationGroup(twist: Twist, reverse: boolean = false) {\n const rule = TWIST_RULE[twist];\n const axis = rule.axis;\n const result: {\n before: number[][];\n after: number[][];\n stickers: { before: number[]; after: number[] };\n } = { before: [], after: [], stickers: { before: [], after: [] } };\n const map: number[][] = new Array(3);\n for (const l of rule.levels) {\n let i = axis.indexOf(-1);\n if (i === -1) i = axis.indexOf(1);\n const s = (reverse ? -1 : 1) * axis[i];\n map[i] = Array(8).fill(l);\n map[(i + s + 3) % 3] = [0, 1, 2, 2, 2, 1, 0, 0];\n map[(i - s + 3) % 3] = [0, 0, 0, 1, 2, 2, 2, 1];\n\n const [x, y, z] = map;\n for (let i = 0; i < 8; i++) {\n const i2 = (i + rule.steps * 2) % 8;\n result.before.push([x[i], y[i], z[i]]);\n result.after.push([x[i2], y[i2], z[i2]]);\n const list = getStickerPermutationGroup(\n [x[i], y[i], z[i]],\n [x[i2], y[i2], z[i2]],\n [twist],\n reverse,\n );\n result.stickers.before.push(...list.before);\n result.stickers.after.push(...list.after);\n }\n }\n\n return result;\n}\n\nexport function getStickerPermutationGroup(\n position: [x: number, y: number, z: number],\n position2: [x: number, y: number, z: number],\n twists: Twist[],\n reverse: boolean = false,\n) {\n const rotateMap: Face[][] = [\n /* x */\n [FACE.U, FACE.F, FACE.D, FACE.B],\n /* y */\n [FACE.F, FACE.R, FACE.B, FACE.L],\n /* z */\n [FACE.U, FACE.L, FACE.D, FACE.R],\n ];\n\n function getNext(face: Face, axis_index: number, twist: Twist): Face {\n const { axis, steps } = TWIST_RULE[twist];\n const map = rotateMap[axis_index];\n const i = map.indexOf(face);\n if (i < 0) return face; // no rotation\n const angle = axis[axis_index];\n const i2 = (i + 4 + (reverse ? -1 : 1) * angle * steps) % 4;\n return map[i2];\n }\n\n const result: { before: number[]; after: number[] } = { before: [], after: [] };\n const [x, y, z] = position;\n const [x2, y2, z2] = position2;\n const faces: Face[] = [];\n if (x === SIDE_MAX) faces.push(FACE.R);\n if (x === SIDE_MIN) faces.push(FACE.L);\n if (y === SIDE_MAX) faces.push(FACE.U);\n if (y === SIDE_MIN) faces.push(FACE.D);\n if (z === SIDE_MAX) faces.push(FACE.F);\n if (z === SIDE_MIN) faces.push(FACE.B);\n\n for (const face of faces) {\n let f = face;\n result.before.push(getStickerIndex(x, y, z, f));\n for (const t of twists) {\n f = getNext(f, 0, t);\n f = getNext(f, 1, t);\n f = getNext(f, 2, t);\n }\n result.after.push(getStickerIndex(x2, y2, z2, f));\n }\n\n return result;\n}\n","import * as THREE from 'three';\nimport * as ARCCUBE from './core.js';\n\nlet DefaultLogoTextures: THREE.Texture | undefined;\n\nexport function loadDefaultLogoTexture(): THREE.Texture {\n if (DefaultLogoTextures) return DefaultLogoTextures;\n const image = <HTMLImageElement>document.createElementNS('http://www.w3.org/1999/xhtml', 'img');\n image.src = DefaultLogo;\n const texture = new THREE.Texture(image);\n texture.needsUpdate = true;\n return texture;\n}\n\nexport function GetSkinByName(name: string): Skin {\n return SkinMap[name];\n}\n\nexport function GetSkinNameList(): string[] {\n return Object.keys(SkinMap);\n}\n\nexport type Skin = {\n name: string;\n enableEnvMap: boolean;\n modelFiles: Record<ARCCUBE.CubeType, string>;\n modelLoading: boolean;\n models: Record<string, THREE.Mesh>;\n cube: {\n material: () => THREE.Material;\n activate: (mesh: THREE.Mesh) => void;\n deactivate: (mesh: THREE.Mesh) => void;\n };\n sticker: {\n material: (x: number, y: number, z: number, color: ARCCUBE.StickerColor) => THREE.Material;\n activate: (mesh: THREE.Mesh) => void;\n deactivate: (mesh: THREE.Mesh) => void;\n };\n};\n\nconst standardSkin: Skin = {\n name: 'Standard',\n enableEnvMap: false,\n modelFiles: {\n axis: 'default-axis',\n center: 'default-center',\n edge: 'default-edge',\n corner: 'default-corner',\n sticker: 'default-sticker',\n },\n modelLoading: false,\n models: {},\n cube: {\n material: () => {\n return new THREE.MeshStandardMaterial({\n color: 0x2f2f2f,\n metalness: 0.8,\n roughness: 0.4,\n });\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = false;\n mat.opacity = 1.0;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.3;\n mat.needsUpdate = true;\n },\n },\n sticker: {\n material: (x: number, y: number, z: number, color: ARCCUBE.StickerColor) => {\n const ColorList = [\n '#ffffff', // U: White\n '#00d800', // F: Green\n '#d80000', // R: Red\n '#ffff00', // D: Yellow\n '#0000d8', // B: Blue\n '#ff8000', // L: Orange\n ];\n\n const col = ColorList[color];\n let texture = undefined;\n /*\n if (x === ARCCUBE.SIDE_MIDDLE && y === ARCCUBE.SIDE_MAX && z === ARCCUBE.SIDE_MIDDLE) {\n // load texture\n // texture = new THREE.TextureLoader().load('./asset/standard/logo.png');\n texture = loadDefaultLogoTexture();\n texture.flipY = false;\n }\n */\n\n const mat = new THREE.MeshStandardMaterial({\n color: col,\n metalness: 0.4,\n roughness: 0.1,\n });\n if (texture != null) {\n mat.map = texture;\n mat.bumpMap = texture;\n }\n\n return mat;\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = false;\n mat.opacity = 1.0;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.3;\n mat.needsUpdate = true;\n },\n },\n};\n\nconst metalicSkin: Skin = {\n name: 'Metalic',\n enableEnvMap: true,\n modelFiles: {\n axis: 'default-axis',\n center: 'default-center',\n edge: 'default-edge',\n corner: 'default-corner',\n sticker: 'default-sticker',\n },\n modelLoading: false,\n models: {},\n cube: {\n material: () => {\n return new THREE.MeshStandardMaterial({\n color: 0xe8e8e8,\n metalness: 1.0,\n roughness: 0.1,\n });\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = false;\n mat.opacity = 1.0;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.3;\n mat.needsUpdate = true;\n },\n },\n sticker: {\n material: (x: number, y: number, z: number, color: ARCCUBE.StickerColor) => {\n const ColorList = [\n '#ffffff', // U: White\n '#00d800', // F: Green\n '#d80000', // R: Red\n '#ffff00', // D: Yellow\n '#0000d8', // B: Blue\n '#ff8000', // L: Orange\n ];\n\n const col = ColorList[color];\n let texture = undefined;\n /*\n if (x === ARCCUBE.SIDE_MIDDLE && y === ARCCUBE.SIDE_MAX && z === ARCCUBE.SIDE_MIDDLE) {\n // load texture\n texture = loadDefaultLogoTexture();\n texture.flipY = false;\n }\n */\n\n const mat = new THREE.MeshStandardMaterial({\n color: col,\n metalness: 0.7,\n roughness: 0.1,\n });\n if (texture != null) mat.map = texture;\n\n return mat;\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = false;\n mat.opacity = 1.0;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.3;\n mat.needsUpdate = true;\n },\n },\n};\n\nconst goldSkin: Skin = {\n name: 'Gold',\n enableEnvMap: true,\n modelFiles: {\n axis: 'default-axis',\n center: 'default-center',\n edge: 'default-edge',\n corner: 'default-corner',\n sticker: 'default-sticker',\n },\n modelLoading: false,\n models: {},\n cube: {\n material: () => {\n return new THREE.MeshStandardMaterial({\n color: 0xfff060,\n metalness: 1.0,\n roughness: 0.0,\n });\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = false;\n mat.opacity = 1.0;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.3;\n mat.needsUpdate = true;\n },\n },\n sticker: {\n material: (x: number, y: number, z: number, color: ARCCUBE.StickerColor) => {\n const ColorList = [\n '#fff8d0', // U: White\n '#00c800', // F: Green\n '#e80000', // R: Red\n '#fff000', // D: Yellow\n '#0000c8', // B: Blue\n '#f87000', // L: Orange\n ];\n\n const col = ColorList[color];\n let texture = undefined;\n /*\n if (x === ARCCUBE.SIDE_MIDDLE && y === ARCCUBE.SIDE_MAX && z === ARCCUBE.SIDE_MIDDLE) {\n // load texture\n texture = loadDefaultLogoTexture();\n texture.flipY = false;\n }\n */\n\n const mat = new THREE.MeshStandardMaterial({\n color: col,\n metalness: 1.0,\n roughness: 0.0,\n });\n if (texture != null) mat.map = texture;\n\n return mat;\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = false;\n mat.opacity = 1.0;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.3;\n mat.needsUpdate = true;\n },\n },\n};\n\nconst acrylicSkin: Skin = {\n name: 'Acrylic',\n enableEnvMap: true,\n modelFiles: {\n axis: 'default-axis',\n center: 'default-center',\n edge: 'default-edge',\n corner: 'default-corner',\n sticker: 'default-sticker',\n },\n modelLoading: false,\n models: {},\n cube: {\n material: () => {\n return new THREE.MeshStandardMaterial({\n color: 0xf4feff,\n metalness: 1.0,\n roughness: 0.0,\n });\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.6;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.2;\n mat.needsUpdate = true;\n },\n },\n sticker: {\n material: (x: number, y: number, z: number, color: ARCCUBE.StickerColor) => {\n const ColorList = [\n '#ffffff', // U: White\n '#00c800', // F: Green\n '#e80000', // R: Red\n '#fff000', // D: Yellow\n '#0000c8', // B: Blue\n '#f87000', // L: Orange\n ];\n\n const col = ColorList[color];\n let texture = undefined;\n /*\n if (x === ARCCUBE.SIDE_MIDDLE && y === ARCCUBE.SIDE_MAX && z === ARCCUBE.SIDE_MIDDLE) {\n // load texture\n texture = loadDefaultLogoTexture();\n texture.flipY = false;\n }\n */\n\n const mat = new THREE.MeshStandardMaterial({\n color: col,\n metalness: 1.0,\n roughness: 0.0,\n });\n if (texture != null) mat.map = texture;\n\n return mat;\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.7;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshStandardMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.2;\n mat.needsUpdate = true;\n },\n },\n};\n\nconst crystalSkin: Skin = {\n name: 'Crystal',\n enableEnvMap: true,\n modelFiles: {\n axis: 'default-axis',\n center: 'default-center',\n edge: 'default-edge',\n corner: 'default-corner',\n sticker: 'default-sticker',\n },\n modelLoading: false,\n models: {},\n cube: {\n material: () => {\n return new THREE.MeshPhysicalMaterial({\n flatShading: false,\n color: '#ffffff',\n roughness: 0.01,\n transmission: 1,\n });\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshPhysicalMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.5;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshPhysicalMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.2;\n mat.needsUpdate = true;\n },\n },\n sticker: {\n material: (x: number, y: number, z: number, color: ARCCUBE.StickerColor) => {\n const ColorList = [\n '#ffffff', // U: White\n '#00c800', // F: Green\n '#e80000', // R: Red\n '#fff000', // D: Yellow\n '#0000c8', // B: Blue\n '#f87000', // L: Orange\n ];\n\n const col = ColorList[color];\n let texture = undefined;\n /*\n if (x === ARCCUBE.SIDE_MIDDLE && y === ARCCUBE.SIDE_MAX && z === ARCCUBE.SIDE_MIDDLE) {\n // load texture\n texture = loadDefaultLogoTexture();\n texture.flipY = false;\n }\n */\n\n const mat = new THREE.MeshBasicMaterial({\n color: col,\n refractionRatio: 0.75,\n reflectivity: 1,\n });\n if (texture != null) mat.map = texture;\n\n return mat;\n },\n activate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshBasicMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.5;\n mat.needsUpdate = true;\n },\n deactivate: (mesh: THREE.Mesh) => {\n const mat = <THREE.MeshBasicMaterial>mesh.material;\n mat.transparent = true;\n mat.opacity = 0.2;\n mat.needsUpdate = true;\n },\n },\n};\n\nexport const DefaultSkin = standardSkin;\n\nexport const SkinMap: Record<string, Skin> = {\n [standardSkin.name]: standardSkin,\n [metalicSkin.name]: metalicSkin,\n [goldSkin.name]: goldSkin,\n [acrylicSkin.name]: acrylicSkin,\n [crystalSkin.name]: crystalSkin,\n};\n\n// GLTF data for embedding cubes and sticker\nexport const DefaultModel: Record<string, string> = {\n 'default-axis':\n 'ewoJImFzc2V0Ijp7CgkJImdlbmVyYXRvciI6Iktocm9ub3MgZ2xURiBCbGVuZGVyIEkvTyB2NC4zLjQ3IiwKCQkidmVyc2lvbiI6IjIuMCIKCX0sCgkic2NlbmUiOjAsCgkic2NlbmVzIjpbCgkJewoJCQkibmFtZSI6IlNjZW5lIiwKCQkJIm5vZGVzIjpbCgkJCQkwCgkJCV0KCQl9CgldLAoJIm5vZGVzIjpbCgkJewoJCQkibWVzaCI6MCwKCQkJIm5hbWUiOiJcdTUxODZcdTY3ZjFYIgoJCX0KCV0sCgkibWVzaGVzIjpbCgkJewoJCQkibmFtZSI6Ilx1NTE4Nlx1NjdmMS4wMDIiLAoJCQkicHJpbWl0aXZlcyI6WwoJCQkJewoJCQkJCSJhdHRyaWJ1dGVzIjp7CgkJCQkJCSJQT1NJVElPTiI6MCwKCQkJCQkJIk5PUk1BTCI6MSwKCQkJCQkJIlRFWENPT1JEXzAiOjIKCQkJCQl9LAoJCQkJCSJpbmRpY2VzIjozCgkJCQl9CgkJCV0KCQl9CgldLAoJImFjY2Vzc29ycyI6WwoJCXsKCQkJImJ1ZmZlclZpZXciOjAsCgkJCSJjb21wb25lbnRUeXBlIjo1MTI2LAoJCQkiY291bnQiOjI4ODAsCgkJCSJtYXgiOlsKCQkJCTEuNTU3NjAwMDIxMzYyMzA0NywKCQkJCTEuNTU3NjAwMDIxMzYyMzA0NywKCQkJCTEuNTU3NjAwMDIxMzYyMzA0NwoJCQldLAoJCQkibWluIjpbCgkJCQktMS41NTc2MDAwMjEzNjIzMDQ3LAoJCQkJLTEuNTU3NjAwMDIxMzYyMzA0NywKCQkJCS0xLjU1NzYwMDAyMTM2MjMwNDcKCQkJXSwKCQkJInR5cGUiOiJWRUMzIgoJCX0sCgkJewoJCQkiYnVmZmVyVmlldyI6MSwKCQkJImNvbXBvbmVudFR5cGUiOjUxMjYsCgkJCSJjb3VudCI6Mjg4MCwKCQkJInR5cGUiOiJWRUMzIgoJCX0sCgkJewoJCQkiYnVmZmVyVmlldyI6MiwKCQkJImNvbXBvbmVudFR5cGUiOjUxMjYsCgkJCSJjb3VudCI6Mjg4MCwKCQkJInR5cGUiOiJWRUMyIgoJCX0sCgkJewoJCQkiYnVmZmVyVmlldyI6MywKCQkJImNvbXBvbmVudFR5cGUiOjUxMjMsCgkJCSJjb3VudCI6NDU3MiwKCQkJInR5cGUiOiJTQ0FMQVIiCgkJfQoJXSwKCSJidWZmZXJWaWV3cyI6WwoJCXsKCQkJImJ1ZmZlciI6MCwKCQkJImJ5dGVMZW5ndGgiOjM0NTYwLAoJCQkiYnl0ZU9mZnNldCI6MCwKCQkJInRhcmdldCI6MzQ5NjIKCQl9LAoJCXsKCQkJImJ1ZmZlciI6MCwKCQkJImJ5dGVMZW5ndGgiOjM0NTYwLAoJCQkiYnl0ZU9mZnNldCI6MzQ1NjAsCgkJCSJ0YXJnZXQiOjM0OTYyCgkJfSwKCQl7CgkJCSJidWZmZXIiOjAsCgkJCSJieXRlTGVuZ3RoIjoyMzA0MCwKCQkJImJ5dGVPZmZzZXQiOjY5MTIwLAoJCQkidGFyZ2V0IjozNDk2MgoJCX0sCgkJewoJCQkiYnVmZmVyIjowLAoJCQkiYnl0ZUxlbmd0aCI6OTE0NCwKCQkJImJ5dGVPZmZzZXQiOjkyMTYwLAoJCQkidGFyZ2V0IjozNDk2MwoJCX0KCV0sCgkiYnVmZmVycyI6WwoJCXsKCQkJImJ5dGVMZW5ndGgiOjEwMTMwNCwKCQkJInVyaSI6ImRhdGE6YXBwbGljYXRpb24vb2N0ZXQtc3RyZWFtO2Jhc2U2NCxjRi9Idnd3MmtqTnl0dUcrY0YvSHZ3dzJrak55dHVHK2NGL0h2d3cya2pOeXR1Rys4eVc4djhMNmlUTlRQKysrOHlXOHY4TDZpVE5UUCsrKzh5Vzh2OEw2aVROVFArKys4eVc4djhMNmlUTlRQKysrZDk3RnY2NHFrVFBpZXVpK2Q5N0Z2NjRxa1RQaWV1aStkOTdGdjY0cWtUUGlldWkrZDk3RnY2NHFrVFBpZXVpK3NjTEJ2MXNuampNZGIrMitzY0xCdjFzbmpqTWRiKzIrc2NMQnYxc25qak1kYisyK3NjTEJ2MXNuampNZGIrMitjRi9IUHd3MmtyTnl0dUcrY0YvSFB3dzJrck55dHVHK2NGL0hQd3cya3JOeXR1Rys4eVc4UDhMNmliTlRQKysrOHlXOFA4TDZpYk5UUCsrKzh5VzhQOEw2aWJOVFArKys4eVc4UDhMNmliTlRQKysrZDk3RlA2NHFrYlBpZXVpK2Q5N0ZQNjRxa2JQaWV1aStkOTdGUDY0cWtiUGlldWkrZDk3RlA2NHFrYlBpZXVpK3NjTEJQMXNuanJNZGIrMitzY0xCUDFzbmpyTWRiKzIrc2NMQlAxc25qck1kYisyK3NjTEJQMXNuanJNZGIrMitjRi9Idnk0anNMMHNZTjIrY0YvSHZ5NGpzTDBzWU4yK2NGL0h2eTRqc0wwc1lOMis4eVc4dndLenVyMTVwdXErOHlXOHZ3S3p1cjE1cHVxKzh5Vzh2d0t6dXIxNXB1cSs4eVc4dndLenVyMTVwdXErZDk3RnZ4aHJ0YjFTQStTK2Q5N0Z2eGhydGIxU0ErUytkOTdGdnhocnRiMVNBK1MrZDk3RnZ4aHJ0YjFTQStTK3NjTEJ2OE5JdWIwdjMraStzY0xCdjhOSXViMHYzK2krc2NMQnY4Tkl1YjB2MytpK3NjTEJ2OE5JdWIwdjMraStjRi9IUHo4anNMMHNZTjIrY0YvSFB6OGpzTDBzWU4yK2NGL0hQejhqc0wwc1lOMis4eVc4UHhPenVyMTVwdXErOHlXOFB4T3p1cjE1cHVxKzh5VzhQeE96dXIxNXB1cSs4eVc4UHhPenVyMTVwdXErZDk3RlB5bHJ0YjFTQStTK2Q5N0ZQeWxydGIxU0ErUytkOTdGUHlscnRiMVNBK1MrZDk3RlB5bHJ0YjFTQStTK3NjTEJQOVJJdWIwdjMraStzY0xCUDlSSXViMHYzK2krc2NMQlA5Ukl1YjB2MytpK3NjTEJQOVJJdWIwdjMraStjRi9IdjhEQUxMNEZpTkMrY0YvSHY4REFMTDRGaU5DK2NGL0h2OERBTEw0RmlOQys4eVc4djZrY043NG1DZDIrOHlXOHY2a2NONzRtQ2QyKzh5Vzh2NmtjTjc0bUNkMis4eVc4djZrY043NG1DZDIrZDk3RnY3UHVNYjZWeU5hK2Q5N0Z2N1B1TWI2VnlOYStkOTdGdjdQdU1iNlZ5TmErZDk3RnY3UHVNYjZWeU5hK3NjTEJ2MTY1TmI1SFhOdStzY0xCdjE2NU5iNUhYTnUrc2NMQnYxNjVOYjVIWE51K3NjTEJ2MTY1TmI1SFhOdStjRi9IUDhyQUxMNEZpTkMrY0YvSFA4ckFMTDRGaU5DK2NGL0hQOHJBTEw0RmlOQys4eVc4UDdFY043NG1DZDIrOHlXOFA3RWNONzRtQ2QyKzh5VzhQN0VjTjc0bUNkMis4eVc4UDdFY043NG1DZDIrZDk3RlA3M3VNYjZWeU5hK2Q5N0ZQNzN1TWI2VnlOYStkOTdGUDczdU1iNlZ5TmErZDk3RlA3M3VNYjZWeU5hK3NjTEJQMmk1TmI1SFhOdStzY0xCUDJpNU5iNUhYTnUrc2NMQlAyaTVOYjVIWE51K3NjTEJQMmk1TmI1SFhOdStjRi9IdjJQTWVyNWFyTHUrY0YvSHYyUE1lcjVhckx1K2NGL0h2MlBNZXI1YXJMdSs4eVc4dnkvcmhMNUs3Y2ErOHlXOHZ5L3JoTDVLN2NhKzh5Vzh2eS9yaEw1SzdjYSs4eVc4dnkvcmhMNUs3Y2ErZDk3RnY3QW9nYjdTVE1HK2Q5N0Z2N0FvZ2I3U1RNRytkOTdGdjdBb2diN1NUTUcrZDk3RnY3QW9nYjdTVE1HK3NjTEJ2MG5wZzc1UmE4VytzY0xCdjBucGc3NVJhOFcrc2NMQnYwbnBnNzVSYThXK3NjTEJ2MG5wZzc1UmE4VytjRi9IUDJ2TWVyNWFyTHUrY0YvSFAydk1lcjVhckx1K2NGL0hQMnZNZXI1YXJMdSs4eVc4UHpQcmhMNUs3Y2ErOHlXOFB6UHJoTDVLN2NhKzh5VzhQelByaEw1SzdjYSs4eVc4UHpQcmhMNUs3Y2ErZDk3RlA3UW9nYjdTVE1HK2Q5N0ZQN1FvZ2I3U1RNRytkOTdGUDdRb2diN1NUTUcrZDk3RlA3UW9nYjdTVE1HK3NjTEJQMDNwZzc1UmE4VytzY0xCUDAzcGc3NVJhOFcrc2NMQlAwM3BnNzVSYThXK3NjTEJQMDNwZzc1UmE4VytjRi9IdjF1YW43NWRtcCsrY0YvSHYxdWFuNzVkbXArK2NGL0h2MXVhbjc1ZG1wKys4eVc4djJNc3FiNWxMS20rOHlXOHYyTXNxYjVsTEttKzh5Vzh2Mk1zcWI1bExLbSs4eVc4djJNc3FiNWxMS20rZDk3RnYxOWpwTDVoWTZTK2Q5N0Z2MTlqcEw1aFk2UytkOTdGdjE5anBMNWhZNlMrZDk3RnYxOWpwTDVoWTZTK3NjTEJ2eWJrcDc0bzVLZStzY0xCdnlia3A3NG81S2Urc2NMQnZ5YmtwNzRvNUtlK3NjTEJ2eWJrcDc0bzVLZStjRi9IUDErYW43NWRtcCsrY0YvSFAxK2FuNzVkbXArK2NGL0hQMSthbjc1ZG1wKys4eVc4UDJjc3FiNWxMS20rOHlXOFAyY3NxYjVsTEttKzh5VzhQMmNzcWI1bExLbSs4eVc4UDJjc3FiNWxMS20rZDk3RlAyTmpwTDVoWTZTK2Q5N0ZQMk5qcEw1aFk2UytkOTdGUDJOanBMNWhZNlMrZDk3RlAyTmpwTDVoWTZTK3NjTEJQeXJrcDc0bzVLZStzY0xCUHlya3A3NG81S2Urc2NMQlB5cmtwNzRvNUtlK3NjTEJQeXJrcDc0bzVLZStjRi9IdjFpc3U3NW56SHErY0YvSHYxaXN1NzVuekhxK2NGL0h2MWlzdTc1bnpIcSs4eVc4djBqdHhyNHg2NFMrOHlXOHYwanR4cjR4NjRTKzh5Vzh2MGp0eHI0eDY0Uys4eVc4djBqdHhyNHg2NFMrZDk3RnY5Qk13YjZ5S0lHK2Q5N0Z2OUJNd2I2eUtJRytkOTdGdjlCTXdiNnlLSUcrZDk3RnY5Qk13YjZ5S0lHK3NjTEJ2MDlyeGI1TDZZTytzY0xCdjA5cnhiNUw2WU8rc2NMQnYwOXJ4YjVMNllPK3NjTEJ2MDlyeGI1TDZZTytjRi9IUDF5c3U3NW56SHErY0YvSFAxeXN1NzVuekhxK2NGL0hQMXlzdTc1bnpIcSs4eVc4UDB6dHhyNHg2NFMrOHlXOFAwenR4cjR4NjRTKzh5VzhQMHp0eHI0eDY0Uys4eVc4UDB6dHhyNHg2NFMrZDk3RlA5Uk13YjZ5S0lHK2Q5N0ZQOVJNd2I2eUtJRytkOTdGUDlSTXdiNnlLSUcrZDk3RlA5Uk13YjZ5S0lHK3NjTEJQMU5yeGI1TDZZTytzY0xCUDFOcnhiNUw2WU8rc2NMQlAxTnJ4YjVMNllPK3NjTEJQMU5yeGI1TDZZTytjRi9IdndPSTBMN0Z3Q3krY0YvSHZ3T0kwTDdGd0N5K2NGL0h2d09JMEw3RndDeSs4eVc4dnlRSjNiNnRIRGUrOHlXOHZ5UUozYjZ0SERlKzh5Vzh2eVFKM2I2dEhEZSs4eVc4dnlRSjNiNnRIRGUrZDk3RnY1UEkxcjY0N2pHK2Q5N0Z2NVBJMXI2NDdqRytkOTdGdjVQSTFyNjQ3akcrZDk3RnY1UEkxcjY0N2pHK3NjTEJ2MFZjMjc1anVUVytzY0xCdjBWYzI3NWp1VFcrc2NMQnYwVmMyNzVqdVRXK3NjTEJ2MFZjMjc1anVUVytjRi9IUHdlSTBMN0Z3Q3krY0YvSFB3ZUkwTDdGd0N5K2NGL0hQd2VJMEw3RndDeSs4eVc4UHlnSjNiNnRIRGUrOHlXOFB5Z0ozYjZ0SERlKzh5VzhQeWdKM2I2dEhEZSs4eVc4UHlnSjNiNnRIRGUrZDk3RlA1ZkkxcjY0N2pHK2Q5N0ZQNWZJMXI2NDdqRytkOTdGUDVmSTFyNjQ3akcrZDk3RlA1ZkkxcjY0N2pHK3NjTEJQMGxjMjc1anVUVytzY0xCUDBsYzI3NWp1VFcrc2NMQlAwbGMyNzVqdVRXK3NjTEJQMGxjMjc1anVUVytjRi9IdnlwZzNiNDJJN0M5Y0YvSHZ5cGczYjQySTdDOWNGL0h2eXBnM2I0Mkk3Qzk4eVc4djNlbTZyNEtzN3E5OHlXOHYzZW02cjRLczdxOTh5Vzh2M2VtNnI0S3M3cTk4eVc4djNlbTZyNEtzN3E5ZDk3RnYxQUQ1TDRnYTdXOWQ5N0Z2MUFENUw0Z2E3VzlkOTdGdjFBRDVMNGdhN1c5ZDk3RnYxQUQ1TDRnYTdXOXNjTEJ2eTNmNkw3TVNMbTlzY0xCdnkzZjZMN01TTG05c2NMQnZ5M2Y2TDdNU0xtOXNjTEJ2eTNmNkw3TVNMbTljRi9IUHk1ZzNiNDJJN0M5Y0YvSFB5NWczYjQySTdDOWNGL0hQeTVnM2I0Mkk3Qzk4eVc4UDN1bTZyNEtzN3E5OHlXOFAzdW02cjRLczdxOTh5VzhQM3VtNnI0S3M3cTk4eVc4UDN1bTZyNEtzN3E5ZDk3RlAxUUQ1TDRnYTdXOWQ5N0ZQMVFENUw0Z2E3VzlkOTdGUDFRRDVMNGdhN1c5ZDk3RlAxUUQ1TDRnYTdXOXNjTEJQekhmNkw3TVNMbTlzY0xCUHpIZjZMN01TTG05c2NMQlB6SGY2TDdNU0xtOXNjTEJQekhmNkw3TVNMbTljRi9IdjNDMjRiNEFBQUNBY0YvSHYzQzI0YjRBQUFDQWNGL0h2M0MyNGI0QUFBQ0E4eVc4djFFLzc3NEFBQUNBOHlXOHYxRS83NzRBQUFDQTh5Vzh2MUUvNzc0QUFBQ0E4eVc4djFFLzc3NEFBQUNBZDk3RnYrQjY2TDVUUCsrdGQ5N0Z2K0I2Nkw1VFArK3RkOTdGditCNjZMNVRQKyt0ZDk3RnYrQjY2TDVUUCsrdHNjTEJ2eHR2N2I1VFArK3RzY0xCdnh0djdiNVRQKyt0c2NMQnZ4dHY3YjVUUCsrdHNjTEJ2eHR2N2I1VFArK3RjRi9IUDNTMjRiNEFBQUNBY0YvSFAzUzI0YjRBQUFDQWNGL0hQM1MyNGI0QUFBQ0E4eVc4UDFVLzc3NEFBQUNBOHlXOFAxVS83NzRBQUFDQTh5VzhQMVUvNzc0QUFBQ0E4eVc4UDFVLzc3NEFBQUNBZDk3RlArUjY2TDVUUCs4dGQ5N0ZQK1I2Nkw1VFArOHRkOTdGUCtSNjZMNVRQKzh0ZDk3RlArUjY2TDVUUCs4dHNjTEJQeDl2N2I1VFArOHRzY0xCUHg5djdiNVRQKzh0c2NMQlB4OXY3YjVUUCs4dHNjTEJQeDl2N2I1VFArOHRjRi9IdnlwZzNiNDJJN0E5Y0YvSHZ5cGczYjQySTdBOWNGL0h2eXBnM2I0Mkk3QTk4eVc4djNlbTZyNEtzN285OHlXOHYzZW02cjRLczdvOTh5Vzh2M2VtNnI0S3M3bzk4eVc4djNlbTZyNEtzN285ZDk3RnYxQUQ1TDRnYTdVOWQ5N0Z2MUFENUw0Z2E3VTlkOTdGdjFBRDVMNGdhN1U5ZDk3RnYxQUQ1TDRnYTdVOXNjTEJ2eTNmNkw3TVNMazlzY0xCdnkzZjZMN01TTGs5c2NMQnZ5M2Y2TDdNU0xrOXNjTEJ2eTNmNkw3TVNMazljRi9IUHk1ZzNiNDJJN0E5Y0YvSFB5NWczYjQySTdBOWNGL0hQeTVnM2I0Mkk3QTk4eVc4UDN1bTZyNEtzN285OHlXOFAzdW02cjRLczdvOTh5VzhQM3VtNnI0S3M3bzk4eVc4UDN1bTZyNEtzN285ZDk3RlAxUUQ1TDRnYTdVOWQ5N0ZQMVFENUw0Z2E3VTlkOTdGUDFRRDVMNGdhN1U5ZDk3RlAxUUQ1TDRnYTdVOXNjTEJQekhmNkw3TVNMazlzY0xCUHpIZjZMN01TTGs5c2NMQlB6SGY2TDdNU0xrOXNjTEJQekhmNkw3TVNMazljRi9IdndPSTBMN0Z3Q3crY0YvSHZ3T0kwTDdGd0N3K2NGL0h2d09JMEw3RndDdys4eVc4dnlRSjNiNnRIRGMrOHlXOHZ5UUozYjZ0SERjKzh5Vzh2eVFKM2I2dEhEYys4eVc4dnlRSjNiNnRIRGMrZDk3RnY1UEkxcjY0N2pFK2Q5N0Z2NVBJMXI2NDdqRStkOTdGdjVQSTFyNjQ3akUrZDk3RnY1UEkxcjY0N2pFK3NjTEJ2MFZjMjc1anVUVStzY0xCdjBWYzI3NWp1VFUrc2NMQnYwVmMyNzVqdVRVK3NjTEJ2MFZjMjc1anVUVStjRi9IUHdlSTBMN0Z3Q3crY0YvSFB3ZUkwTDdGd0N3K2NGL0hQd2VJMEw3RndDdys4eVc4UHlnSjNiNnRIRGMrOHlXOFB5Z0ozYjZ0SERjKzh5VzhQeWdKM2I2dEhEYys4eVc4UHlnSjNiNnRIRGMrZDk3RlA1ZkkxcjY0N2pFK2Q5N0ZQNWZJMXI2NDdqRStkOTdGUDVmSTFyNjQ3akUrZDk3RlA1ZkkxcjY0N2pFK3NjTEJQMGxjMjc1anVUVStzY0xCUDBsYzI3NWp1VFUrc2NMQlAwbGMyNzVqdVRVK3NjTEJQMGxjMjc1anVUVStjRi9IdjFpc3U3NW56SG8rY0YvSHYxaXN1NzVuekhvK2NGL0h2MWlzdTc1bnpIbys4eVc4djBqdHhyNHg2NFErOHlXOHYwanR4cjR4NjRRKzh5Vzh2MGp0eHI0eDY0USs4eVc4djBqdHhyNHg2NFErZDk3RnY5Qk13YjZ5S0lFK2Q5N0Z2OUJNd2I2eUtJRStkOTdGdjlCTXdiNnlLSUUrZDk3RnY5Qk13YjZ5S0lFK3NjTEJ2MDlyeGI1TDZZTStzY0xCdjA5cnhiNUw2WU0rc2NMQnYwOXJ4YjVMNllNK3NjTEJ2MDlyeGI1TDZZTStjRi9IUDF5c3U3NW56SG8rY0YvSFAxeXN1NzVuekhvK2NGL0hQMXlzdTc1bnpIbys4eVc4UDB6dHhyNHg2NFErOHlXOFAwenR4cjR4NjRRKzh5VzhQMHp0eHI0eDY0USs4eVc4UDB6dHhyNHg2NFErZDk3RlA5Uk13YjZ5S0lFK2Q5N0ZQOVJNd2I2eUtJRStkOTdGUDlSTXdiNnlLSUUrZDk3RlA5Uk13YjZ5S0lFK3NjTEJQMU5yeGI1TDZZTStzY0xCUDFOcnhiNUw2WU0rc2NMQlAxTnJ4YjVMNllNK3NjTEJQMU5yeGI1TDZZTStjRi9IdjF1YW43NWRtcDgrY0YvSHYxdWFuNzVkbXA4K2NGL0h2MXVhbjc1ZG1wOCs4eVc4djJNc3FiNWxMS2srOHlXOHYyTXNxYjVsTEtrKzh5Vzh2Mk1zcWI1bExLays4eVc4djJNc3FiNWxMS2srZDk3RnYxOWpwTDVoWTZRK2Q5N0Z2MTlqcEw1aFk2UStkOTdGdjE5anBMNWhZNlErZDk3RnYxOWpwTDVoWTZRK3NjTEJ2eWJrcDc0bzVLYytzY0xCdnlia3A3NG81S2Mrc2NMQnZ5YmtwNzRvNUtjK3NjTEJ2eWJrcDc0bzVLYytjRi9IUDErYW43NWRtcDgrY0YvSFAxK2FuNzVkbXA4K2NGL0hQMSthbjc1ZG1wOCs4eVc4UDJjc3FiNWxMS2srOHlXOFAyY3NxYjVsTEtrKzh5VzhQMmNzcWI1bExLays4eVc4UDJjc3FiNWxMS2srZDk3RlAyTmpwTDVoWTZRK2Q5N0ZQMk5qcEw1aFk2UStkOTdGUDJOanBMNWhZNlErZDk3RlAyTmpwTDVoWTZRK3NjTEJQeXJrcDc0bzVLYytzY0xCUHlya3A3NG81S2Mrc2NMQlB5cmtwNzRvNUtjK3NjTEJQeXJrcDc0bzVLYytjRi9IdjJQTWVyNWFyTHMrY0YvSHYyUE1lcjVhckxzK2NGL0h2MlBNZXI1YXJMcys4eVc4dnkvcmhMNUs3Y1krOHlXOHZ5L3JoTDVLN2NZKzh5Vzh2eS9yaEw1SzdjWSs4eVc4dnkvcmhMNUs3Y1krZDk3RnY3QW9nYjdTVE1FK2Q5N0Z2N0FvZ2I3U1RNRStkOTdGdjdBb2diN1NUTUUrZDk3RnY3QW9nYjdTVE1FK3NjTEJ2MG5wZzc1UmE4VStzY0xCdjBucGc3NVJhOFUrc2NMQnYwbnBnNzVSYThVK3NjTEJ2MG5wZzc1UmE4VStjRi9IUDJ2TWVyNWFyTHMrY0YvSFAydk1lcjVhckxzK2NGL0hQMnZNZXI1YXJMcys4eVc4UHpQcmhMNUs3Y1krOHlXOFB6UHJoTDVLN2NZKzh5VzhQelByaEw1SzdjWSs4eVc4UHpQcmhMNUs3Y1krZDk3RlA3UW9nYjdTVE1FK2Q5N0ZQN1FvZ2I3U1RNRStkOTdGUDdRb2diN1NUTUUrZDk3RlA3UW9nYjdTVE1FK3NjTEJQMDNwZzc1UmE4VStzY0xCUDAzcGc3NVJhOFUrc2NMQlAwM3BnNzVSYThVK3NjTEJQMDNwZzc1UmE4VStjRi9IdjhEQUxMNEZpTkErY0YvSHY4REFMTDRGaU5BK2NGL0h2OERBTEw0RmlOQSs4eVc4djZrY043NG1DZDArOHlXOHY2a2NONzRtQ2QwKzh5Vzh2NmtjTjc0bUNkMCs4eVc4djZrY043NG1DZDArZDk3RnY3UHVNYjZWeU5ZK2Q5N0Z2N1B1TWI2VnlOWStkOTdGdjdQdU1iNlZ5TlkrZDk3RnY3UHVNYjZWeU5ZK3NjTEJ2MTY1TmI1SFhOcytzY0xCdjE2NU5iNUhYTnMrc2NMQnYxNjVOYjVIWE5zK3NjTEJ2MTY1TmI1SFhOcytjRi9IUDhyQUxMNEZpTkErY0YvSFA4ckFMTDRGaU5BK2NGL0hQOHJBTEw0RmlOQSs4eVc4UDdFY043NG1DZDArOHlXOFA3RWNONzRtQ2QwKzh5VzhQN0VjTjc0bUNkMCs4eVc4UDdFY043NG1DZDArZDk3RlA3M3VNYjZWeU5ZK2Q5N0ZQNzN1TWI2VnlOWStkOTdGUDczdU1iNlZ5TlkrZDk3RlA3M3VNYjZWeU5ZK3NjTEJQMmk1TmI1SFhOcytzY0xCUDJpNU5iNUhYTnMrc2NMQlAyaTVOYjVIWE5zK3NjTEJQMmk1TmI1SFhOcytjRi9Idnk0anNMMHNZTjArY0YvSHZ5NGpzTDBzWU4wK2NGL0h2eTRqc0wwc1lOMCs4eVc4dndLenVyMTV