@@ -11,6 +11,7 @@ import {
1111 INTERVAL ,
1212 SIZE ,
1313 SimulationResult ,
14+ TEXT_TOP ,
1415 WIDTH
1516} from '../utils'
1617import {
@@ -66,15 +67,13 @@ export class Contribution implements ContributionJson {
6667async function createWord ( ) {
6768 await init ( )
6869 // 重力
69- const world = new World ( { x : 0.0 , y : - 16 } )
70- world . lengthUnit = 10
70+ const world = new World ( { x : 0.0 , y : - 30 } )
71+ world . lengthUnit = 16
7172
7273 // 地面
7374 world . createCollider (
7475 ColliderDesc . cuboid ( WIDTH , 1 ) ,
75- world . createRigidBody (
76- RigidBodyDesc . fixed ( ) . setCcdEnabled ( true ) . setTranslation ( 0 , - 1 )
77- )
76+ world . createRigidBody ( RigidBodyDesc . fixed ( ) . setTranslation ( 0 , - 1 ) )
7877 )
7978 // 墙壁
8079 world . createCollider (
@@ -93,36 +92,61 @@ async function createWord() {
9392const VELOCITY_THRESHOLD = 1e-3
9493
9594export async function startSimulation (
96- username : string
95+ username : string ,
96+ text : string = username
9797) : Promise < SimulationResult > {
9898 const world = await createWord ( )
9999 const contributionsSet = new Set < Contribution > ( )
100100
101101 // 创建用户名文字静止刚体(居中对齐),同时收集三角网格数据
102- const textPaths = getTextPaths ( username )
103- const totalWidth = textPaths . reduce ( ( sum , { rect } ) => sum + rect . width , 0 )
104- const offsetX = ( WIDTH - totalWidth ) / 2
105- const centerY = HEIGHT / 2
102+ const textPaths = getTextPaths ( text )
103+
104+ // 计算所有字形的整体包围盒(翻转 Y 轴后)
105+ let minX = Infinity ,
106+ maxX = - Infinity ,
107+ minY = Infinity ,
108+ maxY = - Infinity
109+ for ( const { rect } of textPaths ) {
110+ if ( rect . x < minX ) minX = rect . x
111+ if ( rect . x + rect . width > maxX ) maxX = rect . x + rect . width
112+ // 翻转 Y:opentype 字形在基线下方(y 为负),碰撞体需要正向
113+ const top = - rect . y
114+ const bottom = - ( rect . y + rect . height )
115+ if ( bottom < minY ) minY = bottom
116+ if ( top > maxY ) maxY = top
117+ }
118+ const bboxCenterX = ( minX + maxX ) / 2
119+ const offsetX = WIDTH / 2 - bboxCenterX
120+ // 文字顶部距离世界顶部的距离
121+ const centerY = HEIGHT - TEXT_TOP - maxY
106122 const textTriangles : number [ ] [ ] = [ ]
107123
108124 for ( const { paths } of textPaths ) {
109125 const { vertices, indices } = paths
110- const cx = offsetX
126+ if ( ! vertices . length && ! indices . length ) continue
127+ // 翻转 Y 轴:opentype 字形在基线下方(y 为负),物理世界需要正向
128+ const flippedVertices = new Float32Array ( vertices . length )
129+ for ( let i = 0 ; i < vertices . length ; i += 2 ) {
130+ flippedVertices [ i ] = vertices [ i ]
131+ flippedVertices [ i + 1 ] = - vertices [ i + 1 ]
132+ }
111133 world . createCollider (
112- ColliderDesc . trimesh ( vertices , indices ) ,
113- world . createRigidBody ( RigidBodyDesc . fixed ( ) . setTranslation ( cx , centerY ) )
134+ ColliderDesc . trimesh ( flippedVertices , indices ) ,
135+ world . createRigidBody (
136+ RigidBodyDesc . fixed ( ) . setTranslation ( offsetX , centerY )
137+ )
114138 )
115139 for ( let i = 0 ; i < indices . length ; i += 3 ) {
116140 const i0 = indices [ i ] * 2 ,
117141 i1 = indices [ i + 1 ] * 2 ,
118142 i2 = indices [ i + 2 ] * 2
119143 textTriangles . push ( [
120- cx + vertices [ i0 ] ,
121- centerY + vertices [ i0 + 1 ] ,
122- cx + vertices [ i1 ] ,
123- centerY + vertices [ i1 + 1 ] ,
124- cx + vertices [ i2 ] ,
125- centerY + vertices [ i2 + 1 ]
144+ offsetX + flippedVertices [ i0 ] ,
145+ centerY + flippedVertices [ i0 + 1 ] ,
146+ offsetX + flippedVertices [ i1 ] ,
147+ centerY + flippedVertices [ i1 + 1 ] ,
148+ offsetX + flippedVertices [ i2 ] ,
149+ centerY + flippedVertices [ i2 + 1 ]
126150 ] )
127151 }
128152 }
@@ -134,7 +158,7 @@ export async function startSimulation(
134158 ) {
135159 // 动态刚体
136160 const boxCollider = world . createCollider (
137- ColliderDesc . cuboid ( SIZE / 2 , SIZE / 2 ) ,
161+ ColliderDesc . cuboid ( SIZE / 2 , SIZE / 2 ) . setFriction ( 0.2 ) ,
138162 world . createRigidBody (
139163 RigidBodyDesc . dynamic ( ) . setCcdEnabled ( true ) . setTranslation ( x , y )
140164 )
@@ -148,7 +172,7 @@ export async function startSimulation(
148172 const rowLen = githubContributions . contributions . length
149173 const rowWidth = SIZE * rowLen + INTERVAL * ( rowLen - 1 )
150174 const left = ( WIDTH - rowWidth ) / 2
151- const top = HEIGHT * 0.9
175+ const top = HEIGHT * 1
152176
153177 for ( let rowIndex = 0 ; rowIndex < rowLen ; rowIndex ++ ) {
154178 const col = githubContributions . contributions [ rowIndex ]
@@ -211,6 +235,10 @@ export async function startSimulation(
211235 contributions : Array . from ( contributionsSet ) . map ( ( contribution ) =>
212236 contribution . toJSON ( )
213237 ) ,
238+ textPaths : textPaths . map ( ( { pathData, rect } ) => ( {
239+ pathData : pathData ,
240+ rect : rect
241+ } ) ) ,
214242 textTriangles
215243 }
216244}
0 commit comments