Skip to content

Commit 98fc592

Browse files
committed
feat(core): add pierced props support to web-types and metadata generation
- Add pierced props for math types (Vector2/3/4, Euler, Quaternion, Color) - Add shadow pierced props for DirectionalLight, SpotLight, PointLight - Remove unused legacy tools/scripts/json/ directory - Generate 993 pierced prop attributes for IDE autocomplete support Enables IDE autocomplete for patterns like: [rotation.x], [position.y], [scale.z] [shadow.mapSize.width], [shadow.camera.near], etc.
1 parent 139c255 commit 98fc592

File tree

11 files changed

+173
-2605
lines changed

11 files changed

+173
-2605
lines changed

tools/scripts/generate-json.mjs

Lines changed: 173 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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
102173
const 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

Comments
 (0)