@@ -98,6 +98,77 @@ const MATH_TYPE_MAP = {
9898 } ,
9999} ;
100100
101+ // ============================================================================
102+ // Pierced Props Configuration
103+ // ============================================================================
104+
105+ // Math types that support pierced property access (e.g., position.x, rotation.y)
106+ const PIERCEABLE_MATH_TYPES = {
107+ Vector2 : {
108+ components : [ 'x' , 'y' ] ,
109+ componentType : 'number' ,
110+ description : ( prop , comp ) => `Set only the ${ comp } component of ${ prop } ` ,
111+ } ,
112+ Vector3 : {
113+ components : [ 'x' , 'y' , 'z' ] ,
114+ componentType : 'number' ,
115+ description : ( prop , comp ) => `Set only the ${ comp } component of ${ prop } ` ,
116+ } ,
117+ Vector4 : {
118+ components : [ 'x' , 'y' , 'z' , 'w' ] ,
119+ componentType : 'number' ,
120+ description : ( prop , comp ) => `Set only the ${ comp } component of ${ prop } ` ,
121+ } ,
122+ Euler : {
123+ components : [ 'x' , 'y' , 'z' ] ,
124+ componentType : 'number' ,
125+ description : ( prop , comp ) => `Set only the ${ comp } rotation (radians) of ${ prop } ` ,
126+ } ,
127+ Quaternion : {
128+ components : [ 'x' , 'y' , 'z' , 'w' ] ,
129+ componentType : 'number' ,
130+ description : ( prop , comp ) => `Set only the ${ comp } component of ${ prop } ` ,
131+ } ,
132+ Color : {
133+ components : [ 'r' , 'g' , 'b' ] ,
134+ componentType : 'number' ,
135+ description : ( prop , comp ) => `Set only the ${ comp } channel (0-1) of ${ prop } ` ,
136+ } ,
137+ } ;
138+
139+ // Common Object3D properties that support piercing
140+ const OBJECT3D_PIERCEABLE_PROPS = [ 'position' , 'rotation' , 'scale' , 'up' ] ;
141+
142+ // Shadow pierced props for lights that cast shadows
143+ // These are the most commonly used shadow configuration paths
144+ const SHADOW_PIERCED_PROPS = [
145+ // Direct shadow properties
146+ { path : 'shadow.intensity' , type : 'number' , description : 'Shadow intensity (0-1)' } ,
147+ { path : 'shadow.bias' , type : 'number' , description : 'Shadow map bias to reduce artifacts' } ,
148+ { path : 'shadow.normalBias' , type : 'number' , description : 'Shadow normal bias for large scenes' } ,
149+ { path : 'shadow.radius' , type : 'number' , description : 'Shadow blur radius' } ,
150+ { path : 'shadow.blurSamples' , type : 'number' , description : 'Number of samples for VSM shadow blur' } ,
151+ // Shadow map size (Vector2)
152+ { path : 'shadow.mapSize.width' , type : 'number' , description : 'Shadow map width in pixels' } ,
153+ { path : 'shadow.mapSize.height' , type : 'number' , description : 'Shadow map height in pixels' } ,
154+ { path : 'shadow.mapSize.x' , type : 'number' , description : 'Shadow map width (alias for width)' } ,
155+ { path : 'shadow.mapSize.y' , type : 'number' , description : 'Shadow map height (alias for height)' } ,
156+ // Shadow camera properties (common to all shadow-casting lights)
157+ { path : 'shadow.camera.near' , type : 'number' , description : 'Shadow camera near plane' } ,
158+ { path : 'shadow.camera.far' , type : 'number' , description : 'Shadow camera far plane' } ,
159+ // Orthographic camera props (DirectionalLight)
160+ { path : 'shadow.camera.left' , type : 'number' , description : 'Shadow camera left plane (orthographic)' } ,
161+ { path : 'shadow.camera.right' , type : 'number' , description : 'Shadow camera right plane (orthographic)' } ,
162+ { path : 'shadow.camera.top' , type : 'number' , description : 'Shadow camera top plane (orthographic)' } ,
163+ { path : 'shadow.camera.bottom' , type : 'number' , description : 'Shadow camera bottom plane (orthographic)' } ,
164+ // Perspective camera props (SpotLight, PointLight)
165+ { path : 'shadow.camera.fov' , type : 'number' , description : 'Shadow camera field of view (perspective)' } ,
166+ { path : 'shadow.camera.zoom' , type : 'number' , description : 'Shadow camera zoom' } ,
167+ ] ;
168+
169+ // Lights that support shadow casting
170+ const SHADOW_CASTING_LIGHTS = new Set ( [ 'DirectionalLight' , 'SpotLight' , 'PointLight' ] ) ;
171+
101172// THREE.js class categories for documentation URLs
102173const THREE_CATEGORIES = {
103174 // Objects
@@ -530,6 +601,76 @@ function getDocUrl(className) {
530601 return `${ CONFIG . threeDocsBaseUrl } /${ category } /${ className } ` ;
531602}
532603
604+ // ============================================================================
605+ // Pierced Props Generation
606+ // ============================================================================
607+
608+ /**
609+ * Get the math type name from a raw type string
610+ */
611+ function getMathTypeName ( rawType ) {
612+ for ( const mathTypeName of Object . keys ( PIERCEABLE_MATH_TYPES ) ) {
613+ if ( rawType && rawType . includes ( mathTypeName ) ) {
614+ return mathTypeName ;
615+ }
616+ }
617+ return null ;
618+ }
619+
620+ /**
621+ * Generate pierced property definitions for a math-type property
622+ *
623+ * @example
624+ * For property "position" of type Vector3, generates:
625+ * - position.x, position.y, position.z
626+ */
627+ function generateMathTypePiercedProps ( propName , mathTypeName ) {
628+ const mathTypeInfo = PIERCEABLE_MATH_TYPES [ mathTypeName ] ;
629+ if ( ! mathTypeInfo ) return [ ] ;
630+
631+ return mathTypeInfo . components . map ( ( component ) => ( {
632+ name : `${ propName } .${ component } ` ,
633+ type : mathTypeInfo . componentType ,
634+ description : mathTypeInfo . description ( propName , component ) ,
635+ isPierced : true ,
636+ } ) ) ;
637+ }
638+
639+ /**
640+ * Generate all pierced props for an element based on its properties and type
641+ */
642+ function generatePiercedPropsForElement ( element , isObj3D ) {
643+ const piercedProps = [ ] ;
644+
645+ // Generate pierced props for math-type properties
646+ for ( const prop of element . properties ) {
647+ const mathTypeName = getMathTypeName ( prop . rawType ) ;
648+ if ( mathTypeName && PIERCEABLE_MATH_TYPES [ mathTypeName ] ) {
649+ // Only generate for common Object3D props or if explicitly a math type
650+ if ( isObj3D && OBJECT3D_PIERCEABLE_PROPS . includes ( prop . name ) ) {
651+ piercedProps . push ( ...generateMathTypePiercedProps ( prop . name , mathTypeName ) ) ;
652+ } else if ( prop . isMathType ) {
653+ // For other math-type properties, still generate pierced props
654+ piercedProps . push ( ...generateMathTypePiercedProps ( prop . name , mathTypeName ) ) ;
655+ }
656+ }
657+ }
658+
659+ // Add shadow pierced props for shadow-casting lights
660+ if ( SHADOW_CASTING_LIGHTS . has ( element . threeName ) ) {
661+ for ( const shadowProp of SHADOW_PIERCED_PROPS ) {
662+ piercedProps . push ( {
663+ name : shadowProp . path ,
664+ type : shadowProp . type ,
665+ description : shadowProp . description ,
666+ isPierced : true ,
667+ } ) ;
668+ }
669+ }
670+
671+ return piercedProps ;
672+ }
673+
533674/**
534675 * Build element definitions from THREE classes
535676 */
@@ -682,13 +823,18 @@ function buildElementDefinitions(threeClasses, inheritance) {
682823 const elementName = elementMappings . get ( className ) || `ngt-${ toKebabCase ( className ) } ` ;
683824 const isObj3D = isObject3D ( className , inheritance ) ;
684825
685- elements . push ( {
826+ const elementDef = {
686827 elementName,
687828 threeName : className ,
688829 isObject3D : isObj3D ,
689830 properties : classInfo ?. properties || [ ] ,
690831 docUrl : getDocUrl ( className ) ,
691- } ) ;
832+ } ;
833+
834+ // Generate pierced props for this element
835+ elementDef . piercedProps = generatePiercedPropsForElement ( elementDef , isObj3D ) ;
836+
837+ elements . push ( elementDef ) ;
692838 }
693839
694840 // Add special elements
@@ -781,6 +927,18 @@ function generateWebTypes(elements) {
781927 htmlElement . js . properties . push ( jsProp ) ;
782928 }
783929
930+ // Add pierced props as attributes (e.g., [position.x], [shadow.mapSize.width])
931+ if ( element . piercedProps && element . piercedProps . length > 0 ) {
932+ for ( const piercedProp of element . piercedProps ) {
933+ // Add as bound attribute [prop.subprop]
934+ htmlElement . attributes . push ( {
935+ name : `[${ piercedProp . name } ]` ,
936+ description : piercedProp . description ,
937+ value : { kind : 'expression' , type : piercedProp . type } ,
938+ } ) ;
939+ }
940+ }
941+
784942 // Add events
785943 // Node events (available on all elements)
786944 for ( const event of NODE_EVENTS ) {
@@ -863,6 +1021,16 @@ function generateVSCodeMetadata(elements) {
8631021 } ) ;
8641022 }
8651023
1024+ // Add pierced props as attributes (e.g., [position.x], [shadow.mapSize.width])
1025+ if ( element . piercedProps && element . piercedProps . length > 0 ) {
1026+ for ( const piercedProp of element . piercedProps ) {
1027+ tag . attributes . push ( {
1028+ name : `[${ piercedProp . name } ]` ,
1029+ description : piercedProp . description ,
1030+ } ) ;
1031+ }
1032+ }
1033+
8661034 // Add events
8671035 for ( const event of NODE_EVENTS ) {
8681036 tag . attributes . push ( {
@@ -929,9 +1097,12 @@ function main() {
9291097 // Print summary
9301098 const obj3dCount = elements . filter ( ( e ) => e . isObject3D ) . length ;
9311099 const totalProps = elements . reduce ( ( sum , e ) => sum + e . properties . length , 0 ) ;
1100+ const totalPiercedProps = elements . reduce ( ( sum , e ) => sum + ( e . piercedProps ?. length || 0 ) , 0 ) ;
1101+ const shadowLightCount = elements . filter ( ( e ) => SHADOW_CASTING_LIGHTS . has ( e . threeName ) ) . length ;
9321102 console . log ( `\nSummary:` ) ;
9331103 console . log ( ` - ${ elements . length } elements (${ obj3dCount } Object3D descendants)` ) ;
9341104 console . log ( ` - ${ totalProps } total properties` ) ;
1105+ console . log ( ` - ${ totalPiercedProps } pierced props (${ shadowLightCount } shadow-casting lights)` ) ;
9351106 console . log ( ` - ${ OBJECT3D_EVENTS . length } Object3D events` ) ;
9361107 console . log ( ` - ${ NODE_EVENTS . length } node events` ) ;
9371108}
0 commit comments