Skip to content

Commit 48f3c24

Browse files
committed
feat: 四次元超立方体
1 parent cd244f8 commit 48f3c24

6 files changed

Lines changed: 400 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../packages/skill/hikkaku

examples/tesseract/AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
* `src/main.ts`: The main entry point of the application.
2+
3+
* use `bun typecheck` to run type checking.

examples/tesseract/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "tesseract",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "NODE_ENV=development vite",
7+
"typecheck": "tsgo",
8+
"build": "vite build"
9+
},
10+
"dependencies": {
11+
"hikkaku": "workspace:*"
12+
},
13+
"devDependencies": {
14+
"@typescript/native-preview": "^7.0.0-dev.20260127.1",
15+
"vite": "8.0.0-beta.8"
16+
}
17+
}

examples/tesseract/src/main.ts

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
import { Project, type VariableReference } from 'hikkaku'
2+
import {
3+
add,
4+
callProcedure,
5+
changeVariableBy,
6+
defineProcedure,
7+
divide,
8+
equals,
9+
eraseAll,
10+
forEach,
11+
forever,
12+
getItemOfList,
13+
getKeyPressed,
14+
getMouseDown,
15+
getMouseX,
16+
getMouseY,
17+
getVariable,
18+
gotoXY,
19+
gt,
20+
hide,
21+
ifElse,
22+
ifThen,
23+
lengthOfList,
24+
lt,
25+
mathop,
26+
multiply,
27+
or,
28+
penDown,
29+
penUp,
30+
procedureLabel,
31+
replaceItemOfList,
32+
setDragMode,
33+
setPenColorTo,
34+
setPenSizeTo,
35+
setVariableTo,
36+
subtract,
37+
whenFlagClicked,
38+
} from 'hikkaku/blocks'
39+
40+
const TESSERACT_VERTICES = Array.from({ length: 16 }, (_, vertex) => {
41+
const sign = (bit: number) => {
42+
return vertex & (1 << bit) ? 1 : -1
43+
}
44+
45+
return [sign(0), sign(1), sign(2), sign(3)] as const
46+
})
47+
48+
const TESSERACT_EDGES: Array<[number, number]> = []
49+
for (let vertex = 0; vertex < 16; vertex += 1) {
50+
for (let bit = 0; bit < 4; bit += 1) {
51+
const neighbor = vertex ^ (1 << bit)
52+
if (vertex < neighbor) {
53+
TESSERACT_EDGES.push([vertex + 1, neighbor + 1])
54+
}
55+
}
56+
}
57+
58+
const project = new Project()
59+
const renderer = project.createSprite('renderer')
60+
61+
const modelScale = renderer.createVariable('modelScale', 92)
62+
63+
const wCameraDistance = renderer.createVariable('wCameraDistance', 320)
64+
const wFocalLength = renderer.createVariable('wFocalLength', 180)
65+
const cameraDistance = renderer.createVariable('cameraDistance', 320)
66+
const focalLength = renderer.createVariable('focalLength', 220)
67+
68+
const angleX = renderer.createVariable('angleX', -16)
69+
const angleY = renderer.createVariable('angleY', 24)
70+
const angleW = renderer.createVariable('angleW', 18)
71+
const angleZW = renderer.createVariable('angleZW', -12)
72+
73+
const dragging = renderer.createVariable('dragging', 0)
74+
const lastMouseX = renderer.createVariable('lastMouseX', 0)
75+
const lastMouseY = renderer.createVariable('lastMouseY', 0)
76+
77+
const vertexIndex = renderer.createVariable('vertexIndex', 1)
78+
const edgeIndex = renderer.createVariable('edgeIndex', 1)
79+
const edgeFrom = renderer.createVariable('edgeFrom', 1)
80+
const edgeTo = renderer.createVariable('edgeTo', 1)
81+
82+
const workX = renderer.createVariable('workX', 0)
83+
const workY = renderer.createVariable('workY', 0)
84+
const workZ = renderer.createVariable('workZ', 0)
85+
const workW = renderer.createVariable('workW', 0)
86+
87+
const rotatedX = renderer.createVariable('rotatedX', 0)
88+
const rotatedY = renderer.createVariable('rotatedY', 0)
89+
const rotatedZ = renderer.createVariable('rotatedZ', 0)
90+
const rotatedW = renderer.createVariable('rotatedW', 0)
91+
const perspective4 = renderer.createVariable('perspective4', 1)
92+
const perspective3 = renderer.createVariable('perspective3', 1)
93+
94+
const vertexXList = renderer.createList(
95+
'vertexX',
96+
TESSERACT_VERTICES.map(([x]) => x),
97+
)
98+
const vertexYList = renderer.createList(
99+
'vertexY',
100+
TESSERACT_VERTICES.map(([, y]) => y),
101+
)
102+
const vertexZList = renderer.createList(
103+
'vertexZ',
104+
TESSERACT_VERTICES.map(([, , z]) => z),
105+
)
106+
const vertexWList = renderer.createList(
107+
'vertexW',
108+
TESSERACT_VERTICES.map(([, , , w]) => w),
109+
)
110+
111+
const projectedXList = renderer.createList(
112+
'projectedX',
113+
TESSERACT_VERTICES.map(() => 0),
114+
)
115+
const projectedYList = renderer.createList(
116+
'projectedY',
117+
TESSERACT_VERTICES.map(() => 0),
118+
)
119+
120+
const edgeFromList = renderer.createList(
121+
'edgeFrom',
122+
TESSERACT_EDGES.map(([from]) => from),
123+
)
124+
const edgeToList = renderer.createList(
125+
'edgeTo',
126+
TESSERACT_EDGES.map(([, to]) => to),
127+
)
128+
129+
const readVar = (variable: VariableReference) => getVariable(variable)
130+
const stepProcCode = '1step'
131+
132+
renderer.run(() => {
133+
const stepProcedure = defineProcedure(
134+
[procedureLabel(stepProcCode)],
135+
() => {
136+
ifElse(
137+
getMouseDown(),
138+
() => {
139+
ifElse(
140+
equals(readVar(dragging), 0),
141+
() => {
142+
setVariableTo(dragging, 1)
143+
setVariableTo(lastMouseX, getMouseX())
144+
setVariableTo(lastMouseY, getMouseY())
145+
},
146+
() => {
147+
changeVariableBy(
148+
angleY,
149+
multiply(subtract(getMouseX(), readVar(lastMouseX)), 0.8),
150+
)
151+
changeVariableBy(
152+
angleX,
153+
multiply(subtract(readVar(lastMouseY), getMouseY()), 0.8),
154+
)
155+
setVariableTo(lastMouseX, getMouseX())
156+
setVariableTo(lastMouseY, getMouseY())
157+
},
158+
)
159+
},
160+
() => {
161+
setVariableTo(dragging, 0)
162+
changeVariableBy(angleY, 0.2)
163+
},
164+
)
165+
166+
ifThen(or(getKeyPressed('left arrow'), getKeyPressed('a')), () => {
167+
changeVariableBy(angleW, -1.8)
168+
})
169+
ifThen(or(getKeyPressed('right arrow'), getKeyPressed('d')), () => {
170+
changeVariableBy(angleW, 1.8)
171+
})
172+
ifThen(or(getKeyPressed('q'), getKeyPressed('z')), () => {
173+
changeVariableBy(angleZW, -1.6)
174+
})
175+
ifThen(or(getKeyPressed('e'), getKeyPressed('c')), () => {
176+
changeVariableBy(angleZW, 1.6)
177+
})
178+
ifThen(or(getKeyPressed('up arrow'), getKeyPressed('w')), () => {
179+
changeVariableBy(angleX, 0.9)
180+
})
181+
ifThen(or(getKeyPressed('down arrow'), getKeyPressed('s')), () => {
182+
changeVariableBy(angleX, -0.9)
183+
})
184+
185+
changeVariableBy(angleW, 0.45)
186+
changeVariableBy(angleZW, 0.25)
187+
188+
ifThen(gt(readVar(angleX), 89), () => {
189+
setVariableTo(angleX, 89)
190+
})
191+
ifThen(lt(readVar(angleX), -89), () => {
192+
setVariableTo(angleX, -89)
193+
})
194+
195+
eraseAll()
196+
197+
forEach(vertexIndex, lengthOfList(vertexXList), () => {
198+
setVariableTo(
199+
workX,
200+
multiply(
201+
getItemOfList(vertexXList, readVar(vertexIndex)),
202+
readVar(modelScale),
203+
),
204+
)
205+
setVariableTo(
206+
workY,
207+
multiply(
208+
getItemOfList(vertexYList, readVar(vertexIndex)),
209+
readVar(modelScale),
210+
),
211+
)
212+
setVariableTo(
213+
workZ,
214+
multiply(
215+
getItemOfList(vertexZList, readVar(vertexIndex)),
216+
readVar(modelScale),
217+
),
218+
)
219+
setVariableTo(
220+
workW,
221+
multiply(
222+
getItemOfList(vertexWList, readVar(vertexIndex)),
223+
readVar(modelScale),
224+
),
225+
)
226+
227+
setVariableTo(
228+
rotatedX,
229+
subtract(
230+
multiply(readVar(workX), mathop('cos', readVar(angleW))),
231+
multiply(readVar(workW), mathop('sin', readVar(angleW))),
232+
),
233+
)
234+
setVariableTo(
235+
rotatedW,
236+
add(
237+
multiply(readVar(workX), mathop('sin', readVar(angleW))),
238+
multiply(readVar(workW), mathop('cos', readVar(angleW))),
239+
),
240+
)
241+
242+
setVariableTo(
243+
rotatedZ,
244+
subtract(
245+
multiply(readVar(workZ), mathop('cos', readVar(angleZW))),
246+
multiply(readVar(rotatedW), mathop('sin', readVar(angleZW))),
247+
),
248+
)
249+
setVariableTo(
250+
rotatedW,
251+
add(
252+
multiply(readVar(workZ), mathop('sin', readVar(angleZW))),
253+
multiply(readVar(rotatedW), mathop('cos', readVar(angleZW))),
254+
),
255+
)
256+
257+
setVariableTo(
258+
perspective4,
259+
divide(
260+
readVar(wFocalLength),
261+
subtract(readVar(wCameraDistance), readVar(rotatedW)),
262+
),
263+
)
264+
265+
setVariableTo(
266+
rotatedX,
267+
multiply(readVar(rotatedX), readVar(perspective4)),
268+
)
269+
setVariableTo(rotatedY, multiply(readVar(workY), readVar(perspective4)))
270+
setVariableTo(
271+
rotatedZ,
272+
multiply(readVar(rotatedZ), readVar(perspective4)),
273+
)
274+
275+
setVariableTo(
276+
workX,
277+
add(
278+
multiply(readVar(rotatedX), mathop('cos', readVar(angleY))),
279+
multiply(readVar(rotatedZ), mathop('sin', readVar(angleY))),
280+
),
281+
)
282+
setVariableTo(
283+
workW,
284+
subtract(
285+
multiply(readVar(rotatedZ), mathop('cos', readVar(angleY))),
286+
multiply(readVar(rotatedX), mathop('sin', readVar(angleY))),
287+
),
288+
)
289+
290+
setVariableTo(
291+
workY,
292+
subtract(
293+
multiply(readVar(rotatedY), mathop('cos', readVar(angleX))),
294+
multiply(readVar(workW), mathop('sin', readVar(angleX))),
295+
),
296+
)
297+
setVariableTo(
298+
rotatedZ,
299+
add(
300+
multiply(readVar(rotatedY), mathop('sin', readVar(angleX))),
301+
multiply(readVar(workW), mathop('cos', readVar(angleX))),
302+
),
303+
)
304+
305+
setVariableTo(
306+
perspective3,
307+
divide(
308+
readVar(focalLength),
309+
add(readVar(cameraDistance), readVar(rotatedZ)),
310+
),
311+
)
312+
313+
replaceItemOfList(
314+
projectedXList,
315+
readVar(vertexIndex),
316+
multiply(readVar(workX), readVar(perspective3)),
317+
)
318+
replaceItemOfList(
319+
projectedYList,
320+
readVar(vertexIndex),
321+
multiply(readVar(workY), readVar(perspective3)),
322+
)
323+
})
324+
325+
forEach(edgeIndex, lengthOfList(edgeFromList), () => {
326+
setVariableTo(edgeFrom, getItemOfList(edgeFromList, readVar(edgeIndex)))
327+
setVariableTo(edgeTo, getItemOfList(edgeToList, readVar(edgeIndex)))
328+
329+
penUp()
330+
gotoXY(
331+
getItemOfList(projectedXList, readVar(edgeFrom)),
332+
getItemOfList(projectedYList, readVar(edgeFrom)),
333+
)
334+
penDown()
335+
gotoXY(
336+
getItemOfList(projectedXList, readVar(edgeTo)),
337+
getItemOfList(projectedYList, readVar(edgeTo)),
338+
)
339+
penUp()
340+
})
341+
},
342+
true,
343+
)
344+
345+
whenFlagClicked(() => {
346+
hide()
347+
setDragMode('not draggable')
348+
349+
penUp()
350+
setPenSizeTo(1.2)
351+
setPenColorTo('#38bdf8')
352+
353+
setVariableTo(lastMouseX, getMouseX())
354+
setVariableTo(lastMouseY, getMouseY())
355+
setVariableTo(dragging, 0)
356+
357+
forever(() => {
358+
callProcedure(stepProcedure, {}, true)
359+
})
360+
})
361+
})
362+
363+
export default project

examples/tesseract/tsconfig.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"types": ["vite/client"]
5+
}
6+
}

0 commit comments

Comments
 (0)